Files
AuditShield/frontend/lib/api.ts
Vincent 0fe1a1b751 feat: Phase 1 — socle backend FastAPI + frontend Next.js
Backend (FastAPI + SQLAlchemy):
- Modèles : User, Client, Audit, Cible, Vulnérabilité, Action
- Auth JWT (register/login/me) avec bcrypt
- Routes CRUD complets : clients, audits, cibles, vulnérabilités, actions
- Schémas Pydantic v2, migrations Alembic configurées
- Rate limiting (slowapi), CORS, structure scanners/reports pour phase 2

Frontend (Next.js 14 App Router):
- shadcn/ui : Button, Input, Card, Badge, Label
- Page login avec gestion token JWT
- Dashboard avec stats temps réel
- Pages Clients (grille) et Audits (liste) avec recherche
- Layout avec sidebar navigation + protection auth
- Dockerfiles multi-stage (backend + frontend standalone)

Infrastructure:
- docker-compose.yml : postgres, redis, backend, frontend
- docker-compose.prod.yml avec labels Traefik
- .env.example complet
- .gitignore mis à jour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:16:12 +01:00

142 lines
3.6 KiB
TypeScript

const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "";
type RequestOptions = {
method?: string;
body?: unknown;
token?: string;
};
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const { method = "GET", body, token } = options;
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const res = await fetch(`${API_BASE}/api${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const error = await res.json().catch(() => ({ detail: "Erreur réseau" }));
throw new Error(error.detail ?? "Erreur inconnue");
}
if (res.status === 204) return undefined as T;
return res.json();
}
// Auth
export const authApi = {
login: (email: string, password: string) => {
const form = new URLSearchParams({ username: email, password });
return fetch(`${API_BASE}/api/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: form.toString(),
}).then((r) => {
if (!r.ok) throw new Error("Identifiants incorrects");
return r.json() as Promise<{ access_token: string; token_type: string }>;
});
},
me: (token: string) => request<User>("/auth/me", { token }),
};
// Clients
export const clientsApi = {
list: (token: string) => request<Client[]>("/clients/", { token }),
get: (id: number, token: string) => request<Client>(`/clients/${id}`, { token }),
create: (data: ClientCreate, token: string) =>
request<Client>("/clients/", { method: "POST", body: data, token }),
update: (id: number, data: Partial<ClientCreate>, token: string) =>
request<Client>(`/clients/${id}`, { method: "PATCH", body: data, token }),
delete: (id: number, token: string) =>
request<void>(`/clients/${id}`, { method: "DELETE", token }),
};
// Audits
export const auditsApi = {
list: (token: string, clientId?: number) =>
request<Audit[]>(`/audits/${clientId ? `?client_id=${clientId}` : ""}`, { token }),
get: (id: number, token: string) => request<AuditDetail>(`/audits/${id}`, { token }),
create: (data: AuditCreate, token: string) =>
request<Audit>("/audits/", { method: "POST", body: data, token }),
update: (id: number, data: Partial<AuditCreate>, token: string) =>
request<Audit>(`/audits/${id}`, { method: "PATCH", body: data, token }),
};
// Types
export type User = {
id: number;
email: string;
fullName: string;
isActive: boolean;
isAdmin: boolean;
};
export type Client = {
id: number;
nom: string;
contact: string | null;
email: string | null;
telephone: string | null;
notes: string | null;
createdAt: string;
};
export type ClientCreate = {
nom: string;
contact?: string;
email?: string;
telephone?: string;
notes?: string;
};
export type Audit = {
id: number;
clientId: number;
nom: string;
statut: "planifie" | "en_cours" | "termine" | "annule";
dateDebut: string | null;
dateFin: string | null;
scoreGlobal: number | null;
createdAt: string;
};
export type AuditCreate = {
clientId: number;
nom: string;
dateDebut?: string;
};
export type AuditDetail = Audit & {
cibles: Cible[];
vulnerabilites: Vulnerabilite[];
};
export type Cible = {
id: number;
auditId: number;
type: "ip" | "domaine" | "subnet";
valeur: string;
validee: boolean;
};
export type Vulnerabilite = {
id: number;
auditId: number;
criticite: "critique" | "important" | "modere" | "faible";
titre: string;
description: string;
recommandation: string;
cve: string | null;
cvssScore: number | null;
cible: string | null;
};