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>
268 lines
9.2 KiB
JavaScript
268 lines
9.2 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name: groups.ctrl.js (staff)
|
|
* Type of Program: Controller
|
|
* Description: Staff-scoped group endpoints.
|
|
* Returns only the groups the logged-in staff member belongs to,
|
|
* along with their members and associated task lists.
|
|
***********************************************************************************************************************************************************************/
|
|
const { Op } = require('sequelize');
|
|
const mdl_Users = require('../../models/users/users.mdl');
|
|
const { mdl_UserGroups, mdl_UserGroupMembers } = require('../../models/users/user_groups.mdl');
|
|
const { TaskList, Task, TaskRequirement, TaskListGroup } = require('../../models/task/task.mdl');
|
|
const { getFieldValues } = require("../../utils/fieldValues.util");
|
|
const { paginate } = require('../../utils/paginate.util');
|
|
const R = require('../../utils/response.util');
|
|
const { excludeAttributes: usersExclude, jsonbSchemas: usersSchemas, computedAttributes: userComputed } = require('../../models/users/users.attributes');
|
|
|
|
/**
|
|
* GET /api/staff/groups
|
|
* Returns all groups the staff member belongs to,
|
|
* with member count and associated task lists.
|
|
*/
|
|
const getMyGroups = async (req, res) => {
|
|
try {
|
|
const staffUserId = req.user.user_id;
|
|
|
|
const groups = await mdl_UserGroups.findAll({
|
|
include: [
|
|
{
|
|
model: mdl_UserGroupMembers,
|
|
required: true, // INNER JOIN — only groups the staff member is in
|
|
where: { user_id: staffUserId },
|
|
attributes: [],
|
|
},
|
|
{
|
|
model: mdl_Users,
|
|
as: 'members',
|
|
attributes: ['user_id', 'email', 'personal_info', 'is_active'],
|
|
through: { attributes: ['joined_at'] },
|
|
required: false,
|
|
},
|
|
{
|
|
model: TaskList,
|
|
as: 'taskLists',
|
|
attributes: ['task_list_id', 'name', 'description', 'createdAt'],
|
|
through: { attributes: ['assignedAt'] },
|
|
required: false,
|
|
include: [
|
|
{
|
|
model: Task,
|
|
as: 'tasks',
|
|
attributes: ['task_id', 'name', 'deadline', 'status'],
|
|
required: false,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
where: { is_active: true },
|
|
order: [['name', 'ASC']],
|
|
});
|
|
|
|
// Add member_count computed field
|
|
const data = groups.map(g => ({
|
|
...g.toJSON(),
|
|
member_count: g.members?.length ?? 0,
|
|
}));
|
|
|
|
return res.status(200).json({ success: true, data });
|
|
} catch (err) {
|
|
console.error('[staff/groups] getMyGroups error:', err);
|
|
return res.status(500).json({ success: false, message: 'Internal server error.' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* GET /api/staff/groups/:group_id
|
|
* Returns a single group's full details — members + task lists.
|
|
* Staff must be a member of this group.
|
|
*/
|
|
const getGroupById = async (req, res) => {
|
|
try {
|
|
const staffUserId = req.user.user_id;
|
|
const groupId = parseInt(req.params.group_id);
|
|
|
|
// Verify staff membership
|
|
const membership = await mdl_UserGroupMembers.findOne({
|
|
where: { user_id: staffUserId, group_id: groupId },
|
|
});
|
|
if (!membership) {
|
|
return res.status(403).json({ success: false, message: 'You are not a member of this group.' });
|
|
}
|
|
|
|
const group = await mdl_UserGroups.findOne({
|
|
where: { group_id: groupId, is_active: true },
|
|
include: [
|
|
{
|
|
model: mdl_Users,
|
|
as: 'members',
|
|
attributes: ['user_id', 'email', 'personal_info', 'is_active', 'acc_type'],
|
|
through: { attributes: ['joined_at'] },
|
|
},
|
|
{
|
|
model: TaskList,
|
|
as: 'taskLists',
|
|
attributes: ['task_list_id', 'name', 'description', 'createdAt'],
|
|
through: { attributes: ['assignedAt'] },
|
|
include: [
|
|
{
|
|
model: Task,
|
|
as: 'tasks',
|
|
attributes: ['task_id', 'name', 'description', 'deadline', 'status'],
|
|
include: [
|
|
{
|
|
model: TaskRequirement,
|
|
as: 'requirements',
|
|
attributes: ['requirement_id', 'type', 'link_url', 'link_label', 'reference_id', 'reference_label', 'order'],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
if (!group) return res.status(404).json({ success: false, message: 'Group not found.' });
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
data: { ...group.toJSON(), member_count: group.members?.length ?? 0 },
|
|
});
|
|
} catch (err) {
|
|
console.error('[staff/groups] getGroupById error:', err);
|
|
return res.status(500).json({ success: false, message: 'Internal server error.' });
|
|
}
|
|
};
|
|
|
|
// GET /staff/groups/:group_id/members
|
|
// Paginated, searchable, filterable — mirrors getUsers pattern
|
|
const getGroupMembers = async (req, res) => {
|
|
try {
|
|
const staffUserId = req.user.user_id;
|
|
const groupId = parseInt(req.params.group_id);
|
|
|
|
// Verify staff is a member of this group
|
|
const membership = await mdl_UserGroupMembers.findOne({
|
|
where: { user_id: staffUserId, group_id: groupId },
|
|
});
|
|
if (!membership) {
|
|
return res.status(403).json({ success: false, message: 'You are not a member of this group.' });
|
|
}
|
|
|
|
// Confirm group exists
|
|
const groupExists = await mdl_UserGroups.findOne({
|
|
where: { group_id: groupId, is_active: true },
|
|
attributes: ['group_id'],
|
|
});
|
|
if (!groupExists) {
|
|
return res.status(404).json({ success: false, message: 'Group not found.' });
|
|
}
|
|
|
|
const result = await paginate(mdl_Users, req, {
|
|
excludeAttributes: usersExclude,
|
|
jsonbSchemas: usersSchemas,
|
|
jsonbColumn: 'personal_info',
|
|
computedAttributes: userComputed,
|
|
context: "list",
|
|
findOptions: {
|
|
include: [
|
|
{
|
|
model: mdl_UserGroups,
|
|
as: 'groups',
|
|
through: { attributes: [] },
|
|
attributes: ['group_id', 'name', 'group_code'],
|
|
required: true, // INNER JOIN — only users in this group
|
|
where: { group_id: groupId },
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
return R.success(res, 'Group members retrieved.', result);
|
|
} catch (err) {
|
|
console.error('[staff/groups] getGroupMembers error:', err);
|
|
return R.error(res, 'Could not retrieve group members.', 500);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* GET /api/staff/groups/by-code/:group_code
|
|
* Convenience endpoint — look up a group by its invite code.
|
|
* Staff must still be a member.
|
|
*/
|
|
const getGroupByCode = async (req, res) => {
|
|
try {
|
|
const staffUserId = req.user.user_id;
|
|
const groupCode = req.params.group_code.toUpperCase().trim();
|
|
|
|
const group = await mdl_UserGroups.findOne({
|
|
where: { group_code: groupCode, is_active: true },
|
|
});
|
|
|
|
if (!group) return res.status(404).json({ success: false, message: 'Group not found.' });
|
|
|
|
// Verify staff membership
|
|
const membership = await mdl_UserGroupMembers.findOne({
|
|
where: { user_id: staffUserId, group_id: group.group_id },
|
|
});
|
|
if (!membership) {
|
|
return res.status(403).json({ success: false, message: 'You are not a member of this group.' });
|
|
}
|
|
|
|
// Reuse getGroupById logic by patching req.params
|
|
req.params.group_id = group.group_id;
|
|
return getGroupById(req, res);
|
|
} catch (err) {
|
|
console.error('[staff/groups] getGroupByCode error:', err);
|
|
return res.status(500).json({ success: false, message: 'Internal server error.' });
|
|
}
|
|
};
|
|
|
|
// ─── GET /staff/groups/:group_id/members/field-values ────────────────────────
|
|
const getGroupMemberFieldValues = async (req, res) => {
|
|
try {
|
|
const staffUserId = req.user.user_id;
|
|
const groupId = parseInt(req.params.group_id);
|
|
|
|
const membership = await mdl_UserGroupMembers.findOne({
|
|
where: { user_id: staffUserId, group_id: groupId },
|
|
});
|
|
if (!membership) {
|
|
return res.status(403).json({ success: false, message: "You are not a member of this group." });
|
|
}
|
|
|
|
const groupExists = await mdl_UserGroups.findOne({
|
|
where: { group_id: groupId, is_active: true },
|
|
attributes: ["group_id"],
|
|
});
|
|
if (!groupExists) {
|
|
return res.status(404).json({ success: false, message: "Group not found." });
|
|
}
|
|
|
|
return getFieldValues(mdl_Users, "USER", {
|
|
blockedFields: ["password", "otp_code", "otp_expires_at", "personal_info"],
|
|
extraDateFields: ["modifiedAt"],
|
|
allowJsonb: true,
|
|
selfJoin: true,
|
|
// Scope distinct values to this group only
|
|
baseWhere: {
|
|
"$groups.group_id$": groupId,
|
|
},
|
|
associations: {
|
|
groups: {
|
|
model: mdl_UserGroups,
|
|
labelField: "name",
|
|
valueField: "name",
|
|
// INNER JOIN so values are scoped to group members only
|
|
required: true,
|
|
where: { group_id: groupId },
|
|
},
|
|
},
|
|
})(req, res);
|
|
} catch (err) {
|
|
console.error("[staff/groups] getGroupMemberFieldValues error:", err);
|
|
return R.error(res, "Could not retrieve field values.", 500);
|
|
}
|
|
};
|
|
|
|
module.exports = { getMyGroups, getGroupById, getGroupByCode, getGroupMembers, getGroupMemberFieldValues };
|