Add Sonarr and Radarr list integrations

- New `lists` service fetches Sonarr/Radarr library via their REST APIs
  and auto-imports matching titles from TMDB into the user's watchlist
- `app/api/lists/[token]/route.ts` webhook endpoint triggers a list sync
- Unified `IntegrationCard` component replaces `WebhookCard`, handling
  both webhook-style (Plex/Jellyfin/Emby) and list-style (Sonarr/Radarr)
  integrations with per-type config forms
- Schema migration adds `sonarr` and `radarr` to the integration type
  enum and a `listConnections` table for list-based integrations
- 212 tests added for the lists service covering import, deduplication,
  and error handling
This commit is contained in:
2026-03-06 15:34:34 -05:00
parent 8a3717cea7
commit 49cea568a9
27 changed files with 3787 additions and 604 deletions
+6 -6
View File
@@ -2,11 +2,11 @@ import { and, eq, gte } from "drizzle-orm";
import { db } from "@/lib/db/client";
import {
episodes,
integrationEvents,
integrations,
seasons,
userEpisodeWatches,
userMovieWatches,
webhookConnections,
webhookEventLog,
} from "@/lib/db/schema";
import { createLogger } from "@/lib/logger";
import { findByExternalId, searchTv } from "@/lib/tmdb/client";
@@ -283,9 +283,9 @@ function logEvent(
status: "success" | "ignored" | "error",
errorMessage?: string,
) {
db.insert(webhookEventLog)
db.insert(integrationEvents)
.values({
connectionId,
integrationId: connectionId,
eventType:
event?.provider === "plex"
? "media.scrobble"
@@ -300,9 +300,9 @@ function logEvent(
})
.run();
db.update(webhookConnections)
db.update(integrations)
.set({ lastEventAt: new Date() })
.where(eq(webhookConnections.id, connectionId))
.where(eq(integrations.id, connectionId))
.run();
}