api.ts - Client API REST
Localisation : frontend/src/lib/api.ts
Client HTTP pour communiquer avec le backend REST API, avec gestion automatique JWT et i18n.
Fonctionnalités
1. Configuration API_URL Dynamique
const API_BASE_URL =
(typeof window !== "undefined" && (window as any).__ENV__?.API_URL) ||
import.meta.env.PUBLIC_API_URL ||
"http://localhost:8080/api/v1";
Priorité :
window.__ENV__.API_URL(runtime, injecté par Docker/Ansible)import.meta.env.PUBLIC_API_URL(build-time, .env)http://localhost:8080/api/v1(fallback développement)
2. Headers Automatiques
function getHeaders(): HeadersInit {
const token = localStorage.getItem("koprogo_token");
return {
"Content-Type": "application/json",
"Accept-Language": getCurrentLanguage(), // nl, fr, de, en
"Authorization": token ? `Bearer ${token}` : undefined
};
}
Headers injectés :
Content-Type: application/json: Format JSONAccept-Language: nl|fr|de|en: Langue utilisateur (svelte-i18n)Authorization: Bearer <token>: JWT token (si connecté)
3. Gestion d’Erreurs
if (!response.ok) {
const error = await response.text();
throw new Error(error || `API Error: ${response.status}`);
}
Les erreurs sont propagées aux composants Svelte pour affichage utilisateur.
API Publique
apiFetch<T>(endpoint, options)
Fonction de base avec headers automatiques.
Paramètres :
endpoint(string) : Chemin API (/buildings) ou URL complèteoptions(RequestInit) : Options fetch standard
Retour : Promise<T>
Exemple :
const buildings = await apiFetch<Building[]>('/buildings');
api.get<T>(endpoint, options?)
Requête GET.
Exemple :
// Simple GET
const buildings = await api.get<Building[]>('/buildings');
// Avec pagination
const response = await api.get<PageResponse<Building>>(
'/buildings?page=1&per_page=20'
);
api.post<T>(endpoint, data?, options?)
Requête POST avec données JSON.
Exemple :
const newBuilding = await api.post<Building>('/buildings', {
name: 'Résidence du Parc',
address: '123 Rue Example',
city: 'Bruxelles',
postal_code: '1000',
country: 'Belgique',
total_units: 50
});
api.put<T>(endpoint, data?, options?)
Requête PUT pour mise à jour.
Exemple :
const updated = await api.put<Building>(`/buildings/${id}`, {
name: 'Nouveau nom'
});
api.delete<T>(endpoint, options?)
Requête DELETE.
Exemple :
await api.delete(`/buildings/${id}`);
api.download(endpoint, filename)
Téléchargement de fichiers (PDF, Excel).
Exemple :
// Télécharger rapport PCN PDF
await api.download(
`/pcn/export/pdf/${buildingId}`,
'rapport-pcn.pdf'
);
// Télécharger export Excel
await api.download(
`/expenses/export/excel?building_id=${buildingId}`,
'charges.xlsx'
);
Implémentation :
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = filename;
link.click();
Utilisation dans Composants Svelte
Pattern Standard :
<script lang="ts">
import { onMount } from 'svelte';
import { api } from '../lib/api';
import type { Building } from '../lib/types';
let buildings: Building[] = [];
let loading = true;
let error = '';
onMount(async () => {
try {
const response = await api.get<PageResponse<Building>>('/buildings');
buildings = response.data;
} catch (e) {
error = e instanceof Error ? e.message : 'Erreur API';
} finally {
loading = false;
}
});
</script>
Pattern CRUD Complet :
// CREATE
async function createBuilding(data: Partial<Building>) {
const building = await api.post<Building>('/buildings', data);
await loadBuildings(); // Refresh
}
// READ
async function loadBuildings() {
const response = await api.get<PageResponse<Building>>(
`/buildings?page=${currentPage}&per_page=${perPage}`
);
buildings = response.data;
}
// UPDATE
async function updateBuilding(id: string, data: Partial<Building>) {
const building = await api.put<Building>(`/buildings/${id}`, data);
await loadBuildings();
}
// DELETE
async function deleteBuilding(id: string) {
await api.delete(`/buildings/${id}`);
await loadBuildings();
}
Gestion de la Langue
Le header Accept-Language est automatiquement synchronisé avec le store svelte-i18n :
import { get } from "svelte/store";
import { locale } from "svelte-i18n";
function getCurrentLanguage(): string {
const currentLocale = get(locale);
return currentLocale || "nl"; // Fallback néerlandais
}
Le backend reçoit la langue et peut renvoyer des erreurs/messages localisés.
Sécurité
JWT Token :
Stocké dans
localStorageavec clékoprogo_tokenInjecté automatiquement dans header
Authorization: Bearer <token>Accessible côté client (XSS risk, envisager httpOnly cookie)
CORS :
Le backend doit configurer CORS pour accepter Accept-Language et Authorization :
// backend/src/main.rs
Cors::default()
.allow_any_origin()
.allowed_methods(vec!["GET", "POST", "PUT", "DELETE"])
.allowed_headers(vec![
header::AUTHORIZATION,
header::ACCEPT,
header::CONTENT_TYPE,
HeaderName::from_static("accept-language")
])
Limitations Connues
Token localStorage :
Vulnérable XSS. Recommandation : migrer vers httpOnly cookie.
Pas de Retry Logic :
Les erreurs réseau ne sont pas retentées automatiquement.
Pas de Request Cancellation :
Les requêtes ne peuvent pas être annulées (AbortController non implémenté).
Pas de Rate Limiting Client :
Pas de throttling/debouncing des requêtes.
Extensions Futures
AbortController : Annulation de requêtes (navigation rapide)
Retry avec Exponential Backoff : Résilience réseau
Request Deduplication : Éviter requêtes identiques simultanées
Cache HTTP : Utiliser headers
Cache-ControlWebSocket Support : Notifications temps réel
GraphQL Support : Alternative REST pour requêtes complexes
Tests
Le client API doit être testé avec :
// tests/unit/api.test.ts
import { describe, it, expect, vi } from 'vitest';
import { api } from '../src/lib/api';
describe('api.get', () => {
it('should add JWT header if token exists', async () => {
localStorage.setItem('koprogo_token', 'test-token');
global.fetch = vi.fn(() => Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: [] })
}));
await api.get('/buildings');
expect(fetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer test-token'
})
})
);
});
});
Références
Backend API :
backend/src/infrastructure/web/handlers/Types :
frontend/src/lib/types.tsConfig :
frontend/src/lib/config.tsSync Service :
frontend/src/lib/sync.ts