Store badges in Postgres, expose public/admin APIs, render an infinite footer slider, and ship /admin for paste-embed CRUD.
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { requireAdmin } from "@/lib/admin-auth";
|
|
import { prisma } from "@/lib/db";
|
|
import { normalizeBadgeFields, validateBadgeFields } from "@/lib/footer-badges";
|
|
|
|
type Ctx = { params: Promise<{ id: string }> };
|
|
|
|
export async function PATCH(req: NextRequest, ctx: Ctx) {
|
|
const denied = requireAdmin(req);
|
|
if (denied) return denied;
|
|
|
|
const { id } = await ctx.params;
|
|
|
|
let body: unknown;
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
|
|
}
|
|
|
|
const existing = await prisma.footerBadge.findUnique({ where: { id } });
|
|
if (!existing) {
|
|
return NextResponse.json({ error: "Badge not found" }, { status: 404 });
|
|
}
|
|
|
|
const input = (body ?? {}) as Record<string, unknown>;
|
|
const patch: {
|
|
name?: string;
|
|
embedHtml?: string | null;
|
|
imageUrl?: string | null;
|
|
linkUrl?: string | null;
|
|
isActive?: boolean;
|
|
sortOrder?: number;
|
|
} = {};
|
|
|
|
if ("name" in input) patch.name = typeof input.name === "string" ? input.name : existing.name;
|
|
if ("embedHtml" in input) {
|
|
patch.embedHtml = typeof input.embedHtml === "string" ? input.embedHtml : null;
|
|
}
|
|
if ("imageUrl" in input) {
|
|
patch.imageUrl = typeof input.imageUrl === "string" ? input.imageUrl : null;
|
|
}
|
|
if ("linkUrl" in input) {
|
|
patch.linkUrl = typeof input.linkUrl === "string" ? input.linkUrl : null;
|
|
}
|
|
if ("isActive" in input && typeof input.isActive === "boolean") {
|
|
patch.isActive = input.isActive;
|
|
}
|
|
if ("sortOrder" in input && typeof input.sortOrder === "number") {
|
|
patch.sortOrder = input.sortOrder;
|
|
}
|
|
|
|
const fields = normalizeBadgeFields({
|
|
name: patch.name ?? existing.name,
|
|
embedHtml: "embedHtml" in patch ? patch.embedHtml : existing.embedHtml,
|
|
imageUrl: "imageUrl" in patch ? patch.imageUrl : existing.imageUrl,
|
|
linkUrl: "linkUrl" in patch ? patch.linkUrl : existing.linkUrl,
|
|
isActive: patch.isActive ?? existing.isActive,
|
|
sortOrder: patch.sortOrder ?? existing.sortOrder,
|
|
});
|
|
|
|
// Toggle-only updates should not re-validate empty content
|
|
const contentChanging =
|
|
"name" in input || "embedHtml" in input || "imageUrl" in input || "linkUrl" in input;
|
|
if (contentChanging) {
|
|
const error = validateBadgeFields(fields);
|
|
if (error) return NextResponse.json({ error }, { status: 400 });
|
|
}
|
|
|
|
const badge = await prisma.footerBadge.update({
|
|
where: { id },
|
|
data: contentChanging
|
|
? fields
|
|
: {
|
|
...(patch.isActive !== undefined ? { isActive: patch.isActive } : {}),
|
|
...(patch.sortOrder !== undefined ? { sortOrder: fields.sortOrder } : {}),
|
|
},
|
|
});
|
|
|
|
return NextResponse.json({ badge });
|
|
}
|
|
|
|
export async function DELETE(req: NextRequest, ctx: Ctx) {
|
|
const denied = requireAdmin(req);
|
|
if (denied) return denied;
|
|
|
|
const { id } = await ctx.params;
|
|
const existing = await prisma.footerBadge.findUnique({ where: { id } });
|
|
if (!existing) {
|
|
return NextResponse.json({ error: "Badge not found" }, { status: 404 });
|
|
}
|
|
|
|
await prisma.footerBadge.delete({ where: { id } });
|
|
return NextResponse.json({ ok: true });
|
|
}
|