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>
This commit is contained in:
z3n
2026-06-19 14:23:31 +00:00
parent c8b3e393ea
commit 0976c33059
15 changed files with 610 additions and 235 deletions

View File

@@ -45,6 +45,9 @@ export class ApiEntryEntity {
@Column({ nullable: true, type: 'varchar', length: 36, name: 'category_id' })
categoryId!: string | null;
@Column({ type: 'boolean', default: true, name: 'is_current' })
isCurrent!: boolean;
@Column({ type: 'varchar', length: 255, default: '', name: 'contact_functional_name' })
contactFunctionalName!: string;

View File

@@ -13,6 +13,7 @@ export function toApiEntryListItem(entity: ApiEntryEntity): ApiEntryListItem {
provider: entity.provider,
status: entity.status,
categoryId: entity.categoryId,
isCurrent: entity.isCurrent,
createdAt: entity.createdAt.toISOString(),
updatedAt: entity.updatedAt.toISOString(),
};

View File

@@ -43,6 +43,11 @@ export class ApisController {
return this.apisService.remove(id);
}
@Post(':id/set-current')
setCurrent(@Param('id') id: string) {
return this.apisService.setCurrent(id);
}
@Post(':id/regenerate')
regenerate(@Param('id') id: string) {
return this.apisService.regenerate(id);

View File

@@ -25,6 +25,12 @@ export class ApisService {
) {}
async create(dto: CreateApiEntryDto): Promise<ApiEntry> {
// Première version si aucune entrée avec le même name + convention
const existingCount = await this.repo.count({
where: { name: dto.name, convention: dto.convention },
});
const isCurrent = existingCount === 0;
const entity = this.repo.create({
convention: dto.convention,
name: dto.name,
@@ -37,6 +43,7 @@ export class ApisService {
yamlContent: dto.yamlContent,
status: 'PENDING',
categoryId: dto.categoryId ?? null,
isCurrent,
contactFunctionalName: dto.contactFunctionalName,
contactFunctionalEntity: dto.contactFunctionalEntity,
contactFunctionalEmail: dto.contactFunctionalEmail,
@@ -188,6 +195,18 @@ export class ApisService {
return entities.map(toApiEntryListItem);
}
async setCurrent(id: string): Promise<ApiEntry> {
const entry = await this.findOneOrThrow(id);
// Mettre tous les siblings à false
await this.repo.update(
{ name: entry.name, convention: entry.convention },
{ isCurrent: false },
);
// Mettre cet entry à true
await this.repo.update({ id }, { isCurrent: true });
return this.findOneOrThrow(id);
}
async regenerate(id: string): Promise<ApiEntry> {
await this.findOneOrThrow(id);
await this.updateInternal(id, { status: 'PENDING', errorMessage: null });
@@ -222,6 +241,7 @@ function toApiEntry(entity: ApiEntryEntity): ApiEntry {
status: entity.status,
errorMessage: entity.errorMessage,
categoryId: entity.categoryId,
isCurrent: entity.isCurrent,
contactFunctionalName: entity.contactFunctionalName,
contactFunctionalEntity: entity.contactFunctionalEntity,
contactFunctionalEmail: entity.contactFunctionalEmail,

View File

@@ -6,6 +6,7 @@ import {
Delete,
Param,
Body,
Query,
HttpCode,
} from '@nestjs/common';
import { CategoriesService } from './categories.service';
@@ -17,8 +18,8 @@ export class CategoriesController {
constructor(private readonly categoriesService: CategoriesService) {}
@Get()
findAll() {
return this.categoriesService.findAll();
findAll(@Query('search') search?: string) {
return this.categoriesService.findAll(search);
}
/* Déclarés AVANT :id pour éviter que NestJS interprète "browse" comme un UUID */

View File

@@ -605,10 +605,41 @@ export class SeederService implements OnApplicationBootstrap {
await this.reset();
}
const count = await this.apiRepo.count();
if (count > 0) return; /* Idempotent — ne ré-exécute pas si des données existent */
if (count > 0) {
await this.fixIsCurrentFlags();
return;
}
await this.seed();
}
/**
* Corrige les flags is_current après une migration :
* si toutes les versions d'un même groupe (name+convention) sont marquées courantes,
* on ne garde que la plus haute version.
*/
private async fixIsCurrentFlags(): Promise<void> {
await this.dataSource.query(`
UPDATE api_entries SET is_current = false
WHERE id IN (
SELECT a.id
FROM api_entries a
WHERE a.is_current = true
AND EXISTS (
SELECT 1 FROM api_entries b
WHERE b.name = a.name AND b.convention = a.convention
AND b.is_current = true
AND b.id != a.id
AND (
b.version_major > a.version_major
OR (b.version_major = a.version_major AND b.version_minor > a.version_minor)
OR (b.version_major = a.version_major AND b.version_minor = a.version_minor
AND b.version_patch > a.version_patch)
)
)
)
`);
}
private async reset(): Promise<void> {
/* Supprimer toutes les entrées puis les catégories */
await this.apiRepo.delete({});
@@ -697,6 +728,7 @@ export class SeederService implements OnApplicationBootstrap {
versionMajor: 3,
versionMinor: 0,
versionPatch: 4,
isCurrent: true,
description:
'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI specification.',
type: 'OPENAPI',
@@ -775,6 +807,7 @@ export class SeederService implements OnApplicationBootstrap {
versionMajor: 3,
versionMinor: 0,
versionPatch: 3,
isCurrent: false,
description:
'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI specification.',
type: 'OPENAPI',
@@ -795,6 +828,7 @@ export class SeederService implements OnApplicationBootstrap {
versionMajor: 3,
versionMinor: 0,
versionPatch: 2,
isCurrent: false,
description:
'A sample API that uses a petstore as an example to demonstrate features in the OpenAPI specification.',
type: 'OPENAPI',