const AdminRepository = require('../../repositories/admin/AdminRepository'); const UserDocumentRepository = require('../../repositories/documents/UserDocumentRepository'); const UserRepository = require('../../repositories/user/UserRepository'); const { s3 } = require('../../utils/exoscaleUploader'); const { GetObjectCommand } = require('@aws-sdk/client-s3'); const { getSignedUrl } = require('@aws-sdk/s3-request-presigner'); const { DeleteObjectCommand } = require('@aws-sdk/client-s3'); const pidusage = require('pidusage'); const os = require('os'); const { logger } = require('../../middleware/logger'); class AdminService { static async getUserStats(unitOfWork) { logger.info('AdminService.getUserStats:start'); try { const stats = await AdminRepository.getUserStats(unitOfWork.connection); logger.info('AdminService.getUserStats:success', stats); return stats; } catch (error) { logger.error('AdminService.getUserStats:error', { error: error.message }); throw error; } } static async getUserList(unitOfWork) { logger.info('AdminService.getUserList:start'); try { const list = await AdminRepository.getUserList(unitOfWork.connection); logger.info('AdminService.getUserList:success', { count: list.length }); return list; } catch (error) { logger.error('AdminService.getUserList:error', { error: error.message }); throw error; } } static async getVerificationPendingUsers(unitOfWork) { logger.info('AdminService.getVerificationPendingUsers:start'); try { const users = await AdminRepository.getVerificationPendingUsers(unitOfWork.connection); logger.info('AdminService.getVerificationPendingUsers:success', { count: users.length }); return users; } catch (error) { logger.error('AdminService.getVerificationPendingUsers:error', { error: error.message }); throw error; } } static async getUnverifiedUsers(unitOfWork) { logger.info('AdminService.getUnverifiedUsers:start'); try { const users = await AdminRepository.getUnverifiedUsers(unitOfWork.connection); logger.info('AdminService.getUnverifiedUsers:success', { count: users.length }); return users; } catch (error) { logger.error('AdminService.getUnverifiedUsers:error', { error: error.message }); throw error; } } static async verifyUser(unitOfWork, userId, permissions) { logger.info('AdminService.verifyUser:start', { userId, permissions }); try { // 1. Fetch user const userRepo = new UserRepository(unitOfWork); const user = await userRepo.findUserByEmailOrId(Number(userId)); if (!user) throw new Error('User not found'); logger.info('AdminService.verifyUser:user_fetched', { userId }); // 2. Fetch documents and contracts const documents = await AdminRepository.getUserDocuments(unitOfWork.connection, userId); const contracts = await AdminRepository.getUserContracts(unitOfWork.connection, userId); logger.info('AdminService.verifyUser:documents_fetched', { userId, documentsCount: documents.length, contractsCount: contracts.length }); // 3. Fetch ID document metadata const idDocs = await AdminRepository.getUserIdDocuments(unitOfWork.connection, userId); logger.info('AdminService.verifyUser:id_docs_fetched', { userId, idDocsCount: idDocs.length }); // 4. Generate signed URLs for front/back of ID documents const idDocumentsWithUrls = await Promise.all( idDocs.map(async doc => { let frontUrl = null, backUrl = null; if (doc.front_object_storage_id) { try { const command = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: doc.front_object_storage_id }); frontUrl = await getSignedUrl(s3, command, { expiresIn: 900 }); } catch (err) { frontUrl = null; } } if (doc.back_object_storage_id) { try { const command = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: doc.back_object_storage_id }); backUrl = await getSignedUrl(s3, command, { expiresIn: 900 }); } catch (err) { backUrl = null; } } return { ...doc, frontUrl, backUrl }; }) ); logger.info('AdminService.verifyUser:id_docs_urls_generated', { userId, idDocsCount: idDocumentsWithUrls.length }); // 5. Update user_status await AdminRepository.verifyUser(unitOfWork.connection, userId); logger.info('AdminService.verifyUser:user_status_updated', { userId }); // 6. Assign permissions if (permissions.length > 0) { await AdminRepository.assignPermissions(unitOfWork.connection, userId, permissions); logger.info('AdminService.verifyUser:permissions_assigned', { userId, permissions }); } // 7. Return updated user data logger.info('AdminService.verifyUser:success', { userId }); return { message: 'User verified and permissions updated', user: user.getPublicData(), documents, contracts, idDocuments: idDocumentsWithUrls }; } catch (error) { logger.error('AdminService.verifyUser:error', { userId, error: error.message }); throw error; } } static async getFullUserAccountDetails(unitOfWork, userId) { logger.info('AdminService.getFullUserAccountDetails:start', { userId }); try { const user = await AdminRepository.getUserById(unitOfWork.connection, userId); if (!user) throw new Error('User not found'); const personalProfile = await AdminRepository.getPersonalProfile(unitOfWork.connection, userId); const companyProfile = await AdminRepository.getCompanyProfile(unitOfWork.connection, userId); const permissions = await AdminRepository.getUserPermissions(unitOfWork.connection, userId); logger.info('AdminService.getFullUserAccountDetails:success', { userId }); return { user, personalProfile, companyProfile, permissions }; } catch (error) { logger.error('AdminService.getFullUserAccountDetails:error', { userId, error: error.message }); throw error; } } static async getDetailedUserInfo(unitOfWork, userId) { logger.info('AdminService.getDetailedUserInfo:start', { userId }); try { // Get basic user info const user = await AdminRepository.getUserById(unitOfWork.connection, userId); if (!user) throw new Error('User not found'); // Get profiles const personalProfile = await AdminRepository.getPersonalProfile(unitOfWork.connection, userId); const companyProfile = await AdminRepository.getCompanyProfile(unitOfWork.connection, userId); // Get permissions const permissions = await AdminRepository.getUserPermissions(unitOfWork.connection, userId); // Get user status const [statusRows] = await unitOfWork.connection.query( `SELECT * FROM user_status WHERE user_id = ? LIMIT 1`, [userId] ); const userStatus = statusRows.length ? statusRows[0] : null; // Get documents const documents = await AdminRepository.getUserDocuments(unitOfWork.connection, userId); const contracts = await AdminRepository.getUserContracts(unitOfWork.connection, userId); // Get ID documents with signed URLs const idDocs = await AdminRepository.getUserIdDocuments(unitOfWork.connection, userId); const idDocumentsWithUrls = await Promise.all( idDocs.map(async doc => { let frontUrl = null, backUrl = null; if (doc.front_object_storage_id) { try { const command = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: doc.front_object_storage_id }); frontUrl = await getSignedUrl(s3, command, { expiresIn: 900 }); } catch (err) { frontUrl = null; } } if (doc.back_object_storage_id) { try { const command = new GetObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: doc.back_object_storage_id }); backUrl = await getSignedUrl(s3, command, { expiresIn: 900 }); } catch (err) { backUrl = null; } } return { ...doc, frontUrl, backUrl }; }) ); logger.info('AdminService.getDetailedUserInfo:success', { userId }); return { user, personalProfile, companyProfile, permissions, userStatus, documents, contracts, idDocuments: idDocumentsWithUrls }; } catch (error) { logger.error('AdminService.getDetailedUserInfo:error', { userId, error: error.message }); throw error; } } static async updateUserPermissions(unitOfWork, userId, permissions) { logger.info('AdminService.updateUserPermissions:start', { userId, permissions }); try { await AdminRepository.updateUserPermissions(unitOfWork.connection, userId, permissions); logger.info('AdminService.updateUserPermissions:success', { userId, permissions }); } catch (error) { logger.error('AdminService.updateUserPermissions:error', { userId, error: error.message }); throw error; } } static async getServerStatus() { logger.info('AdminService.getServerStatus:start'); try { const stats = await pidusage(process.pid); const uptimeSeconds = process.uptime(); const uptimeDays = Math.floor(uptimeSeconds / (60 * 60 * 24)); const uptimeHours = Math.floor((uptimeSeconds % (60 * 60 * 24)) / 3600); const totalMem = os.totalmem(); logger.info('AdminService.getServerStatus:success', { status: 'Online', uptime: { days: uptimeDays, hours: uptimeHours } }); return { status: 'Online', uptime: { days: uptimeDays, hours: uptimeHours }, cpuUsagePercent: Math.round(stats.cpu), memory: { used: (stats.memory / (1024 ** 3)).toFixed(1), total: (totalMem / (1024 ** 3)).toFixed(1) }, logs: [] }; } catch (error) { logger.error('AdminService.getServerStatus:error', { error: error.message }); throw error; } } static async deleteUser(unitOfWork, userId) { logger.info('AdminService.deleteUser:start', { userId }); try { // 1. Fetch all object storage IDs for user documents const docRepo = new UserDocumentRepository(unitOfWork); const objectIds = await docRepo.getAllObjectStorageIdsForUser(userId); // 2. Delete each document from S3 for (const obj of objectIds) { try { if (obj.object_storage_id) { await s3.send(new DeleteObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: obj.object_storage_id })); logger.info('AdminService.deleteUser:s3_deleted', { userId, objectKey: obj.object_storage_id }); } // For user_id_documents: front/back if (obj.front_object_storage_id) { await s3.send(new DeleteObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: obj.front_object_storage_id })); logger.info('AdminService.deleteUser:s3_deleted', { userId, objectKey: obj.front_object_storage_id }); } if (obj.back_object_storage_id) { await s3.send(new DeleteObjectCommand({ Bucket: process.env.EXOSCALE_BUCKET, Key: obj.back_object_storage_id })); logger.info('AdminService.deleteUser:s3_deleted', { userId, objectKey: obj.back_object_storage_id }); } } catch (err) { logger.warn('AdminService.deleteUser:s3_delete_failed', { userId, error: err.message, objectKey: obj.object_storage_id || obj.front_object_storage_id || obj.back_object_storage_id }); } } // 3. Delete user from DB (cascades) const userRepo = new UserRepository(unitOfWork); await userRepo.deleteUserById(userId); logger.info('AdminService.deleteUser:success', { userId }); } catch (error) { logger.error('AdminService.deleteUser:error', { userId, error: error.message }); throw error; } } static async archiveUser(unitOfWork, userId) { logger.info('AdminService.archiveUser:start', { userId }); try { // First, get the current status to store it const [statusRows] = await unitOfWork.connection.query( `SELECT status FROM user_status WHERE user_id = ? LIMIT 1`, [userId] ); if (statusRows.length === 0) { throw new Error('User status not found'); } const currentStatus = statusRows[0].status; // Don't archive if already archived if (currentStatus === 'archived') { logger.warn('AdminService.archiveUser:already_archived', { userId }); return { message: 'User is already archived' }; } // Store the previous status and update to archived await unitOfWork.connection.query( `UPDATE user_status SET status = 'archived', previous_status = ? WHERE user_id = ?`, [currentStatus, userId] ); logger.info('AdminService.archiveUser:success', { userId, previousStatus: currentStatus }); } catch (error) { logger.error('AdminService.archiveUser:error', { userId, error: error.message }); throw error; } } static async unarchiveUser(unitOfWork, userId) { logger.info('AdminService.unarchiveUser:start', { userId }); try { // Get the current status and previous status const [statusRows] = await unitOfWork.connection.query( `SELECT status, previous_status FROM user_status WHERE user_id = ? LIMIT 1`, [userId] ); if (statusRows.length === 0) { throw new Error('User status not found'); } const currentStatus = statusRows[0].status; const previousStatus = statusRows[0].previous_status; // Only unarchive if currently archived if (currentStatus !== 'archived') { logger.warn('AdminService.unarchiveUser:not_archived', { userId, currentStatus }); return { message: 'User is not archived' }; } // Restore to previous status or default to 'pending' if no previous status exists const restoreStatus = previousStatus || 'pending'; await unitOfWork.connection.query( `UPDATE user_status SET status = ?, previous_status = NULL WHERE user_id = ?`, [restoreStatus, userId] ); logger.info('AdminService.unarchiveUser:success', { userId, restoredStatus: restoreStatus }); } catch (error) { logger.error('AdminService.unarchiveUser:error', { userId, error: error.message }); throw error; } } static async updateUserStatus(unitOfWork, userId, newStatus) { logger.info('AdminService.updateUserStatus:start', { userId, newStatus }); try { // Validate status const validStatuses = ['inactive', 'pending', 'active', 'suspended', 'archived']; if (!validStatuses.includes(newStatus)) { throw new Error(`Invalid status: ${newStatus}. Must be one of: ${validStatuses.join(', ')}`); } // Get current status const [statusRows] = await unitOfWork.connection.query( `SELECT status FROM user_status WHERE user_id = ? LIMIT 1`, [userId] ); if (statusRows.length === 0) { throw new Error('User status not found'); } const currentStatus = statusRows[0].status; // Don't update if already at target status if (currentStatus === newStatus) { logger.warn('AdminService.updateUserStatus:already_at_status', { userId, status: newStatus }); return { message: `User is already ${newStatus}` }; } // Update status await unitOfWork.connection.query( `UPDATE user_status SET status = ? WHERE user_id = ?`, [newStatus, userId] ); logger.info('AdminService.updateUserStatus:success', { userId, oldStatus: currentStatus, newStatus }); return { message: `User status updated from ${currentStatus} to ${newStatus}` }; } catch (error) { logger.error('AdminService.updateUserStatus:error', { userId, error: error.message }); throw error; } } static async updateUserVerification(unitOfWork, userId, isAdminVerified) { logger.info('AdminService.updateUserVerification:start', { userId, isAdminVerified }); try { // Update the is_admin_verified field in user_status const timestamp = isAdminVerified === 1 ? new Date() : null; await unitOfWork.connection.query( `UPDATE user_status SET is_admin_verified = ?, admin_verified_at = ? WHERE user_id = ?`, [isAdminVerified, timestamp, userId] ); logger.info('AdminService.updateUserVerification:success', { userId, isAdminVerified }); } catch (error) { logger.error('AdminService.updateUserVerification:error', { userId, error: error.message }); throw error; } } static async updateUserProfile(unitOfWork, userId, profileData, userType) { logger.info('AdminService.updateUserProfile:start', { userId, userType }); try { if (userType === 'personal') { const { first_name, last_name, phone, date_of_birth, address, city, zip_code, country } = profileData; await unitOfWork.connection.query( `UPDATE personal_profiles SET first_name = ?, last_name = ?, phone = ?, date_of_birth = ?, address = ?, city = ?, zip_code = ?, country = ? WHERE user_id = ?`, [first_name, last_name, phone, date_of_birth, address, city, zip_code, country, userId] ); } else if (userType === 'company') { const { company_name, registration_number, tax_id, phone, address, city, zip_code, country } = profileData; await unitOfWork.connection.query( `UPDATE company_profiles SET company_name = ?, registration_number = ?, tax_id = ?, phone = ?, address = ?, city = ?, zip_code = ?, country = ? WHERE user_id = ?`, [company_name, registration_number, tax_id, phone, address, city, zip_code, country, userId] ); } logger.info('AdminService.updateUserProfile:success', { userId, userType }); } catch (error) { logger.error('AdminService.updateUserProfile:error', { userId, error: error.message }); throw error; } } } module.exports = AdminService;