feat: complete platform operations roadmap
This commit is contained in:
40
server/src/routes/bunny-webhooks.ts
Normal file
40
server/src/routes/bunny-webhooks.ts
Normal 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();
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user