Ampelos dashboard: the web face, and the owner of the schema
Split out of the single Ampelos repository. Next.js app, Drizzle schema and migrations, brand art, planning notes. What left: scripts/, which was the agent's job library misfiled under web/ and imported nothing from src/; and deploy/truenas, whose broadcast posts to the scan listener on :3427 -- an agent script -- so it belongs beside the thing it talks to. This repository keeps the schema. The agent speaks raw SQL against the same tables and holds no copy of it, so a rename here can break it silently where it used to be one commit. The README says so, and the agent carries a snapshot to check against. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { auth } from "@/auth";
|
||||
import { db } from "@/db/client";
|
||||
import { classics, watchingNowItems } from "@/db/schema";
|
||||
import { and, inArray, isNull } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
type RequestBody = {
|
||||
action?: "add" | "remove";
|
||||
mediaItemIds?: string[];
|
||||
};
|
||||
|
||||
async function removeActiveWatchNow(mediaItemIds: string[]) {
|
||||
if (!mediaItemIds.length) return;
|
||||
|
||||
await db
|
||||
.update(watchingNowItems)
|
||||
.set({ removedAt: new Date() })
|
||||
.where(and(inArray(watchingNowItems.mediaItemId, mediaItemIds), isNull(watchingNowItems.removedAt)));
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await auth();
|
||||
if (!session) return Response.json({ error: "Unauthorized" }, { status: 401 });
|
||||
if (!session.user.isAdmin) return Response.json({ error: "Forbidden" }, { status: 403 });
|
||||
|
||||
const body = (await request.json()) as RequestBody;
|
||||
const mediaItemIds = Array.isArray(body.mediaItemIds)
|
||||
? body.mediaItemIds.filter((id): id is string => typeof id === "string" && id.length > 0)
|
||||
: [];
|
||||
|
||||
if (!body.action || !mediaItemIds.length) {
|
||||
return Response.json({ error: "Missing action or mediaItemIds" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (body.action === "add") {
|
||||
await Promise.all(
|
||||
mediaItemIds.map((mediaItemId) =>
|
||||
db
|
||||
.insert(classics)
|
||||
.values({ mediaItemId, note: "Bulk marked as Timeless Classic", addedBy: session.user.id })
|
||||
.onConflictDoNothing({ target: classics.mediaItemId }),
|
||||
),
|
||||
);
|
||||
await removeActiveWatchNow(mediaItemIds);
|
||||
} else {
|
||||
await db.delete(classics).where(inArray(classics.mediaItemId, mediaItemIds));
|
||||
}
|
||||
|
||||
revalidatePath("/admin/inventory");
|
||||
revalidatePath("/");
|
||||
return Response.json({ ok: true });
|
||||
}
|
||||
Reference in New Issue
Block a user