Full-stack app for participatory water pricing using Bezier curves. - Backend: FastAPI + SQLAlchemy + SQLite with JWT auth - Frontend: Nuxt 4 + TypeScript with interactive SVG editor - Math engine: cubic Bezier tarification with Cardano solver - Admin: commune management, household import, vote monitoring, CMS - Citizen: interactive curve editor, vote submission - Docker-compose deployment ready Includes fixes for: - Impact table snake_case/camelCase property mismatch - CMS content backend API + frontend editor (was stub) - Admin route protection middleware - Public content display on commune page - Vote confirmation page link fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
1.7 KiB
TypeScript
68 lines
1.7 KiB
TypeScript
/**
|
|
* Composable for API calls to the FastAPI backend.
|
|
*/
|
|
export function useApi() {
|
|
const config = useRuntimeConfig()
|
|
const baseURL = config.public.apiBase as string
|
|
|
|
function getToken(): string | null {
|
|
if (import.meta.client) {
|
|
return localStorage.getItem('sejeteralo_token')
|
|
}
|
|
return null
|
|
}
|
|
|
|
async function apiFetch<T>(
|
|
path: string,
|
|
options: RequestInit = {},
|
|
): Promise<T> {
|
|
const headers: Record<string, string> = {
|
|
...(options.headers as Record<string, string> || {}),
|
|
}
|
|
|
|
const token = getToken()
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`
|
|
}
|
|
|
|
if (options.body && !(options.body instanceof FormData)) {
|
|
headers['Content-Type'] = 'application/json'
|
|
}
|
|
|
|
let response: Response
|
|
try {
|
|
response = await fetch(`${baseURL}${path}`, {
|
|
...options,
|
|
headers,
|
|
})
|
|
} catch (err) {
|
|
throw new Error(`Impossible de contacter le serveur (${baseURL}). Vérifiez que le backend est lancé.`)
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ detail: response.statusText }))
|
|
throw new Error(error.detail || `Erreur API ${response.status}`)
|
|
}
|
|
|
|
const text = await response.text()
|
|
if (!text) return {} as T
|
|
return JSON.parse(text)
|
|
}
|
|
|
|
return {
|
|
get: <T>(path: string) => apiFetch<T>(path),
|
|
post: <T>(path: string, body?: unknown) =>
|
|
apiFetch<T>(path, {
|
|
method: 'POST',
|
|
body: body instanceof FormData ? body : JSON.stringify(body),
|
|
}),
|
|
put: <T>(path: string, body?: unknown) =>
|
|
apiFetch<T>(path, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(body),
|
|
}),
|
|
delete: <T>(path: string) =>
|
|
apiFetch<T>(path, { method: 'DELETE' }),
|
|
}
|
|
}
|