"use server"; import { eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { MANAGED_SETTING_KEYS } from "@/app/admin/settings/cms-settings-config"; import { requirePermissionRateLimited } from "@/lib/admin/guard"; import { db, WebsiteSetting } from "@/lib/db"; import { actionOk, adminAction } from "@/lib/foundation/action"; import { HABBO_GAMEDATA_HOTEL_SETTING_KEY, normalizeHabboGamedataHotel, } from "@/lib/habbo-gamedata-hotel"; import { PERMS } from "@/lib/permissions"; import { clearOfficialHabboFurnidataCache } from "@/lib/services/habbo-furnidata-cache"; import { clearBadgeCache } from "@/lib/services/habboassets"; import { siteSettings } from "@/lib/services/site-settings"; const managedKeySet = new Set(MANAGED_SETTING_KEYS); function normalizeSettingValue(key: string, value: string): string { if (key === HABBO_GAMEDATA_HOTEL_SETTING_KEY) { return normalizeHabboGamedataHotel(value); } return value; } function bustGamedataCachesIfNeeded(key: string): void { if (key === HABBO_GAMEDATA_HOTEL_SETTING_KEY) { clearOfficialHabboFurnidataCache(); clearBadgeCache(); } } const saveManagedSchema = z.object({ settings: z.record(z.string(), z.string()), }); export const saveManagedSettings = adminAction( { permission: PERMS.SETTINGS_EDIT, schema: saveManagedSchema, rateLimitKey: "admin-settings-save", rateLimitMax: 30, }, async (ctx) => { const entries = Object.entries(ctx.data.settings) .filter(([key]) => managedKeySet.has(key)) .map(([key, value]) => [key, normalizeSettingValue(key, value)] as const); await Promise.all( entries.map(([key, value]) => db .insert(WebsiteSetting) .values({ key, value }) .onDuplicateKeyUpdate({ set: { value } }), ), ); await siteSettings.reload(); if (entries.some(([key]) => key === HABBO_GAMEDATA_HOTEL_SETTING_KEY)) { clearOfficialHabboFurnidataCache(); clearBadgeCache(); } revalidatePath("/admin/settings"); revalidatePath("/admin/catalog"); return actionOk({ saved: entries.length }); }, ); export async function updateSetting(formData: FormData): Promise { await requirePermissionRateLimited(PERMS.SETTINGS_EDIT); const key = String(formData.get("key") ?? "") .normalize("NFC") .trim(); const value = normalizeSettingValue( key, String(formData.get("value") ?? "").normalize("NFC"), ); if (!key) return; await db .insert(WebsiteSetting) .values({ key, value }) .onDuplicateKeyUpdate({ set: { value } }); await siteSettings.reload(); bustGamedataCachesIfNeeded(key); revalidatePath("/admin/settings"); } export async function createSetting(formData: FormData): Promise { await requirePermissionRateLimited(PERMS.SETTINGS_EDIT); const key = String(formData.get("key") ?? "") .normalize("NFC") .trim() .slice(0, 255); const value = normalizeSettingValue( key, String(formData.get("value") ?? "").normalize("NFC"), ); const comment = String(formData.get("comment") ?? "") .normalize("NFC") .trim() .slice(0, 255); if (!key) return; await db .insert(WebsiteSetting) .values({ key, value, comment: comment || null }) .onDuplicateKeyUpdate({ set: { value } }); await siteSettings.reload(); bustGamedataCachesIfNeeded(key); revalidatePath("/admin/settings"); } export async function deleteSetting(formData: FormData): Promise { await requirePermissionRateLimited(PERMS.SETTINGS_EDIT); const key = String(formData.get("key") ?? "") .normalize("NFC") .trim(); if (!key) return; await db.delete(WebsiteSetting).where(eq(WebsiteSetting.key, key)); await siteSettings.reload(); bustGamedataCachesIfNeeded(key); revalidatePath("/admin/settings"); }