fix: invalidate cached public site settings
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-18 17:52:16 +08:00
parent 33f25f4b21
commit bd98282bc5
3 changed files with 75 additions and 13 deletions

View File

@ -67,6 +67,7 @@ import {
EXTERNAL_IMAGE_API_CONFIG_KEY, EXTERNAL_IMAGE_API_CONFIG_KEY,
normalizeExternalImageApiConfig, normalizeExternalImageApiConfig,
} from "@/lib/external-image-api/config"; } from "@/lib/external-image-api/config";
import { clearScopedSiteSettingsCache } from "@/lib/site-settings/public-cache";
const settingSchema = z.object({ const settingSchema = z.object({
key: z.string(), key: z.string(),
@ -270,6 +271,7 @@ export async function POST(request: Request) {
value: normalizeExternalImageApiConfig(value), value: normalizeExternalImageApiConfig(value),
updatedBy: session.user.id, updatedBy: session.user.id,
}); });
clearScopedSiteSettingsCache();
return Response.json({ return Response.json({
setting: saved[0], setting: saved[0],
@ -388,6 +390,7 @@ export async function POST(request: Request) {
value: nextValue, value: nextValue,
updatedBy: session.user.id, updatedBy: session.user.id,
}); });
clearScopedSiteSettingsCache();
return Response.json({ setting: setting[0] }); return Response.json({ setting: setting[0] });
} catch (error) { } catch (error) {

View File

@ -98,6 +98,13 @@ import {
normalizeOssImageMigrationConfig, normalizeOssImageMigrationConfig,
OSS_MIGRATION_CONFIG_KEY, OSS_MIGRATION_CONFIG_KEY,
} from "@/lib/storage/migration-config"; } from "@/lib/storage/migration-config";
import {
clearScopedSiteSettingsCache,
deleteScopedSiteSettingsCache,
getScopedSiteSettingsCache,
PUBLIC_SITE_SETTINGS_CACHE_TTL_MS,
setScopedSiteSettingsCache,
} from "@/lib/site-settings/public-cache";
import { import {
getHomeChatAnimationHtmlBytes, getHomeChatAnimationHtmlBytes,
HOME_CHAT_ANIMATION_CONFIG_KEY, HOME_CHAT_ANIMATION_CONFIG_KEY,
@ -502,22 +509,19 @@ const PUBLIC_SITE_SETTINGS_SCOPES = new Set<string>([
"home", "home",
]); ]);
const SCOPED_SITE_SETTINGS_SERVER_CACHE_TTL_MS = 30_000;
const scopedSiteSettingsServerCache = new Map<
string,
{ data: Record<string, unknown>; expiresAt: number }
>();
function createScopedSiteSettingsResponse( function createScopedSiteSettingsResponse(
scope: PublicSiteSettingsScope, scope: PublicSiteSettingsScope,
data: Record<string, unknown>, data: Record<string, unknown>,
cacheStatus: "HIT" | "MISS" = "MISS", cacheStatus: "HIT" | "MISS" = "MISS",
cacheKey: string = scope cacheKey: string = scope
) { ) {
scopedSiteSettingsServerCache.set(cacheKey, { if (cacheStatus !== "HIT") {
data, setScopedSiteSettingsCache(
expiresAt: Date.now() + SCOPED_SITE_SETTINGS_SERVER_CACHE_TTL_MS, cacheKey,
}); data,
PUBLIC_SITE_SETTINGS_CACHE_TTL_MS
);
}
const response = NextResponse.json(data); const response = NextResponse.json(data);
// Lightweight public settings change rarely; keep them browser-local briefly // Lightweight public settings change rarely; keep them browser-local briefly
@ -883,7 +887,7 @@ export async function GET(request: NextRequest) {
const cacheKey = brandName.trim() const cacheKey = brandName.trim()
? `${scopedScope}:${brandName.trim().toLowerCase()}` ? `${scopedScope}:${brandName.trim().toLowerCase()}`
: scopedScope; : scopedScope;
const cached = scopedSiteSettingsServerCache.get(cacheKey); const cached = getScopedSiteSettingsCache(cacheKey);
if (cached && cached.expiresAt > Date.now()) { if (cached && cached.expiresAt > Date.now()) {
return createScopedSiteSettingsResponse( return createScopedSiteSettingsResponse(
scopedScope, scopedScope,
@ -892,6 +896,9 @@ export async function GET(request: NextRequest) {
cacheKey cacheKey
); );
} }
if (cached) {
deleteScopedSiteSettingsCache(cacheKey);
}
return getScopedPublicSiteSettings(scopedScope, { return getScopedPublicSiteSettings(scopedScope, {
brandName, brandName,
@ -2625,7 +2632,7 @@ export async function PUT(request: NextRequest) {
} }
await persistHomeAnimationConfig(); await persistHomeAnimationConfig();
scopedSiteSettingsServerCache.clear(); clearScopedSiteSettingsCache();
return NextResponse.json({ return NextResponse.json({
...updated, ...updated,
@ -3274,7 +3281,7 @@ export async function PUT(request: NextRequest) {
}, },
}); });
} }
scopedSiteSettingsServerCache.clear(); clearScopedSiteSettingsCache();
return NextResponse.json({ return NextResponse.json({
...created, ...created,

View File

@ -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<string, unknown>;
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<string, unknown>,
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 };