- Shared types : ajout isCurrent sur ApiEntry et ApiEntryListItem - Backend entity : colonne is_current (boolean, default true) - Backend service : isCurrent calculé à la création, setCurrent() marque une version comme courante (siblings → false) - Backend controller : POST /api/apis/:id/set-current - Frontend ApiService : méthode setCurrent() - Browse/upload : affichage badge "Courante" via isCurrent - Seeder : données de démo mises à jour - Documentation : api-reference, architecture, local-setup, CLAUDE.md 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.2 KiB
Architecture — Datacat
Vue d'ensemble
Datacat est un catalogue de données d'API organisé en monorepo pnpm :
datacat/
├── back/ # API NestJS + TypeORM + PostgreSQL (port 3000 interne, 3010 hôte)
├── front-public/ # Frontend Angular 19 avec DSFR (port 4200)
├── shared/ # Types TypeScript partagés (@datacat/shared)
└── infra/ # Docker Compose, Dockerfiles, Kubernetes
Flux principal
Utilisateur upload YAML
│
▼
POST /api/uploads (multipart, 1 à 20 fichiers)
│
├─ 1 fichier → lecture directe
└─ N fichiers → détection du fichier principal + bundling (redocly bundle / asyncapi bundle)
│
▼
ApisService.create()
→ ApiEntry en base (status: PENDING)
│
▼ (fire-and-forget)
DocsGenerationService.generate(id)
│
├─ type ASYNCAPI → asyncapi generate fromTemplate input.yaml @asyncapi/html-template
└─ type OPENAPI → redocly build-docs input.yaml --output index.html
│
▼
ApiEntry mis à jour
→ status: GENERATED + htmlPath: /app/docs-output/<uuid>/index.html
OU status: ERROR + errorMessage: <raison>
│
▼
GET /api/apis/:id/docs → res.sendFile(htmlPath)
Services NestJS
ApisService (back/src/modules/apis/apis.service.ts)
CRUD sur la table api_entries. Expose deux méthodes de mise à jour :
update(id, dto)— pour le controller (champs éditables par l'utilisateur)updateInternal(id, fields)— pourDocsGenerationService(status, htmlPath, errorMessage)
UploadsController (back/src/modules/uploads/uploads.controller.ts)
Gère l'upload multipart via FilesInterceptor (jusqu'à 20 fichiers).
- Détecte automatiquement le type (
openapi:ouasyncapi:en début de fichier) - Si plusieurs fichiers : détecte le fichier principal et bundle les autres via la CLI correspondante
- Lance
DocsGenerationService.generate()en fire-and-forget après création
DocsGenerationService (back/src/modules/docs-generation/docs-generation.service.ts)
Génère la documentation HTML à partir du YAML stocké en base :
- AsyncAPI :
asyncapi generate fromTemplate <yaml> @asyncapi/html-template --output <dir> --no-interactive --install --force-write - OpenAPI :
redocly build-docs <yaml> --output <dir>/index.html - Sortie :
$DOCS_OUTPUT_DIR/<uuid>/index.html(défaut :/app/docs-output)
CategoriesService (back/src/modules/categories/categories.service.ts)
CRUD sur la table categories + endpoint browse qui retourne :
- La catégorie courante
- Le fil d'Ariane (breadcrumb)
- Les sous-catégories directes avec comptages
- Les APIs directement dans la catégorie
SeederService (back/src/modules/seeder/seeder.service.ts)
Injecte des données de démonstration au démarrage (idempotent : ne re-seed que si la table est vide).
Schéma de base de données
Table api_entries
CREATE TABLE api_entries (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
convention VARCHAR(30) NOT NULL DEFAULT 'CONSULTER'
CHECK (convention IN ('CONSULTER','ENREGISTRER','ETRE_NOTIFIE')),
name VARCHAR(255) NOT NULL DEFAULT '',
provider VARCHAR(255) NOT NULL DEFAULT '',
version_major INT NOT NULL DEFAULT 0,
version_minor INT NOT NULL DEFAULT 0,
version_patch INT NOT NULL DEFAULT 0,
description TEXT,
type VARCHAR(10) NOT NULL CHECK (type IN ('ASYNCAPI', 'OPENAPI')),
yaml_content TEXT NOT NULL,
html_path VARCHAR(500),
status VARCHAR(10) NOT NULL DEFAULT 'PENDING'
CHECK (status IN ('PENDING', 'GENERATED', 'ERROR')),
error_message TEXT,
category_id VARCHAR(36),
contact_functional_name VARCHAR(255) NOT NULL DEFAULT '',
contact_functional_entity VARCHAR(255) NOT NULL DEFAULT '',
contact_functional_email VARCHAR(255) NOT NULL DEFAULT '',
contact_technical_name VARCHAR(255) NOT NULL DEFAULT '',
contact_technical_entity VARCHAR(255) NOT NULL DEFAULT '',
contact_technical_email VARCHAR(255) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
titleetversionsont des champs calculés (pas en DB) :
title=CONVENTION_LABEL + ' ' + name(ex:"Consulter Petstore")version="major.minor.patch"(ex:"1.0.0")
Table categories
CREATE TABLE categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
parent_id VARCHAR(36), -- auto-référence (pas de FK TypeORM)
order_index INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Les relations
category_id/parent_idsont des colonnes varchar sans contrainte de clé étrangère TypeORM. La suppression d'une catégorie est rejetée avec 400 si le dossier contient des APIs ou des sous-dossiers (vérification applicative avantDELETE).
Dépendances externes
| Outil | Version | Usage |
|---|---|---|
@asyncapi/cli |
6.0.0 |
Génération HTML AsyncAPI (bundles generator 3.x) |
@redocly/cli |
latest | Génération HTML OpenAPI + bundling multi-fichiers |
Les deux CLIs sont installés dans les images Docker (infra/Dockerfile.back.dev pour le développement).
Important
: utiliser
@asyncapi/cli@6.0.0— la version 4.x ne supporte qu'AsyncAPI spec ≤ 2.6.0. Le template@asyncapi/html-templateest pré-installé dans l'image pour éviter les téléchargements au premier démarrage.
Frontend Angular 19
Structure des pages (lazy loading via loadComponent) :
| Route | Composant | Description |
|---|---|---|
/ |
redirect → /browse |
|
/browse |
BrowseComponent |
Navigation par catégories (racine) |
/browse/:id |
BrowseComponent |
Navigation dans une catégorie |
/catalog |
CatalogComponent |
Liste toutes les APIs avec recherche/filtre |
/catalog/:id |
ApiDetailComponent |
Détail d'une API (métadonnées + actions) |
/catalog/:id/docs |
DocViewerComponent |
Iframe plein écran vers la doc générée |
/upload |
UploadComponent |
Stepper 3 étapes pour importer un YAML |
Conventions : standalone components, signals (signal(), computed()), inject(), ChangeDetectionStrategy.OnPush.
Infrastructure Docker (développement)
docker compose -f infra/docker-compose.yml up -d
| Service | Image | Port interne | Port hôte |
|---|---|---|---|
postgres |
postgres:17 |
5432 | 5432 |
backend |
Dockerfile.back.dev |
3000 | 3010 |
frontend |
Dockerfile.front.dev |
4200 | 4200 |
Le backend est exposé sur 3010 (pas 3000) car Gitea tourne sur 3000 sur la machine hôte.