Files
SBC/web/src/lib/api.js
T
Claude 96dca2a802 Application complète SLUC Business Club (React + Express + PostgreSQL, Docker)
Implémente l'intégralité de la maquette « SLUC Business Club.dc.html » :
- Site public : accueil (héro administrable, carrousel membres, agenda avec
  inscription en ligne, rencontres passées, demande d'adhésion), annuaire
  avec recherche/filtres/fiche détaillée, page association
- Espace membre : connexion, édition de fiche avec aperçu direct, upload
  logo/photo, statut d'adhésion par saison, changement de mot de passe
- Espace admin : tableau de bord, membres (validation par saison),
  rencontres (CRUD + inscrits + impression + export Excel), inscriptions,
  catégories, contenu du site

Architecture : 3 conteneurs Docker Compose — PostgreSQL 16 (réseau interne,
rôle applicatif restreint), API Express (non-root, read-only fs), nginx
non privilégié (frontend React + reverse-proxy + CSP stricte).

Sécurité : requêtes 100 % paramétrées, bcrypt + JWT httpOnly SameSite=Strict,
vérification d'Origin (CSRF), validation zod, rate limiting, uploads vérifiés
par octets magiques avec noms aléatoires, aucun secret committé.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
2026-07-10 19:43:07 +00:00

53 lines
1.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
async function request(path, options = {}) {
const res = await fetch(path, {
headers: options.body instanceof FormData ? {} : { 'Content-Type': 'application/json' },
credentials: 'same-origin',
...options,
body:
options.body instanceof FormData
? options.body
: options.body
? JSON.stringify(options.body)
: undefined,
});
let data = null;
try {
data = await res.json();
} catch {
/* non-JSON response */
}
if (!res.ok) {
const err = new Error(data?.error || `Erreur ${res.status}`);
err.status = res.status;
throw err;
}
return data;
}
export const api = {
get: (path) => request(path),
post: (path, body) => request(path, { method: 'POST', body }),
put: (path, body) => request(path, { method: 'PUT', body }),
del: (path) => request(path, { method: 'DELETE' }),
upload: (path, file) => {
const fd = new FormData();
fd.append('file', file);
return request(path, { method: 'POST', body: fd });
},
};
const MOIS = ['Jan', 'Fév', 'Mars', 'Avr', 'Mai', 'Juin', 'Juil', 'Août', 'Sept', 'Oct', 'Nov', 'Déc'];
export function dateParts(dateStr) {
const d = new Date(dateStr);
if (Number.isNaN(d.getTime())) return { jour: '--', mois: '' };
return { jour: String(d.getUTCDate()).padStart(2, '0'), mois: MOIS[d.getUTCMonth()] };
}
export function seasonLabel(d = new Date()) {
const y = d.getFullYear();
return d.getMonth() >= 8 ? `${y}–${y + 1}` : `${y - 1}–${y}`;
}
export const statutLabel = (s) => (s === 'confirmee' ? 'Confirmée' : 'En attente');