first commit
This commit is contained in:
130
apps/api/src/app/app.module.ts
Normal file
130
apps/api/src/app/app.module.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { IdentityApplicationModule } from '@task-stream/api/identity/application';
|
||||
import { IdentityInfrastructureModule } from '@task-stream/api/identity/infrastructure';
|
||||
import {
|
||||
AuthRequest,
|
||||
IdentityPresentersModule,
|
||||
WsAuthenticator,
|
||||
} from '@task-stream/api/identity/presenters';
|
||||
import { SharedModule } from '@task-stream/api/shared/application';
|
||||
import { SharedInfrastructureModule } from '@task-stream/api/shared/infrastructure';
|
||||
import { TrackerApplicationModule } from '@task-stream/api/tracker/application';
|
||||
import { TrackerInfrastructureModule } from '@task-stream/api/tracker/infrastructure';
|
||||
import { TrackerPresentersModule } from '@task-stream/api/tracker/presenters';
|
||||
import { join } from 'node:path';
|
||||
import { configSchema } from './config.schema';
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
/*
|
||||
* Composition root. Each bounded context is assembled outside-in:
|
||||
* presenters (GraphQL/HTTP) → application (services, CQRS handlers) → infrastructure (adapters)
|
||||
*/
|
||||
const identity = IdentityPresentersModule.withApplication(
|
||||
IdentityApplicationModule.withInfrastructure(IdentityInfrastructureModule),
|
||||
);
|
||||
const tracker = TrackerPresentersModule.withApplication(
|
||||
TrackerApplicationModule.withInfrastructure(TrackerInfrastructureModule),
|
||||
);
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
validationSchema: configSchema,
|
||||
cache: true,
|
||||
}),
|
||||
|
||||
// Write model: PostgreSQL (event store + lookup tables)
|
||||
TypeOrmModule.forRootAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
type: 'postgres',
|
||||
url: config.getOrThrow<string>('POSTGRES_URL'),
|
||||
autoLoadEntities: true,
|
||||
synchronize: config.get<boolean>('POSTGRES_SYNCHRONIZE'),
|
||||
}),
|
||||
}),
|
||||
|
||||
// Read model: MongoDB
|
||||
MongooseModule.forRootAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
uri: config.getOrThrow<string>('MONGO_URL'),
|
||||
}),
|
||||
}),
|
||||
|
||||
// Event store (PostgreSQL) → Redis stream → events bridge, AggregateRehydrator
|
||||
SharedModule.withInfrastructure(
|
||||
SharedInfrastructureModule.forRootAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
redisUrl: config.getOrThrow<string>('REDIS_URL'),
|
||||
streamKey: config.getOrThrow<string>('EVENT_STREAM_KEY'),
|
||||
maxLength: config.getOrThrow<number>('EVENT_STREAM_MAX_LENGTH'),
|
||||
eventsBridgeEnabled: config.getOrThrow<boolean>(
|
||||
'EVENTS_BRIDGE_ENABLED',
|
||||
),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
|
||||
// GraphQL + realtime subscriptions over graphql-ws
|
||||
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
||||
driver: ApolloDriver,
|
||||
imports: [identity],
|
||||
inject: [WsAuthenticator, ConfigService],
|
||||
useFactory: (ws: WsAuthenticator, config: ConfigService) => ({
|
||||
// Writes the SDL for frontend codegen; empty = keep the schema in memory.
|
||||
autoSchemaFile: config.get<string>('GRAPHQL_SCHEMA_FILE')
|
||||
? join(
|
||||
process.cwd(),
|
||||
config.get<string>('GRAPHQL_SCHEMA_FILE') as string,
|
||||
)
|
||||
: true,
|
||||
sortSchema: true,
|
||||
// GraphiQL on GET /graphql (send the Pireon token in the Headers tab).
|
||||
graphiql: config.get<boolean>('GRAPHQL_PLAYGROUND')
|
||||
? { url: '/graphql' }
|
||||
: false,
|
||||
playground: false,
|
||||
includeStacktraceInErrorResponses:
|
||||
config.get('NODE_ENV') === 'development',
|
||||
introspection: config.get<boolean>('GRAPHQL_PLAYGROUND'),
|
||||
subscriptions: {
|
||||
'graphql-ws': {
|
||||
path: '/graphql',
|
||||
// connection_init payload: { "authorization": "Bearer <pireon token>" }
|
||||
onConnect: async (ctx) => {
|
||||
try {
|
||||
(ctx.extra as { auth?: AuthRequest }).auth =
|
||||
await ws.authenticate(ctx.connectionParams);
|
||||
return true;
|
||||
} catch {
|
||||
return false; // closes the socket with 4403 Forbidden
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
context: ({
|
||||
req,
|
||||
extra,
|
||||
}: {
|
||||
req?: AuthRequest;
|
||||
extra?: { auth?: AuthRequest };
|
||||
}) => ({
|
||||
req: extra?.auth ?? req,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
identity,
|
||||
tracker,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class AppModule {}
|
||||
Reference in New Issue
Block a user