// Studies: list, load, save (with conflict checks), earlier versions, delete. import { canEditProject, deleteProject, getAllProjects, getProject, getProjectEdited, getVersion, listVersions, restoreVersion, upsertProject } from '../db.js'; import { requireAuth } from '../auth.js'; import { isSaveConflict, parseBase } from '../projectSync.js'; export function registerProjectRoutes(app) { // GET /api/projects — list all project summaries owned by the current user // --------------------------------------------------------------------------- app.get('/api/projects', requireAuth, (req, res) => { try { const projects = getAllProjects(req.session.userId); res.json(projects); } catch (err) { console.error('GET /api/projects error:', err); res.status(500).json({ error: 'Failed to list projects.' }); } }); // --------------------------------------------------------------------------- // GET /api/projects/:id — fetch a single full project owned by the current user // --------------------------------------------------------------------------- app.get('/api/projects/:id', requireAuth, (req, res) => { try { const project = getProject(req.params.id, req.session.userId); if (!project) return res.status(404).json({ error: 'Project not found.' }); res.json(project); } catch (err) { console.error('GET /api/projects/:id error:', err); res.status(500).json({ error: 'Failed to load project.' }); } }); // --------------------------------------------------------------------------- // PUT /api/projects/:id — create or update a project owned by the current user // --------------------------------------------------------------------------- app.put('/api/projects/:id', requireAuth, (req, res) => { try { const body = req.body; if (!body || typeof body !== 'object') { return res.status(400).json({ error: 'Invalid JSON body.' }); } if (!body.id || !body.title || !Array.isArray(body.chapters)) { return res.status(400).json({ error: 'Missing required fields: id, title, chapters.' }); } if (body.id !== req.params.id) { return res.status(400).json({ error: 'URL id does not match body id.' }); } if (body.id.length > 100) return res.status(400).json({ error: 'Invalid project id.' }); if (body.title.length > 500) return res.status(400).json({ error: 'Project title is too long.' }); // Refuse to overwrite a copy another device saved after the one this device last saw const stored = getProjectEdited(body.id, req.session.userId); if (isSaveConflict({ stored, base: parseBase(req.get('X-Base-Edited')), force: req.query.force === '1' })) { return res.status(409).json({ error: 'This study was changed on another device.', code: 'conflict', serverEdited: stored }); } const saved = upsertProject(body, req.session.userId); if (!saved) { return res.status(403).json({ error: 'That project belongs to a different account.' }); } res.json(saved); } catch (err) { console.error('PUT /api/projects/:id error:', err); res.status(500).json({ error: 'Failed to save project.' }); } }); // --------------------------------------------------------------------------- // Earlier versions of a study: list, look at one, and put one back (the copy it replaces is kept too) // --------------------------------------------------------------------------- app.get('/api/projects/:id/versions', requireAuth, (req, res) => { if (!canEditProject(req.params.id, req.session.userId)) return res.status(404).json({ error: 'Project not found.' }); res.json({ versions: listVersions(req.params.id) }); }); app.get('/api/projects/:id/versions/:versionId', requireAuth, (req, res) => { if (!canEditProject(req.params.id, req.session.userId)) return res.status(404).json({ error: 'Project not found.' }); const version = getVersion(req.params.id, Number(req.params.versionId)); if (!version) return res.status(404).json({ error: 'That version is gone.' }); res.json(version); }); app.post('/api/projects/:id/versions/:versionId/restore', requireAuth, (req, res) => { try { const restored = restoreVersion(req.params.id, Number(req.params.versionId), req.session.userId); if (!restored) return res.status(404).json({ error: 'That version is gone.' }); res.json(restored); } catch (err) { console.error('POST restore error:', err); res.status(500).json({ error: 'Failed to restore that version.' }); } }); // --------------------------------------------------------------------------- // DELETE /api/projects/:id — remove a project owned by the current user // --------------------------------------------------------------------------- app.delete('/api/projects/:id', requireAuth, (req, res) => { try { deleteProject(req.params.id, req.session.userId); res.json({ ok: true }); } catch (err) { console.error('DELETE /api/projects/:id error:', err); res.status(500).json({ error: 'Failed to delete project.' }); } }); }