Initial commit

This commit is contained in:
admin
2026-05-26 22:46:00 +02:00
commit 7e2c2ff89c
256 changed files with 51523 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
import { NextRequest, NextResponse } from "next/server";
import path from "node:path";
import fs from "node:fs/promises";
import crypto from "node:crypto";
import { revalidatePath } from "next/cache";
import { rawDb } from "@/lib/db/client";
import { safeJoin } from "@/lib/safePath";
import { assertLocalRequest } from "@/lib/api/localOnly";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
const PORTRAIT_ROOT = path.join(process.cwd(), "data", "portraits");
const ALLOWED_EXT = new Set([".jpg", ".jpeg", ".png", ".webp"]);
const SLOT_COLS: Record<string, { path: string; zoom: string; ox: string; oy: string }> = {
"1": { path: "portrait_path", zoom: "portrait_zoom", ox: "portrait_offset_x", oy: "portrait_offset_y" },
"2": { path: "portrait2_path", zoom: "portrait2_zoom", ox: "portrait2_offset_x", oy: "portrait2_offset_y" },
"3": { path: "portrait3_path", zoom: "portrait3_zoom", ox: "portrait3_offset_x", oy: "portrait3_offset_y" },
"4": { path: "portrait4_path", zoom: "portrait4_zoom", ox: "portrait4_offset_x", oy: "portrait4_offset_y" },
"h": { path: "portraith_path", zoom: "portraith_zoom", ox: "portraith_offset_x", oy: "portraith_offset_y" },
};
export async function POST(req: NextRequest, ctx: { params: Promise<{ id: string }> }) {
const blocked = assertLocalRequest(req);
if (blocked) return blocked;
const { id } = await ctx.params;
const numId = Number(id);
if (!Number.isFinite(numId)) return NextResponse.json({ error: "bad id" }, { status: 400 });
const url = new URL(req.url);
const slot = (url.searchParams.get("slot") ?? "1") as keyof typeof SLOT_COLS;
const cols = SLOT_COLS[slot];
if (!cols) return NextResponse.json({ error: "bad slot" }, { status: 400 });
const actress = rawDb.prepare(`SELECT id, slug, ${cols.path} AS prevPath FROM actresses WHERE id = ?`).get(numId) as
| { id: number; slug: string; prevPath: string | null }
| undefined;
if (!actress) return NextResponse.json({ error: "actress not found" }, { status: 404 });
const form = await req.formData();
const file = form.get("file");
if (!(file instanceof File)) return NextResponse.json({ error: "missing file" }, { status: 400 });
const ext = path.extname(file.name).toLowerCase();
if (!ALLOWED_EXT.has(ext)) return NextResponse.json({ error: "unsupported format" }, { status: 415 });
const buf = Buffer.from(await file.arrayBuffer());
const sha = crypto.createHash("sha256").update(buf).digest("hex").slice(0, 16);
const filename = `${actress.id}-${slot}-${sha}${ext}`;
await fs.mkdir(PORTRAIT_ROOT, { recursive: true });
await fs.writeFile(path.join(PORTRAIT_ROOT, filename), buf);
if (actress.prevPath && actress.prevPath !== filename) {
const prevAbs = safeJoin(PORTRAIT_ROOT, actress.prevPath);
if (prevAbs) await fs.rm(prevAbs, { force: true }).catch(() => {});
}
rawDb.prepare(`
UPDATE actresses
SET ${cols.path} = ?, ${cols.zoom} = 1, ${cols.ox} = 0, ${cols.oy} = 0
WHERE id = ?
`).run(filename, actress.id);
revalidatePath("/actress");
revalidatePath(`/actress/${actress.slug}`);
return NextResponse.json({ portraitPath: filename });
}