diff --git a/back/src/modules/apis/api-entry.entity.ts b/back/src/modules/apis/api-entry.entity.ts index 656ae51..4d57969 100644 --- a/back/src/modules/apis/api-entry.entity.ts +++ b/back/src/modules/apis/api-entry.entity.ts @@ -33,6 +33,24 @@ export class ApiEntryEntity { @Column({ nullable: true, type: 'varchar', length: 36, name: 'category_id' }) categoryId!: string | null; + @Column({ nullable: true, type: 'text', name: 'functional_doc' }) + functionalDoc!: string | null; + + @Column({ nullable: true, type: 'text', name: 'technical_doc' }) + technicalDoc!: string | null; + + @Column({ nullable: true, type: 'varchar', length: 500, name: 'external_doc_url' }) + externalDocUrl!: string | null; + + @Column({ nullable: true, type: 'varchar', length: 255, name: 'contact_functional' }) + contactFunctional!: string | null; + + @Column({ nullable: true, type: 'varchar', length: 255, name: 'contact_technical' }) + contactTechnical!: string | null; + + @Column({ nullable: true, type: 'text', name: 'access_rights' }) + accessRights!: string | null; + @CreateDateColumn({ name: 'created_at' }) createdAt!: Date; diff --git a/back/src/modules/apis/apis.service.ts b/back/src/modules/apis/apis.service.ts index e3d2dbd..e7e5e9d 100644 --- a/back/src/modules/apis/apis.service.ts +++ b/back/src/modules/apis/apis.service.ts @@ -35,6 +35,12 @@ export class ApisService { yamlContent: dto.yamlContent, status: 'PENDING', categoryId: dto.categoryId ?? null, + functionalDoc: dto.functionalDoc ?? null, + technicalDoc: dto.technicalDoc ?? null, + externalDocUrl: dto.externalDocUrl ?? null, + contactFunctional: dto.contactFunctional ?? null, + contactTechnical: dto.contactTechnical ?? null, + accessRights: dto.accessRights ?? null, }); const saved = await this.repo.save(entity); return toApiEntry(saved); @@ -115,6 +121,12 @@ function toApiEntry(entity: ApiEntryEntity): ApiEntry { status: entity.status, errorMessage: entity.errorMessage, categoryId: entity.categoryId, + functionalDoc: entity.functionalDoc, + technicalDoc: entity.technicalDoc, + externalDocUrl: entity.externalDocUrl, + contactFunctional: entity.contactFunctional, + contactTechnical: entity.contactTechnical, + accessRights: entity.accessRights, createdAt: entity.createdAt.toISOString(), updatedAt: entity.updatedAt.toISOString(), }; diff --git a/back/src/modules/apis/dto/create-api-entry.dto.ts b/back/src/modules/apis/dto/create-api-entry.dto.ts index 9887b6a..0f74e92 100644 --- a/back/src/modules/apis/dto/create-api-entry.dto.ts +++ b/back/src/modules/apis/dto/create-api-entry.dto.ts @@ -24,4 +24,28 @@ export class CreateApiEntryDto { @IsUUID() @IsOptional() categoryId?: string; + + @IsString() + @IsOptional() + functionalDoc?: string; + + @IsString() + @IsOptional() + technicalDoc?: string; + + @IsString() + @IsOptional() + externalDocUrl?: string; + + @IsString() + @IsOptional() + contactFunctional?: string; + + @IsString() + @IsOptional() + contactTechnical?: string; + + @IsString() + @IsOptional() + accessRights?: string; } diff --git a/back/src/modules/apis/dto/update-api-entry.dto.ts b/back/src/modules/apis/dto/update-api-entry.dto.ts index 05185fa..3f44e1a 100644 --- a/back/src/modules/apis/dto/update-api-entry.dto.ts +++ b/back/src/modules/apis/dto/update-api-entry.dto.ts @@ -16,4 +16,28 @@ export class UpdateApiEntryDto { @IsUUID() @IsOptional() categoryId?: string | null; + + @IsString() + @IsOptional() + functionalDoc?: string | null; + + @IsString() + @IsOptional() + technicalDoc?: string | null; + + @IsString() + @IsOptional() + externalDocUrl?: string | null; + + @IsString() + @IsOptional() + contactFunctional?: string | null; + + @IsString() + @IsOptional() + contactTechnical?: string | null; + + @IsString() + @IsOptional() + accessRights?: string | null; } diff --git a/back/src/modules/seeder/seeder.service.ts b/back/src/modules/seeder/seeder.service.ts index 5f4aee7..fdd1db9 100644 --- a/back/src/modules/seeder/seeder.service.ts +++ b/back/src/modules/seeder/seeder.service.ts @@ -1,6 +1,7 @@ import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; +import * as fs from 'fs/promises'; import { ApiEntryEntity } from '../apis/api-entry.entity'; import { CategoryEntity } from '../categories/category.entity'; import { DocsGenerationService } from '../docs-generation/docs-generation.service'; @@ -595,11 +596,26 @@ export class SeederService implements OnApplicationBootstrap { ) {} async onApplicationBootstrap(): Promise { + const forceReseed = process.env.FORCE_RESEED === 'true'; + if (forceReseed) { + await this.reset(); + } const count = await this.apiRepo.count(); if (count > 0) return; /* Idempotent — ne ré-exécute pas si des données existent */ await this.seed(); } + private async reset(): Promise { + /* Supprimer toutes les entrées puis les catégories */ + await this.apiRepo.delete({}); + await this.categoryRepo.delete({}); + /* Nettoyer le répertoire docs-output */ + const docsDir = process.env.DOCS_OUTPUT_DIR ?? '/app/docs-output'; + await fs.rm(docsDir, { recursive: true, force: true }); + await fs.mkdir(docsDir, { recursive: true }); + console.log('[Seeder] Reset complet — données supprimées, docs nettoyées.'); + } + private async seed(): Promise { /* Catégories racines */ const ecommerce = await this.categoryRepo.save( @@ -680,6 +696,12 @@ export class SeederService implements OnApplicationBootstrap { yamlContent: PETSTORE_YAML, status: 'PENDING', categoryId: produits.id, + functionalDoc: 'Cas d\'usage : gestion du catalogue produits. Permet de lister, créer et consulter des animaux de compagnie. Usage typique : intégration e-commerce, démo API REST.', + technicalDoc: 'Authentification Bearer token requise. Pagination via paramètre `limit` (max 100). Réponses JSON selon schéma OpenAPI 3.0. Endpoint base : https://petstore.swagger.io/v3.', + externalDocUrl: null, + contactFunctional: 'Équipe Produit', + contactTechnical: 'Équipe API', + accessRights: 'API publique — aucune habilitation requise.', }), this.apiRepo.create({ title: 'Streetlights Kafka API', @@ -690,6 +712,12 @@ export class SeederService implements OnApplicationBootstrap { yamlContent: STREETLIGHTS_YAML, status: 'PENDING', categoryId: evenements.id, + functionalDoc: 'Gestion de l\'éclairage urbain IoT. Permet de recevoir les mesures de luminosité des lampadaires et d\'envoyer des commandes d\'allumage/extinction à distance.', + technicalDoc: 'Broker Kafka sécurisé avec SCRAM-SHA-512. Topics : smartylighting.streetlights.1.0.*. Schémas de messages définis en AsyncAPI 3.0. Adresse broker : test.mykafkacluster.org:18092.', + externalDocUrl: null, + contactFunctional: 'Bureau Infrastructure', + contactTechnical: 'ops@example.com', + accessRights: 'Accès sur demande — habilitation DSI requise.', }), this.apiRepo.create({ title: 'Account Service', @@ -699,6 +727,12 @@ export class SeederService implements OnApplicationBootstrap { yamlContent: ACCOUNT_SERVICE_YAML, status: 'PENDING', categoryId: notifications.id, + functionalDoc: 'Cycle de vie du compte utilisateur. Publie des événements lors de l\'inscription et du changement de mot de passe. Utilisé par les services de notification et d\'audit.', + technicalDoc: 'Consommer via broker AMQP ou Kafka. Canaux : user/signedup, user/passwordchanged. Payloads JSON avec horodatage ISO 8601. Spec AsyncAPI 3.0.', + externalDocUrl: null, + contactFunctional: 'Équipe Comptes', + contactTechnical: 'dev-core@example.com', + accessRights: 'Interne uniquement — accès réservé aux services du SI.', }), this.apiRepo.create({ title: 'Orders API', @@ -708,6 +742,12 @@ export class SeederService implements OnApplicationBootstrap { yamlContent: ORDERS_YAML, status: 'PENDING', categoryId: commandes.id, + functionalDoc: 'Gestion des commandes clients : création, consultation, mise à jour du statut. Supporte les webhooks pour les transitions de statut (confirmed, shipped, delivered, cancelled).', + technicalDoc: 'REST + JWT Bearer. Pagination via paramètres `page` et `limit`. Webhook de statut : POST vers URL configurée lors de la transition. Base URL : https://api.example.com/v1.', + externalDocUrl: null, + contactFunctional: 'Équipe Commerce', + contactTechnical: 'api-orders@example.com', + accessRights: 'Partenaires agréés uniquement — convention de partenariat requise.', }), this.apiRepo.create({ title: 'Auth API', @@ -717,6 +757,12 @@ export class SeederService implements OnApplicationBootstrap { yamlContent: AUTH_YAML, status: 'PENDING', categoryId: auth.id, + functionalDoc: 'SSO centralisé avec tokens JWT. Gère la connexion, le rafraîchissement de token et la déconnexion. Exposé à tous les services du SI comme fournisseur d\'identité.', + technicalDoc: 'Bearer JWT (RS256). Endpoint principal : POST /auth/login. Refresh via POST /auth/refresh. Durée access token : 15 min. Durée refresh token : 7 jours. Base URL : https://auth.example.com/v1.', + externalDocUrl: null, + contactFunctional: 'Équipe Sécurité', + contactTechnical: 'securite@example.com', + accessRights: 'Restreint — habilitation RSSI requise avant intégration.', }), ]); diff --git a/back/src/modules/uploads/uploads.controller.ts b/back/src/modules/uploads/uploads.controller.ts index 0b9f922..639ba17 100644 --- a/back/src/modules/uploads/uploads.controller.ts +++ b/back/src/modules/uploads/uploads.controller.ts @@ -48,6 +48,12 @@ export class UploadsController { description?: string; type?: ApiType; categoryId?: string; + functionalDoc?: string; + technicalDoc?: string; + externalDocUrl?: string; + contactFunctional?: string; + contactTechnical?: string; + accessRights?: string; }, ) { if (!files || files.length === 0) throw new BadRequestException('YAML file is required'); @@ -86,6 +92,12 @@ export class UploadsController { type: apiType, yamlContent, categoryId: body.categoryId || undefined, + functionalDoc: body.functionalDoc || undefined, + technicalDoc: body.technicalDoc || undefined, + externalDocUrl: body.externalDocUrl || undefined, + contactFunctional: body.contactFunctional || undefined, + contactTechnical: body.contactTechnical || undefined, + accessRights: body.accessRights || undefined, }); /* Fire-and-forget : génération asynchrone */ diff --git a/front-public/src/app/pages/api-detail/api-detail.component.ts b/front-public/src/app/pages/api-detail/api-detail.component.ts index 476479c..e63fad1 100644 --- a/front-public/src/app/pages/api-detail/api-detail.component.ts +++ b/front-public/src/app/pages/api-detail/api-detail.component.ts @@ -111,6 +111,81 @@ import { ApiEntry } from '@datacat/shared'; + + @if (entry.functionalDoc) { +
+
+
+

Documentation fonctionnelle

+

{{ entry.functionalDoc }}

+
+
+
+ } + + + @if (entry.technicalDoc) { +
+
+
+

Documentation technique

+

{{ entry.technicalDoc }}

+
+
+
+ } + + + @if (entry.contactFunctional || entry.contactTechnical) { +
+
+
+

Contacts

+
+ @if (entry.contactFunctional) { +
+
Contact fonctionnel
+
{{ entry.contactFunctional }}
+
+ } + @if (entry.contactTechnical) { +
+
Contact technique
+
{{ entry.contactTechnical }}
+
+ } +
+
+
+
+ } + + + @if (entry.accessRights) { +
+
+
+

Droits d'accès

+

{{ entry.accessRights }}

+
+
+
+ } + + + @if (entry.externalDocUrl) { + + } + -
+
@@ -171,6 +171,84 @@ import { Category } from '@datacat/shared'; >
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+