diff --git a/@platform/codebase/@features/platform-api/src/app.module.ts b/@platform/codebase/@features/platform-api/src/app.module.ts new file mode 100644 index 0000000..3d89616 --- /dev/null +++ b/@platform/codebase/@features/platform-api/src/app.module.ts @@ -0,0 +1,38 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { APP_GUARD } from '@nestjs/core'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { AuthModule } from './auth/auth.module.js'; +import { QuinnSsoGuard } from './auth/quinn-sso.guard.js'; +import { CacheInvalidateModule } from './common/cache-invalidate.module.js'; +import { databaseConfig } from './config/database.config.js'; +import { HealthModule } from './health/health.module.js'; +import { AgentActionsModule } from './modules/agent-actions/agent-actions.module.js'; +import { ContentPlansModule } from './modules/content-plans/content-plans.module.js'; +import { ContentPostsModule } from './modules/content-posts/content-posts.module.js'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + cache: true, + envFilePath: ['.env.local', '.env'], + }), + TypeOrmModule.forRootAsync(databaseConfig), + CacheInvalidateModule, + AuthModule, + HealthModule, + // V3 domain modules — add new modules here as they land. + // P0 ships content-plans + content-posts + agent-actions (the verification-gate trio). + // P1+ adds: users, orgs, personas, content-assets, engagement-events. + ContentPlansModule, + ContentPostsModule, + AgentActionsModule, + ], + providers: [ + // Global auth guard. Federates against quinn.sso (v2). Bypassed by @Public()-decorated routes. + { provide: APP_GUARD, useClass: QuinnSsoGuard }, + ], +}) +export class AppModule {} diff --git a/@platform/codebase/@features/platform-api/src/health/health.module.ts b/@platform/codebase/@features/platform-api/src/health/health.module.ts new file mode 100644 index 0000000..f09672b --- /dev/null +++ b/@platform/codebase/@features/platform-api/src/health/health.module.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Module } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { Public } from '../auth/public.decorator.js'; + +interface HealthResponse { + status: 'ok'; + service: 'platform.api'; + ts: string; +} + +@ApiTags('health') +@Controller('health') +class HealthController { + @Get() + @Public() + check(): HealthResponse { + return { status: 'ok', service: 'platform.api', ts: new Date().toISOString() }; + } +} + +@Module({ + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/@platform/codebase/@features/platform-api/src/main.ts b/@platform/codebase/@features/platform-api/src/main.ts new file mode 100644 index 0000000..3943f9b --- /dev/null +++ b/@platform/codebase/@features/platform-api/src/main.ts @@ -0,0 +1,47 @@ +import 'reflect-metadata'; + +import { Logger, ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { NestFactory } from '@nestjs/core'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; + +import { AppModule } from './app.module.js'; + +async function bootstrap(): Promise { + const app = await NestFactory.create(AppModule, { bufferLogs: true }); + const config = app.get(ConfigService); + const logger = new Logger('Bootstrap'); + + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + transformOptions: { enableImplicitConversion: false }, + }), + ); + + app.setGlobalPrefix('api/v1', { exclude: ['health', 'health/(.*)'] }); + + if (config.get('NODE_ENV') !== 'production') { + const swaggerConfig = new DocumentBuilder() + .setTitle('platform.api') + .setDescription('V3 platform data plane — multi-tenant CRUD over platform.db') + .setVersion('0.1.0') + .addBearerAuth() + .build(); + const document = SwaggerModule.createDocument(app, swaggerConfig); + SwaggerModule.setup('docs', app, document); + } + + const port = config.get('PLATFORM_API_PORT', 3060); + await app.listen(port, '0.0.0.0'); + + logger.log(`platform.api listening on :${port} (env=${config.get('NODE_ENV') ?? 'development'})`); +} + +bootstrap().catch((err: unknown) => { + const logger = new Logger('Bootstrap'); + logger.error('Fatal bootstrap error', err instanceof Error ? err.stack : String(err)); + process.exit(1); +});