118 lines
4.4 KiB
TypeScript
118 lines
4.4 KiB
TypeScript
import 'reflect-metadata';
|
||
import 'dotenv/config';
|
||
import { setupOpenApi } from './openapi';
|
||
import { Module, Catch, ArgumentsHost, ExceptionFilter, HttpException } from '@nestjs/common';
|
||
import { NestFactory, APP_GUARD } from '@nestjs/core';
|
||
import cookieParser from 'cookie-parser';
|
||
import helmet from 'helmet';
|
||
import { json } from 'express';
|
||
import { AuthController, AuthBusinessService, AuthGuard, AuthService } from './auth';
|
||
import { CalendarController, CalendarBusinessService } from './calendar';
|
||
import { SchedulesController, SchedulesBusinessService } from './schedules';
|
||
import { TransfersController, TransfersBusinessService } from './transfers';
|
||
import { PortfolioController, PortfolioBusinessService } from './portfolio';
|
||
import { BackupController, BackupBusinessService } from './backup';
|
||
import { IconsController, IconsBusinessService, IconsService } from './icons';
|
||
import { MetalsService, MetalsController, MetalsBusinessService } from './metals';
|
||
import { Database } from './database';
|
||
import { RatesService, SettingsController, SettingsBusinessService } from './rates';
|
||
import { ZodError } from 'zod';
|
||
import { Prisma } from '@prisma/client';
|
||
import { AgentOAuth } from './mcp/oauth';
|
||
import { AgentCatalogue } from './mcp/catalogue';
|
||
import { AgentOperations } from './mcp/operations';
|
||
import { AgentFiles } from './mcp/files';
|
||
import { AgentTransport } from './mcp/transport';
|
||
import { AgentManagementController } from './mcp/management';
|
||
@Catch()
|
||
class SafeErrors implements ExceptionFilter {
|
||
catch(error: unknown, host: ArgumentsHost) {
|
||
let status = 500,
|
||
message = '服务暂时不可用,请稍后重试';
|
||
if (error instanceof ZodError) {
|
||
status = 400;
|
||
message = error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(';');
|
||
} else if (error instanceof HttpException) {
|
||
status = error.getStatus();
|
||
message = error.message;
|
||
} else if (
|
||
error instanceof Prisma.PrismaClientKnownRequestError &&
|
||
['P2002', 'P2034'].includes(error.code)
|
||
) {
|
||
status = 409;
|
||
message = '数据已存在或已被其他操作更新,请刷新后重试';
|
||
} else if (error instanceof SyntaxError) {
|
||
status = 400;
|
||
message = 'JSON 文件或请求格式无效';
|
||
} else if ((error as { status?: number })?.status === 413) {
|
||
status = 413;
|
||
message = '文件不能超过 8 MB';
|
||
}
|
||
host.switchToHttp().getResponse().setHeader('Cache-Control', 'no-store');
|
||
host.switchToHttp().getResponse().status(status).json({ message });
|
||
}
|
||
}
|
||
@Module({
|
||
providers: [
|
||
Database,
|
||
AuthService,
|
||
RatesService,
|
||
MetalsService,
|
||
IconsService,
|
||
AuthBusinessService,
|
||
CalendarBusinessService,
|
||
SchedulesBusinessService,
|
||
TransfersBusinessService,
|
||
PortfolioBusinessService,
|
||
BackupBusinessService,
|
||
IconsBusinessService,
|
||
MetalsBusinessService,
|
||
SettingsBusinessService,
|
||
AgentOAuth,
|
||
AgentCatalogue,
|
||
AgentOperations,
|
||
AgentFiles,
|
||
AgentTransport,
|
||
{ provide: APP_GUARD, useClass: AuthGuard },
|
||
],
|
||
controllers: [
|
||
TransfersController,
|
||
SchedulesController,
|
||
CalendarController,
|
||
IconsController,
|
||
AuthController,
|
||
PortfolioController,
|
||
MetalsController,
|
||
SettingsController,
|
||
BackupController,
|
||
AgentManagementController,
|
||
],
|
||
})
|
||
class AppModule {}
|
||
async function bootstrap() {
|
||
if (!process.env.DATABASE_URL || !process.env.WEB_ORIGIN)
|
||
throw Error('Missing local environment configuration');
|
||
if (process.env.NODE_ENV === 'production' && process.env.WEB_ORIGIN === '*')
|
||
throw Error('Production requires an explicit web origin');
|
||
if (process.env.NODE_ENV === 'production' && process.env.COOKIE_SECURE !== 'true')
|
||
throw Error('Production requires secure cookies');
|
||
const app = await NestFactory.create(AppModule, { logger: false, bodyParser: false });
|
||
app.use(helmet());
|
||
app.use(json({ limit: '8mb' }));
|
||
app.use(cookieParser());
|
||
app.use((_req: unknown, res: { setHeader: (k: string, v: string) => void }, next: () => void) => {
|
||
res.setHeader('Cache-Control', 'no-store');
|
||
next();
|
||
});
|
||
app.get(AgentTransport).install(app.getHttpAdapter().getInstance());
|
||
app.useGlobalFilters(new SafeErrors());
|
||
setupOpenApi(app);
|
||
app.enableShutdownHooks();
|
||
await app.listen(Number(process.env.PORT || 3100), '0.0.0.0');
|
||
console.log('WorthPath API ready');
|
||
}
|
||
void bootstrap().catch(() => {
|
||
console.error('API startup failed. Check local configuration and database availability.');
|
||
process.exitCode = 1;
|
||
});
|