mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
153 lines
5.3 KiB
JavaScript
153 lines
5.3 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* 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 };
|