cba5d9c3ac
Sticky sidebar with back-to-app link. Overview: user counts, recipe photos, AI calls/month. User management with role/tier editing. Audit log (all admin actions). Storage page with photo breakdown by tier. AI config showing DB vs .env key source. Site settings page to override .env values at runtime (encrypted in DB).
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import { type NextRequest, NextResponse } from "next/server";
|
|
import { headers } from "next/headers";
|
|
import { auth } from "@/lib/auth/server";
|
|
import { db, users, auditLogs, eq } from "@epicure/db";
|
|
import { setSiteSetting, type SiteSettingKey } from "@/lib/site-settings";
|
|
import { randomUUID } from "crypto";
|
|
|
|
const ALLOWED_KEYS: SiteSettingKey[] = [
|
|
"OPENAI_API_KEY",
|
|
"ANTHROPIC_API_KEY",
|
|
"OPENROUTER_API_KEY",
|
|
"OPENROUTER_DEFAULT_MODEL",
|
|
"OLLAMA_BASE_URL",
|
|
"NEXT_PUBLIC_VAPID_PUBLIC_KEY",
|
|
"VAPID_PRIVATE_KEY",
|
|
];
|
|
|
|
async function requireAdmin() {
|
|
const session = await auth.api.getSession({ headers: await headers() });
|
|
if (!session) return null;
|
|
const [dbUser] = await db.select({ role: users.role }).from(users).where(eq(users.id, session.user.id));
|
|
if (dbUser?.role !== "admin") return null;
|
|
return session;
|
|
}
|
|
|
|
export async function PUT(req: NextRequest) {
|
|
const session = await requireAdmin();
|
|
if (!session) return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
|
|
const body = (await req.json()) as Record<string, string | null>;
|
|
|
|
const changed: string[] = [];
|
|
for (const [key, value] of Object.entries(body)) {
|
|
if (!ALLOWED_KEYS.includes(key as SiteSettingKey)) continue;
|
|
await setSiteSetting(key as SiteSettingKey, value, session.user.id);
|
|
changed.push(key);
|
|
}
|
|
|
|
if (changed.length > 0) {
|
|
await db.insert(auditLogs).values({
|
|
id: randomUUID(),
|
|
userId: session.user.id,
|
|
action: "admin.settings.update",
|
|
targetType: "site_settings",
|
|
metadata: JSON.stringify({ keys: changed }),
|
|
createdAt: new Date(),
|
|
});
|
|
}
|
|
|
|
return NextResponse.json({ ok: true });
|
|
}
|