Files
datacat/documentation/architecture.md
z3n 921a6e652b 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>
2026-05-22 08:42:54 +00:00

6.1 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) — pour DocsGenerationService (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: ou asyncapi: 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(),
  title         VARCHAR(255) NOT NULL,
  version       VARCHAR(50)  NOT NULL,
  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),
  created_at    TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
  updated_at    TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

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_id sont des colonnes varchar sans contrainte de clé étrangère TypeORM. La suppression d'une catégorie désassocie automatiquement ses APIs et sous-catégories enfants (logique applicative).


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-template est 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.