Added opaque join-token support across backend and frontend: new migration/model/service/endpoints, guest controllers now resolve tokens, and the demo seeder seeds a token. Tenant event details list/manage tokens with copy/revoke actions, and the guest PWA uses tokens end-to-end (routing, storage, uploads, achievements, etc.). Docs TODO updated to reflect completed steps.

This commit is contained in:
Codex Agent
2025-10-12 10:32:37 +02:00
parent d04e234ca0
commit 9394c3171e
73 changed files with 3277 additions and 911 deletions

View File

@@ -7,6 +7,7 @@ export interface EventData {
default_locale: string;
created_at: string;
updated_at: string;
join_token?: string | null;
type?: {
slug: string;
name: string;
@@ -33,14 +34,14 @@ export interface EventStats {
latestPhotoAt: string | null;
}
export async function fetchEvent(slug: string): Promise<EventData> {
const res = await fetch(`/api/v1/events/${slug}`);
export async function fetchEvent(eventKey: string): Promise<EventData> {
const res = await fetch(`/api/v1/events/${encodeURIComponent(eventKey)}`);
if (!res.ok) throw new Error('Event fetch failed');
return await res.json();
}
export async function fetchStats(slug: string): Promise<EventStats> {
const res = await fetch(`/api/v1/events/${slug}/stats`, {
export async function fetchStats(eventKey: string): Promise<EventStats> {
const res = await fetch(`/api/v1/events/${encodeURIComponent(eventKey)}/stats`, {
headers: {
'X-Device-Id': getDeviceId(),
},
@@ -48,17 +49,17 @@ export async function fetchStats(slug: string): Promise<EventStats> {
if (!res.ok) throw new Error('Stats fetch failed');
const json = await res.json();
return {
onlineGuests: json.onlineGuests ?? 0,
tasksSolved: json.tasksSolved ?? 0,
latestPhotoAt: json.latestPhotoAt ?? null,
onlineGuests: json.online_guests ?? json.onlineGuests ?? 0,
tasksSolved: json.tasks_solved ?? json.tasksSolved ?? 0,
latestPhotoAt: json.latest_photo_at ?? json.latestPhotoAt ?? null,
};
}
export async function getEventPackage(slug: string): Promise<EventPackage | null> {
const res = await fetch(`/api/v1/events/${slug}/package`);
const res = await fetch(`/api/v1/events/${encodeURIComponent(slug)}/package`);
if (!res.ok) {
if (res.status === 404) return null;
throw new Error('Failed to load event package');
}
return await res.json();
}
}

View File

@@ -77,7 +77,7 @@ export async function uploadPhoto(slug: string, file: File, taskId?: number, emo
if (emotionSlug) formData.append('emotion_slug', emotionSlug);
formData.append('device_id', getDeviceId());
const res = await fetch(`/api/v1/events/${slug}/upload`, {
const res = await fetch(`/api/v1/events/${encodeURIComponent(slug)}/upload`, {
method: 'POST',
credentials: 'include',
body: formData,
@@ -99,4 +99,3 @@ export async function uploadPhoto(slug: string, file: File, taskId?: number, emo
const json = await res.json();
return json.photo_id ?? json.id ?? json.data?.id ?? 0;
}