Files
compor-academy/server/src/routes/bunny-webhooks.ts
Cauê Faleiros ad736758a7
All checks were successful
CI / Validate frontend and API (push) Successful in 39s
CI / Build and publish Docker images (push) Successful in 22s
fix: localize validation and api messages
2026-09-08 13:40:38 -03:00

41 lines
2.1 KiB
TypeScript

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: 'Os webhooks do Bunny não estão configurados.' });
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: 'Assinatura do webhook do Bunny inválida.' });
}
const input = webhookSchema.parse(request.body);
if (input.VideoLibraryId !== config.BUNNY_STREAM_LIBRARY_ID) {
return reply.code(400).send({ error: 'Biblioteca de vídeos Bunny inesperada.' });
}
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();
});
};