# 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//index.html OU status: ERROR + errorMessage: │ ▼ 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 @asyncapi/html-template --output --no-interactive --install --force-write` - **OpenAPI** : `redocly build-docs --output /index.html` - Sortie : `$DOCS_OUTPUT_DIR//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` ```sql 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` ```sql 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.