import { Router, Request, Response, NextFunction } from 'express'; import trainingController from '../controllers/training.controller'; import { authenticate, optionalAuth } from '../middleware/auth.middleware'; import { requirePermission, getUserRole } from '../middleware/rbac.middleware'; const router = Router(); // --------------------------------------------------------------------------- // injectTeilnahmenFlag // // Sets req.canSeeTeilnahmen = true for Gruppenführer and above. // Regular Mitglieder see only attendance counts; officers see the full list. // --------------------------------------------------------------------------- async function injectTeilnahmenFlag( req: Request, _res: Response, next: NextFunction ): Promise { try { if (req.user) { const role = await getUserRole(req.user.id); const ROLE_ORDER: Record = { bewerber: -1, mitglied: 0, gruppenfuehrer: 1, kommandant: 2, admin: 3, }; (req as any).canSeeTeilnahmen = (ROLE_ORDER[role] ?? 0) >= ROLE_ORDER.gruppenfuehrer; } } catch (_err) { // Non-fatal — default to restricted view } next(); } // --------------------------------------------------------------------------- // Routes // --------------------------------------------------------------------------- /** * GET /api/training * Public list of upcoming events (limit param). * Optional auth to include own RSVP status. */ router.get('/', optionalAuth, trainingController.getUpcoming); /** * GET /api/training/calendar?from=&to= * Events in a date range for the calendar view. * Optional auth to include own RSVP status. */ router.get('/calendar', optionalAuth, trainingController.getCalendarRange); /** * GET /api/training/calendar.ics?token= * iCal export — authenticated via per-user calendar token OR Bearer JWT. * No `authenticate` enforced here; controller resolves auth itself. * See training.controller.ts for full auth tradeoff discussion. */ router.get('/calendar.ics', optionalAuth, trainingController.getIcalExport); /** * GET /api/training/calendar-token * Returns (or creates) the user's personal iCal subscribe token + URL. * Requires authentication. */ router.get('/calendar-token', authenticate, trainingController.getCalendarToken); /** * GET /api/training/stats?year= * Annual participation stats. * Requires Kommandant or above (requirePermission('reports:read')). */ router.get( '/stats', authenticate, requirePermission('reports:read'), trainingController.getStats ); /** * GET /api/training/:id * Single event with attendance counts. * Gruppenführer+ also gets the full attendee list. */ router.get( '/:id', authenticate, injectTeilnahmenFlag, trainingController.getById ); /** * POST /api/training * Create a new training event. * Requires Gruppenführer or above (requirePermission('training:write')). */ router.post( '/', authenticate, requirePermission('training:write'), trainingController.createEvent ); /** * PATCH /api/training/:id * Update an existing event. * Requires Gruppenführer or above. */ router.patch( '/:id', authenticate, requirePermission('training:write'), trainingController.updateEvent ); /** * DELETE /api/training/:id * Soft-cancel an event (sets abgesagt=true, records reason). * Requires Kommandant or above. */ router.delete( '/:id', authenticate, requirePermission('training:cancel'), trainingController.cancelEvent ); /** * PATCH /api/training/:id/attendance * Any authenticated member updates their own RSVP. */ router.patch( '/:id/attendance', authenticate, trainingController.updateRsvp ); /** * POST /api/training/:id/attendance/mark * Gruppenführer bulk-marks who actually appeared. */ router.post( '/:id/attendance/mark', authenticate, requirePermission('training:mark_attendance'), trainingController.markAttendance ); export default router;