feat: localize guest endpoints and caching

This commit is contained in:
Codex Agent
2025-11-12 15:48:06 +01:00
parent d91108c883
commit 062932ce38
19 changed files with 1538 additions and 595 deletions

View File

@@ -1,4 +1,5 @@
import { getDeviceId } from '../lib/device';
import { DEFAULT_LOCALE } from '../i18n/messages';
export interface AchievementBadge {
id: string;
@@ -86,25 +87,60 @@ function safeString(value: unknown): string {
return typeof value === 'string' ? value : '';
}
type FetchAchievementsOptions = {
guestName?: string;
locale?: string;
signal?: AbortSignal;
forceRefresh?: boolean;
};
type AchievementsCacheEntry = {
data: AchievementsPayload;
etag: string | null;
};
const achievementsCache = new Map<string, AchievementsCacheEntry>();
export async function fetchAchievements(
eventToken: string,
guestName?: string,
signal?: AbortSignal
options: FetchAchievementsOptions = {}
): Promise<AchievementsPayload> {
const { guestName, signal, forceRefresh } = options;
const locale = options.locale ?? DEFAULT_LOCALE;
const params = new URLSearchParams();
if (guestName && guestName.trim().length > 0) {
params.set('guest_name', guestName.trim());
}
if (locale) {
params.set('locale', locale);
}
const deviceId = getDeviceId();
const cacheKey = [eventToken, locale, guestName?.trim() ?? '', deviceId].join(':');
const cached = forceRefresh ? null : achievementsCache.get(cacheKey);
const headers: HeadersInit = {
'X-Device-Id': deviceId,
'Cache-Control': 'no-store',
Accept: 'application/json',
'X-Locale': locale,
};
if (cached?.etag) {
headers['If-None-Match'] = cached.etag;
}
const response = await fetch(`/api/v1/events/${encodeURIComponent(eventToken)}/achievements?${params.toString()}`, {
method: 'GET',
headers: {
'X-Device-Id': getDeviceId(),
'Cache-Control': 'no-store',
},
headers,
signal,
});
if (response.status === 304 && cached) {
return cached.data;
}
if (!response.ok) {
const message = await response.text();
throw new Error(message || 'Achievements request failed');
@@ -190,7 +226,7 @@ export async function fetchAchievements(
thumbnail: row.thumbnail ? safeString(row.thumbnail) : null,
}));
return {
const payload: AchievementsPayload = {
summary: {
totalPhotos: toNumber(summary.total_photos),
uniqueGuests: toNumber(summary.unique_guests),
@@ -209,4 +245,11 @@ export async function fetchAchievements(
},
feed,
};
achievementsCache.set(cacheKey, {
data: payload,
etag: response.headers.get('ETag'),
});
return payload;
}