mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -11,13 +11,15 @@ const { Op, Sequelize } = require('sequelize');
|
||||
const sequelize = require('../../config/db.config');
|
||||
|
||||
const { Task, TaskList, TaskRequirement, TaskListGroup, mdl_UserGroups } = require('../../models/task/task.mdl');
|
||||
// const { mdl_UserGroups } = require('../../models/users/user_groups.mdl');
|
||||
const mdl_Users = require('../../models/users/users.mdl');
|
||||
const { adminExclude, jsonbSchemas, computedAttributes } = require('../../models/task/task.attributes');
|
||||
|
||||
const R = require('../../utils/response.util');
|
||||
const { paginate } = require('../../utils/paginate.util');
|
||||
const { filterableFields } = require('../../models/task/task.attributes');
|
||||
const { getFieldValues } = require('../../utils/fieldValues.util');
|
||||
const { archiveOne, archiveMany } = require("../../utils/courses/archive.util");
|
||||
const { restoreOne, restoreMany } = require("../../utils/courses/restore.util");
|
||||
|
||||
// ─── Allowed filter/sort fields ───────────────────────────────────────────────
|
||||
const TASK_LIST_FIELDS = ['name', 'description', 'createdAt', 'updatedAt', 'deletedAt'];
|
||||
@@ -146,17 +148,19 @@ exports.updateTaskList = async (req, res) => {
|
||||
// ─── ARCHIVE (soft-delete) ────────────────────────────────────────────────────
|
||||
|
||||
exports.archiveTaskList = async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { taskListId } = req.params;
|
||||
|
||||
const taskList = await TaskList.findByPk(taskListId);
|
||||
if (!taskList) return R.error(res, 'Task list not found.', 404);
|
||||
|
||||
await taskList.update({ deletedBy: req.user.user_id });
|
||||
await taskList.destroy();
|
||||
const record = await archiveOne(TaskList, { task_list_id: taskListId },
|
||||
req.user.user_id, t
|
||||
);
|
||||
if (!record) { await t.rollback(); return R.error(res, 'Task list not found.', 404); }
|
||||
|
||||
await t.commit();
|
||||
return R.success(res, 'Task list archived successfully.');
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
console.error('[ADMIN][ARCHIVE TASK LIST]', err);
|
||||
return R.error(res, 'Could not archive task list.', 500);
|
||||
}
|
||||
@@ -165,18 +169,17 @@ exports.archiveTaskList = async (req, res) => {
|
||||
// ─── RESTORE ──────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.restoreTaskList = async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { taskListId } = req.params;
|
||||
|
||||
const taskList = await TaskList.findByPk(taskListId, { paranoid: false });
|
||||
if (!taskList) return R.error(res, 'Task list not found.', 404);
|
||||
if (!taskList.deletedAt) return R.error(res, 'Task list is not archived.', 400);
|
||||
const record = await restoreOne(TaskList, { task_list_id: taskListId, deletedAt: { [Op.not]: null } }, req.user.user_id, t);
|
||||
if (!record) { await t.rollback(); return R.error(res, 'Task list not found or not archived.', 404); }
|
||||
|
||||
await taskList.restore();
|
||||
await taskList.update({ deletedBy: null, updatedBy: req.user.user_id });
|
||||
|
||||
return R.success(res, 'Task list restored successfully.', taskList);
|
||||
await t.commit();
|
||||
return R.success(res, 'Task list restored successfully.', record);
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
console.error('[ADMIN][RESTORE TASK LIST]', err);
|
||||
return R.error(res, 'Could not restore task list.', 500);
|
||||
}
|
||||
@@ -198,17 +201,9 @@ exports.bulkArchiveTaskLists = async (req, res) => {
|
||||
if (!activeIds.length)
|
||||
return R.error(res, 'All selected task lists are already archived.', 400);
|
||||
|
||||
await TaskList.update(
|
||||
{ deletedBy: req.user.user_id },
|
||||
{ where: { task_list_id: { [Op.in]: activeIds } }, transaction: t }
|
||||
);
|
||||
await TaskList.destroy({
|
||||
where: { task_list_id: { [Op.in]: activeIds } },
|
||||
transaction: t,
|
||||
});
|
||||
|
||||
const count = await archiveMany(TaskList, 'task_list_id', activeIds, req.user.user_id, t);
|
||||
await t.commit();
|
||||
return R.success(res, `${activeIds.length} task list(s) archived successfully.`, {
|
||||
return R.success(res, `${count} task list(s) archived successfully.`, {
|
||||
archived_ids: activeIds,
|
||||
skipped_ids: ids.filter((id) => !activeIds.includes(id)),
|
||||
});
|
||||
@@ -235,14 +230,9 @@ exports.bulkRestoreTaskLists = async (req, res) => {
|
||||
if (!deletedIds.length)
|
||||
return R.error(res, 'All selected task lists are already active.', 400);
|
||||
|
||||
await TaskList.restore({ where: { task_list_id: { [Op.in]: deletedIds } }, transaction: t });
|
||||
await TaskList.update(
|
||||
{ deletedBy: null, updatedBy: req.user.user_id },
|
||||
{ where: { task_list_id: { [Op.in]: deletedIds } }, paranoid: false, transaction: t }
|
||||
);
|
||||
|
||||
const count = await restoreMany(TaskList, 'task_list_id', deletedIds, req.user.user_id, t);
|
||||
await t.commit();
|
||||
return R.success(res, `${deletedIds.length} task list(s) restored successfully.`, {
|
||||
return R.success(res, `${count} task list(s) restored successfully.`, {
|
||||
restored_ids: deletedIds,
|
||||
skipped_ids: ids.filter((id) => !deletedIds.includes(id)),
|
||||
});
|
||||
@@ -651,17 +641,22 @@ exports.getArchivedTasks = async (req, res) => {
|
||||
// ─── ARCHIVE (soft-delete) ────────────────────────────────────────────────────
|
||||
|
||||
exports.archiveTask = async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { taskListId, taskId } = req.params;
|
||||
|
||||
const task = await Task.findOne({ where: { task_id: taskId, task_list_id: taskListId } });
|
||||
if (!task) return R.error(res, 'Task not found.', 404);
|
||||
|
||||
await task.update({ deletedBy: req.user.user_id });
|
||||
await task.destroy();
|
||||
const record = await archiveOne(
|
||||
Task,
|
||||
{ task_id: taskId, task_list_id: taskListId },
|
||||
req.user.user_id,
|
||||
t
|
||||
);
|
||||
if (!record) { await t.rollback(); return R.error(res, 'Task not found.', 404); }
|
||||
|
||||
await t.commit();
|
||||
return R.success(res, 'Task archived successfully.');
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
console.error('[ADMIN][ARCHIVE TASK]', err);
|
||||
return R.error(res, 'Could not archive task.', 500);
|
||||
}
|
||||
@@ -670,21 +665,22 @@ exports.archiveTask = async (req, res) => {
|
||||
// ─── RESTORE ──────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.restoreTask = async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { taskListId, taskId } = req.params;
|
||||
|
||||
const task = await Task.findOne({
|
||||
where: { task_id: taskId, task_list_id: taskListId },
|
||||
paranoid: false,
|
||||
});
|
||||
if (!task) return R.error(res, 'Task not found.', 404);
|
||||
if (!task.deletedAt) return R.error(res, 'Task is not archived.', 400);
|
||||
const record = await restoreOne(
|
||||
Task,
|
||||
{ task_id: taskId, task_list_id: taskListId, deletedAt: { [Op.not]: null } },
|
||||
req.user.user_id,
|
||||
t
|
||||
);
|
||||
if (!record) { await t.rollback(); return R.error(res, 'Task not found or not archived.', 404); }
|
||||
|
||||
await task.restore();
|
||||
await task.update({ deletedBy: null, updatedBy: req.user.user_id });
|
||||
|
||||
return R.success(res, 'Task restored successfully.', task);
|
||||
await t.commit();
|
||||
return R.success(res, 'Task restored successfully.', record);
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
console.error('[ADMIN][RESTORE TASK]', err);
|
||||
return R.error(res, 'Could not restore task.', 500);
|
||||
}
|
||||
@@ -700,26 +696,16 @@ exports.bulkArchiveTasks = async (req, res) => {
|
||||
if (!Array.isArray(ids) || !ids.length)
|
||||
return R.error(res, 'No task IDs provided.', 400);
|
||||
|
||||
const tasks = await Task.findAll({
|
||||
where: { task_id: ids, task_list_id: taskListId },
|
||||
});
|
||||
const tasks = await Task.findAll({ where: { task_id: ids, task_list_id: taskListId } });
|
||||
if (!tasks.length) return R.error(res, 'No tasks found.', 404);
|
||||
|
||||
const activeIds = tasks.filter((task) => !task.deletedAt).map((task) => task.task_id);
|
||||
if (!activeIds.length)
|
||||
return R.error(res, 'All selected tasks are already archived.', 400);
|
||||
|
||||
await Task.update(
|
||||
{ deletedBy: req.user.user_id },
|
||||
{ where: { task_id: { [Op.in]: activeIds }, task_list_id: taskListId }, transaction: t }
|
||||
);
|
||||
await Task.destroy({
|
||||
where: { task_id: { [Op.in]: activeIds }, task_list_id: taskListId },
|
||||
transaction: t,
|
||||
});
|
||||
|
||||
const count = await archiveMany(Task, 'task_id', activeIds, req.user.user_id, t);
|
||||
await t.commit();
|
||||
return R.success(res, `${activeIds.length} task(s) archived successfully.`, {
|
||||
return R.success(res, `${count} task(s) archived successfully.`, {
|
||||
archived_ids: activeIds,
|
||||
skipped_ids: ids.filter((id) => !activeIds.includes(id)),
|
||||
});
|
||||
@@ -750,17 +736,9 @@ exports.bulkRestoreTasks = async (req, res) => {
|
||||
if (!deletedIds.length)
|
||||
return R.error(res, 'All selected tasks are already active.', 400);
|
||||
|
||||
await Task.restore({
|
||||
where: { task_id: { [Op.in]: deletedIds }, task_list_id: taskListId },
|
||||
transaction: t,
|
||||
});
|
||||
await Task.update(
|
||||
{ deletedBy: null, updatedBy: req.user.user_id },
|
||||
{ where: { task_id: { [Op.in]: deletedIds }, task_list_id: taskListId }, paranoid: false, transaction: t }
|
||||
);
|
||||
|
||||
const count = await restoreMany(Task, 'task_id', deletedIds, req.user.user_id, t);
|
||||
await t.commit();
|
||||
return R.success(res, `${deletedIds.length} task(s) restored successfully.`, {
|
||||
return R.success(res, `${count} task(s) restored successfully.`, {
|
||||
restored_ids: deletedIds,
|
||||
skipped_ids: ids.filter((id) => !deletedIds.includes(id)),
|
||||
});
|
||||
@@ -771,102 +749,5 @@ exports.bulkRestoreTasks = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// ─── GET TASK FIELD VALUES ───────────────────────────────────────────────────
|
||||
exports.getTaskFieldValues = async (req, res) => {
|
||||
try {
|
||||
const { field } = req.query;
|
||||
if (!field) return R.error(res, "Field is required.", 400);
|
||||
|
||||
const allowedFields = Object.keys(Task.rawAttributes).filter(
|
||||
(f) => Task.rawAttributes[f].filterable
|
||||
);
|
||||
const auditByFields = ["createdBy", "updatedBy", "deletedBy"];
|
||||
const dateFields = ["createdAt", "updatedAt", "deletedAt", "deadline"];
|
||||
|
||||
if (!allowedFields.includes(field) && !dateFields.includes(field))
|
||||
return R.error(res, "Invalid or restricted field.", 400);
|
||||
|
||||
if (auditByFields.includes(field)) {
|
||||
const [rows] = await sequelize.query(`
|
||||
SELECT DISTINCT u."personal_info"->'name'->>'full_name' AS value
|
||||
FROM tasks t
|
||||
JOIN users u ON u.user_id = t."${field}"
|
||||
WHERE t."${field}" IS NOT NULL
|
||||
AND u."personal_info"->'name'->>'full_name' IS NOT NULL
|
||||
ORDER BY value ASC
|
||||
`);
|
||||
return R.success(res, "Field values retrieved.", rows.map((r) => r.value).filter(Boolean));
|
||||
}
|
||||
|
||||
if (dateFields.includes(field)) {
|
||||
const results = await Task.findAll({
|
||||
attributes: [[Sequelize.fn("DISTINCT", Sequelize.fn("DATE", Sequelize.col(field))), "value"]],
|
||||
where: { [field]: { [Op.ne]: null } },
|
||||
order: [[Sequelize.fn("DATE", Sequelize.col(field)), "DESC"]],
|
||||
raw: true,
|
||||
});
|
||||
return R.success(res, "Field values retrieved.", results.map((r) => r.value).filter(Boolean));
|
||||
}
|
||||
|
||||
const results = await Task.findAll({
|
||||
attributes: [[Sequelize.fn("DISTINCT", Sequelize.col(field)), "value"]],
|
||||
where: { [field]: { [Op.ne]: null } },
|
||||
raw: true,
|
||||
});
|
||||
return R.success(res, "Field values retrieved.", results.map((r) => r.value).filter(Boolean).sort());
|
||||
|
||||
} catch (err) {
|
||||
console.error("[TASK][GET FIELD VALUES]", err);
|
||||
return R.error(res, "Could not retrieve field values.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── GET TASK LIST FIELD VALUES ───────────────────────────────────────────────
|
||||
exports.getTaskListFieldValues = async (req, res) => {
|
||||
try {
|
||||
const { field } = req.query;
|
||||
if (!field) return R.error(res, 'Field is required.', 400);
|
||||
|
||||
const allowedFields = Object.keys(TaskList.rawAttributes).filter(
|
||||
(f) => TaskList.rawAttributes[f].filterable
|
||||
);
|
||||
const auditByFields = ['createdBy', 'updatedBy', 'deletedBy'];
|
||||
const dateFields = ['createdAt', 'updatedAt', 'deletedAt'];
|
||||
|
||||
if (!allowedFields.includes(field) && !dateFields.includes(field))
|
||||
return R.error(res, "Invalid or restricted field.", 400);
|
||||
|
||||
if (auditByFields.includes(field)) {
|
||||
const [rows] = await sequelize.query(`
|
||||
SELECT DISTINCT u."personal_info"->'name'->>'full_name' AS value
|
||||
FROM task_lists tl
|
||||
JOIN users u ON u.user_id = tl."${field}"
|
||||
WHERE tl."${field}" IS NOT NULL
|
||||
AND u."personal_info"->'name'->>'full_name' IS NOT NULL
|
||||
ORDER BY value ASC
|
||||
`);
|
||||
return R.success(res, 'Field values retrieved.', rows.map((r) => r.value).filter(Boolean));
|
||||
}
|
||||
|
||||
if (dateFields.includes(field)) {
|
||||
const results = await TaskList.findAll({
|
||||
attributes: [[Sequelize.fn('DISTINCT', Sequelize.fn('DATE', Sequelize.col(field))), 'value']],
|
||||
where: { [field]: { [Op.ne]: null } },
|
||||
order: [[Sequelize.fn('DATE', Sequelize.col(field)), 'DESC']],
|
||||
raw: true,
|
||||
});
|
||||
return R.success(res, 'Field values retrieved.', results.map((r) => r.value).filter(Boolean));
|
||||
}
|
||||
|
||||
const results = await TaskList.findAll({
|
||||
attributes: [[Sequelize.fn('DISTINCT', Sequelize.col(field)), 'value']],
|
||||
where: { [field]: { [Op.ne]: null } },
|
||||
raw: true,
|
||||
});
|
||||
return R.success(res, 'Field values retrieved.', results.map((r) => r.value).filter(Boolean).sort());
|
||||
|
||||
} catch (err) {
|
||||
console.error('[TASK LIST][GET FIELD VALUES]', err);
|
||||
return R.error(res, 'Could not retrieve field values.', 500);
|
||||
}
|
||||
};
|
||||
exports.getTaskFieldValues = getFieldValues(Task, "TASK");
|
||||
exports.getTaskListFieldValues = getFieldValues(TaskList, "TASKLIST");
|
||||
Reference in New Issue
Block a user