import path from "node:path"; import { ORPCError } from "@orpc/server"; import type { JobPublisher } from "@rakazo/adapter-kit"; import { mediaLibrarySyncJob } from "@rakazo/adapter-kit"; import { checkMediaLibraryFolder, mediaLibrarySourceView } from "@rakazo/adapters"; import type { Actor, MediaLibrarySourceAddInput } from "@rakazo/contracts"; import type { PrismaClient } from "@rakazo/db"; export interface MediaLibraryRouteDeps { prisma: PrismaClient; jobs: JobPublisher; } /** Library folders are paths on the server, so only its owner adds them, like host folders. */ function requireDeploymentOwner(actor: Actor) { if (!actor.isDeploymentOwner) throw new ORPCError("FORBIDDEN"); } async function ownSource(deps: MediaLibraryRouteDeps, actor: Actor, sourceId: string) { const source = await deps.prisma.mediaLibrarySource.findFirst({ where: { id: sourceId, userId: actor.userId }, }); if (!source) throw new ORPCError("NOT_FOUND"); return source; } export async function listMediaLibrarySources(deps: MediaLibraryRouteDeps, actor: Actor) { requireDeploymentOwner(actor); const rows = await deps.prisma.mediaLibrarySource.findMany({ where: { userId: actor.userId }, orderBy: { createdAt: "asc" }, }); return rows.map(mediaLibrarySourceView); } export async function addMediaLibrarySource( deps: MediaLibraryRouteDeps, actor: Actor, input: MediaLibrarySourceAddInput, ) { requireDeploymentOwner(actor); const problem = await checkMediaLibraryFolder(input.path); if (problem) throw new ORPCError("BAD_REQUEST", { message: problem }); const folder = path.resolve(input.path); const existing = await deps.prisma.mediaLibrarySource.findUnique({ where: { userId_path: { userId: actor.userId, path: folder } }, }); if (existing) return mediaLibrarySourceView(existing); const source = await deps.prisma.mediaLibrarySource.create({ data: { userId: actor.userId, path: folder, status: "syncing" }, }); await deps.jobs.enqueue(mediaLibrarySyncJob(source.id)); return mediaLibrarySourceView(source); } export async function removeMediaLibrarySource( deps: MediaLibraryRouteDeps, actor: Actor, sourceId: string, ) { requireDeploymentOwner(actor); await ownSource(deps, actor, sourceId); await deps.prisma.mediaLibrarySource.deleteMany({ where: { id: sourceId, userId: actor.userId }, }); return { ok: true as const }; } export async function syncMediaLibrarySourceNow( deps: MediaLibraryRouteDeps, actor: Actor, sourceId: string, ) { requireDeploymentOwner(actor); await ownSource(deps, actor, sourceId); const source = await deps.prisma.mediaLibrarySource.update({ where: { id: sourceId }, data: { status: "syncing", error: null }, }); await deps.jobs.enqueue(mediaLibrarySyncJob(sourceId)); return mediaLibrarySourceView(source); }