Files
datacat/documentation/architecture.md
z3n 0976c33059 feat(api-detail): isCurrent + setCurrent + màj doc et seeder
- 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>
2026-06-19 14:23:31 +00:00

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) — 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(),
  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()
);

title et version sont 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_id sont 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 avant DELETE).


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.