/** * Required External Modules and Interfaces */ import express, {Request, Response} from 'express'; import * as EventsAdminService from './events.admin.service'; import * as SongsAdminService from './songs.admin.service'; import {sendServerError} from '../feedback.errors'; /** * Router Definition */ export const eventsAdminRouter = express.Router(); /** * @swagger * /feedback/admin/events: * get: * summary: List all events (admin) * description: All events, published or not, past or future, with submission counts. * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * responses: * 200: * description: Success * content: * application/json: * schema: * type: array * items: * $ref: '#/components/schemas/EventAdminSummary' * 401: * description: Unauthorized * post: * summary: Create an event * description: Auto-generates the slug from the name and event year; defaults feedback_deadline to event_date + 14 days 23:59:59 unless supplied. * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [name, eventDate] * properties: * name: * type: string * subtitle: * type: string * eventDate: * type: string * format: date * feedbackDeadline: * type: string * format: date-time * introText: * type: string * posterImageUrl: * type: string * responses: * 201: * description: Created * 400: * description: Missing required fields * 401: * description: Unauthorized */ eventsAdminRouter.get('/', async (req: Request, res: Response) => { try { res.status(200).send(await EventsAdminService.listEventsAdmin()); } catch (e: any) { sendServerError(res, e); } }); eventsAdminRouter.post('/', async (req: Request, res: Response) => { try { const {name, subtitle, eventDate, feedbackDeadline, introText, posterImageUrl} = req.body || {}; if (!name || !eventDate) { res.status(400).send({status: 'BAD_REQUEST', message: 'name and eventDate are required'}); return; } const eventId = await EventsAdminService.createEvent( {name, subtitle, eventDate, feedbackDeadline, introText, posterImageUrl}, res.locals.admin.email ); res.status(201).send({eventId}); } catch (e: any) { sendServerError(res, e); } }); /** * @swagger * /feedback/admin/events/{eventId}: * get: * summary: Get one event (admin) * description: Full event detail including setlist and assigned questions. * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * responses: * 200: * description: Success * content: * application/json: * schema: * $ref: '#/components/schemas/EventAdminDetail' * 404: * description: Unknown event * 401: * description: Unauthorized * put: * summary: Update an event * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * responses: * 200: * description: Updated * 404: * description: Unknown event * 401: * description: Unauthorized * delete: * summary: Delete an event * description: Refuses with 409 if submissions exist unless ?force=true is passed. * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * - in: query * name: force * schema: * type: boolean * responses: * 204: * description: Deleted * 404: * description: Unknown event * 409: * description: Submissions exist and force was not set * 401: * description: Unauthorized */ eventsAdminRouter.get('/:eventId', async (req: Request, res: Response) => { try { const event = await EventsAdminService.getEventAdmin(Number(req.params.eventId)); if (!event) { res.status(404).send({status: 'NOT_FOUND'}); return; } res.status(200).send(event); } catch (e: any) { sendServerError(res, e); } }); eventsAdminRouter.put('/:eventId', async (req: Request, res: Response) => { try { const updated = await EventsAdminService.updateEvent(Number(req.params.eventId), req.body || {}); if (!updated) { res.status(404).send({status: 'NOT_FOUND'}); return; } res.status(200).send({status: 'OK'}); } catch (e: any) { sendServerError(res, e); } }); eventsAdminRouter.delete('/:eventId', async (req: Request, res: Response) => { try { const force = req.query.force === 'true'; const result = await EventsAdminService.deleteEvent(Number(req.params.eventId), force); if (result === 'NOT_FOUND') { res.status(404).send({status: 'NOT_FOUND'}); return; } if (result === 'HAS_SUBMISSIONS') { res.status(409).send({status: 'HAS_SUBMISSIONS', message: 'This event has submissions. Pass ?force=true to delete anyway.'}); return; } res.status(204).send(); } catch (e: any) { sendServerError(res, e); } }); /** * @swagger * /feedback/admin/events/{eventId}/songs: * get: * summary: Get an event's setlist * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * responses: * 200: * description: Success * 401: * description: Unauthorized * post: * summary: Add a song to an event's setlist * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [title] * properties: * title: * type: string * composer: * type: string * responses: * 201: * description: Created * 400: * description: Missing title * 401: * description: Unauthorized */ eventsAdminRouter.get('/:eventId/songs', async (req: Request, res: Response) => { try { const event = await EventsAdminService.getEventAdmin(Number(req.params.eventId)); if (!event) { res.status(404).send({status: 'NOT_FOUND'}); return; } res.status(200).send(event.songs); } catch (e: any) { sendServerError(res, e); } }); eventsAdminRouter.post('/:eventId/songs', async (req: Request, res: Response) => { try { const {title, composer} = req.body || {}; if (!title) { res.status(400).send({status: 'BAD_REQUEST', message: 'title is required'}); return; } const songId = await SongsAdminService.addSong(Number(req.params.eventId), title, composer || null); res.status(201).send({songId}); } catch (e: any) { sendServerError(res, e); } }); /** * @swagger * /feedback/admin/events/{eventId}/songs/order: * put: * summary: Bulk reorder an event's setlist * description: Rewrites song positions as a dense 0..n-1 sequence in one transaction. * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [songIds] * properties: * songIds: * type: array * items: * type: integer * responses: * 200: * description: Reordered * 401: * description: Unauthorized */ eventsAdminRouter.put('/:eventId/songs/order', async (req: Request, res: Response) => { try { const songIds: number[] = req.body?.songIds || []; await EventsAdminService.reorderSongs(Number(req.params.eventId), songIds); res.status(200).send({status: 'OK'}); } catch (e: any) { sendServerError(res, e); } }); /** * @swagger * /feedback/admin/events/{eventId}/questions: * get: * summary: Get an event's assigned questions * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * responses: * 200: * description: Success * 401: * description: Unauthorized * put: * summary: Bulk-set an event's assigned questions * description: One transaction - inserts new, updates existing, deletes removed. Keeps the admin UI a simple save-the-whole-list form. * tags: [feedback-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: eventId * required: true * schema: * type: integer * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [items] * properties: * items: * type: array * items: * type: object * properties: * questionId: * type: integer * position: * type: integer * isActive: * type: boolean * responses: * 200: * description: Saved * 401: * description: Unauthorized */ eventsAdminRouter.get('/:eventId/questions', async (req: Request, res: Response) => { try { const event = await EventsAdminService.getEventAdmin(Number(req.params.eventId)); if (!event) { res.status(404).send({status: 'NOT_FOUND'}); return; } res.status(200).send(event.questions); } catch (e: any) { sendServerError(res, e); } }); eventsAdminRouter.put('/:eventId/questions', async (req: Request, res: Response) => { try { const items = req.body?.items || []; await EventsAdminService.setEventQuestions(Number(req.params.eventId), items); res.status(200).send({status: 'OK'}); } catch (e: any) { sendServerError(res, e); } });