implement tasks

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-17 17:28:11 +08:00
parent bae079d5d8
commit 941590f51a
11 changed files with 327 additions and 103 deletions
+88 -44
View File
@@ -36,6 +36,30 @@ const normalizeUrl = (url) => {
return `https://${url}`;
};
// Deduped { user_id, group_id } rows for every user in a group assigned to this
// task list — shared by the requirements-changed and submissions-toggled notify
// blocks in updateTask. First group membership wins if a user is in more than one.
async function getTaskListMembers(taskListId) {
const groupRows = await TaskListGroup.findAll({
where: { task_list_id: taskListId },
attributes: ['group_id'],
});
const groupIds = groupRows.map((r) => r.group_id);
if (!groupIds.length) return [];
const memberRows = await mdl_UserGroupMembers.findAll({
where: { group_id: groupIds },
attributes: ['user_id', 'group_id'],
});
const seenUsers = new Set();
return memberRows.filter(({ user_id }) => {
if (seenUsers.has(user_id)) return false;
seenUsers.add(user_id);
return true;
});
}
// ─── Allowed filter/sort fields ───────────────────────────────────────────────
const TASK_LIST_FIELDS = ['name', 'description', 'createdAt', 'updatedAt', 'deletedAt'];
const TASK_FIELDS = ['name', 'description', 'deadline', 'order_index', 'is_required', 'status', 'createdAt', 'updatedAt', 'deletedAt'];
@@ -875,12 +899,19 @@ exports.updateTask = async (req, res) => {
return R.error(res, 'Task not found.', 404);
}
const { name, description, deadline, status, is_required, requirements, prerequisite_task_ids } = req.body;
const { name, description, deadline, status, is_required, accepts_submissions, requirements, prerequisite_task_ids } = req.body;
await task.update(
{ name, description, deadline: deadline || null, status, is_required, updatedBy: req.user.user_id },
{ transaction: t }
);
const wasAccepting = task.accepts_submissions;
const updates = { updatedBy: req.user.user_id };
if (name !== undefined) updates.name = name;
if (description !== undefined) updates.description = description;
if (deadline !== undefined) updates.deadline = deadline || null;
if (status !== undefined) updates.status = status;
if (is_required !== undefined) updates.is_required = is_required;
if (accepts_submissions !== undefined) updates.accepts_submissions = accepts_submissions;
await task.update(updates, { transaction: t });
// ─────────────────────────────────────────────────────────────────────────────
// PATCH: exports.updateTask in task.controller.js (admin)
@@ -951,53 +982,66 @@ exports.updateTask = async (req, res) => {
// ── Notify assigned users when requirements changed ────────────────────
if (Array.isArray(requirements)) {
try {
const groupRows = await TaskListGroup.findAll({
where: { task_list_id: task.task_list_id },
attributes: ['group_id'],
});
const groupIds = groupRows.map((r) => r.group_id);
const members = await getTaskListMembers(task.task_list_id);
if (groupIds.length) {
const memberRows = await mdl_UserGroupMembers.findAll({
where: { group_id: groupIds },
attributes: ['user_id', 'group_id'],
if (members.length) {
const now = new Date();
// Title/message are identical for every member — render once,
// then vary only the per-member groupId in the data payload.
const notify = NOTIFICATION_REGISTRY.task_requirements_updated.build({
taskName: full.name,
taskListId: task.task_list_id,
taskId: task.task_id,
groupId: null,
});
// One notification per user — first group membership wins if they're in more than one.
const seenUsers = new Set();
const members = memberRows.filter(({ user_id }) => {
if (seenUsers.has(user_id)) return false;
seenUsers.add(user_id);
return true;
});
if (members.length) {
const now = new Date();
// Title/message are identical for every member — render once,
// then vary only the per-member groupId in the data payload.
const notify = NOTIFICATION_REGISTRY.task_requirements_updated.build({
taskName: full.name,
taskListId: task.task_list_id,
groupId: null,
});
await UserNotification.bulkCreate(
members.map(({ user_id, group_id }) => ({
user_id,
...notify,
data: { ...notify.data, groupId: group_id },
seen: false,
createdAt: now,
updatedAt: now,
})),
{ validate: false }
);
}
await UserNotification.bulkCreate(
members.map(({ user_id, group_id }) => ({
user_id,
...notify,
data: { ...notify.data, groupId: group_id },
seen: false,
createdAt: now,
updatedAt: now,
})),
{ validate: false }
);
}
} catch (notifyErr) {
console.error('[ADMIN][UPDATE TASK][NOTIFY]', notifyErr);
}
}
// ── Notify assigned users when accepting-submissions toggled ───────────
if (accepts_submissions !== undefined && accepts_submissions !== wasAccepting) {
try {
const members = await getTaskListMembers(task.task_list_id);
if (members.length) {
const now = new Date();
const registryKey = accepts_submissions ? 'task_submissions_reopened' : 'task_submissions_closed';
const notify = NOTIFICATION_REGISTRY[registryKey].build({
taskName: full.name,
taskListId: task.task_list_id,
taskId: task.task_id,
groupId: null,
});
await UserNotification.bulkCreate(
members.map(({ user_id, group_id }) => ({
user_id,
...notify,
data: { ...notify.data, groupId: group_id },
seen: false,
createdAt: now,
updatedAt: now,
})),
{ validate: false }
);
}
} catch (notifyErr) {
console.error('[ADMIN][UPDATE TASK][NOTIFY SUBMISSIONS]', notifyErr);
}
}
return R.success(res, 'Task updated successfully.', full);
} catch (err) {
await t.rollback();