const User = require('../models/User'); const Appointment = require('../models/Appointment'); const Invitation = require('../models/Invitation'); const Withdrawal = require('../models/Withdrawal'); const LoginHistory = require('../models/LoginHistory'); const { Op } = require('sequelize'); const { Parser } = require('json2csv'); /** * Admin User Service * Handles user management operations for admin panel */ /** * Get paginated user list with search and filters * @param {Object} options - Query options * @returns {Promise} User list with pagination */ const getUserList = async (options = {}) => { const { page = 1, limit = 20, search = '', status = '', language = '', sortBy = 'createdAt', sortOrder = 'DESC', } = options; const offset = (page - 1) * limit; // Build where clause const where = {}; // Search by nickname, real name, phone, or wechat ID if (search) { where[Op.or] = [ { nickname: { [Op.like]: `%${search}%` } }, { realName: { [Op.like]: `%${search}%` } }, { phone: { [Op.like]: `%${search}%` } }, { wechatId: { [Op.like]: `%${search}%` } }, ]; } // Filter by status if (status) { where.status = status; } // Filter by language if (language) { where.language = language; } // Query users const { count, rows: users } = await User.findAndCountAll({ where, limit: parseInt(limit), offset, order: [[sortBy, sortOrder]], attributes: [ 'id', 'uid', 'nickname', 'avatar', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance', 'invitationCode', 'status', 'createdAt', 'updatedAt', ], }); return { users, pagination: { total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / limit), }, }; }; /** * Get detailed user information * @param {String} userId - User ID * @returns {Promise} User details with related data */ const getUserDetails = async (userId) => { // Get user const user = await User.findByPk(userId, { attributes: [ 'id', 'uid', 'wechatOpenId', 'nickname', 'avatar', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance', 'invitationCode', 'invitedBy', 'status', 'createdAt', 'updatedAt', ], }); if (!user) { throw new Error('User not found'); } // Get inviter information if exists let inviter = null; if (user.invitedBy) { inviter = await User.findByPk(user.invitedBy, { attributes: ['id', 'nickname', 'realName'], }); } // Get appointment statistics const appointmentStats = await Appointment.findAll({ where: { userId }, attributes: [ 'status', [require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'], ], group: ['status'], raw: true, }); const appointmentCounts = { total: 0, pending: 0, confirmed: 0, 'in-progress': 0, completed: 0, cancelled: 0, }; appointmentStats.forEach((stat) => { appointmentCounts[stat.status] = parseInt(stat.count); appointmentCounts.total += parseInt(stat.count); }); // Get invitation statistics const invitationStats = await Invitation.findAll({ where: { inviterId: userId }, attributes: [ [require('sequelize').fn('COUNT', require('sequelize').col('id')), 'totalInvites'], [require('sequelize').fn('SUM', require('sequelize').col('reward_amount')), 'totalRewards'], ], raw: true, }); const invitationCounts = { totalInvites: parseInt(invitationStats[0]?.totalInvites || 0), totalRewards: parseFloat(invitationStats[0]?.totalRewards || 0), }; // Get withdrawal statistics const withdrawalStats = await Withdrawal.findAll({ where: { userId }, attributes: [ 'status', [require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'], [require('sequelize').fn('SUM', require('sequelize').col('amount')), 'total'], ], group: ['status'], raw: true, }); const withdrawalCounts = { total: 0, totalAmount: 0, waiting: 0, processing: 0, completed: 0, rejected: 0, }; withdrawalStats.forEach((stat) => { withdrawalCounts[stat.status] = parseInt(stat.count); withdrawalCounts.total += parseInt(stat.count); withdrawalCounts.totalAmount += parseFloat(stat.total || 0); }); // Get recent login history const loginHistory = await LoginHistory.findAll({ where: { userId, userType: 'user' }, limit: 10, order: [['loginAt', 'DESC']], attributes: ['id', 'ipAddress', 'userAgent', 'deviceInfo', 'loginAt'], }); return { user: user.toJSON(), inviter, appointments: appointmentCounts, invitations: invitationCounts, withdrawals: withdrawalCounts, loginHistory, }; }; /** * Update user status * @param {String} userId - User ID * @param {String} status - New status (active/suspended) * @returns {Promise} Updated user */ const updateUserStatus = async (userId, status) => { const user = await User.findByPk(userId); if (!user) { throw new Error('User not found'); } if (!['active', 'suspended'].includes(status)) { throw new Error('Invalid status value'); } user.status = status; await user.save(); return user; }; /** * Export user data to CSV * @param {Object} options - Query options (same as getUserList) * @returns {Promise} CSV string */ const exportUsersToCSV = async (options = {}) => { // Get all users matching criteria (no pagination) const { search = '', status = '', language = '' } = options; const where = {}; if (search) { where[Op.or] = [ { nickname: { [Op.like]: `%${search}%` } }, { realName: { [Op.like]: `%${search}%` } }, { phone: { [Op.like]: `%${search}%` } }, { wechatId: { [Op.like]: `%${search}%` } }, ]; } if (status) { where.status = status; } if (language) { where.language = language; } const users = await User.findAll({ where, order: [['createdAt', 'DESC']], attributes: [ 'id', 'nickname', 'realName', 'phone', 'whatsapp', 'wechatId', 'language', 'balance', 'invitationCode', 'status', 'createdAt', ], }); // Convert to plain objects const userData = users.map((user) => ({ ID: user.id, Nickname: user.nickname || '', 'Real Name': user.realName || '', Phone: user.phone || '', WhatsApp: user.whatsapp || '', 'WeChat ID': user.wechatId || '', Language: user.language, Balance: user.balance, 'Invitation Code': user.invitationCode || '', Status: user.status, 'Created At': user.createdAt.toISOString(), })); // Convert to CSV const json2csvParser = new Parser(); const csv = json2csvParser.parse(userData); return csv; }; module.exports = { getUserList, getUserDetails, updateUserStatus, exportUsersToCSV, };