🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
335 lines
8.7 KiB
TypeScript
335 lines
8.7 KiB
TypeScript
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
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();
|