feat: initial scaffold — MVP catalogue de données d'API

- Backend NestJS : CRUD api_entries + categories, upload YAML multi-fichiers,
  génération docs AsyncAPI (@asyncapi/cli@6.0.0) et OpenAPI (redocly)
- Fix: route wildcard GET /api/apis/:id/*path pour servir les assets statiques
  (CSS/JS) générés par AsyncAPI HTML template (contournement bug path-to-regexp v8)
- Frontend Angular 19 : pages catalog, browse, api-detail, doc-viewer, upload (DSFR)
- Seeder de données de démo (idempotent)
- Docker Compose dev + Dockerfiles + manifests K8s
- Documentation : README, architecture, référence API REST

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-05-22 08:42:54 +00:00
commit 921a6e652b
87 changed files with 16719 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { Injectable, Inject, forwardRef } from '@nestjs/common';
import { execFile } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs/promises';
import * as path from 'path';
import { ApisService } from '../apis/apis.service';
const execFileAsync = promisify(execFile);
@Injectable()
export class DocsGenerationService {
private readonly outputDir = process.env.DOCS_OUTPUT_DIR ?? '/app/docs-output';
constructor(
@Inject(forwardRef(() => ApisService))
private readonly apisService: ApisService,
) {}
async generate(entryId: string): Promise<void> {
const entry = await this.apisService.findOneOrThrow(entryId);
const entryDir = path.join(this.outputDir, entryId);
await fs.mkdir(entryDir, { recursive: true });
const yamlPath = path.join(entryDir, 'input.yaml');
await fs.writeFile(yamlPath, entry.yamlContent, 'utf-8');
try {
if (entry.type === 'ASYNCAPI') {
await this.generateAsyncApi(yamlPath, entryDir);
} else {
await this.generateOpenApi(yamlPath, entryDir);
}
const htmlPath = path.join(entryDir, 'index.html');
await this.apisService.updateInternal(entryId, { htmlPath, status: 'GENERATED' });
} catch (error) {
await this.apisService.updateInternal(entryId, {
status: 'ERROR',
errorMessage: String(error),
});
}
}
private async generateAsyncApi(yamlPath: string, outputDir: string): Promise<void> {
await execFileAsync('asyncapi', [
'generate', 'fromTemplate',
yamlPath,
'@asyncapi/html-template',
'--output', outputDir,
'--no-interactive',
'--install',
'--force-write',
]);
}
private async generateOpenApi(yamlPath: string, outputDir: string): Promise<void> {
await execFileAsync('redocly', [
'build-docs',
yamlPath,
'--output', path.join(outputDir, 'index.html'),
]);
}
}