2026-09-17 19:14:41 +08:00
|
|
|
|
// services/api/src/app-cn.module.ts
|
2026-09-17 19:51:56 +08:00
|
|
|
|
// 根模块:Prisma(@Global,封装 @photo/db-cn)、日志(nestjs-pino / pino-http)、
|
2026-09-17 21:17:16 +08:00
|
|
|
|
// request-id 中间件、全局异常过滤器、全局校验管道(Task 13 起,DTO 经
|
|
|
|
|
|
// class-validator 声明、ValidationPipe 强制,失败由过滤器映射 400 VALIDATION_FAILED)、
|
|
|
|
|
|
// /v1/health、/v1/auth/*(Task 13)。
|
|
|
|
|
|
// 功能模块(albums/uploads/photos/public)自 Task 15+ 起逐个加入。
|
|
|
|
|
|
// 过滤器/管道经 APP_FILTER/APP_PIPE、中间件经 NestModule 在模块层注册——e2e 直接
|
|
|
|
|
|
// import 本模块即可获得与生产一致的管线(全局前缀除外,由入口设置)。
|
|
|
|
|
|
import {
|
|
|
|
|
|
type MiddlewareConsumer,
|
|
|
|
|
|
Module,
|
|
|
|
|
|
type NestModule,
|
|
|
|
|
|
ValidationPipe,
|
|
|
|
|
|
} from '@nestjs/common';
|
|
|
|
|
|
import { APP_FILTER, APP_PIPE } from '@nestjs/core';
|
2026-09-17 19:14:41 +08:00
|
|
|
|
import { LoggerModule } from 'nestjs-pino';
|
|
|
|
|
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
2026-09-17 21:34:22 +08:00
|
|
|
|
import { AlbumsModule } from './albums/albums.module';
|
2026-09-18 08:54:44 +08:00
|
|
|
|
import { AdminModule } from './admin/admin.module';
|
2026-09-17 19:14:41 +08:00
|
|
|
|
import { AllExceptionsFilter } from './all-exceptions.filter';
|
2026-09-17 21:17:16 +08:00
|
|
|
|
import { AuthModule } from './auth/auth.module';
|
2026-09-17 19:14:41 +08:00
|
|
|
|
import { HealthController } from './health.controller';
|
2026-09-17 23:51:29 +08:00
|
|
|
|
import { PhotosModule } from './photos/photos.module';
|
2026-09-17 19:51:56 +08:00
|
|
|
|
import { PrismaModule } from './prisma/prisma.module';
|
2026-09-17 23:33:57 +08:00
|
|
|
|
import { PublicModule } from './public/public.module';
|
2026-09-17 19:14:41 +08:00
|
|
|
|
import { RequestIdMiddleware, resolveRequestId } from './request-id.middleware';
|
2026-09-17 22:42:13 +08:00
|
|
|
|
import { UploadsModule } from './uploads/uploads.module';
|
2026-09-17 20:54:30 +08:00
|
|
|
|
import { resolveLogLevel } from './shared/log-level';
|
|
|
|
|
|
import type { ServerRequestWithId } from './shared/request-with-id';
|
2026-09-17 19:14:41 +08:00
|
|
|
|
|
|
|
|
|
|
@Module({
|
|
|
|
|
|
imports: [
|
2026-09-17 19:51:56 +08:00
|
|
|
|
// @Global:功能模块(Task 12+)直接注入 PrismaService,无需 re-import。
|
|
|
|
|
|
PrismaModule,
|
2026-09-17 21:17:16 +08:00
|
|
|
|
AuthModule,
|
2026-09-17 21:34:22 +08:00
|
|
|
|
// 相册域(Task 14 起步:PermissionsService;控制器自 Task 15 加入)。
|
|
|
|
|
|
AlbumsModule,
|
2026-09-17 22:42:13 +08:00
|
|
|
|
// 上传域(Task 18 init;Task 19 renew/complete/history)。
|
|
|
|
|
|
UploadsModule,
|
2026-09-17 23:33:57 +08:00
|
|
|
|
// 公开(观众端)域(Task 22 getPublicAlbum;Task 23 公开照片读)。
|
|
|
|
|
|
PublicModule,
|
2026-09-18 08:54:44 +08:00
|
|
|
|
// 照片管理域(Task 24:可见性生命周期 PATCH/DELETE;P1b Task 4 增补
|
|
|
|
|
|
// /albums/{albumId}/photos 管理列表)。
|
2026-09-17 23:51:29 +08:00
|
|
|
|
PhotosModule,
|
2026-09-18 08:54:44 +08:00
|
|
|
|
// 管理辅助域(P1b Task 4:/admin/users 账号前缀查询)。
|
|
|
|
|
|
AdminModule,
|
2026-09-17 19:14:41 +08:00
|
|
|
|
LoggerModule.forRoot({
|
|
|
|
|
|
pinoHttp: {
|
|
|
|
|
|
// 与 RequestIdMiddleware 共用同一解析函数:优先透传 x-request-id,
|
|
|
|
|
|
// 其次复用已生成的 req.id,否则生成 UUID;并回写响应头。
|
|
|
|
|
|
genReqId: (req: IncomingMessage, res: ServerResponse) =>
|
2026-09-17 20:54:30 +08:00
|
|
|
|
resolveRequestId(req as ServerRequestWithId, res),
|
2026-09-17 19:14:41 +08:00
|
|
|
|
redact: {
|
2026-09-17 20:54:30 +08:00
|
|
|
|
// 授权头 + Cookie(会话凭据,Task 7 遗留项③):请求/响应两侧都打码,
|
|
|
|
|
|
// 防止日志聚合层留存可重放的凭据。
|
|
|
|
|
|
paths: [
|
|
|
|
|
|
'req.headers.authorization',
|
|
|
|
|
|
'req.headers.cookie',
|
|
|
|
|
|
'res.headers.authorization',
|
|
|
|
|
|
'res.headers.set-cookie',
|
|
|
|
|
|
],
|
2026-09-17 19:14:41 +08:00
|
|
|
|
censor: '[REDACTED]',
|
|
|
|
|
|
},
|
2026-09-17 20:54:30 +08:00
|
|
|
|
// Task 7 遗留项⑤:非法 LOG_LEVEL 在模块装配期(即启动期)快速失败,
|
|
|
|
|
|
// 而不是让 pino 首次写日志时才炸或静默吞掉输出。
|
|
|
|
|
|
level: resolveLogLevel(process.env.LOG_LEVEL),
|
2026-09-17 19:14:41 +08:00
|
|
|
|
},
|
|
|
|
|
|
}),
|
|
|
|
|
|
],
|
|
|
|
|
|
controllers: [HealthController],
|
2026-09-17 21:17:16 +08:00
|
|
|
|
providers: [
|
|
|
|
|
|
{ provide: APP_FILTER, useClass: AllExceptionsFilter },
|
|
|
|
|
|
// 全局校验管道(Task 13):whitelist 剥离 DTO 未声明的属性(契约未禁未知
|
|
|
|
|
|
// 字段,剥离而非拒绝);校验失败抛 BadRequestException → 过滤器映射
|
|
|
|
|
|
// 400 VALIDATION_FAILED。经 APP_PIPE 在模块层注册,e2e 复用同一管线。
|
|
|
|
|
|
{
|
|
|
|
|
|
provide: APP_PIPE,
|
|
|
|
|
|
useFactory: () => new ValidationPipe({ whitelist: true }),
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
2026-09-17 19:14:41 +08:00
|
|
|
|
})
|
|
|
|
|
|
export class AppCnModule implements NestModule {
|
|
|
|
|
|
configure(consumer: MiddlewareConsumer): void {
|
|
|
|
|
|
consumer.apply(RequestIdMiddleware).forRoutes('*');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|