Files
task-team/api/src/routes/groups.js
Admin 926a584789 WebAuthn biometric + PWA widget + UI header fixes + mobile responsive
- WebAuthn: register/auth options, device management
- PWA widget page + manifest shortcuts
- Group schedule endpoint (timezones + locations)
- UI #3-#6: compact headers on tasks/calendar/projects/goals
- UI #9: mobile responsive top bars
- webauthn_credentials table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 01:54:54 +00:00

142 lines
5.7 KiB
JavaScript

// Task Team — Groups CRUD — 2026-03-29
async function groupRoutes(app) {
app.get('/groups', async (req) => {
const lang = req.query.lang;
const { rows } = await app.db.query(
'SELECT * FROM task_groups ORDER BY order_index ASC'
);
const data = rows.map(row => {
if (lang && row.names && row.names[lang]) {
return { ...row, name: row.names[lang] };
}
return row;
});
return { data };
});
app.get('/groups/:id', async (req) => {
const lang = req.query.lang;
const { rows } = await app.db.query('SELECT * FROM task_groups WHERE id = $1', [req.params.id]);
if (!rows.length) throw { statusCode: 404, message: 'Group not found' };
const row = rows[0];
if (lang && row.names && row.names[lang]) {
row.name = row.names[lang];
}
return { data: row };
});
app.post('/groups', async (req) => {
const { name, color, icon, order_index, time_zones, user_id } = req.body;
const { rows } = await app.db.query(
'INSERT INTO task_groups (user_id, name, color, icon, order_index, time_zones) VALUES ($1,$2,$3,$4,$5,$6) RETURNING *',
[user_id, name, color, icon || '', order_index || 0, JSON.stringify(time_zones || [])]
);
return { data: rows[0] };
});
app.put('/groups/:id', async (req) => {
const { name, color, icon, order_index, time_zones, locations } = req.body;
const { rows } = await app.db.query(
`UPDATE task_groups SET name=COALESCE($1,name), color=COALESCE($2,color), icon=COALESCE($3,icon),
order_index=COALESCE($4,order_index), time_zones=COALESCE($5,time_zones),
locations=COALESCE($6,locations), updated_at=NOW()
WHERE id=$7 RETURNING *`,
[name, color, icon, order_index,
time_zones ? JSON.stringify(time_zones) : null,
locations ? JSON.stringify(locations) : null,
req.params.id]
);
if (!rows.length) throw { statusCode: 404, message: 'Group not found' };
return { data: rows[0] };
});
app.delete('/groups/:id', async (req) => {
const { rowCount } = await app.db.query('DELETE FROM task_groups WHERE id = $1', [req.params.id]);
if (!rowCount) throw { statusCode: 404, message: 'Group not found' };
return { status: 'deleted' };
});
app.put('/groups/reorder', async (req) => {
const { order } = req.body; // [{id, order_index}]
const client = await app.db.connect();
try {
await client.query('BEGIN');
for (const item of order) {
await client.query('UPDATE task_groups SET order_index=$1, updated_at=NOW() WHERE id=$2', [item.order_index, item.id]);
}
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
return { status: 'ok' };
});
// Update time zones for a group
app.put('/groups/:id/timezones', async (req) => {
const { time_zones } = req.body;
if (!Array.isArray(time_zones)) {
throw { statusCode: 400, message: 'time_zones must be an array of [{days, from, to}]' };
}
for (const tz of time_zones) {
if (!Array.isArray(tz.days) || !tz.from || !tz.to) {
throw { statusCode: 400, message: 'Each timezone must have days (array), from (HH:MM), to (HH:MM)' };
}
}
const { rows } = await app.db.query(
'UPDATE task_groups SET time_zones = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
[JSON.stringify(time_zones), req.params.id]
);
if (!rows.length) throw { statusCode: 404, message: 'Group not found' };
return { data: rows[0] };
});
// Update GPS locations for a group
app.put('/groups/:id/locations', async (req) => {
const { locations } = req.body;
if (!Array.isArray(locations)) {
throw { statusCode: 400, message: 'locations must be an array of [{name, lat, lng, radius_m}]' };
}
for (const loc of locations) {
if (!loc.name || loc.lat === undefined || loc.lng === undefined) {
throw { statusCode: 400, message: 'Each location must have name, lat, lng' };
}
if (typeof loc.lat !== 'number' || typeof loc.lng !== 'number') {
throw { statusCode: 400, message: 'lat and lng must be numbers' };
}
}
const { rows } = await app.db.query(
'UPDATE task_groups SET locations = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
[JSON.stringify(locations), req.params.id]
);
if (!rows.length) throw { statusCode: 404, message: 'Group not found' };
return { data: rows[0] };
});
// Combined schedule: time zones + locations for a group
app.get('/groups/:id/schedule', async (req) => {
const { rows } = await app.db.query(
'SELECT id, name, time_zones, locations FROM task_groups WHERE id = $1',
[req.params.id]
);
if (!rows.length) throw { statusCode: 404, message: 'Group not found' };
const g = rows[0];
return {
data: {
group_id: g.id,
group_name: g.name,
time_zones: g.time_zones || [],
locations: g.locations || [],
summary: {
tz_count: (g.time_zones || []).length,
loc_count: (g.locations || []).length
}
}
};
});
}
module.exports = groupRoutes;