import "server-only"; import { FALLBACK_HOTEL_NAME } from "@/lib/brand"; import { db, WebsiteSetting } from "@/lib/db"; import { logger } from "@/lib/logger"; import { redis } from "@/lib/redis"; const DEFAULTS: Record = { hotel_name: FALLBACK_HOTEL_NAME, habbo_imaging_url: "/imaging", logo_url: "", nitro_client_url: "", habbo_gamedata_hotel: "it", }; const CACHE_TTL_MS = 300_000; const REDIS_CACHE_KEY = "site_settings"; class SiteSettings { private cache: Map | null = null; private async loadFromDb(): Promise> { try { const rows = await db .select({ key: WebsiteSetting.key, value: WebsiteSetting.value }) .from(WebsiteSetting); return new Map(rows.map((r) => [r.key, r.value])); } catch { logger.warn("Failed to load site settings from database, using defaults"); return new Map(Object.entries(DEFAULTS)); } } private async load(): Promise> { if (redis) { try { const cached = await redis.get(REDIS_CACHE_KEY); if (cached) { const parsed = JSON.parse(cached) as Record; return new Map(Object.entries(parsed)); } } catch { logger.warn("Redis cache read failed for site settings"); } } if (this.cache !== null) return this.cache; const map = await this.loadFromDb(); this.cache = map; if (redis) { try { const obj = Object.fromEntries(map.entries()); await redis.setex( REDIS_CACHE_KEY, Math.ceil(CACHE_TTL_MS / 1000), JSON.stringify(obj), ); } catch { logger.warn("Failed to write site settings to Redis cache"); } } return map; } async getAll(): Promise> { return await this.load(); } async get( key: string, fallback: string | null = null, ): Promise { const map = await this.load(); if (map.has(key)) return map.get(key) as string; // eslint-disable-next-line security/detect-object-injection -- guarded by `key in DEFAULTS` if (key in DEFAULTS) return DEFAULTS[key] as string; return fallback; } async getMany(keys: string[]): Promise> { const map = await this.load(); const result: Record = {}; for (const key of keys) { result[key] = map.get(key) ?? (key in DEFAULTS ? (DEFAULTS[key] as string) : null); } return result; } async getBool(key: string, fallback = false): Promise { const v = await this.get(key, null); if (v === null) return fallback; const s = v.toLowerCase(); return s === "1" || s === "true"; } async update(key: string, value: string): Promise { await db .insert(WebsiteSetting) .values({ key, value }) .onDuplicateKeyUpdate({ set: { value } }); await this.reload(); } async reload(): Promise { this.cache = null; if (redis) { try { await redis.del(REDIS_CACHE_KEY); } catch { logger.warn("Failed to invalidate Redis cache for site settings"); } } } } export const siteSettings = new SiteSettings();