import { Injectable, NestMiddleware, Logger } from '@nestjs/common'; import { FastifyRequest, FastifyReply } from 'fastify'; import { TenantDatabaseService } from './tenant-database.service'; @Injectable() export class TenantMiddleware implements NestMiddleware { private readonly logger = new Logger(TenantMiddleware.name); constructor(private readonly tenantDbService: TenantDatabaseService) {} async use( req: FastifyRequest['raw'], res: FastifyReply['raw'], next: () => void, ) { try { // Extract subdomain from hostname const host = req.headers.host || ''; const hostname = host.split(':')[0]; // Remove port if present const parts = hostname.split('.'); // For local development, accept x-tenant-id header as fallback let tenantId = req.headers['x-tenant-id'] as string; let subdomain: string | null = null; // Extract subdomain (e.g., "acme" from "acme.routebox.co") if (parts.length > 2) { subdomain = parts[0]; // Ignore www subdomain if (subdomain === 'www') { subdomain = null; } } // Get tenant by subdomain if available if (subdomain) { const tenant = await this.tenantDbService.getTenantByDomain(subdomain); if (tenant) { tenantId = tenant.id; this.logger.log( `Tenant identified: ${tenant.name} (${tenant.id}) from subdomain: ${subdomain}`, ); } else { this.logger.warn(`No tenant found for subdomain: ${subdomain}`); } } if (tenantId) { // Attach tenant info to request object (req as any).tenantId = tenantId; if (subdomain) { (req as any).subdomain = subdomain; } } else { this.logger.warn(`No tenant identified from host: ${hostname}`); } next(); } catch (error) { this.logger.error('Error in tenant middleware', error); next(); } } }