fix: add TenantContextMiddleware to initialize tenant context from X-Tenant-Id header

All services using TenantAwareRepository require AsyncLocalStorage tenant
context to set the correct PostgreSQL search_path. The middleware reads
X-Tenant-Id from request headers and wraps the request with
TenantContextService.run(), using schema naming convention it0_t_{tenantId}.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-21 23:58:01 -08:00
parent 806113554b
commit 5b6e7ee363
3 changed files with 32 additions and 2 deletions

View File

@ -1,9 +1,14 @@
import { DynamicModule, Module } from '@nestjs/common';
import { DynamicModule, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
import { TenantContextMiddleware } from './tenant-context.middleware';
@Module({})
export class DatabaseModule {
export class DatabaseModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(TenantContextMiddleware).forRoutes('*');
}
static forRoot(): DynamicModule {
return {
module: DatabaseModule,

View File

@ -1,3 +1,4 @@
export * from './tenant-aware.repository';
export * from './database.module';
export * from './tenant-provisioning.service';
export * from './tenant-context.middleware';

View File

@ -0,0 +1,24 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { TenantContextService } from '@it0/common';
import { TenantInfo } from '@it0/common';
@Injectable()
export class TenantContextMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const tenantId = req.headers['x-tenant-id'] as string;
if (!tenantId) {
return next();
}
const tenantInfo: TenantInfo = {
tenantId,
tenantName: tenantId,
plan: 'free',
schemaName: `it0_t_${tenantId}`,
};
TenantContextService.run(tenantInfo, () => next());
}
}