import { db, siteSettings, eq } from "@epicure/db"; import { encrypt, decrypt } from "@/lib/encrypt"; export type SiteSettingKey = | "OPENAI_API_KEY" | "ANTHROPIC_API_KEY" | "OPENROUTER_API_KEY" | "OPENROUTER_DEFAULT_MODEL" | "OLLAMA_BASE_URL" | "NEXT_PUBLIC_VAPID_PUBLIC_KEY" | "VAPID_PRIVATE_KEY" | "SIGNUPS_DISABLED" | "DEFAULT_TEXT_PROVIDER" | "DEFAULT_TEXT_MODEL" | "DEFAULT_VISION_PROVIDER" | "DEFAULT_VISION_MODEL" | "DEFAULT_MEAL_PLAN_PROVIDER" | "DEFAULT_MEAL_PLAN_MODEL" | "DEFAULT_CHAT_PROVIDER" | "DEFAULT_CHAT_MODEL" | "AI_TOOL_CALLING_ENABLED" | "GITEA_URL" | "GITEA_TOKEN" | "GITEA_REPO" | "GITEA_WEBHOOK_SECRET"; const SECRET_KEYS: SiteSettingKey[] = [ "OPENAI_API_KEY", "ANTHROPIC_API_KEY", "OPENROUTER_API_KEY", "VAPID_PRIVATE_KEY", "GITEA_TOKEN", "GITEA_WEBHOOK_SECRET", ]; export function isSecretKey(key: SiteSettingKey): boolean { return SECRET_KEYS.includes(key); } export async function getSiteSetting(key: SiteSettingKey): Promise { try { const row = await db.query.siteSettings.findFirst({ where: eq(siteSettings.key, key) }); if (row?.value) { return row.isSecret ? decrypt(row.value) : row.value; } } catch { // fall through to env } return process.env[key] ?? null; } export async function getAllSiteSettings(): Promise> { const rows = await db.query.siteSettings.findMany(); const dbMap = new Map(rows.map((r) => [r.key, r])); const KNOWN_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", "DEFAULT_TEXT_PROVIDER", "DEFAULT_TEXT_MODEL", "DEFAULT_VISION_PROVIDER", "DEFAULT_VISION_MODEL", "DEFAULT_MEAL_PLAN_PROVIDER", "DEFAULT_MEAL_PLAN_MODEL", "DEFAULT_CHAT_PROVIDER", "DEFAULT_CHAT_MODEL", "AI_TOOL_CALLING_ENABLED", "GITEA_URL", "GITEA_TOKEN", "GITEA_REPO", "GITEA_WEBHOOK_SECRET", ]; const result: Record = {}; for (const k of KNOWN_KEYS) { const row = dbMap.get(k); const secret = isSecretKey(k); if (row) { result[k] = { value: secret ? "••••••••••••" : (row.value ?? null), isSecret: secret, fromDb: true, }; } else { const envVal = process.env[k]; result[k] = { value: envVal ? (secret ? "••••••••••••" : envVal) : null, isSecret: secret, fromDb: false, }; } } return result; } export async function isSignupsDisabled(): Promise { return (await getSiteSetting("SIGNUPS_DISABLED")) === "true"; } /** Defaults to enabled — the chatbot's createRecipe/addToShoppingList tools * only get turned off explicitly, e.g. for a local model that can't reliably * call tools. */ export async function isAiToolCallingEnabled(): Promise { return (await getSiteSetting("AI_TOOL_CALLING_ENABLED")) !== "false"; } export async function setSiteSetting( key: SiteSettingKey, value: string | null, updatedById: string ): Promise { if (value === null || value === "") { await db.delete(siteSettings).where(eq(siteSettings.key, key)); return; } const secret = isSecretKey(key); const stored = secret ? encrypt(value) : value; await db .insert(siteSettings) .values({ key, value: stored, isSecret: secret, updatedById, updatedAt: new Date() }) .onConflictDoUpdate({ target: siteSettings.key, set: { value: stored, updatedAt: new Date(), updatedById }, }); }