89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
import 'reflect-metadata';
|
|
|
|
import { Module } from '@nestjs/common';
|
|
import { NestFactory } from '@nestjs/core';
|
|
import { DocumentBuilder, SwaggerModule, type OpenAPIObject } from '@nestjs/swagger';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { format } from 'prettier';
|
|
|
|
import { HealthController } from '../apps/api/src/health.controller';
|
|
import { MarketOverviewController } from '../apps/api/src/market-overview/market-overview.controller';
|
|
import { ShortTermMarketController } from '../apps/api/src/market-overview/short-term.controller';
|
|
import {
|
|
HealthService,
|
|
MYSQL_HEALTH_PORT,
|
|
REDIS_HEALTH_PORT,
|
|
} from '../apps/api/src/health.service';
|
|
import {
|
|
MarketOverviewService,
|
|
TDX_CLIENT_PORT,
|
|
} from '../apps/api/src/market-overview/market-overview.service';
|
|
import { createAktoolsClient } from '../apps/api/src/market-overview/aktools/aktools.client';
|
|
import {
|
|
AKTOOLS_CLIENT_PORT,
|
|
ShortTermMarketService,
|
|
} from '../apps/api/src/market-overview/short-term.service';
|
|
import { DisabledHealthClient } from '../libs/infrastructure/src/disabled-health.client';
|
|
|
|
@Module({
|
|
controllers: [HealthController, MarketOverviewController, ShortTermMarketController],
|
|
providers: [
|
|
HealthService,
|
|
MarketOverviewService,
|
|
{ provide: TDX_CLIENT_PORT, useValue: { request: async () => ({}) } },
|
|
ShortTermMarketService,
|
|
{
|
|
provide: AKTOOLS_CLIENT_PORT,
|
|
useValue: createAktoolsClient({
|
|
enabled: false,
|
|
baseUrl: 'http://127.0.0.1:8080',
|
|
timeoutMs: 10_000,
|
|
}),
|
|
},
|
|
{ provide: REDIS_HEALTH_PORT, useValue: new DisabledHealthClient('文档生成不连接 Redis') },
|
|
{ provide: MYSQL_HEALTH_PORT, useValue: new DisabledHealthClient('文档生成不连接 MySQL') },
|
|
],
|
|
})
|
|
class OpenApiModule {}
|
|
|
|
function sortObject(value: unknown): unknown {
|
|
if (Array.isArray(value)) return value.map(sortObject);
|
|
if (!value || typeof value !== 'object') return value;
|
|
return Object.fromEntries(
|
|
Object.entries(value as Record<string, unknown>)
|
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
.map(([key, child]) => [key, sortObject(child)]),
|
|
);
|
|
}
|
|
|
|
/** 从真实 Controller/DTO 生成确定性 OpenAPI 文档,生成期间不连接外部基础设施。 */
|
|
export async function createOpenApiDocument(): Promise<OpenAPIObject> {
|
|
const app = await NestFactory.create(OpenApiModule, { logger: false });
|
|
app.setGlobalPrefix('api/v1');
|
|
const options = new DocumentBuilder()
|
|
.setTitle('A 股市场分析工具 API')
|
|
.setVersion('0.0.8-draft')
|
|
.build();
|
|
const document = SwaggerModule.createDocument(app, options);
|
|
await app.close();
|
|
return sortObject(document) as OpenAPIObject;
|
|
}
|
|
|
|
async function writeOpenApiDocument(): Promise<void> {
|
|
const serverDirectory = path.resolve(__dirname, '..');
|
|
const output = path.join(serverDirectory, 'openapi', 'openapi.json');
|
|
const serialized = await format(JSON.stringify(await createOpenApiDocument()), {
|
|
parser: 'json',
|
|
});
|
|
await mkdir(path.dirname(output), { recursive: true });
|
|
await writeFile(output, serialized, 'utf8');
|
|
}
|
|
|
|
if (require.main === module) {
|
|
void writeOpenApiDocument().catch((error: unknown) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|
|
}
|