import { Response, NextFunction } from 'express'; import { tenantsService } from './tenants.service.js'; import { TenantStatus } from '../auth/entities/index.js'; import { TenantPlan } from './entities/index.js'; import { safeValidateCreateTenant, safeValidateUpdateTenant, safeValidateUpdateTenantSettings, } from './dto/index.js'; import { ApiResponse, AuthenticatedRequest, ValidationError, PaginationParams } from '../../shared/types/index.js'; /** * TenantsController * Handles HTTP requests for tenant management. * Most endpoints require super_admin role. */ export class TenantsController { /** * GET /tenants - List all tenants (super_admin only) * Supports pagination, filtering by status/plan, and search */ async findAll(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const page = parseInt(req.query.page as string) || 1; const limit = Math.min(parseInt(req.query.limit as string) || 20, 100); const sortBy = req.query.sortBy as string || 'name'; const sortOrder = (req.query.sortOrder as 'asc' | 'desc') || 'asc'; const params: PaginationParams = { page, limit, sortBy, sortOrder }; // Build filter const filter: { status?: TenantStatus; plan?: TenantPlan; search?: string } = {}; if (req.query.status) { filter.status = req.query.status as TenantStatus; } if (req.query.plan) { filter.plan = req.query.plan as TenantPlan; } if (req.query.search) { filter.search = req.query.search as string; } const result = await tenantsService.findAll(params, filter); const response: ApiResponse = { success: true, data: result.tenants, meta: { page, limit, total: result.total, totalPages: Math.ceil(result.total / limit), }, }; res.json(response); } catch (error) { next(error); } } /** * GET /tenants/current - Get current user's tenant * Available to any authenticated user */ async getCurrent(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.user!.tenantId; const tenant = await tenantsService.findById(tenantId); const response: ApiResponse = { success: true, data: tenant, }; res.json(response); } catch (error) { next(error); } } /** * GET /tenants/:id - Get tenant by ID (super_admin only) */ async findById(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const includeSettings = req.query.includeSettings === 'true'; const tenant = await tenantsService.findById(tenantId, includeSettings); const response: ApiResponse = { success: true, data: tenant, }; res.json(response); } catch (error) { next(error); } } /** * GET /tenants/:id/stats - Get tenant statistics */ async getStats(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const stats = await tenantsService.getTenantStats(tenantId); const response: ApiResponse = { success: true, data: stats, }; res.json(response); } catch (error) { next(error); } } /** * POST /tenants - Create new tenant (super_admin only) */ async create(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = safeValidateCreateTenant(req.body); if (!validation.success) { throw new ValidationError('Datos invalidos', validation.error.errors); } const createdBy = req.user!.userId; const tenant = await tenantsService.create(validation.data, createdBy); const response: ApiResponse = { success: true, data: tenant, message: 'Tenant creado exitosamente', }; res.status(201).json(response); } catch (error) { next(error); } } /** * PUT /tenants/:id - Update tenant */ async update(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = safeValidateUpdateTenant(req.body); if (!validation.success) { throw new ValidationError('Datos invalidos', validation.error.errors); } const tenantId = req.params.id; const updatedBy = req.user!.userId; const tenant = await tenantsService.update(tenantId, validation.data, updatedBy); const response: ApiResponse = { success: true, data: tenant, message: 'Tenant actualizado exitosamente', }; res.json(response); } catch (error) { next(error); } } /** * POST /tenants/:id/suspend - Suspend tenant (super_admin only) */ async suspend(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const updatedBy = req.user!.userId; const tenant = await tenantsService.suspend(tenantId, updatedBy); const response: ApiResponse = { success: true, data: tenant, message: 'Tenant suspendido exitosamente', }; res.json(response); } catch (error) { next(error); } } /** * POST /tenants/:id/activate - Activate tenant (super_admin only) */ async activate(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const updatedBy = req.user!.userId; const tenant = await tenantsService.activate(tenantId, updatedBy); const response: ApiResponse = { success: true, data: tenant, message: 'Tenant activado exitosamente', }; res.json(response); } catch (error) { next(error); } } /** * DELETE /tenants/:id - Soft delete tenant (super_admin only) * Only allowed if tenant has no active users */ async delete(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const deletedBy = req.user!.userId; await tenantsService.delete(tenantId, deletedBy); const response: ApiResponse = { success: true, message: 'Tenant eliminado exitosamente', }; res.json(response); } catch (error) { next(error); } } /** * GET /tenants/:id/settings - Get tenant settings */ async getSettings(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const settings = await tenantsService.getSettings(tenantId); const response: ApiResponse = { success: true, data: settings, }; res.json(response); } catch (error) { next(error); } } /** * PUT /tenants/:id/settings - Update tenant settings */ async updateSettings(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const validation = safeValidateUpdateTenantSettings(req.body); if (!validation.success) { throw new ValidationError('Datos invalidos', validation.error.errors); } const tenantId = req.params.id; const updatedBy = req.user!.userId; const settings = await tenantsService.updateSettings( tenantId, validation.data, updatedBy ); const response: ApiResponse = { success: true, data: settings, message: 'Configuracion actualizada exitosamente', }; res.json(response); } catch (error) { next(error); } } /** * GET /tenants/:id/can-add-user - Check if tenant can add more users */ async canAddUser(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const result = await tenantsService.canAddUser(tenantId); const response: ApiResponse = { success: true, data: result, }; res.json(response); } catch (error) { next(error); } } /** * GET /tenants/:id/can-use-storage - Check if tenant has available storage */ async canUseStorage(req: AuthenticatedRequest, res: Response, next: NextFunction): Promise { try { const tenantId = req.params.id; const requiredMb = parseInt(req.query.requiredMb as string) || 0; const result = await tenantsService.canUseStorage(tenantId, requiredMb); const response: ApiResponse = { success: true, data: result, }; res.json(response); } catch (error) { next(error); } } } export const tenantsController = new TenantsController();