import createClient from 'openapi-fetch'; import { error, fail, redirect, type ActionFailure, type RequestEvent } from '@sveltejs/kit'; import type { paths } from '$lib/api/schema'; import type { SessionUser } from '$lib/types'; import { apiConfig } from '$lib/server/config'; interface ApiClientOptions { fetch: typeof fetch; accessToken?: string; } export function createApiClient({ fetch, accessToken }: ApiClientOptions) { return createClient({ baseUrl: apiConfig.baseUrl, fetch, headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined }); } export type ApiClient = ReturnType; export function apiFor({ fetch, locals }: Pick): ApiClient { return createApiClient({ fetch, accessToken: locals.accessToken ?? undefined }); } export function loginRedirect(url: URL): never { redirect(302, `/auth?returnTo=${encodeURIComponent(url.pathname + url.search)}`); } export function requireUser(locals: App.Locals, url: URL): SessionUser { if (!locals.user) loginRedirect(url); return locals.user; } export function optionalUser( locals: App.Locals, url: URL, allowAnonymousListening: boolean ): SessionUser | null { if (locals.user) return locals.user; if (!allowAnonymousListening) loginRedirect(url); return null; } export function formString(form: FormData, key: string): string { return String(form.get(key) ?? ''); } export function formFile(form: FormData, key: string): File | null { const value = form.get(key); return value instanceof File && value.size > 0 ? value : null; } interface ActionContext { api: ApiClient; form: FormData; params: Record; locals: App.Locals; url: URL; } export function authedAction( handler: (context: ActionContext) => R | Promise, unauthorizedMessage = 'Inicia sesión.' ) { return async (event: RequestEvent) => { if (!event.locals.accessToken) return fail(401, { message: unauthorizedMessage }); const form = await event.request.formData(); return handler({ api: apiFor(event), form, params: event.params as Record, locals: event.locals, url: event.url }); }; } export function unwrapOrError( result: { data?: T; error?: unknown; response?: Response }, message: string, status?: number ): T { if (result.error || result.data === undefined) { error(status ?? result.response?.status ?? 502, message); } return result.data; } export function apiErrorDetail(error: unknown): string | undefined { if (typeof error !== 'object' || error === null) return undefined; const { errors, detail, title } = error as { errors?: Record; detail?: unknown; title?: unknown; }; if (errors && typeof errors === 'object') { const messages = Object.values(errors) .flat() .filter((entry): entry is string => typeof entry === 'string'); if (messages.length > 0) return messages.join(' '); } if (typeof detail === 'string' && detail !== '') return detail; if (typeof title === 'string' && title !== '') return title; return undefined; } type Failure = ActionFailure<{ message: string; detail?: string }>; export function failOnError(result: { error?: unknown }, message: string): Failure | undefined { return result.error ? fail(502, { message, detail: apiErrorDetail(result.error) }) : undefined; } export function requireData( result: { data?: T; error?: unknown }, message: string ): { data: T; failure?: undefined } | { data?: undefined; failure: Failure } { if (result.error || result.data === undefined) { return { failure: fail(502, { message, detail: apiErrorDetail(result.error) }) }; } return { data: result.data }; }