From 09660d750313f754f22d9423da6d79ff9ced3312 Mon Sep 17 00:00:00 2001 From: rgrgogu Date: Thu, 7 May 2026 12:36:39 +0800 Subject: [PATCH] Adjusted --- controllers/admin/users.controller.js | 108 +++++++++++++++++++++++++- routes/admin/admin.routes.js | 2 + 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/controllers/admin/users.controller.js b/controllers/admin/users.controller.js index c9a9db9..721dce9 100644 --- a/controllers/admin/users.controller.js +++ b/controllers/admin/users.controller.js @@ -129,6 +129,66 @@ exports.deactivateUser = async (req, res) => { } }; +// ─── BULK DEACTIVATE users ──────────────────────────────────────────────────── +exports.bulkDeactivateUsers = async (req, res) => { + try { + const { ids } = req.body; // expects { ids: [1, 2, 3] } + + if (!Array.isArray(ids) || ids.length === 0) + return R.error(res, 'No user IDs provided.', 400); + + if (ids.includes(req.user.user_id)) + return R.error(res, 'You cannot deactivate your own account.', 400); + + const users = await mdl_Users.findAll({ + where: { user_id: ids }, + }); + + if (!users.length) + return R.error(res, 'No users found.', 404); + + // Filter only currently active users — skip already-deactivated ones + const activeUsers = users.filter((u) => u.is_active && !u.deletedAt); + + if (!activeUsers.length) + return R.error(res, 'All selected users are already deactivated.', 400); + + const activeIds = activeUsers.map((u) => u.user_id); + + // Soft-delete + mark who deactivated them + await mdl_Users.update( + { is_active: false, deletedBy: req.user.user_id }, + { where: { user_id: activeIds } } + ); + + // Sequelize paranoid bulk destroy (sets deletedAt) + await mdl_Users.destroy({ + where: { user_id: activeIds }, + }); + + // Force logout all their active sessions + await mdl_UserSessions.update( + { + is_active: false, + logout_info: { + date: new Date().toISOString(), + forced_by: req.user.user_id, + }, + }, + { where: { user_id: activeIds } } + ); + + return R.success(res, `${activeIds.length} user(s) deactivated successfully.`, { + deactivated_ids: activeIds, + // Inform caller if some were already inactive and got skipped + skipped_ids: ids.filter((id) => !activeIds.includes(id)), + }); + } catch (err) { + console.error("[ADMIN][BULK DEACTIVATE USERS]", err); + return R.error(res, 'Could not deactivate users.', 500); + } +}; + // ─── RESTORE user ────────────────────────────────────────────────────────────── exports.restoreUser = async (req, res) => { try { @@ -143,6 +203,7 @@ exports.restoreUser = async (req, res) => { await user.update({ is_active: true, updatedBy: req.user.user_id, // ← who restored + deletedBy: null }); return R.success(res, 'User restored successfully.'); @@ -152,6 +213,47 @@ exports.restoreUser = async (req, res) => { } }; +// ─── BULK RESTORE users ──────────────────────────────────────────────────────── +exports.bulkRestoreUsers = async (req, res) => { + try { + const { ids } = req.body; + + if (!Array.isArray(ids) || ids.length === 0) + return R.error(res, 'No user IDs provided.', 400); + + const users = await mdl_Users.findAll({ + where: { user_id: ids }, + paranoid: false, + }); + + if (!users.length) + return R.error(res, 'No users found.', 404); + + // Filter only soft-deleted users — skip already-active ones + const deletedUsers = users.filter((u) => u.deletedAt); + + if (!deletedUsers.length) + return R.error(res, 'All selected users are already active.', 400); + + const deletedIds = deletedUsers.map((u) => u.user_id); + + await mdl_Users.restore({ where: { user_id: deletedIds } }); + + await mdl_Users.update( + { is_active: true, updatedBy: req.user.user_id, deletedBy: null }, + { where: { user_id: deletedIds }, paranoid: false } + ); + + return R.success(res, `${deletedIds.length} user(s) restored successfully.`, { + restored_ids: deletedIds, + skipped_ids: ids.filter((id) => !deletedIds.includes(id)), + }); + } catch (err) { + console.error("[ADMIN][BULK RESTORE USERS]", err); + return R.error(res, 'Could not restore users.', 500); + } +}; + exports.getGroups = async (req, res) => { try { const result = await paginate(mdl_UserGroups, req, { @@ -530,9 +632,9 @@ exports.getArchivedUsers = async (req, res) => { try { const result = await paginate(mdl_Users, req, { excludeAttributes: usersExclude, - jsonbSchemas: usersSchemas, - jsonbColumn: 'personal_info', - auditOptions: { mdl_Users, parentAlias: 'User' }, + jsonbSchemas: usersSchemas, + jsonbColumn: 'personal_info', + auditOptions: { mdl_Users, parentAlias: 'User' }, findOptions: { paranoid: false, // ← include soft-deleted rows where: { diff --git a/routes/admin/admin.routes.js b/routes/admin/admin.routes.js index 3508334..71b2147 100644 --- a/routes/admin/admin.routes.js +++ b/routes/admin/admin.routes.js @@ -36,6 +36,8 @@ const { adminLimiter, sensitiveOpsLimiter } = require('../../middleware/rateLimi router.use(authenticate, requireAdmin(), adminLimiter); // ── User management ──────────────────────────────────────────────────────────── +router.post('/users/bulk/restore', sensitiveOpsLimiter, usersCtrl.bulkRestoreUsers); // ← before /:id +router.delete('/users/bulk', sensitiveOpsLimiter, usersCtrl.bulkDeactivateUsers); // ← before /:id router.get('/users/field-values', usersCtrl.getUserFieldValues); // ← before /:id router.get('/users/archived', usersCtrl.getArchivedUsers); // ← before /:id router.get('/users', usersCtrl.getUsers);