CentralBackend/services/admin/AdminService.js

475 lines
18 KiB
JavaScript

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;