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('.'); this.logger.log(`Host header: ${host}, hostname: ${hostname}, parts: ${JSON.stringify(parts)}`); // For local development, accept x-tenant-id header let tenantId = req.headers['x-tenant-id'] as string; let subdomain: string | null = null; this.logger.log(`Host header: ${host}, hostname: ${hostname}, parts: ${JSON.stringify(parts)}, x-tenant-id: ${tenantId}`); // If x-tenant-id is explicitly provided, use it directly if (tenantId) { this.logger.log(`Using explicit x-tenant-id: ${tenantId}`); (req as any).tenantId = tenantId; next(); return; } // Extract subdomain (e.g., "tenant1" from "tenant1.routebox.co") // For production domains with 3+ parts, extract first part as subdomain if (parts.length >= 3) { subdomain = parts[0]; // Ignore www subdomain if (subdomain === 'www') { subdomain = null; } } // For development (e.g., tenant1.localhost), also check 2 parts else if (parts.length === 2 && parts[1] === 'localhost') { subdomain = parts[0]; } this.logger.log(`Extracted subdomain: ${subdomain}`); // Get tenant by subdomain if available if (subdomain) { try { const tenant = await this.tenantDbService.getTenantByDomain(subdomain); if (tenant) { tenantId = tenant.id; this.logger.log( `Tenant identified: ${tenant.name} (${tenant.id}) from subdomain: ${subdomain}`, ); } } catch (error) { this.logger.warn(`No tenant found for subdomain: ${subdomain}`, error.message); // Fall back to using subdomain as tenantId directly if domain lookup fails tenantId = subdomain; this.logger.log(`Using subdomain as tenantId fallback: ${tenantId}`); } } 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(); } } }