Files
dashboard/backend/src/routes/training.routes.ts
Matthias Hochmeister 620bacc6b5 add features
2026-02-27 19:50:14 +01:00

150 lines
3.9 KiB
TypeScript

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<void> {
try {
if (req.user) {
const role = await getUserRole(req.user.id);
const ROLE_ORDER: Record<string, number> = {
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=<ISO>&to=<ISO>
* 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=<calendarToken>
* 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=<YYYY>
* 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;