feat: complete platform operations roadmap
All checks were successful
CI / Validate frontend and API (push) Successful in 1m58s
CI / Build and publish Docker images (push) Successful in 22s

This commit is contained in:
Cauê Faleiros
2026-09-04 10:07:07 -03:00
parent 2c137529bc
commit 9b6fea4b81
19 changed files with 589 additions and 66 deletions

View File

@@ -0,0 +1,40 @@
import type { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
import { recordAudit } from '../audit.js';
import { config } from '../config.js';
import { pool } from '../db/pool.js';
import { bunnyMediaStatus, isBunnyWebhookConfigured, verifyBunnyWebhookSignature } from '../providers/bunny.js';
const webhookSchema = z.object({
VideoGuid: z.string().uuid(),
VideoLibraryId: z.coerce.number().int().positive(),
Status: z.coerce.number().int().nonnegative(),
Length: z.coerce.number().nonnegative().optional(),
});
export const bunnyWebhookRoutes: FastifyPluginAsync = async (app) => {
app.post('/bunny', async (request, reply) => {
if (!isBunnyWebhookConfigured()) return reply.code(503).send({ error: 'Bunny webhooks are not configured' });
const rawBody = (request as typeof request & { rawBody?: Buffer }).rawBody;
const signatureHeader = request.headers.signature || request.headers['x-bunny-signature'];
const signature = Array.isArray(signatureHeader) ? signatureHeader[0] : signatureHeader;
if (!rawBody || !verifyBunnyWebhookSignature(rawBody, signature)) {
return reply.code(401).send({ error: 'Invalid Bunny webhook signature' });
}
const input = webhookSchema.parse(request.body);
if (input.VideoLibraryId !== config.BUNNY_STREAM_LIBRARY_ID) {
return reply.code(400).send({ error: 'Unexpected Bunny video library' });
}
const status = bunnyMediaStatus(input.Status);
await pool.query(
`update lesson_media
set status = $2::media_status,
duration_seconds = coalesce($3, duration_seconds),
metadata = metadata || jsonb_build_object('bunnyStatus', $4, 'bunnyWebhookAt', now())
where provider = 'bunny' and external_id = $1`,
[input.VideoGuid, status, input.Length ? Math.round(input.Length) : null, input.Status],
);
await recordAudit({ action: 'media.bunny.webhook_received', subjectType: 'video', subjectId: input.VideoGuid, metadata: { status, providerStatus: input.Status }, ipAddress: request.ip });
return reply.code(204).send();
});
};