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:
@@ -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;
|
||||
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user