import { db } from '../db.js'; function categoriesWithMetrics({ includeArchived = false } = {}) { const cats = db.prepare( `SELECT * FROM categories ${includeArchived ? '' : 'WHERE archived = 0'} ORDER BY sort_order, id` ).all(); const metrics = db.prepare('SELECT * FROM category_metrics ORDER BY sort_order, id').all(); const byCat = new Map(); for (const m of metrics) { if (!byCat.has(m.category_id)) byCat.set(m.category_id, []); byCat.get(m.category_id).push(m); } return cats.map((c) => ({ ...c, metrics: byCat.get(c.id) || [] })); } export { categoriesWithMetrics }; export default async function categoryRoutes(app) { app.get('/api/categories', async (req) => { const includeArchived = req.query?.all === '1'; return categoriesWithMetrics({ includeArchived }); }); app.post('/api/categories', async (req, reply) => { const { name, emoji, color, metrics } = req.body || {}; if (!name) return reply.code(400).send({ error: 'Name required' }); const maxOrder = db.prepare('SELECT COALESCE(MAX(sort_order), -1) AS m FROM categories').get().m; const result = db.prepare( 'INSERT INTO categories (name, emoji, color, sort_order) VALUES (?, ?, ?, ?)' ).run(name, emoji || '⚽', color || '#EF0107', maxOrder + 1); const catId = result.lastInsertRowid; const list = Array.isArray(metrics) && metrics.length ? metrics : [{ name: 'Minutes', unit: 'min', kind: 'duration', step: 5 }]; list.forEach((m, i) => { db.prepare( 'INSERT INTO category_metrics (category_id, name, unit, kind, step, higher_is_better, track_record, sort_order) ' + 'VALUES (?, ?, ?, ?, ?, ?, ?, ?)' ).run(catId, m.name || 'Value', m.unit || '', m.kind || 'count', m.step || 1, m.higher_is_better ?? 1, m.track_record ? 1 : 0, i); }); return categoriesWithMetrics({ includeArchived: true }).find((c) => c.id === Number(catId)); }); app.put('/api/categories/:id', async (req, reply) => { const id = Number(req.params.id); const cat = db.prepare('SELECT * FROM categories WHERE id = ?').get(id); if (!cat) return reply.code(404).send({ error: 'Not found' }); const { name, emoji, color, archived } = req.body || {}; db.prepare( 'UPDATE categories SET name = ?, emoji = ?, color = ?, archived = ? WHERE id = ?' ).run(name ?? cat.name, emoji ?? cat.emoji, color ?? cat.color, archived !== undefined ? (archived ? 1 : 0) : cat.archived, id); return categoriesWithMetrics({ includeArchived: true }).find((c) => c.id === id); }); // Add a metric to an existing category. app.post('/api/categories/:id/metrics', async (req, reply) => { const id = Number(req.params.id); const cat = db.prepare('SELECT id FROM categories WHERE id = ?').get(id); if (!cat) return reply.code(404).send({ error: 'Not found' }); const { name, unit, kind, step, higher_is_better, track_record } = req.body || {}; const maxOrder = db.prepare( 'SELECT COALESCE(MAX(sort_order), -1) AS m FROM category_metrics WHERE category_id = ?' ).get(id).m; db.prepare( 'INSERT INTO category_metrics (category_id, name, unit, kind, step, higher_is_better, track_record, sort_order) ' + 'VALUES (?, ?, ?, ?, ?, ?, ?, ?)' ).run(id, name || 'Value', unit || '', kind || 'count', step || 1, higher_is_better ?? 1, track_record ? 1 : 0, maxOrder + 1); return categoriesWithMetrics({ includeArchived: true }).find((c) => c.id === id); }); // Update an existing metric (step, record tracking, manual record value, etc.). app.put('/api/metrics/:id', async (req, reply) => { const id = Number(req.params.id); const m = db.prepare('SELECT * FROM category_metrics WHERE id = ?').get(id); if (!m) return reply.code(404).send({ error: 'Not found' }); const b = req.body || {}; // `record` may be explicitly set to null to clear it. const record = 'record' in b ? (b.record === null || b.record === '' ? null : Number(b.record)) : m.record; db.prepare( 'UPDATE category_metrics SET name = ?, unit = ?, kind = ?, step = ?, ' + 'higher_is_better = ?, track_record = ?, record = ? WHERE id = ?' ).run( b.name != null ? String(b.name) : m.name, b.unit != null ? String(b.unit) : m.unit, b.kind != null ? String(b.kind) : m.kind, b.step != null ? Number(b.step) || 1 : m.step, b.higher_is_better != null ? (b.higher_is_better ? 1 : 0) : m.higher_is_better, b.track_record != null ? (b.track_record ? 1 : 0) : m.track_record, record, id, ); return db.prepare('SELECT * FROM category_metrics WHERE id = ?').get(id); }); app.delete('/api/metrics/:id', async (req, reply) => { const id = Number(req.params.id); db.prepare('DELETE FROM category_metrics WHERE id = ?').run(id); return reply.send({ ok: true }); }); }