import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { db } from "@/lib/db"; import { siteSettings, systemSettings } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; import { auth } from "@/app/(auth)/auth"; import { defaultMembershipConfig, defaultMembershipUserOverrides, normalizeMembershipConfig, normalizeMembershipUserOverrides, } from "@/lib/membership/config"; import { defaultPointsPackages, defaultPointsSystemConfig, normalizePointsPackages, normalizePointsSystemConfig, POINTS_PACKAGES_KEY, POINTS_SYSTEM_CONFIG_KEY, } from "@/lib/points/config"; import { defaultDulupayConfig, DULUPAY_CONFIG_KEY, normalizeDulupayConfig, normalizeDulupayForPublic, } from "@/lib/payment/dulupay-config"; import { AGENT_SECONDARY_TYPE_CONFIG_KEY, defaultAgentSecondaryTypeConfig, normalizeAgentSecondaryTypeConfig, } from "@/lib/agents/secondary-type-config"; import { defaultOssImageMigrationConfig, normalizeOssImageMigrationConfig, OSS_MIGRATION_CONFIG_KEY, } from "@/lib/storage/migration-config"; import type { AgentCategoryTreeConfig } from "@/types/agent"; const HOME_VARIANT_KEY = "home_variant"; // Historically stored as "home_surprise_categories" but used for the featured gallery categories. const HOME_SURPRISE_CATEGORIES_KEY = "home_surprise_categories"; const AI_DETAIL_GENERATOR_ENABLED_KEY = "ai_detail_generator_enabled"; const LOVART_COMPOSER_PREVIEW_TEXTS_KEY = "lovart_composer_preview_texts"; const LOVART_COMPOSER_PREFIX_KEY = "lovart_composer_prefix"; const LOVART_PROMPT_TAG_VISIBILITY_KEY = "lovart_prompt_tag_visibility"; const CONTACT_CONFIG_KEY = "contact_config"; const MEMBERSHIP_CONFIG_KEY = "membership_config"; const MEMBERSHIP_USER_OVERRIDES_KEY = "membership_user_plan_overrides"; const STORAGE_FAILOVER_CONFIG_KEY = "storage_failover_config"; const REALTIME_CANVAS_CONFIG_KEY = "realtime_canvas_config"; const AGENT_CATEGORY_TREE_CONFIG_KEY = "agent_category_tree_config"; function normalizeHomeVariant(value: unknown): "classic" | "lovart" { return value === "lovart" ? "lovart" : "classic"; } function normalizeSurpriseCategories(value: unknown): string[] { if (!Array.isArray(value)) { return ["全部", "精选"]; } const normalized = value .map((item) => String(item || "").trim()) .filter(Boolean) // legacy typo compatibility: treat "惊喜" as "精选" .map((item) => (item === "惊喜" ? "精选" : item)) .slice(0, 12); if (normalized.length === 0) { return ["全部", "精选"]; } const unique = Array.from(new Set(normalized)); if (!unique.includes("全部")) { unique.unshift("全部"); } return unique; } function normalizeLovartComposerPreviewTexts(value: unknown): string[] { const defaults = [ "打造引人注目的社交媒体视觉", "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ]; if (!Array.isArray(value)) { return defaults; } const normalized = value .map((item) => String(item || "").trim()) .filter(Boolean) .slice(0, 20); return normalized.length > 0 ? normalized : defaults; } function normalizeLovartComposerPrefix(value: unknown): string { const normalized = String(value || "").trim(); if (!normalized) { return "让Buyi-ai"; } return normalized.slice(0, 40); } function normalizeLovartPromptTagVisibility(value: unknown): { nanoBananaPro: boolean; design: boolean; branding: boolean; illustration: boolean; ecommerce: boolean; video: boolean; } { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { nanoBananaPro: raw.nanoBananaPro !== false, design: raw.design !== false, branding: raw.branding !== false, illustration: raw.illustration !== false, ecommerce: raw.ecommerce !== false, video: raw.video !== false, }; } function normalizeContactConfig(value: unknown): { phone: string; wechatQrUrl: string; } { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const phone = String(raw.phone || "").trim().slice(0, 64); const wechatQrUrl = String(raw.wechatQrUrl || "").trim().slice(0, 1000); return { phone, wechatQrUrl, }; } function normalizeStorageFailoverConfig(value: unknown): { enabled: boolean; backupStorageConfigId: string; } { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const backupStorageConfigId = String(raw.backupStorageConfigId || "").trim(); return { enabled: raw.enabled === true, backupStorageConfigId, }; } function normalizeAgentCategoryTreeConfig(value: unknown): AgentCategoryTreeConfig { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const normalizeList = (input: unknown) => { if (!Array.isArray(input)) return [] as string[]; return Array.from( new Set( input .map((item) => String(item || "").trim()) .filter(Boolean) .slice(0, 200) ) ); }; return { productCategories: normalizeList(raw.productCategories), audienceTags: normalizeList(raw.audienceTags), usageScenarios: normalizeList(raw.usageScenarios), brands: normalizeList(raw.brands), }; } // GET /api/site-settings - 获取网站设置 (公开) export async function GET() { try { const session = await auth().catch(() => null); const isAdmin = session?.user?.type === "admin"; const [ settings, notificationSetting, homeVariantSetting, surpriseCategoriesSetting, aiDetailGeneratorEnabledSetting, lovartComposerPreviewTextsSetting, lovartComposerPrefixSetting, lovartPromptTagVisibilitySetting, contactConfigSetting, membershipConfigSetting, membershipOverridesSetting, pointsSystemConfigSetting, pointsPackagesSetting, dulupayConfigSetting, realtimeCanvasConfigSetting, storageFailoverConfigSetting, ossImageMigrationConfigSetting, agentCategoryTreeConfigSetting, agentSecondaryTypeConfigSetting, ] = await Promise.all([ db.select().from(siteSettings).limit(1).then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, "login_notification")) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, HOME_VARIANT_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, HOME_SURPRISE_CATEGORIES_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AI_DETAIL_GENERATOR_ENABLED_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_COMPOSER_PREVIEW_TEXTS_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_COMPOSER_PREFIX_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, LOVART_PROMPT_TAG_VISIBILITY_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, CONTACT_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, MEMBERSHIP_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, MEMBERSHIP_USER_OVERRIDES_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, POINTS_SYSTEM_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, POINTS_PACKAGES_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, DULUPAY_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, REALTIME_CANVAS_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, STORAGE_FAILOVER_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, OSS_MIGRATION_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_CATEGORY_TREE_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), db .select() .from(systemSettings) .where(eq(systemSettings.key, AGENT_SECONDARY_TYPE_CONFIG_KEY)) .limit(1) .then(rows => rows[0]), ]); const notification = notificationSetting?.value || { enabled: false, title: "", content: "" }; const homeVariant = normalizeHomeVariant(homeVariantSetting?.value); const surpriseCategories = normalizeSurpriseCategories(surpriseCategoriesSetting?.value); const aiDetailGenerationEnabled = Boolean(aiDetailGeneratorEnabledSetting?.value); const lovartComposerPreviewTexts = normalizeLovartComposerPreviewTexts( lovartComposerPreviewTextsSetting?.value ); const lovartComposerPrefix = normalizeLovartComposerPrefix( lovartComposerPrefixSetting?.value ); const lovartPromptTagVisibility = normalizeLovartPromptTagVisibility( lovartPromptTagVisibilitySetting?.value ); const contactConfig = normalizeContactConfig(contactConfigSetting?.value); const membershipConfig = normalizeMembershipConfig(membershipConfigSetting?.value); const membershipUserOverrides = normalizeMembershipUserOverrides( membershipOverridesSetting?.value ); const pointsSystemConfig = normalizePointsSystemConfig( pointsSystemConfigSetting?.value ); const pointsPackages = normalizePointsPackages(pointsPackagesSetting?.value); const dulupayConfig = isAdmin ? normalizeDulupayConfig(dulupayConfigSetting?.value) : normalizeDulupayForPublic(dulupayConfigSetting?.value); const realtimeCanvasServiceEnabled = (realtimeCanvasConfigSetting?.value as { enabled?: boolean } | undefined)?.enabled !== false; const storageFailoverConfig = normalizeStorageFailoverConfig( storageFailoverConfigSetting?.value ); const ossImageMigrationConfig = normalizeOssImageMigrationConfig( ossImageMigrationConfigSetting?.value ); const agentCategoryTreeConfig = normalizeAgentCategoryTreeConfig( agentCategoryTreeConfigSetting?.value ); const agentSecondaryTypeConfig = normalizeAgentSecondaryTypeConfig( agentSecondaryTypeConfigSetting?.value ); // 如果没有设置记录,返回默认值 const baseSettings = settings || { siteName: "AI Chatbot", siteLogo: null, siteLogoDark: null, favicon: null, showAutoModel: true, defaultTheme: "system", usageTimeEnabled: false, usageTimeStart: "08:00", usageTimeEnd: "20:00", usageTimeMessage: "由壹点累了也是需要休息的", bannerEnabled: true, bannerText: "升级会员, Nano Banana Pro 免费365天!", bannerLink: null, toolbox1Name: "工具箱", toolbox1Enabled: true, toolbox2Name: "工具箱2", toolbox2Enabled: false, tutorialVideoUrl: null, tutorialEnabled: false, rankingEnabled: true, canvasEnabled: true, agentEnabled: true, agentCategoryTreeConfig: normalizeAgentCategoryTreeConfig(undefined), agentSecondaryTypeConfig: defaultAgentSecondaryTypeConfig, realtimeCanvasEnabled: true, realtimeCanvasServiceEnabled: true, storageFailoverConfig, ossImageMigrationConfig, canvasDefaultModelId: null, canvasAutoModeDefaultModelId: null, canvasDetailDefaultModelId: null, lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: "立即升级", lovartHeroTitle: "由壹点AI 让设计更简单", lovartHeroSubtitle: "懂你的设计代理,帮你搞定一切", homeVariant, surpriseCategories, aiDetailGenerationEnabled, lovartComposerPrefix, lovartComposerPreviewTexts, lovartPromptTagVisibility, contactConfig, membershipConfig, membershipUserOverrides, pointsSystemConfig, pointsPackages, dulupayConfig, }; const response = NextResponse.json({ ...baseSettings, lovartPromoText: baseSettings.lovartPromoText || "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: baseSettings.lovartPromoActionText || "立即升级", lovartHeroTitle: baseSettings.lovartHeroTitle || "由壹点AI 让设计更简单", lovartHeroSubtitle: baseSettings.lovartHeroSubtitle || "懂你的设计代理,帮你搞定一切", notification, homeVariant, surpriseCategories, aiDetailGenerationEnabled, lovartComposerPrefix, lovartComposerPreviewTexts, lovartPromptTagVisibility, contactConfig, agentCategoryTreeConfig, agentSecondaryTypeConfig, membershipConfig, membershipUserOverrides, pointsSystemConfig, pointsPackages, dulupayConfig, realtimeCanvasServiceEnabled, storageFailoverConfig, ossImageMigrationConfig, }); // 禁用缓存,确保总是获取最新设置 response.headers.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate"); response.headers.set("Pragma", "no-cache"); response.headers.set("Expires", "0"); return response; } catch (error) { console.error("[Site Settings API] Get error:", error); return NextResponse.json( { error: "Failed to fetch site settings" }, { status: 500 } ); } } const updateSchema = z.object({ siteName: z.string().min(1).max(128).optional(), siteLogo: z.string().nullable().optional(), siteLogoDark: z.string().nullable().optional(), favicon: z.string().nullable().optional(), showAutoModel: z.boolean().optional(), defaultTheme: z.enum(["system", "light", "dark"]).optional(), usageTimeEnabled: z.boolean().optional(), usageTimeStart: z.string().length(5).optional(), usageTimeEnd: z.string().length(5).optional(), usageTimeMessage: z.string().optional(), bannerEnabled: z.boolean().optional(), bannerText: z.string().optional(), bannerLink: z.string().nullable().optional(), toolbox1Name: z.string().max(64).optional(), toolbox1Enabled: z.boolean().optional(), toolbox2Name: z.string().max(64).optional(), toolbox2Enabled: z.boolean().optional(), tutorialVideoUrl: z.string().nullable().optional(), tutorialEnabled: z.boolean().optional(), rankingEnabled: z.boolean().optional(), canvasEnabled: z.boolean().optional(), agentEnabled: z.boolean().optional(), agentCategoryTreeConfig: z .object({ productCategories: z.array(z.string().trim().min(1).max(64)).max(200).optional(), audienceTags: z.array(z.string().trim().min(1).max(64)).max(200).optional(), usageScenarios: z.array(z.string().trim().min(1).max(64)).max(200).optional(), brands: z.array(z.string().trim().min(1).max(64)).max(200).optional(), }) .optional(), agentSecondaryTypeConfig: z .object({ image: z.array(z.string().trim().min(1).max(64)).max(200).optional(), video: z.array(z.string().trim().min(1).max(64)).max(200).optional(), text: z.array(z.string().trim().min(1).max(64)).max(200).optional(), }) .optional(), realtimeCanvasEnabled: z.boolean().optional(), aiDetailGenerationEnabled: z.boolean().optional(), canvasDefaultModelId: z.string().nullable().optional(), canvasAutoModeDefaultModelId: z.string().nullable().optional(), canvasDetailDefaultModelId: z.string().nullable().optional(), lovartPromoText: z.string().max(300).optional(), lovartPromoActionText: z.string().max(32).optional(), lovartHeroTitle: z.string().max(300).optional(), lovartHeroSubtitle: z.string().max(300).optional(), homeVariant: z.enum(["classic", "lovart"]).optional(), surpriseCategories: z.array(z.string().trim().min(1).max(20)).max(12).optional(), lovartComposerPrefix: z.string().trim().min(1).max(40).optional(), lovartComposerPreviewTexts: z.array(z.string().trim().min(1).max(120)).max(20).optional(), lovartPromptTagVisibility: z .object({ nanoBananaPro: z.boolean().optional(), design: z.boolean().optional(), branding: z.boolean().optional(), illustration: z.boolean().optional(), ecommerce: z.boolean().optional(), video: z.boolean().optional(), }) .optional(), contactConfig: z .object({ phone: z.string().max(64).optional(), wechatQrUrl: z.string().max(1000).optional(), }) .optional(), membershipConfig: z.unknown().optional(), membershipUserOverrides: z.unknown().optional(), pointsSystemConfig: z.unknown().optional(), pointsPackages: z.unknown().optional(), dulupayConfig: z.unknown().optional(), storageFailoverConfig: z .object({ enabled: z.boolean().optional(), backupStorageConfigId: z.string().max(128).optional(), }) .optional(), ossImageMigrationConfig: z .object({ enabled: z.boolean().optional(), sourceStorageConfigId: z.string().max(128).optional(), targetStorageConfigId: z.string().max(128).optional(), migrateAfterHours: z.number().min(1).max(720).optional(), cleanupAfterHours: z.number().min(1).max(1440).optional(), batchSize: z.number().min(1).max(100).optional(), }) .optional(), }); // PUT /api/site-settings - 更新网站设置(仅管理员) export async function PUT(request: NextRequest) { try { const session = await auth(); if (!session?.user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } if (session.user.type !== "admin") { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const body = await request.json(); const data = updateSchema.parse(body); const { homeVariant, surpriseCategories, aiDetailGenerationEnabled, lovartComposerPrefix, lovartComposerPreviewTexts, lovartPromptTagVisibility, contactConfig, agentCategoryTreeConfig, agentSecondaryTypeConfig, membershipConfig, membershipUserOverrides, pointsSystemConfig, pointsPackages, dulupayConfig, storageFailoverConfig, ossImageMigrationConfig, ...siteSettingsPayload } = data; // 查找现有设置 const [existing] = await db.select().from(siteSettings).limit(1); if (existing) { // 更新现有设置 const [updated] = await db .update(siteSettings) .set({ ...siteSettingsPayload, agentEnabled: siteSettingsPayload.agentEnabled ?? existing.agentEnabled ?? true, updatedAt: new Date(), updatedBy: session.user.id, }) .where(eq(siteSettings.id, existing.id)) .returning(); if (homeVariant !== undefined) { await db .insert(systemSettings) .values({ key: HOME_VARIANT_KEY, value: homeVariant, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: homeVariant, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (surpriseCategories !== undefined) { const normalized = normalizeSurpriseCategories(surpriseCategories); await db .insert(systemSettings) .values({ key: HOME_SURPRISE_CATEGORIES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (aiDetailGenerationEnabled !== undefined) { await db .insert(systemSettings) .values({ key: AI_DETAIL_GENERATOR_ENABLED_KEY, value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPreviewTexts !== undefined) { const normalized = normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREVIEW_TEXTS_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPrefix !== undefined) { const normalized = normalizeLovartComposerPrefix(lovartComposerPrefix); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREFIX_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartPromptTagVisibility !== undefined) { const normalized = normalizeLovartPromptTagVisibility(lovartPromptTagVisibility); await db .insert(systemSettings) .values({ key: LOVART_PROMPT_TAG_VISIBILITY_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (contactConfig !== undefined) { const normalized = normalizeContactConfig(contactConfig); await db .insert(systemSettings) .values({ key: CONTACT_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentCategoryTreeConfig !== undefined) { const normalized = normalizeAgentCategoryTreeConfig(agentCategoryTreeConfig); await db .insert(systemSettings) .values({ key: AGENT_CATEGORY_TREE_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (agentSecondaryTypeConfig !== undefined) { const normalized = normalizeAgentSecondaryTypeConfig(agentSecondaryTypeConfig); await db .insert(systemSettings) .values({ key: AGENT_SECONDARY_TYPE_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipConfig !== undefined) { const normalized = normalizeMembershipConfig(membershipConfig); await db .insert(systemSettings) .values({ key: MEMBERSHIP_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipUserOverrides !== undefined) { const normalized = normalizeMembershipUserOverrides(membershipUserOverrides); await db .insert(systemSettings) .values({ key: MEMBERSHIP_USER_OVERRIDES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsSystemConfig !== undefined) { const normalized = normalizePointsSystemConfig(pointsSystemConfig); await db .insert(systemSettings) .values({ key: POINTS_SYSTEM_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsPackages !== undefined) { const normalized = normalizePointsPackages(pointsPackages); await db .insert(systemSettings) .values({ key: POINTS_PACKAGES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (dulupayConfig !== undefined) { const normalized = normalizeDulupayConfig(dulupayConfig); await db .insert(systemSettings) .values({ key: DULUPAY_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (storageFailoverConfig !== undefined) { const normalized = normalizeStorageFailoverConfig(storageFailoverConfig); await db .insert(systemSettings) .values({ key: STORAGE_FAILOVER_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (ossImageMigrationConfig !== undefined) { const normalized = normalizeOssImageMigrationConfig(ossImageMigrationConfig); await db .insert(systemSettings) .values({ key: OSS_MIGRATION_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } return NextResponse.json({ ...updated, homeVariant: homeVariant ?? normalizeHomeVariant(undefined), surpriseCategories: surpriseCategories !== undefined ? normalizeSurpriseCategories(surpriseCategories) : normalizeSurpriseCategories(undefined), aiDetailGenerationEnabled: aiDetailGenerationEnabled ?? false, lovartComposerPrefix: lovartComposerPrefix !== undefined ? normalizeLovartComposerPrefix(lovartComposerPrefix) : normalizeLovartComposerPrefix(undefined), lovartComposerPreviewTexts: lovartComposerPreviewTexts !== undefined ? normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts) : normalizeLovartComposerPreviewTexts(undefined), lovartPromptTagVisibility: lovartPromptTagVisibility !== undefined ? normalizeLovartPromptTagVisibility(lovartPromptTagVisibility) : normalizeLovartPromptTagVisibility(undefined), contactConfig: contactConfig !== undefined ? normalizeContactConfig(contactConfig) : normalizeContactConfig(undefined), agentCategoryTreeConfig: agentCategoryTreeConfig !== undefined ? normalizeAgentCategoryTreeConfig(agentCategoryTreeConfig) : normalizeAgentCategoryTreeConfig(undefined), agentSecondaryTypeConfig: agentSecondaryTypeConfig !== undefined ? normalizeAgentSecondaryTypeConfig(agentSecondaryTypeConfig) : defaultAgentSecondaryTypeConfig, membershipConfig: membershipConfig !== undefined ? normalizeMembershipConfig(membershipConfig) : defaultMembershipConfig, membershipUserOverrides: membershipUserOverrides !== undefined ? normalizeMembershipUserOverrides(membershipUserOverrides) : defaultMembershipUserOverrides, pointsSystemConfig: pointsSystemConfig !== undefined ? normalizePointsSystemConfig(pointsSystemConfig) : defaultPointsSystemConfig, pointsPackages: pointsPackages !== undefined ? normalizePointsPackages(pointsPackages) : defaultPointsPackages, dulupayConfig: dulupayConfig !== undefined ? normalizeDulupayConfig(dulupayConfig) : defaultDulupayConfig, storageFailoverConfig: storageFailoverConfig !== undefined ? normalizeStorageFailoverConfig(storageFailoverConfig) : normalizeStorageFailoverConfig(undefined), ossImageMigrationConfig: ossImageMigrationConfig !== undefined ? normalizeOssImageMigrationConfig(ossImageMigrationConfig) : defaultOssImageMigrationConfig, }); } else { // 创建新设置 const [created] = await db .insert(siteSettings) .values({ siteName: siteSettingsPayload.siteName || "AI Chatbot", siteLogo: siteSettingsPayload.siteLogo, siteLogoDark: siteSettingsPayload.siteLogoDark, favicon: siteSettingsPayload.favicon, showAutoModel: siteSettingsPayload.showAutoModel ?? true, defaultTheme: siteSettingsPayload.defaultTheme ?? "system", usageTimeEnabled: siteSettingsPayload.usageTimeEnabled ?? false, usageTimeStart: siteSettingsPayload.usageTimeStart ?? "08:00", usageTimeEnd: siteSettingsPayload.usageTimeEnd ?? "20:00", usageTimeMessage: siteSettingsPayload.usageTimeMessage ?? "由壹点累了也是需要休息的", bannerEnabled: siteSettingsPayload.bannerEnabled ?? true, bannerText: siteSettingsPayload.bannerText ?? "升级会员, Nano Banana Pro 免费365天!", bannerLink: siteSettingsPayload.bannerLink, toolbox1Name: siteSettingsPayload.toolbox1Name ?? "工具箱", toolbox1Enabled: siteSettingsPayload.toolbox1Enabled ?? true, toolbox2Name: siteSettingsPayload.toolbox2Name ?? "工具箱2", toolbox2Enabled: siteSettingsPayload.toolbox2Enabled ?? false, tutorialVideoUrl: siteSettingsPayload.tutorialVideoUrl, tutorialEnabled: siteSettingsPayload.tutorialEnabled ?? false, rankingEnabled: siteSettingsPayload.rankingEnabled ?? true, canvasEnabled: siteSettingsPayload.canvasEnabled ?? true, agentEnabled: siteSettingsPayload.agentEnabled ?? true, realtimeCanvasEnabled: siteSettingsPayload.realtimeCanvasEnabled ?? true, canvasDefaultModelId: siteSettingsPayload.canvasDefaultModelId, canvasAutoModeDefaultModelId: siteSettingsPayload.canvasAutoModeDefaultModelId, canvasDetailDefaultModelId: siteSettingsPayload.canvasDetailDefaultModelId, lovartPromoText: siteSettingsPayload.lovartPromoText ?? "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: siteSettingsPayload.lovartPromoActionText ?? "立即升级", lovartHeroTitle: siteSettingsPayload.lovartHeroTitle ?? "由壹点AI 让设计更简单", lovartHeroSubtitle: siteSettingsPayload.lovartHeroSubtitle ?? "懂你的设计代理,帮你搞定一切", updatedBy: session.user.id, }) .returning(); if (homeVariant !== undefined) { await db .insert(systemSettings) .values({ key: HOME_VARIANT_KEY, value: homeVariant, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: homeVariant, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (surpriseCategories !== undefined) { const normalized = normalizeSurpriseCategories(surpriseCategories); await db .insert(systemSettings) .values({ key: HOME_SURPRISE_CATEGORIES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (aiDetailGenerationEnabled !== undefined) { await db .insert(systemSettings) .values({ key: AI_DETAIL_GENERATOR_ENABLED_KEY, value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: Boolean(aiDetailGenerationEnabled), updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPreviewTexts !== undefined) { const normalized = normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREVIEW_TEXTS_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartComposerPrefix !== undefined) { const normalized = normalizeLovartComposerPrefix(lovartComposerPrefix); await db .insert(systemSettings) .values({ key: LOVART_COMPOSER_PREFIX_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (lovartPromptTagVisibility !== undefined) { const normalized = normalizeLovartPromptTagVisibility(lovartPromptTagVisibility); await db .insert(systemSettings) .values({ key: LOVART_PROMPT_TAG_VISIBILITY_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (contactConfig !== undefined) { const normalized = normalizeContactConfig(contactConfig); await db .insert(systemSettings) .values({ key: CONTACT_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipConfig !== undefined) { const normalized = normalizeMembershipConfig(membershipConfig); await db .insert(systemSettings) .values({ key: MEMBERSHIP_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (membershipUserOverrides !== undefined) { const normalized = normalizeMembershipUserOverrides(membershipUserOverrides); await db .insert(systemSettings) .values({ key: MEMBERSHIP_USER_OVERRIDES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsSystemConfig !== undefined) { const normalized = normalizePointsSystemConfig(pointsSystemConfig); await db .insert(systemSettings) .values({ key: POINTS_SYSTEM_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (pointsPackages !== undefined) { const normalized = normalizePointsPackages(pointsPackages); await db .insert(systemSettings) .values({ key: POINTS_PACKAGES_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (dulupayConfig !== undefined) { const normalized = normalizeDulupayConfig(dulupayConfig); await db .insert(systemSettings) .values({ key: DULUPAY_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (storageFailoverConfig !== undefined) { const normalized = normalizeStorageFailoverConfig(storageFailoverConfig); await db .insert(systemSettings) .values({ key: STORAGE_FAILOVER_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } if (ossImageMigrationConfig !== undefined) { const normalized = normalizeOssImageMigrationConfig(ossImageMigrationConfig); await db .insert(systemSettings) .values({ key: OSS_MIGRATION_CONFIG_KEY, value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }) .onConflictDoUpdate({ target: systemSettings.key, set: { value: normalized, updatedAt: new Date(), updatedBy: session.user.id, }, }); } return NextResponse.json({ ...created, homeVariant: homeVariant ?? normalizeHomeVariant(undefined), surpriseCategories: surpriseCategories !== undefined ? normalizeSurpriseCategories(surpriseCategories) : normalizeSurpriseCategories(undefined), aiDetailGenerationEnabled: aiDetailGenerationEnabled ?? false, lovartComposerPrefix: lovartComposerPrefix !== undefined ? normalizeLovartComposerPrefix(lovartComposerPrefix) : normalizeLovartComposerPrefix(undefined), lovartComposerPreviewTexts: lovartComposerPreviewTexts !== undefined ? normalizeLovartComposerPreviewTexts(lovartComposerPreviewTexts) : normalizeLovartComposerPreviewTexts(undefined), lovartPromptTagVisibility: lovartPromptTagVisibility !== undefined ? normalizeLovartPromptTagVisibility(lovartPromptTagVisibility) : normalizeLovartPromptTagVisibility(undefined), contactConfig: contactConfig !== undefined ? normalizeContactConfig(contactConfig) : normalizeContactConfig(undefined), membershipConfig: membershipConfig !== undefined ? normalizeMembershipConfig(membershipConfig) : defaultMembershipConfig, membershipUserOverrides: membershipUserOverrides !== undefined ? normalizeMembershipUserOverrides(membershipUserOverrides) : defaultMembershipUserOverrides, pointsSystemConfig: pointsSystemConfig !== undefined ? normalizePointsSystemConfig(pointsSystemConfig) : defaultPointsSystemConfig, pointsPackages: pointsPackages !== undefined ? normalizePointsPackages(pointsPackages) : defaultPointsPackages, dulupayConfig: dulupayConfig !== undefined ? normalizeDulupayConfig(dulupayConfig) : defaultDulupayConfig, storageFailoverConfig: storageFailoverConfig !== undefined ? normalizeStorageFailoverConfig(storageFailoverConfig) : normalizeStorageFailoverConfig(undefined), ossImageMigrationConfig: ossImageMigrationConfig !== undefined ? normalizeOssImageMigrationConfig(ossImageMigrationConfig) : defaultOssImageMigrationConfig, }); } } catch (error) { console.error("[Site Settings API] Update error:", error); if (error instanceof z.ZodError) { return NextResponse.json( { error: "Invalid request data", details: error.errors }, { status: 400 } ); } return NextResponse.json( { error: "Failed to update site settings" }, { status: 500 } ); } }