diff --git a/app/api/admin/settings/route.ts b/app/api/admin/settings/route.ts index 37aabde9e..ab4b38c94 100644 --- a/app/api/admin/settings/route.ts +++ b/app/api/admin/settings/route.ts @@ -67,6 +67,7 @@ import { EXTERNAL_IMAGE_API_CONFIG_KEY, normalizeExternalImageApiConfig, } from "@/lib/external-image-api/config"; +import { clearScopedSiteSettingsCache } from "@/lib/site-settings/public-cache"; const settingSchema = z.object({ key: z.string(), @@ -270,6 +271,7 @@ export async function POST(request: Request) { value: normalizeExternalImageApiConfig(value), updatedBy: session.user.id, }); + clearScopedSiteSettingsCache(); return Response.json({ setting: saved[0], @@ -388,6 +390,7 @@ export async function POST(request: Request) { value: nextValue, updatedBy: session.user.id, }); + clearScopedSiteSettingsCache(); return Response.json({ setting: setting[0] }); } catch (error) { diff --git a/app/api/site-settings/route.ts b/app/api/site-settings/route.ts index dcc0367ad..0e4adec2f 100644 --- a/app/api/site-settings/route.ts +++ b/app/api/site-settings/route.ts @@ -98,6 +98,13 @@ import { normalizeOssImageMigrationConfig, OSS_MIGRATION_CONFIG_KEY, } from "@/lib/storage/migration-config"; +import { + clearScopedSiteSettingsCache, + deleteScopedSiteSettingsCache, + getScopedSiteSettingsCache, + PUBLIC_SITE_SETTINGS_CACHE_TTL_MS, + setScopedSiteSettingsCache, +} from "@/lib/site-settings/public-cache"; import { getHomeChatAnimationHtmlBytes, HOME_CHAT_ANIMATION_CONFIG_KEY, @@ -502,22 +509,19 @@ const PUBLIC_SITE_SETTINGS_SCOPES = new Set([ "home", ]); -const SCOPED_SITE_SETTINGS_SERVER_CACHE_TTL_MS = 30_000; -const scopedSiteSettingsServerCache = new Map< - string, - { data: Record; expiresAt: number } ->(); - function createScopedSiteSettingsResponse( scope: PublicSiteSettingsScope, data: Record, cacheStatus: "HIT" | "MISS" = "MISS", cacheKey: string = scope ) { - scopedSiteSettingsServerCache.set(cacheKey, { - data, - expiresAt: Date.now() + SCOPED_SITE_SETTINGS_SERVER_CACHE_TTL_MS, - }); + if (cacheStatus !== "HIT") { + setScopedSiteSettingsCache( + cacheKey, + data, + PUBLIC_SITE_SETTINGS_CACHE_TTL_MS + ); + } const response = NextResponse.json(data); // Lightweight public settings change rarely; keep them browser-local briefly @@ -883,7 +887,7 @@ export async function GET(request: NextRequest) { const cacheKey = brandName.trim() ? `${scopedScope}:${brandName.trim().toLowerCase()}` : scopedScope; - const cached = scopedSiteSettingsServerCache.get(cacheKey); + const cached = getScopedSiteSettingsCache(cacheKey); if (cached && cached.expiresAt > Date.now()) { return createScopedSiteSettingsResponse( scopedScope, @@ -892,6 +896,9 @@ export async function GET(request: NextRequest) { cacheKey ); } + if (cached) { + deleteScopedSiteSettingsCache(cacheKey); + } return getScopedPublicSiteSettings(scopedScope, { brandName, @@ -2625,7 +2632,7 @@ export async function PUT(request: NextRequest) { } await persistHomeAnimationConfig(); - scopedSiteSettingsServerCache.clear(); + clearScopedSiteSettingsCache(); return NextResponse.json({ ...updated, @@ -3274,7 +3281,7 @@ export async function PUT(request: NextRequest) { }, }); } - scopedSiteSettingsServerCache.clear(); + clearScopedSiteSettingsCache(); return NextResponse.json({ ...created, diff --git a/lib/site-settings/public-cache.ts b/lib/site-settings/public-cache.ts new file mode 100644 index 000000000..b7e588634 --- /dev/null +++ b/lib/site-settings/public-cache.ts @@ -0,0 +1,52 @@ +const PUBLIC_SITE_SETTINGS_CACHE_TTL_MS = 30_000; +const PUBLIC_SITE_SETTINGS_CACHE_KEY = Symbol.for( + "ai-chatbot.public-site-settings-cache", +); + +type PublicSiteSettingsCacheEntry = { + data: Record; + expiresAt: number; +}; + +function getPublicSiteSettingsCacheStore() { + const globalCache = globalThis as typeof globalThis & { + [PUBLIC_SITE_SETTINGS_CACHE_KEY]?: Map< + string, + PublicSiteSettingsCacheEntry + >; + }; + + if (!globalCache[PUBLIC_SITE_SETTINGS_CACHE_KEY]) { + globalCache[PUBLIC_SITE_SETTINGS_CACHE_KEY] = new Map< + string, + PublicSiteSettingsCacheEntry + >(); + } + + return globalCache[PUBLIC_SITE_SETTINGS_CACHE_KEY]!; +} + +export function getScopedSiteSettingsCache(cacheKey: string) { + return getPublicSiteSettingsCacheStore().get(cacheKey) ?? null; +} + +export function setScopedSiteSettingsCache( + cacheKey: string, + data: Record, + ttlMs: number = PUBLIC_SITE_SETTINGS_CACHE_TTL_MS, +) { + getPublicSiteSettingsCacheStore().set(cacheKey, { + data, + expiresAt: Date.now() + ttlMs, + }); +} + +export function deleteScopedSiteSettingsCache(cacheKey: string) { + getPublicSiteSettingsCacheStore().delete(cacheKey); +} + +export function clearScopedSiteSettingsCache() { + getPublicSiteSettingsCacheStore().clear(); +} + +export { PUBLIC_SITE_SETTINGS_CACHE_TTL_MS };