/*********************************************************************************************************************************************************************** * File Name: users.ctrl.js (staff) * Type of Program: Controller * Description: Staff-scoped user endpoints. * A staff member can only see users who belong to at least one * of the same groups the staff member belongs to. ***********************************************************************************************************************************************************************/ const { Op } = require('sequelize'); const mdl_Users = require('../../models/users/users.mdl'); const { mdl_UserGroups, mdl_UserGroupMembers } = require('../../models/users/user_groups.mdl'); /** * GET /api/staff/users * Returns all users who share at least one group with the requesting staff member. * Supports optional query: ?group_id=27 or ?group_code=CG2027 */ const getUsers = async (req, res) => { try { const staffUserId = req.user.user_id; const { group_id, group_code, page = 1, limit = 20 } = req.query; const offset = (parseInt(page) - 1) * parseInt(limit); // 1. Find the groups the staff member belongs to const staffMemberships = await mdl_UserGroupMembers.findAll({ where: { user_id: staffUserId }, attributes: ['group_id'], raw: true, }); let scopedGroupIds = staffMemberships.map(m => m.group_id); if (scopedGroupIds.length === 0) { return res.status(200).json({ success: true, data: [], total: 0, page, limit }); } // 2. Filter down to a specific group if requested if (group_id) { const gid = parseInt(group_id); if (!scopedGroupIds.includes(gid)) { return res.status(403).json({ success: false, message: 'You do not have access to this group.' }); } scopedGroupIds = [gid]; } if (group_code) { const group = await mdl_UserGroups.findOne({ where: { group_code: group_code.toUpperCase().trim(), deletedAt: null }, attributes: ['group_id'], raw: true, }); if (!group || !scopedGroupIds.includes(group.group_id)) { return res.status(403).json({ success: false, message: 'You do not have access to this group.' }); } scopedGroupIds = [group.group_id]; } // 3. Find all user_ids in those groups (excluding the staff member themselves) const memberships = await mdl_UserGroupMembers.findAll({ where: { group_id: { [Op.in]: scopedGroupIds } }, attributes: ['user_id'], raw: true, }); const userIds = [...new Set(memberships.map(m => m.user_id))].filter(id => id !== staffUserId); // 4. Fetch users const { count, rows } = await mdl_Users.findAndCountAll({ where: { user_id: { [Op.in]: userIds } }, attributes: [ 'user_id', 'email', 'is_active', 'is_verified', 'acc_type', 'personal_info', 'createdAt', ], include: [ { model: mdl_UserGroups, as: 'groups', attributes: ['group_id', 'name', 'group_code'], through: { attributes: ['joined_at'] }, where: { group_id: { [Op.in]: scopedGroupIds } }, required: true, }, ], limit: parseInt(limit), offset, order: [['createdAt', 'DESC']], }); return res.status(200).json({ success: true, data: rows, total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / parseInt(limit)), }); } catch (err) { console.error('[staff/users] getUsers error:', err); return res.status(500).json({ success: false, message: 'Internal server error.' }); } }; /** * GET /api/staff/users/:user_id * Get a single user's profile — only if they share a group with the staff member. */ const getUserById = async (req, res) => { try { const staffUserId = req.user.user_id; const targetUserId = parseInt(req.params.user_id); // Get staff's groups const staffMemberships = await mdl_UserGroupMembers.findAll({ where: { user_id: staffUserId }, attributes: ['group_id'], raw: true, }); const scopedGroupIds = staffMemberships.map(m => m.group_id); // Check target user shares a group const sharedMembership = await mdl_UserGroupMembers.findOne({ where: { user_id: targetUserId, group_id: { [Op.in]: scopedGroupIds }, }, }); if (!sharedMembership) { return res.status(403).json({ success: false, message: 'User not in your scope.' }); } const user = await mdl_Users.findByPk(targetUserId, { attributes: ['user_id', 'email', 'is_active', 'is_verified', 'acc_type', 'personal_info', 'createdAt'], include: [ { model: mdl_UserGroups, as: 'groups', attributes: ['group_id', 'name', 'group_code'], through: { attributes: ['joined_at'] }, }, ], }); if (!user) return res.status(404).json({ success: false, message: 'User not found.' }); return res.status(200).json({ success: true, data: user }); } catch (err) { console.error('[staff/users] getUserById error:', err); return res.status(500).json({ success: false, message: 'Internal server error.' }); } }; module.exports = { getUsers, getUserById };