432 lines
17 KiB
JavaScript
432 lines
17 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 inactive
|
|
if (currentStatus === 'inactive') {
|
|
logger.warn('AdminService.archiveUser:already_inactive', { userId });
|
|
return { message: 'User is already archived' };
|
|
}
|
|
|
|
// Store the previous status and update to inactive
|
|
await unitOfWork.connection.query(
|
|
`UPDATE user_status
|
|
SET status = 'inactive',
|
|
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 inactive
|
|
if (currentStatus !== 'inactive') {
|
|
logger.warn('AdminService.unarchiveUser:not_inactive', { 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 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;
|