/** * Management Portal API Routes * Completely separate authentication system for security isolation */ const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); // Fixed: use bcryptjs instead of bcrypt const { Op } = require('sequelize'); // Add Sequelize operators const { Tenant, User, ManagementUser } = require('../models'); // Management-specific authentication middleware - NO shared auth with tenants const requireManagementAuth = (req, res, next) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ success: false, message: 'Management authentication required' }); } try { // Use separate JWT secret for management const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production'; const decoded = jwt.verify(token, MANAGEMENT_SECRET); // Verify this is a management token with proper role if (!decoded.isManagement || !['super_admin', 'platform_admin'].includes(decoded.role)) { return res.status(403).json({ success: false, message: 'Insufficient management privileges' }); } req.managementUser = { id: decoded.userId, username: decoded.username, role: decoded.role, isManagement: true }; next(); } catch (error) { return res.status(403).json({ success: false, message: 'Invalid management token', error: error.message }); } }; // Management login endpoint - separate from tenant auth router.post('/auth/login', async (req, res) => { try { const { username, password } = req.body; // Use ManagementUser model instead of hardcoded users const managementUser = await ManagementUser.findByCredentials(username, password); if (!managementUser) { return res.status(401).json({ success: false, message: 'Invalid management credentials' }); } const MANAGEMENT_SECRET = process.env.MANAGEMENT_JWT_SECRET || 'mgmt-super-secret-change-in-production'; const token = jwt.sign({ userId: managementUser.id, username: managementUser.username, role: managementUser.role, isManagement: true }, MANAGEMENT_SECRET, { expiresIn: '8h' }); res.json({ success: true, token, user: { id: managementUser.id, username: managementUser.username, email: managementUser.email, first_name: managementUser.first_name, last_name: managementUser.last_name, role: managementUser.role } }); } catch (error) { res.status(500).json({ success: false, message: 'Management authentication error', error: error.message }); } }); // Apply management authentication to all other routes router.use(requireManagementAuth); // Security audit logging for all management operations router.use((req, res, next) => { const auditLog = { timestamp: new Date().toISOString(), user: req.managementUser.username, role: req.managementUser.role, method: req.method, path: req.path, ip: req.ip, userAgent: req.headers['user-agent'] }; console.log('[MANAGEMENT AUDIT]', JSON.stringify(auditLog)); next(); }); /** * GET /api/management/system-info - Comprehensive platform system information */ router.get('/system-info', async (req, res) => { try { const { exec } = require('child_process'); const https = require('https'); const { promisify } = require('util'); const execAsync = promisify(exec); // Get basic statistics const tenantCount = await Tenant.count(); const userCount = await User.count(); // Get container metrics using internal health endpoints let containerMetrics = {}; const containerEndpoints = [ // Application containers with custom health endpoints { name: 'drone-detection-backend', url: 'http://drone-detection-backend:3000/health/metrics', type: 'app' }, { name: 'drone-detection-frontend', url: 'http://drone-detection-frontend:80/health/metrics', type: 'app' }, { name: 'drone-detection-management', url: 'http://drone-detection-management:3001/health/metrics', type: 'app' }, // Database containers - try standard health endpoints { name: 'postgres', url: 'http://postgres:5432', type: 'database' }, { name: 'redis', url: 'http://redis:6379', type: 'cache' }, // Infrastructure containers { name: 'nginx', url: 'http://nginx:80/nginx_status', type: 'proxy' }, { name: 'nginx-proxy-manager', url: 'http://nginx-proxy-manager:81/api/health', type: 'proxy' } ]; // Try internal container health endpoints first try { const https = require('https'); const http = require('http'); const healthChecks = await Promise.allSettled( containerEndpoints.map(async ({ name, url, type }) => { return new Promise((resolve, reject) => { const urlObj = new URL(url); const client = urlObj.protocol === 'https:' ? https : http; const req = client.request({ hostname: urlObj.hostname, port: urlObj.port, path: urlObj.pathname, method: 'GET', timeout: 3000 }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const metrics = res.headers['content-type']?.includes('application/json') ? JSON.parse(data) : { status: 'healthy', raw: data }; resolve({ name, metrics: { ...metrics, type, source: 'health_endpoint' } }); } catch (e) { resolve({ name, metrics: { status: 'responding', type, source: 'basic_check', data: data.substring(0, 100) } }); } }); }); req.on('error', (error) => { resolve({ name, metrics: { status: 'unreachable', type, error: error.message, source: 'health_check_failed' } }); }); req.on('timeout', () => { req.destroy(); resolve({ name, metrics: { status: 'timeout', type, source: 'health_check_failed' } }); }); req.end(); }); }) ); healthChecks.forEach((result) => { if (result.status === 'fulfilled') { containerMetrics[result.value.name] = result.value.metrics; } }); } catch (healthError) { console.log('Container health checks failed, trying Docker stats...'); } // Fallback to Docker stats for ALL containers (not just our apps) try { const { stdout } = await execAsync('docker stats --no-stream --format "table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.MemPerc}}\\t{{.NetIO}}\\t{{.BlockIO}}"'); const lines = stdout.trim().split('\n').slice(1); lines.forEach(line => { const [container, cpu, memUsage, memPerc, netIO, blockIO] = line.split('\t'); if (container && cpu) { // Determine container type let type = 'unknown'; if (container.includes('postgres') || container.includes('mysql') || container.includes('mongo')) type = 'database'; else if (container.includes('redis') || container.includes('memcached')) type = 'cache'; else if (container.includes('nginx') || container.includes('proxy') || container.includes('traefik')) type = 'proxy'; else if (container.includes('drone-detection') || container.includes('uamils')) type = 'application'; else if (container.includes('elasticsearch') || container.includes('kibana') || container.includes('logstash')) type = 'logging'; else if (container.includes('prometheus') || container.includes('grafana')) type = 'monitoring'; // If we don't have health endpoint data, use docker stats if (!containerMetrics[container]) { containerMetrics[container] = { cpu: cpu, memory: { usage: memUsage, percentage: memPerc }, network: netIO, disk: blockIO, type: type, source: 'docker_stats' }; } else { // Enhance existing health data with docker stats containerMetrics[container] = { ...containerMetrics[container], cpu: cpu, memory: { usage: memUsage, percentage: memPerc }, network: netIO, disk: blockIO }; } } }); } catch (dockerError) { console.log('Docker stats failed, trying docker compose...'); // Try container inspection via docker compose try { const { stdout: composeStatus } = await execAsync('docker-compose ps --format json'); const containers = JSON.parse(`[${composeStatus.split('\n').filter(line => line.trim()).join(',')}]`); containers.forEach(container => { if (container.Name && !containerMetrics[container.Name]) { let type = 'unknown'; const name = container.Name.toLowerCase(); if (name.includes('postgres') || name.includes('mysql') || name.includes('mongo')) type = 'database'; else if (name.includes('redis') || name.includes('memcached')) type = 'cache'; else if (name.includes('nginx') || name.includes('proxy')) type = 'proxy'; else if (name.includes('drone-detection') || name.includes('uamils')) type = 'application'; containerMetrics[container.Name] = { status: container.State, health: container.Health || 'unknown', ports: container.Ports, type: type, source: 'docker_compose' }; } }); } catch (composeError) { // Final fallback - try to detect containers via process list try { const { stdout: processes } = await execAsync('ps aux | grep -E "(postgres|redis|nginx|docker)" | grep -v grep'); const processLines = processes.split('\n').filter(line => line.trim()); const detectedServices = {}; processLines.forEach(line => { if (line.includes('postgres')) detectedServices['postgres-process'] = { status: 'running', type: 'database', source: 'process_list' }; if (line.includes('redis')) detectedServices['redis-process'] = { status: 'running', type: 'cache', source: 'process_list' }; if (line.includes('nginx')) detectedServices['nginx-process'] = { status: 'running', type: 'proxy', source: 'process_list' }; }); if (Object.keys(detectedServices).length > 0) { containerMetrics = { ...containerMetrics, ...detectedServices }; } else { containerMetrics = { error: 'All container monitoring methods failed', attempts: ['health_endpoints', 'docker_stats', 'docker_compose', 'process_list'], lastError: composeError.message }; } } catch (processError) { containerMetrics = { error: 'All container monitoring methods failed', attempts: ['health_endpoints', 'docker_stats', 'docker_compose', 'process_list'], lastError: processError.message }; } } } // Get system memory and CPU info let systemMetrics = {}; try { const { stdout: memInfo } = await execAsync('free -m'); const memLines = memInfo.split('\n')[1].split(/\s+/); const totalMem = parseInt(memLines[1]); const usedMem = parseInt(memLines[2]); const { stdout: cpuInfo } = await execAsync('top -bn1 | grep "Cpu(s)" | sed "s/.*, *\\([0-9.]*\\)%* id.*/\\1/" | awk \'{print 100 - $1}\''); const cpuUsage = parseFloat(cpuInfo.trim()); const { stdout: diskInfo } = await execAsync('df -h / | awk \'NR==2{print $3 " / " $2 " (" $5 ")"}\''); systemMetrics = { memory: { used: `${usedMem}MB`, total: `${totalMem}MB`, percentage: Math.round((usedMem / totalMem) * 100) }, cpu: { usage: `${cpuUsage.toFixed(1)}%`, percentage: cpuUsage }, disk: diskInfo.trim() }; } catch (sysError) { console.log('System metrics not available:', sysError.message); systemMetrics = { error: 'System metrics not available', message: sysError.message, memory: { used: 'N/A', total: 'N/A', percentage: 0 }, cpu: { usage: 'N/A', percentage: 0 }, disk: 'N/A' }; } // Check SSL certificate expiry const checkSSLCert = (hostname) => { return new Promise((resolve) => { const options = { hostname: hostname, port: 443, method: 'GET', timeout: 5000 }; const req = https.request(options, (res) => { const cert = res.connection.getPeerCertificate(); if (cert && cert.valid_to) { const expiryDate = new Date(cert.valid_to); const daysUntilExpiry = Math.ceil((expiryDate - new Date()) / (1000 * 60 * 60 * 24)); resolve({ status: daysUntilExpiry > 30 ? 'valid' : daysUntilExpiry > 7 ? 'warning' : 'critical', expiresAt: expiryDate.toISOString(), daysUntilExpiry: daysUntilExpiry, issuer: cert.issuer?.O || 'Unknown', subject: cert.subject?.CN || hostname }); } else { resolve({ status: 'error', expiresAt: null, error: 'Certificate not found' }); } }); req.on('error', () => { resolve({ status: 'error', expiresAt: null, error: 'Connection failed' }); }); req.on('timeout', () => { req.destroy(); resolve({ status: 'error', expiresAt: null, error: 'Timeout' }); }); req.end(); }); }; // Check SSL for management host only const managementHost = 'management.dev.uggla.uamils.com'; let sslStatus = {}; try { const sslCheck = await checkSSLCert(managementHost); sslStatus[managementHost] = sslCheck; } catch (sslError) { console.log('SSL check failed:', sslError.message); sslStatus[managementHost] = { status: 'error', expiresAt: null, error: 'SSL check failed: ' + sslError.message }; } res.json({ success: true, data: { platform: { name: 'UAMILS Platform', version: '1.0.0', environment: process.env.NODE_ENV || 'development', uptime: formatUptime(process.uptime()) }, statistics: { tenants: tenantCount, total_users: userCount, uptime_seconds: process.uptime() }, system: systemMetrics, containers: containerMetrics, ssl: sslStatus, security: { management_access_level: req.managementUser.role, last_backup: process.env.LAST_BACKUP_DATE || 'Not configured' }, timestamp: new Date().toISOString() } }); } catch (error) { console.error('Management: Error fetching system info:', error); res.status(500).json({ success: false, message: 'Failed to fetch system information', error: error.message }); } }); // Helper function to format uptime function formatUptime(seconds) { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (days > 0) { return `${days}d ${hours}h ${minutes}m`; } else if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } } /** * GET /api/management/tenants - List all tenants with admin details */ router.get('/tenants', async (req, res) => { try { const { limit = 50, offset = 0, search, auth_provider } = req.query; const whereClause = {}; if (search) { whereClause[Op.or] = [ { name: { [Op.iLike]: `%${search}%` } }, { slug: { [Op.iLike]: `%${search}%` } }, { domain: { [Op.iLike]: `%${search}%` } } ]; } if (auth_provider) { whereClause.auth_provider = auth_provider; } const tenants = await Tenant.findAndCountAll({ where: whereClause, include: [{ model: User, as: 'users', attributes: ['id', 'username', 'email', 'role', 'last_login', 'created_at'], limit: 10 }], limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: tenants.rows, pagination: { total: tenants.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(tenants.count / parseInt(limit)) } }); } catch (error) { console.error('Management: Error fetching tenants:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenants' }); } }); /** * POST /api/management/tenants - Create new tenant */ router.post('/tenants', async (req, res) => { try { const tenantData = req.body; // Enhanced validation for management portal if (!tenantData.name || !tenantData.slug) { return res.status(400).json({ success: false, message: 'Name and slug are required' }); } // Convert empty domain string to null to avoid unique constraint issues if (tenantData.domain === '') { tenantData.domain = null; } // Check for unique slug const existingTenant = await Tenant.findOne({ where: { slug: tenantData.slug } }); if (existingTenant) { return res.status(409).json({ success: false, message: 'Tenant slug already exists' }); } // Check for unique domain if provided if (tenantData.domain) { const existingDomain = await Tenant.findOne({ where: { domain: tenantData.domain } }); if (existingDomain) { return res.status(409).json({ success: false, message: 'Domain already exists for another tenant' }); } } // Log management action console.log(`Management: Admin ${req.managementUser.username} creating tenant: ${tenantData.name}`); const tenant = await Tenant.create(tenantData); res.status(201).json({ success: true, data: tenant, message: 'Tenant created successfully' }); } catch (error) { console.error('Management: Error creating tenant:', error); res.status(500).json({ success: false, message: 'Failed to create tenant' }); } }); /** * GET /api/management/tenants/:id - Get tenant details */ router.get('/tenants/:id', async (req, res) => { try { const tenant = await Tenant.findByPk(req.params.id, { include: [{ model: User, as: 'users', attributes: ['id', 'username', 'email', 'role', 'last_login', 'created_at'] }] }); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } res.json({ success: true, data: tenant }); } catch (error) { console.error('Management: Error fetching tenant:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenant' }); } }); /** * PUT /api/management/tenants/:id - Update tenant */ router.put('/tenants/:id', async (req, res) => { try { const tenant = await Tenant.findByPk(req.params.id); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } const updateData = req.body; // Convert empty domain string to null to avoid unique constraint issues if (updateData.domain === '') { updateData.domain = null; } // Check for unique domain if provided and different from current if (updateData.domain && updateData.domain !== tenant.domain) { const existingDomain = await Tenant.findOne({ where: { domain: updateData.domain, id: { [require('sequelize').Op.ne]: tenant.id } } }); if (existingDomain) { return res.status(409).json({ success: false, message: 'Domain already exists for another tenant' }); } } // Log management action console.log(`Management: Admin ${req.managementUser.username} updating tenant: ${tenant.name}`); await tenant.update(updateData); res.json({ success: true, data: tenant, message: 'Tenant updated successfully' }); } catch (error) { console.error('Management: Error updating tenant:', error); res.status(500).json({ success: false, message: 'Failed to update tenant' }); } }); /** * DELETE /api/management/tenants/:id - Delete tenant */ router.delete('/tenants/:id', async (req, res) => { try { const tenant = await Tenant.findByPk(req.params.id); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Prevent deletion of default tenant if (tenant.slug === 'default') { return res.status(403).json({ success: false, message: 'Cannot delete default tenant' }); } // Log management action console.log(`Management: Admin ${req.managementUser.username} deleting tenant: ${tenant.name}`); await tenant.destroy(); res.json({ success: true, message: 'Tenant deleted successfully' }); } catch (error) { console.error('Management: Error deleting tenant:', error); res.status(500).json({ success: false, message: 'Failed to delete tenant' }); } }); /** * GET /api/management/users - List all users across tenants */ router.get('/users', async (req, res) => { try { const { limit = 50, offset = 0, search, role, tenant_id } = req.query; const whereClause = {}; if (search) { whereClause[Op.or] = [ { username: { [Op.iLike]: `%${search}%` } }, { email: { [Op.iLike]: `%${search}%` } } ]; } if (role) { whereClause.role = role; } if (tenant_id) { whereClause.tenant_id = tenant_id; } const users = await User.findAndCountAll({ where: whereClause, include: [{ model: Tenant, as: 'tenant', attributes: ['id', 'name', 'slug'] }], attributes: { exclude: ['password'] }, // Never expose passwords limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: users.rows, pagination: { total: users.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(users.count / parseInt(limit)) } }); } catch (error) { console.error('Management: Error fetching users:', error); res.status(500).json({ success: false, message: 'Failed to fetch users' }); } }); /** * GET /api/management/system/info - System information */ router.get('/system/info', async (req, res) => { try { // Gather system statistics const tenantCount = await Tenant.count(); const userCount = await User.count(); const activeTenantsCount = await Tenant.count({ where: { is_active: true } }); const systemInfo = { version: process.env.APP_VERSION || '1.0.0', environment: process.env.NODE_ENV || 'production', uptime: process.uptime(), database: { status: 'connected', version: 'PostgreSQL 15', connections: 5, // You can get this from pg pool maxConnections: 100 }, statistics: { tenants: tenantCount, users: userCount, activeTenants: activeTenantsCount }, memory: { used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) + 'MB', total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024) + 'MB', percentage: Math.round((process.memoryUsage().heapUsed / process.memoryUsage().heapTotal) * 100) }, lastBackup: new Date(Date.now() - 24*60*60*1000).toISOString(), // Mock ssl: { status: 'valid', expiresAt: new Date(Date.now() + 90*24*60*60*1000).toISOString() // Mock 90 days } }; res.json({ success: true, data: systemInfo }); } catch (error) { console.error('Management: Error fetching system info:', error); res.status(500).json({ success: false, message: 'Failed to fetch system information' }); } }); /** * POST /api/management/tenants/:tenantId/users - Create user in specific tenant */ router.post('/tenants/:tenantId/users', async (req, res) => { try { const { tenantId } = req.params; const userData = req.body; // Verify tenant exists const tenant = await Tenant.findByPk(tenantId); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } // Check if user already exists in this tenant const existingUser = await User.findOne({ where: { username: userData.username, tenant_id: tenantId } }); if (existingUser) { return res.status(409).json({ success: false, message: 'User already exists in this tenant' }); } // Hash password const bcrypt = require('bcryptjs'); const hashedPassword = await bcrypt.hash(userData.password, 10); // Create user with tenant association const user = await User.create({ ...userData, password_hash: hashedPassword, // Use correct field name tenant_id: tenantId, created_by: req.managementUser.username }); // Remove password_hash from response const userResponse = user.toJSON(); delete userResponse.password_hash; console.log(`Management: Admin ${req.managementUser.username} created user ${userData.username} in tenant ${tenant.name}`); res.status(201).json({ success: true, data: userResponse, message: 'User created successfully' }); } catch (error) { console.error('Management: Error creating user:', error); res.status(500).json({ success: false, message: 'Failed to create user', error: error.message }); } }); /** * PUT /api/management/tenants/:tenantId/users/:userId - Update user in tenant */ router.put('/tenants/:tenantId/users/:userId', async (req, res) => { try { const { tenantId, userId } = req.params; const updates = req.body; const user = await User.findOne({ where: { id: userId, tenant_id: tenantId }, include: [{ model: Tenant, as: 'tenant', attributes: ['name'] }] }); if (!user) { return res.status(404).json({ success: false, message: 'User not found in this tenant' }); } // Hash password if provided if (updates.password) { const bcrypt = require('bcryptjs'); updates.password_hash = await bcrypt.hash(updates.password, 10); delete updates.password; // Remove plain password } await user.update(updates); // Remove password_hash from response const userResponse = user.toJSON(); delete userResponse.password_hash; console.log(`Management: Admin ${req.managementUser.username} updated user ${user.username} in tenant ${user.tenant.name}`); res.json({ success: true, data: userResponse, message: 'User updated successfully' }); } catch (error) { console.error('Management: Error updating user:', error); res.status(500).json({ success: false, message: 'Failed to update user', error: error.message }); } }); /** * DELETE /api/management/tenants/:tenantId/users/:userId - Delete user from tenant */ router.delete('/tenants/:tenantId/users/:userId', async (req, res) => { try { const { tenantId, userId } = req.params; const user = await User.findOne({ where: { id: userId, tenant_id: tenantId }, include: [{ model: Tenant, as: 'tenant', attributes: ['name'] }] }); if (!user) { return res.status(404).json({ success: false, message: 'User not found in this tenant' }); } // Prevent deleting the last admin user if (user.role === 'admin') { const adminCount = await User.count({ where: { tenant_id: tenantId, role: 'admin' } }); if (adminCount <= 1) { return res.status(403).json({ success: false, message: 'Cannot delete the last admin user in tenant' }); } } console.log(`Management: Admin ${req.managementUser.username} deleting user ${user.username} from tenant ${user.tenant.name}`); await user.destroy(); res.json({ success: true, message: 'User deleted successfully' }); } catch (error) { console.error('Management: Error deleting user:', error); res.status(500).json({ success: false, message: 'Failed to delete user', error: error.message }); } }); /** * GET /api/management/tenants/:tenantId/users - Get all users in a tenant */ router.get('/tenants/:tenantId/users', async (req, res) => { try { const { tenantId } = req.params; const { limit = 50, offset = 0, search, role } = req.query; // Verify tenant exists const tenant = await Tenant.findByPk(tenantId); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } const whereClause = { tenant_id: tenantId }; if (search) { whereClause[Op.or] = [ { username: { [Op.iLike]: `%${search}%` } }, { email: { [Op.iLike]: `%${search}%` } }, { first_name: { [Op.iLike]: `%${search}%` } }, { last_name: { [Op.iLike]: `%${search}%` } } ]; } if (role) { whereClause.role = role; } const users = await User.findAndCountAll({ where: whereClause, attributes: { exclude: ['password_hash'] }, // Exclude password_hash, not password limit: Math.min(parseInt(limit), 100), offset: parseInt(offset), order: [['created_at', 'DESC']] }); res.json({ success: true, data: users.rows, pagination: { total: users.count, limit: parseInt(limit), offset: parseInt(offset), pages: Math.ceil(users.count / parseInt(limit)) }, tenant: { id: tenant.id, name: tenant.name, slug: tenant.slug } }); } catch (error) { console.error('Management: Error fetching tenant users:', error); res.status(500).json({ success: false, message: 'Failed to fetch tenant users', error: error.message }); } }); /** * POST /api/management/tenants/:tenantId/activate - Activate tenant */ router.post('/tenants/:tenantId/activate', async (req, res) => { try { const { tenantId } = req.params; const tenant = await Tenant.findByPk(tenantId); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } await tenant.update({ is_active: true }); console.log(`Management: Admin ${req.managementUser.username} activated tenant ${tenant.name}`); res.json({ success: true, data: tenant, message: 'Tenant activated successfully' }); } catch (error) { console.error('Management: Error activating tenant:', error); res.status(500).json({ success: false, message: 'Failed to activate tenant' }); } }); /** * POST /api/management/tenants/:tenantId/deactivate - Deactivate tenant */ router.post('/tenants/:tenantId/deactivate', async (req, res) => { try { const { tenantId } = req.params; const tenant = await Tenant.findByPk(tenantId); if (!tenant) { return res.status(404).json({ success: false, message: 'Tenant not found' }); } await tenant.update({ is_active: false }); console.log(`Management: Admin ${req.managementUser.username} deactivated tenant ${tenant.name}`); res.json({ success: true, data: tenant, message: 'Tenant deactivated successfully' }); } catch (error) { console.error('Management: Error deactivating tenant:', error); res.status(500).json({ success: false, message: 'Failed to deactivate tenant' }); } }); module.exports = router;