feat(api-detail): navigation entre versions + bouton nouvelle version

- GET /api/apis/:id/versions — retourne toutes les versions du même API
  (même name + convention), triées par version DESC
- Sélecteur DSFR sur la page détail quand plusieurs versions existent ;
  changement de version navigue vers /catalog/:newId en rechargeant
  le composant via abonnement à paramMap (fix réutilisation instance Angular)
- Bouton "Nouvelle version" → /upload?from=:id avec pré-remplissage du
  formulaire (convention, name, provider, contacts, version patch+1)
- Seeder : 2 versions démo supplémentaires de "Référentiel Produits"
  (v3.0.3, v3.0.2) pour tester le sélecteur
- Fix import ApiStatus manquant dans apis.service.ts

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>
This commit is contained in:
z3n
2026-06-16 15:07:31 +00:00
parent 1adb6820dd
commit e1a3dbc85b
6 changed files with 138 additions and 8 deletions

View File

@@ -64,6 +64,11 @@ export class ApisController {
res.send(entry.yamlContent);
}
@Get(':id/versions')
getVersions(@Param('id') id: string) {
return this.apisService.findVersionsOf(id);
}
/** Sert les assets statiques (CSS, JS) générés par AsyncAPI CLI */
@Get(':id/*path')
async getDocAsset(@Param('id') id: string, @Req() req: Request, @Res() res: Response) {

View File

@@ -4,7 +4,7 @@ import { Repository } from 'typeorm';
import { ApiEntryEntity } from './api-entry.entity';
import { CreateApiEntryDto } from './dto/create-api-entry.dto';
import { UpdateApiEntryDto } from './dto/update-api-entry.dto';
import { ApiListResponse, ApiListQuery, ApiEntry, ApiType, ApiConvention, API_CONVENTION_LABELS } from '@datacat/shared';
import { ApiListResponse, ApiListQuery, ApiEntry, ApiEntryListItem, ApiType, ApiStatus, ApiConvention, API_CONVENTION_LABELS } from '@datacat/shared';
import { toApiEntryListItem } from './api-entry.mapper';
import { DocsGenerationService } from '../docs-generation/docs-generation.service';
@@ -107,6 +107,19 @@ export class ApisService {
await this.repo.delete(id);
}
async findVersionsOf(id: string): Promise<ApiEntryListItem[]> {
const entry = await this.findOneOrThrow(id);
const entities = await this.repo.find({
where: { name: entry.name, convention: entry.convention },
order: {
versionMajor: 'DESC',
versionMinor: 'DESC',
versionPatch: 'DESC',
},
});
return entities.map(toApiEntryListItem);
}
async regenerate(id: string): Promise<ApiEntry> {
await this.findOneOrThrow(id);
await this.updateInternal(id, { status: 'PENDING', errorMessage: null });

View File

@@ -765,6 +765,46 @@ export class SeederService implements OnApplicationBootstrap {
contactTechnicalEntity: 'API Commerce',
contactTechnicalEmail: 'api-orders@example.com',
}),
this.apiRepo.create({
convention: 'CONSULTER',
name: 'Référentiel Produits',
versionMajor: 3,
versionMinor: 0,
versionPatch: 3,
description:
'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI specification.',
type: 'OPENAPI',
yamlContent: PETSTORE_YAML,
status: 'PENDING',
categoryId: produits.id,
provider: 'DINUM',
contactFunctionalName: 'Alice Martin',
contactFunctionalEntity: 'Équipe Produit',
contactFunctionalEmail: 'alice.martin@example.gouv.fr',
contactTechnicalName: 'Bob Dupont',
contactTechnicalEntity: 'Équipe API',
contactTechnicalEmail: 'bob.dupont@example.gouv.fr',
}),
this.apiRepo.create({
convention: 'CONSULTER',
name: 'Référentiel Produits',
versionMajor: 3,
versionMinor: 0,
versionPatch: 2,
description:
'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI specification.',
type: 'OPENAPI',
yamlContent: PETSTORE_YAML,
status: 'PENDING',
categoryId: produits.id,
provider: 'DINUM',
contactFunctionalName: 'Alice Martin',
contactFunctionalEntity: 'Équipe Produit',
contactFunctionalEmail: 'alice.martin@example.gouv.fr',
contactTechnicalName: 'Bob Dupont',
contactTechnicalEntity: 'Équipe API',
contactTechnicalEmail: 'bob.dupont@example.gouv.fr',
}),
this.apiRepo.create({
convention: 'CONSULTER',
name: 'Authentification JWT',
@@ -786,7 +826,7 @@ export class SeederService implements OnApplicationBootstrap {
}),
]);
console.log('[Seeder] 8 catégories et 5 APIs de démo créées — génération docs en cours...');
console.log('[Seeder] 8 catégories et 7 APIs de démo créées — génération docs en cours...');
/* Déclenche la génération de docs en fire-and-forget pour chaque API */
for (const entry of entries) {