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:
64
back/src/modules/docs-generation/docs-generation.service.ts
Normal file
64
back/src/modules/docs-generation/docs-generation.service.ts
Normal 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'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user