feat: initial scaffold — MVP catalogue de données d'API
- 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>
This commit is contained in:
367
documentation/api-reference.md
Normal file
367
documentation/api-reference.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# 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**
|
||||
```json
|
||||
{ "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**
|
||||
```json
|
||||
{
|
||||
"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
|
||||
}
|
||||
```
|
||||
|
||||
> `items` contient des `ApiEntryListItem` (sans `yamlContent` ni `htmlPath`).
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/apis/:id`
|
||||
|
||||
Retourne le détail complet d'une entrée.
|
||||
|
||||
**Réponse 200**
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
```json
|
||||
{
|
||||
"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 (`htmlPath` est null)
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/apis/:id/yaml`
|
||||
|
||||
Télécharge le fichier YAML source.
|
||||
|
||||
**Réponse 200**
|
||||
- `Content-Type: application/x-yaml`
|
||||
- `Content-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
|
||||
- `title` ou `version` manquant
|
||||
- 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**
|
||||
```json
|
||||
{
|
||||
"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`
|
||||
```json
|
||||
{
|
||||
"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`
|
||||
```json
|
||||
{
|
||||
"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**
|
||||
```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)
|
||||
```json
|
||||
{
|
||||
"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**
|
||||
```json
|
||||
{
|
||||
"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`) |
|
||||
Reference in New Issue
Block a user