From 5de988d23bae73212ee1b2930d7e5b61d0bb06ed Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Wed, 20 May 2026 20:59:48 +0200 Subject: [PATCH] feat(backend): sessions routes --- packages/backend/src/app.ts | 2 + packages/backend/src/routes/sessions.ts | 57 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 packages/backend/src/routes/sessions.ts diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 4a9cb60..2d5db9e 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -4,6 +4,7 @@ import type { Db } from './db/client.js'; import { ApiError } from './lib/errors.js'; import { cardsRouter } from './routes/cards.js'; import { lessonsRouter } from './routes/lessons.js'; +import { sessionsRouter } from './routes/sessions.js'; export function createApp(db: Db): Express { const app = express(); @@ -12,6 +13,7 @@ export function createApp(db: Db): Express { app.get('/api/health', (_req, res) => res.json({ ok: true })); app.use('/api/lessons', lessonsRouter(db)); app.use('/api', cardsRouter(db)); + app.use('/api/sessions', sessionsRouter(db)); app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => { if (err instanceof ZodError) { diff --git a/packages/backend/src/routes/sessions.ts b/packages/backend/src/routes/sessions.ts new file mode 100644 index 0000000..ebf2640 --- /dev/null +++ b/packages/backend/src/routes/sessions.ts @@ -0,0 +1,57 @@ +import { Router } from 'express'; +import { attemptCreateSchema, sessionStartSchema } from '@flashcard/shared'; +import type { Db } from '../db/client.js'; +import { + abandonSession, endSession, getActiveSession, getNextItem, getSessionState, + recordAttempt, startSession, +} from '../services/sessions.js'; +import { ApiError } from '../lib/errors.js'; + +export function sessionsRouter(db: Db): Router { + const r = Router(); + + r.post('/', async (req, res, next) => { + try { + const input = sessionStartSchema.parse(req.body); + res.status(201).json(await startSession(db, input)); + } catch (e) { next(e); } + }); + + r.get('/active', async (_req, res, next) => { + try { res.json(await getActiveSession(db)); } catch (e) { next(e); } + }); + + r.get('/:id', async (req, res, next) => { + try { + const state = await getSessionState(db, Number(req.params.id)); + if (!state) throw ApiError.notFound('Session'); + res.json(state); + } catch (e) { next(e); } + }); + + r.get('/:id/next', async (req, res, next) => { + try { + const item = await getNextItem(db, Number(req.params.id)); + if (!item) { res.json({ done: true }); return; } + res.json({ done: false, item }); + } catch (e) { next(e); } + }); + + r.post('/:id/attempts', async (req, res, next) => { + try { + const input = attemptCreateSchema.parse(req.body); + await recordAttempt(db, Number(req.params.id), input); + res.status(204).end(); + } catch (e) { next(e); } + }); + + r.post('/:id/end', async (req, res, next) => { + try { res.json(await endSession(db, Number(req.params.id))); } catch (e) { next(e); } + }); + + r.post('/:id/abandon', async (req, res, next) => { + try { res.json(await abandonSession(db, Number(req.params.id))); } catch (e) { next(e); } + }); + + return r; +}