import express, {Request, Response} from 'express'; import * as VouchersAdminService from './vouchers.admin.service.js'; import {sendServerError} from '../tickets.errors.js'; export const vouchersAdminRouter = express.Router(); /** * @swagger * /tickets/admin/vouchers: * get: * summary: List vouchers (admin) * description: Filterable by event and status. * tags: [tickets-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: query * name: eventId * schema: * type: integer * - in: query * name: status * schema: * $ref: '#/components/schemas/VoucherStatus' * responses: * 200: * description: Success * content: * application/json: * schema: * type: array * items: * $ref: '#/components/schemas/VoucherCode' * 401: * description: Unauthorized */ vouchersAdminRouter.get('/', async (req: Request, res: Response) => { try { const eventId = req.query.eventId !== undefined ? Number(req.query.eventId) : undefined; const status = req.query.status as any; res.status(200).send(await VouchersAdminService.listVouchers({eventId, status})); } catch (e: any) { sendServerError(res, e); } }); /** * @swagger * /tickets/admin/vouchers/wildcard: * post: * summary: Batch-generate wildcard codes * description: Generates `quantity` codes sharing the same eligible events and max-guest count, grouped under one batchId. * tags: [tickets-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [eventIds, quantity] * properties: * eventIds: * type: array * items: * type: integer * maxGuests: * type: integer * default: 2 * quantity: * type: integer * responses: * 201: * description: Created * content: * application/json: * schema: * type: object * properties: * codes: * type: array * items: * type: string * 400: * description: Invalid input * 401: * description: Unauthorized */ vouchersAdminRouter.post('/wildcard', async (req: Request, res: Response) => { try { const {eventIds, maxGuests, quantity} = req.body || {}; if (!Array.isArray(eventIds) || eventIds.length === 0 || !quantity) { res.status(400).send({status: 'BAD_REQUEST', message: 'eventIds and quantity are required'}); return; } const codes = await VouchersAdminService.generateWildcardBatch( {eventIds, maxGuests: maxGuests || 2, quantity}, res.locals.admin.email ); res.status(201).send({codes}); } catch (e: any) { res.status(400).send({status: 'BAD_REQUEST', message: e.message}); } }); /** * @swagger * /tickets/admin/vouchers/personalized: * post: * summary: Bulk-create personalized codes * description: One code per row (name, email, eligible events, max guests), grouped under one batchId. * tags: [tickets-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * requestBody: * required: true * content: * application/json: * schema: * type: object * required: [rows] * properties: * rows: * type: array * items: * type: object * required: [name, email, eventIds] * properties: * name: * type: string * email: * type: string * eventIds: * type: array * items: * type: integer * maxGuests: * type: integer * default: 2 * responses: * 201: * description: Created * 400: * description: Invalid input * 401: * description: Unauthorized */ vouchersAdminRouter.post('/personalized', async (req: Request, res: Response) => { try { const rows = (req.body?.rows || []).map((r: any) => ({ name: r.name, email: r.email, eventIds: r.eventIds || [], maxGuests: r.maxGuests || 2 })); const codes = await VouchersAdminService.generatePersonalizedBatch(rows, res.locals.admin.email); res.status(201).send({codes}); } catch (e: any) { res.status(400).send({status: 'BAD_REQUEST', message: e.message}); } }); /** * @swagger * /tickets/admin/vouchers/{code}: * get: * summary: Get a single voucher (admin) * tags: [tickets-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: code * required: true * schema: * type: string * responses: * 200: * description: Success * 404: * description: Unknown code * 401: * description: Unauthorized */ vouchersAdminRouter.get('/:code', async (req: Request, res: Response) => { try { const voucher = await VouchersAdminService.getVoucher(req.params.code); if (!voucher) { res.status(404).send({status: 'NOT_FOUND'}); return; } res.status(200).send(voucher); } catch (e: any) { sendServerError(res, e); } }); /** * @swagger * /tickets/admin/vouchers/{code}/void: * post: * summary: Void an unredeemed code * description: Only allowed while the code is UNUSED. Logs to the voucher's audit trail. * tags: [tickets-admin] * parameters: * - $ref: '#/components/parameters/SessionIdHeader' * - $ref: '#/components/parameters/SessionKeyHeader' * - in: path * name: code * required: true * schema: * type: string * requestBody: * content: * application/json: * schema: * type: object * properties: * reason: * type: string * responses: * 200: * description: Voided * 404: * description: Unknown code * 409: * description: Code is not in UNUSED status * 401: * description: Unauthorized */ vouchersAdminRouter.post('/:code/void', async (req: Request, res: Response) => { try { const result = await VouchersAdminService.voidCode(req.params.code, res.locals.admin.email, req.body?.reason || null); if (result === 'NOT_FOUND') { res.status(404).send({status: 'NOT_FOUND'}); return; } if (result === 'NOT_UNUSED') { res.status(409).send({status: 'NOT_UNUSED', message: 'Only unused codes can be voided.'}); return; } res.status(200).send({status: 'OK'}); } catch (e: any) { sendServerError(res, e); } });