import { type FastifyInstance } from 'fastify'; import { createFormatProfile, getAllFormatProfiles, getFormatProfileById, updateFormatProfile, deleteFormatProfile, } from '../../db/repositories/format-profile-repository'; // ── JSON Schemas for Fastify Validation ── const createFormatProfileBodySchema = { type: 'object' as const, required: ['name'], properties: { name: { type: 'string' as const, minLength: 1 }, videoResolution: { type: 'string' as const, nullable: true }, audioCodec: { type: 'string' as const, nullable: true }, audioBitrate: { type: 'string' as const, nullable: true }, containerFormat: { type: 'string' as const, nullable: true }, isDefault: { type: 'boolean' as const }, subtitleLanguages: { type: 'string' as const, nullable: true }, embedSubtitles: { type: 'boolean' as const }, }, additionalProperties: false, }; const updateFormatProfileBodySchema = { type: 'object' as const, properties: { name: { type: 'string' as const, minLength: 1 }, videoResolution: { type: 'string' as const, nullable: true }, audioCodec: { type: 'string' as const, nullable: true }, audioBitrate: { type: 'string' as const, nullable: true }, containerFormat: { type: 'string' as const, nullable: true }, isDefault: { type: 'boolean' as const }, subtitleLanguages: { type: 'string' as const, nullable: true }, embedSubtitles: { type: 'boolean' as const }, }, additionalProperties: false, }; // ── Route Plugin ── /** * Format profile CRUD route plugin. * * Registers: * POST /api/v1/format-profile — create a new format profile * GET /api/v1/format-profile — list all format profiles * GET /api/v1/format-profile/:id — get a single format profile * PUT /api/v1/format-profile/:id — update format profile fields * DELETE /api/v1/format-profile/:id — delete a format profile */ export async function formatProfileRoutes(fastify: FastifyInstance): Promise { // ── POST /api/v1/format-profile ── fastify.post<{ Body: { name: string; videoResolution?: string | null; audioCodec?: string | null; audioBitrate?: string | null; containerFormat?: string | null; isDefault?: boolean; subtitleLanguages?: string | null; embedSubtitles?: boolean; }; }>( '/api/v1/format-profile', { schema: { body: createFormatProfileBodySchema }, }, async (request, reply) => { const profile = await createFormatProfile(fastify.db, request.body); return reply.status(201).send(profile); } ); // ── GET /api/v1/format-profile ── fastify.get('/api/v1/format-profile', async (_request, _reply) => { return getAllFormatProfiles(fastify.db); }); // ── GET /api/v1/format-profile/:id ── fastify.get<{ Params: { id: string } }>( '/api/v1/format-profile/:id', async (request, reply) => { const id = parseInt(request.params.id, 10); if (isNaN(id)) { return reply.status(400).send({ statusCode: 400, error: 'Bad Request', message: 'Format profile ID must be a number', }); } const profile = await getFormatProfileById(fastify.db, id); if (!profile) { return reply.status(404).send({ statusCode: 404, error: 'Not Found', message: `Format profile with ID ${id} not found`, }); } return profile; } ); // ── PUT /api/v1/format-profile/:id ── fastify.put<{ Params: { id: string }; Body: { name?: string; videoResolution?: string | null; audioCodec?: string | null; audioBitrate?: string | null; containerFormat?: string | null; isDefault?: boolean; subtitleLanguages?: string | null; embedSubtitles?: boolean; }; }>( '/api/v1/format-profile/:id', { schema: { body: updateFormatProfileBodySchema }, }, async (request, reply) => { const id = parseInt(request.params.id, 10); if (isNaN(id)) { return reply.status(400).send({ statusCode: 400, error: 'Bad Request', message: 'Format profile ID must be a number', }); } // Guard: prevent unsetting isDefault on the default profile const existing = await getFormatProfileById(fastify.db, id); if (!existing) { return reply.status(404).send({ statusCode: 404, error: 'Not Found', message: `Format profile with ID ${id} not found`, }); } if (existing.isDefault && request.body.isDefault === false) { return reply.status(400).send({ statusCode: 400, error: 'Bad Request', message: 'Cannot unset isDefault on the default format profile', }); } const updated = await updateFormatProfile(fastify.db, id, request.body); if (!updated) { return reply.status(404).send({ statusCode: 404, error: 'Not Found', message: `Format profile with ID ${id} not found`, }); } return updated; } ); // ── DELETE /api/v1/format-profile/:id ── fastify.delete<{ Params: { id: string } }>( '/api/v1/format-profile/:id', async (request, reply) => { const id = parseInt(request.params.id, 10); if (isNaN(id)) { return reply.status(400).send({ statusCode: 400, error: 'Bad Request', message: 'Format profile ID must be a number', }); } // Guard: prevent deleting the default profile const profile = await getFormatProfileById(fastify.db, id); if (!profile) { return reply.status(404).send({ statusCode: 404, error: 'Not Found', message: `Format profile with ID ${id} not found`, }); } if (profile.isDefault) { return reply.status(403).send({ statusCode: 403, error: 'Forbidden', message: 'Cannot delete the default format profile', }); } await deleteFormatProfile(fastify.db, id); return reply.status(204).send(); } ); }