Files
sofa/app/api/webhooks/[token]/route.ts
T
jakeandClaude Opus 4.6 bafa45ec79 Add Emby webhook integration and remove unused username field
Add Emby as a third media server integration alongside Plex and Jellyfin.
Emby webhooks use a similar payload format (JSON with nested Item object
and ProviderIds). Also removes the mediaServerUsername field from all
webhook connections since it was never used for authentication — the
token-in-URL is the sole auth mechanism. This simplifies the connection
UX to a single "Connect" button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 00:50:32 -05:00

66 lines
1.8 KiB
TypeScript

import { eq } from "drizzle-orm";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { db } from "@/lib/db/client";
import { webhookConnections } from "@/lib/db/schema";
import { createLogger } from "@/lib/logger";
import type { WebhookEvent } from "@/lib/services/webhooks";
import {
parseEmbyPayload,
parseJellyfinPayload,
parsePlexPayload,
processWebhook,
} from "@/lib/services/webhooks";
const log = createLogger("webhooks");
export async function POST(
req: NextRequest,
{ params }: { params: Promise<{ token: string }> },
) {
const { token } = await params;
// Look up connection by token — this IS the auth
const connection = db
.select()
.from(webhookConnections)
.where(eq(webhookConnections.token, token))
.get();
if (!connection || !connection.enabled) {
// Always return 200 to avoid retry storms from media servers
return NextResponse.json({ ok: true });
}
try {
let event: WebhookEvent | null;
if (connection.provider === "plex") {
const formData = await req.formData();
event = parsePlexPayload(formData);
} else if (connection.provider === "emby") {
const body = await req.json();
event = parseEmbyPayload(body);
} else {
const body = await req.json();
event = parseJellyfinPayload(body);
}
if (!event) {
// Not a relevant event type — silently ignore
return NextResponse.json({ ok: true });
}
await processWebhook(
connection.id,
connection.userId,
connection.provider,
event,
);
} catch (err) {
// Swallow errors — never return non-200 to media servers
log.debug("Webhook processing failed:", err);
}
return NextResponse.json({ ok: true });
}