stage 1 of oauth removal, switch to sanctum pat tokens
This commit is contained in:
@@ -838,10 +838,17 @@ function eventEndpoint(slug: string): string {
|
||||
return `/api/v1/tenant/events/${encodeURIComponent(slug)}`;
|
||||
}
|
||||
|
||||
export async function getEvents(): Promise<TenantEvent[]> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/events');
|
||||
const data = await jsonOrThrow<EventListResponse>(response, 'Failed to load events');
|
||||
return (data.data ?? []).map(normalizeEvent);
|
||||
export async function getEvents(options?: { force?: boolean }): Promise<TenantEvent[]> {
|
||||
return cachedFetch(
|
||||
CacheKeys.events,
|
||||
async () => {
|
||||
const response = await authorizedFetch('/api/v1/tenant/events');
|
||||
const data = await jsonOrThrow<EventListResponse>(response, 'Failed to load events');
|
||||
return (data.data ?? []).map(normalizeEvent);
|
||||
},
|
||||
DEFAULT_CACHE_TTL,
|
||||
options?.force === true,
|
||||
);
|
||||
}
|
||||
|
||||
export async function createEvent(payload: EventSavePayload): Promise<{ event: TenantEvent; balance: number }> {
|
||||
@@ -851,7 +858,9 @@ export async function createEvent(payload: EventSavePayload): Promise<{ event: T
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await jsonOrThrow<CreatedEventResponse>(response, 'Failed to create event');
|
||||
return { event: normalizeEvent(data.data), balance: data.balance };
|
||||
const result = { event: normalizeEvent(data.data), balance: data.balance };
|
||||
invalidateTenantApiCache([CacheKeys.events, CacheKeys.dashboard]);
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function updateEvent(slug: string, payload: Partial<EventSavePayload>): Promise<TenantEvent> {
|
||||
@@ -861,7 +870,9 @@ export async function updateEvent(slug: string, payload: Partial<EventSavePayloa
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const data = await jsonOrThrow<EventResponse>(response, 'Failed to update event');
|
||||
return normalizeEvent(data.data);
|
||||
const event = normalizeEvent(data.data);
|
||||
invalidateTenantApiCache([CacheKeys.events, CacheKeys.dashboard]);
|
||||
return event;
|
||||
}
|
||||
|
||||
export async function getEvent(slug: string): Promise<TenantEvent> {
|
||||
@@ -1130,38 +1141,52 @@ async function fetchTenantPackagesEndpoint(): Promise<Response> {
|
||||
return first;
|
||||
}
|
||||
|
||||
export async function getDashboardSummary(): Promise<DashboardSummary | null> {
|
||||
const response = await authorizedFetch('/api/v1/tenant/dashboard');
|
||||
if (response.status === 404) {
|
||||
return null;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
const fallbackMessage = i18n.t('dashboard:errors.loadFailed', 'Dashboard konnte nicht geladen werden.');
|
||||
emitApiErrorEvent({ message: fallbackMessage, status: response.status });
|
||||
console.error('[API] Failed to load dashboard', response.status, payload);
|
||||
throw new Error(fallbackMessage);
|
||||
}
|
||||
const json = (await response.json()) as JsonValue;
|
||||
return normalizeDashboard(json);
|
||||
export async function getDashboardSummary(options?: { force?: boolean }): Promise<DashboardSummary | null> {
|
||||
return cachedFetch(
|
||||
CacheKeys.dashboard,
|
||||
async () => {
|
||||
const response = await authorizedFetch('/api/v1/tenant/dashboard');
|
||||
if (response.status === 404) {
|
||||
return null;
|
||||
}
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
const fallbackMessage = i18n.t('dashboard:errors.loadFailed', 'Dashboard konnte nicht geladen werden.');
|
||||
emitApiErrorEvent({ message: fallbackMessage, status: response.status });
|
||||
console.error('[API] Failed to load dashboard', response.status, payload);
|
||||
throw new Error(fallbackMessage);
|
||||
}
|
||||
const json = (await response.json()) as JsonValue;
|
||||
return normalizeDashboard(json);
|
||||
},
|
||||
DEFAULT_CACHE_TTL,
|
||||
options?.force === true,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getTenantPackagesOverview(): Promise<{
|
||||
export async function getTenantPackagesOverview(options?: { force?: boolean }): Promise<{
|
||||
packages: TenantPackageSummary[];
|
||||
activePackage: TenantPackageSummary | null;
|
||||
}> {
|
||||
const response = await fetchTenantPackagesEndpoint();
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
const fallbackMessage = i18n.t('common:errors.generic', 'Etwas ist schiefgelaufen. Bitte versuche es erneut.');
|
||||
emitApiErrorEvent({ message: fallbackMessage, status: response.status });
|
||||
console.error('[API] Failed to load tenant packages', response.status, payload);
|
||||
throw new Error(fallbackMessage);
|
||||
}
|
||||
const data = (await response.json()) as TenantPackagesResponse;
|
||||
const packages = Array.isArray(data.data) ? data.data.map(normalizeTenantPackage) : [];
|
||||
const activePackage = data.active_package ? normalizeTenantPackage(data.active_package) : null;
|
||||
return { packages, activePackage };
|
||||
return cachedFetch(
|
||||
CacheKeys.packages,
|
||||
async () => {
|
||||
const response = await fetchTenantPackagesEndpoint();
|
||||
if (!response.ok) {
|
||||
const payload = await safeJson(response);
|
||||
const fallbackMessage = i18n.t('common:errors.generic', 'Etwas ist schiefgelaufen. Bitte versuche es erneut.');
|
||||
emitApiErrorEvent({ message: fallbackMessage, status: response.status });
|
||||
console.error('[API] Failed to load tenant packages', response.status, payload);
|
||||
throw new Error(fallbackMessage);
|
||||
}
|
||||
const data = (await response.json()) as TenantPackagesResponse;
|
||||
const packages = Array.isArray(data.data) ? data.data.map(normalizeTenantPackage) : [];
|
||||
const activePackage = data.active_package ? normalizeTenantPackage(data.active_package) : null;
|
||||
return { packages, activePackage };
|
||||
},
|
||||
DEFAULT_CACHE_TTL * 5,
|
||||
options?.force === true,
|
||||
);
|
||||
}
|
||||
|
||||
export type NotificationPreferenceResponse = {
|
||||
@@ -1651,3 +1676,67 @@ export async function removeEventMember(eventIdentifier: number | string, member
|
||||
throw new Error('Failed to remove member');
|
||||
}
|
||||
}
|
||||
type CacheEntry<T> = {
|
||||
value?: T;
|
||||
expiresAt: number;
|
||||
promise?: Promise<T>;
|
||||
};
|
||||
|
||||
const tenantApiCache = new Map<string, CacheEntry<unknown>>();
|
||||
const DEFAULT_CACHE_TTL = 60_000;
|
||||
|
||||
const CacheKeys = {
|
||||
dashboard: 'tenant:dashboard',
|
||||
events: 'tenant:events',
|
||||
packages: 'tenant:packages',
|
||||
} as const;
|
||||
|
||||
function cachedFetch<T>(
|
||||
key: string,
|
||||
fetcher: () => Promise<T>,
|
||||
ttl: number = DEFAULT_CACHE_TTL,
|
||||
force = false,
|
||||
): Promise<T> {
|
||||
if (force) {
|
||||
tenantApiCache.delete(key);
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const existing = tenantApiCache.get(key) as CacheEntry<T> | undefined;
|
||||
|
||||
if (!force && existing) {
|
||||
if (existing.promise) {
|
||||
return existing.promise;
|
||||
}
|
||||
|
||||
if (existing.value !== undefined && existing.expiresAt > now) {
|
||||
return Promise.resolve(existing.value);
|
||||
}
|
||||
}
|
||||
|
||||
const promise = fetcher()
|
||||
.then((value) => {
|
||||
tenantApiCache.set(key, { value, expiresAt: Date.now() + ttl });
|
||||
return value;
|
||||
})
|
||||
.catch((error) => {
|
||||
tenantApiCache.delete(key);
|
||||
throw error;
|
||||
});
|
||||
|
||||
tenantApiCache.set(key, { value: existing?.value, expiresAt: existing?.expiresAt ?? 0, promise });
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function invalidateTenantApiCache(keys?: string | string[]): void {
|
||||
if (!keys) {
|
||||
tenantApiCache.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = Array.isArray(keys) ? keys : [keys];
|
||||
for (const key of entries) {
|
||||
tenantApiCache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user