- Backend NestJS : CRUD api_entries + categories, upload YAML multi-fichiers, génération docs AsyncAPI (@asyncapi/cli@6.0.0) et OpenAPI (redocly) - Fix: route wildcard GET /api/apis/:id/*path pour servir les assets statiques (CSS/JS) générés par AsyncAPI HTML template (contournement bug path-to-regexp v8) - Frontend Angular 19 : pages catalog, browse, api-detail, doc-viewer, upload (DSFR) - Seeder de données de démo (idempotent) - Docker Compose dev + Dockerfiles + manifests K8s - Documentation : README, architecture, référence API REST Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
7.6 KiB
Référence API — Datacat
Base URL en développement : http://localhost:3010
Toutes les routes JSON retournent Content-Type: application/json.
Health
GET /health
Vérifie que le serveur est opérationnel.
Réponse 200
{ "status": "ok" }
APIs (/api/apis)
GET /api/apis
Liste les entrées avec filtres optionnels et pagination.
Query parameters
| Paramètre | Type | Défaut | Description |
|---|---|---|---|
search |
string | — | Recherche dans le titre et la description (case-insensitive) |
type |
ASYNCAPI | OPENAPI |
— | Filtre par type |
categoryId |
UUID | — | Filtre par catégorie |
page |
number | 1 |
Page (min 1) |
limit |
number | 20 |
Taille de page (min 1, max 100) |
Réponse 200
{
"items": [
{
"id": "uuid",
"title": "My API",
"version": "1.0.0",
"description": "...",
"type": "OPENAPI",
"status": "GENERATED",
"categoryId": "uuid-or-null",
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
],
"total": 42,
"page": 1,
"limit": 20
}
itemscontient desApiEntryListItem(sansyamlContentnihtmlPath).
GET /api/apis/:id
Retourne le détail complet d'une entrée.
Réponse 200
{
"id": "uuid",
"title": "My API",
"version": "1.0.0",
"description": "...",
"type": "OPENAPI",
"yamlContent": "openapi: '3.0.0'\n...",
"htmlPath": "/app/docs-output/uuid/index.html",
"status": "GENERATED",
"errorMessage": null,
"categoryId": "uuid-or-null",
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
Réponse 404 — entrée introuvable
PATCH /api/apis/:id
Met à jour les métadonnées d'une entrée (champs éditables par l'utilisateur).
Body JSON (tous les champs sont optionnels)
{
"title": "New title",
"version": "2.0.0",
"description": "Updated description",
"categoryId": "uuid-or-null"
}
Réponse 200 — ApiEntry complet mis à jour
Réponse 404 — entrée introuvable
DELETE /api/apis/:id
Supprime une entrée.
Réponse 204 — succès (pas de corps)
Réponse 404 — entrée introuvable
POST /api/apis/:id/regenerate
Relance la génération de documentation pour une entrée existante.
Remet le statut à PENDING et déclenche DocsGenerationService.generate() en arrière-plan.
Réponse 200 — ApiEntry avec status: "PENDING"
Réponse 404 — entrée introuvable
GET /api/apis/:id/docs
Retourne le fichier HTML de documentation généré.
Réponse 200 — text/html (fichier statique servi via res.sendFile)
Réponse 404
- Entrée introuvable
- Documentation pas encore générée (
htmlPathest null)
GET /api/apis/:id/yaml
Télécharge le fichier YAML source.
Réponse 200
Content-Type: application/x-yamlContent-Disposition: attachment; filename="<title>.yaml"- Corps : contenu YAML brut
Réponse 404 — entrée introuvable
Upload (/api/uploads)
POST /api/uploads
Crée une nouvelle entrée à partir d'un ou plusieurs fichiers YAML. Lance la génération de documentation en arrière-plan (fire-and-forget).
Content-Type : multipart/form-data
Champs du formulaire
| Champ | Type | Requis | Description |
|---|---|---|---|
files |
file (1 à 20) | Oui | Fichiers YAML (.yaml ou .yml) |
title |
string | Oui | Titre de l'API |
version |
string | Oui | Version (ex: 1.0.0) |
description |
string | Non | Description libre |
type |
ASYNCAPI | OPENAPI |
Non | Type (détecté automatiquement si absent) |
categoryId |
UUID | Non | Catégorie parente |
Détection automatique du type
Le type est détecté à partir du contenu YAML :
- Présence de
openapi:en début de ligne →OPENAPI - Présence de
asyncapi:en début de ligne →ASYNCAPI
Si plusieurs fichiers sont fournis, le fichier contenant la clé racine est considéré comme le fichier principal.
Les autres sont bundlés via redocly bundle (OpenAPI) ou asyncapi bundle (AsyncAPI).
Réponse 201 — ApiEntry créé avec status: "PENDING"
Réponse 400
- Aucun fichier fourni
titleouversionmanquant- Fichier non YAML
- Type non détectable et non fourni
- Aucun fichier principal trouvé dans un upload multi-fichiers
Catégories (/api/categories)
GET /api/categories
Retourne toutes les catégories, triées par order_index puis name.
Réponse 200
{
"items": [
{
"id": "uuid",
"name": "Infrastructure",
"description": "...",
"parentId": null,
"orderIndex": 0,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z"
}
]
}
GET /api/categories/browse
Navigation à la racine : retourne les catégories de premier niveau et les APIs sans catégorie.
Réponse 200 — CategoryBrowseResponse
{
"category": null,
"breadcrumb": [],
"subcategories": [
{
"id": "uuid",
"name": "Infrastructure",
"description": "...",
"parentId": null,
"orderIndex": 0,
"subcategoryCount": 2,
"apiCount": 5,
"createdAt": "...",
"updatedAt": "..."
}
],
"apis": [
{
"id": "uuid",
"title": "API sans catégorie",
...
}
]
}
GET /api/categories/browse/:id
Navigation dans une catégorie : retourne ses sous-catégories et ses APIs directes.
Réponse 200 — CategoryBrowseResponse
{
"category": { "id": "uuid", "name": "Infrastructure", ... },
"breadcrumb": [
{ "id": "parent-uuid", "name": "Catégorie parente", ... },
{ "id": "uuid", "name": "Infrastructure", ... }
],
"subcategories": [ ... ],
"apis": [ ... ]
}
Réponse 404 — catégorie introuvable
GET /api/categories/:id
Retourne le détail d'une catégorie.
Réponse 200 — Category
Réponse 404 — catégorie introuvable
POST /api/categories
Crée une nouvelle catégorie.
Body JSON
{
"name": "Infrastructure",
"description": "APIs d'infrastructure",
"parentId": "uuid-or-null",
"orderIndex": 0
}
| Champ | Type | Requis |
|---|---|---|
name |
string | Oui |
description |
string | Non |
parentId |
UUID | Non |
orderIndex |
number | Non (défaut: 0) |
Réponse 201 — Category créée
PATCH /api/categories/:id
Met à jour une catégorie.
Body JSON (tous les champs sont optionnels)
{
"name": "Nouveau nom",
"description": "...",
"parentId": "uuid-or-null",
"orderIndex": 1
}
Réponse 200 — Category mise à jour
Réponse 404 — catégorie introuvable
DELETE /api/categories/:id
Supprime une catégorie.
Les APIs et sous-catégories enfants sont désassociées (categoryId/parentId mis à null).
Réponse 204 — succès (pas de corps)
Réponse 404 — catégorie introuvable
Codes d'erreur communs
| Code | Description |
|---|---|
| 400 | Requête invalide (champ manquant, format incorrect) |
| 404 | Ressource introuvable |
| 500 | Erreur interne serveur |
Format d'erreur
{
"statusCode": 404,
"message": "API entry <uuid> not found",
"error": "Not Found"
}
Statuts d'une entrée API
| Statut | Description |
|---|---|
PENDING |
Entrée créée, génération de doc en cours |
GENERATED |
Documentation HTML disponible |
ERROR |
La génération a échoué (voir errorMessage) |