mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -16,6 +16,9 @@ const mdl_UserSessions = require('../../models/users/user_sessions.mdl');
|
||||
const mdl_Achievements = require('../../models/users/achievements.mdl');
|
||||
const mdl_UserBans = require('../../models/users/user_bans.mdl');
|
||||
const { mdl_UserGroups, mdl_UserGroupMembers } = require('../../models/users/user_groups.mdl');
|
||||
const mdl_UserTiers = require('../../models/tiers/user_tiers.mdl');
|
||||
const mdl_Payments = require('../../models/tiers/payments.mdl');
|
||||
const mdl_QuizAttempt = require('../../models/courses/quiz_attempt.mdl');
|
||||
|
||||
const { sendEmail } = require('../../services/email.service');
|
||||
const { fmtDate } = require('../../utils/datetime.util');
|
||||
@@ -291,6 +294,127 @@ exports.bulkRestoreUsers = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// ─── PERMANENT DELETE ─────────────────────────────────────────────────────────
|
||||
//
|
||||
// The live DB has several FKs on users.user_id that are NO ACTION rather than
|
||||
// the CASCADE the migration source claims (schema drift) — achievements,
|
||||
// quiz_attempts, user_tiers, and payments all block a hard delete unless their
|
||||
// rows for this user are removed first. Also null out the "who did this"
|
||||
// actor columns (granted_by/revoked_by/lifted_by) on OTHER users' records,
|
||||
// since those aren't this user's own data and shouldn't be deleted.
|
||||
//
|
||||
// UserBan.banned_by is NOT NULL (a ban record must always keep its banner),
|
||||
// so a user who has ever banned someone can't be nulled out or hard-deleted —
|
||||
// getBannerBlockedIds() below identifies those and callers must exclude them.
|
||||
async function purgeUserDependents(userIds, t) {
|
||||
await mdl_Achievements.destroy({ where: { user_id: userIds }, transaction: t });
|
||||
await mdl_QuizAttempt.destroy({ where: { user_id: userIds }, transaction: t });
|
||||
await mdl_UserTiers.destroy({ where: { user_id: userIds }, transaction: t });
|
||||
await mdl_Payments.destroy({ where: { user_id: userIds }, transaction: t });
|
||||
|
||||
await mdl_Achievements.update({ granted_by: null }, { where: { granted_by: userIds }, transaction: t });
|
||||
await mdl_UserTiers.update({ granted_by: null }, { where: { granted_by: userIds }, transaction: t });
|
||||
await mdl_UserTiers.update({ revoked_by: null }, { where: { revoked_by: userIds }, transaction: t });
|
||||
await mdl_UserBans.update({ lifted_by: null }, { where: { lifted_by: userIds }, transaction: t });
|
||||
}
|
||||
|
||||
async function getBannerBlockedIds(userIds, t) {
|
||||
const bans = await mdl_UserBans.findAll({
|
||||
where: { banned_by: userIds }, attributes: ['banned_by'], group: ['banned_by'], transaction: t,
|
||||
});
|
||||
return bans.map((b) => b.banned_by);
|
||||
}
|
||||
|
||||
exports.permanentlyDeleteUser = async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
if (Number(req.params.id) === req.user.user_id) {
|
||||
await t.rollback();
|
||||
return R.error(res, 'You cannot permanently delete your own account.', 400);
|
||||
}
|
||||
|
||||
const user = await mdl_Users.findOne({
|
||||
where: { user_id: req.params.id }, paranoid: false, transaction: t,
|
||||
});
|
||||
if (!user) { await t.rollback(); return R.error(res, 'User not found.', 404); }
|
||||
if (!user.deletedAt) { await t.rollback(); return R.error(res, 'User must be deactivated before it can be permanently deleted.', 400); }
|
||||
|
||||
const bannerBlockedIds = await getBannerBlockedIds([user.user_id], t);
|
||||
if (bannerBlockedIds.length) {
|
||||
await t.rollback();
|
||||
return R.error(res, 'Cannot delete: this user has banned other users, and ban records must keep their banner on file.', 400);
|
||||
}
|
||||
|
||||
await purgeUserDependents([user.user_id], t);
|
||||
await user.destroy({ force: true, transaction: t });
|
||||
|
||||
await t.commit();
|
||||
|
||||
logActivity(req.user.user_id, 'permanently_delete_user', { entityType: 'user', entityId: Number(req.params.id) });
|
||||
|
||||
return R.success(res, 'User permanently deleted.');
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
console.error('[ADMIN][PERMANENT DELETE USER]', err);
|
||||
return R.error(res, 'Could not permanently delete user.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── BULK PERMANENT DELETE ────────────────────────────────────────────────────
|
||||
|
||||
exports.bulkPermanentlyDeleteUsers = async (req, res) => {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
const { ids } = req.body;
|
||||
if (!Array.isArray(ids) || !ids.length) {
|
||||
await t.rollback();
|
||||
return R.error(res, 'No user IDs provided.', 400);
|
||||
}
|
||||
|
||||
if (ids.includes(req.user.user_id)) {
|
||||
await t.rollback();
|
||||
return R.error(res, 'You cannot permanently delete your own account.', 400);
|
||||
}
|
||||
|
||||
const users = await mdl_Users.findAll({ where: { user_id: ids }, paranoid: false, transaction: t });
|
||||
if (!users.length) { await t.rollback(); return R.error(res, 'No users found.', 404); }
|
||||
|
||||
const deletedUsers = users.filter((u) => u.deletedAt);
|
||||
if (!deletedUsers.length) {
|
||||
await t.rollback();
|
||||
return R.error(res, 'All selected users must be deactivated before they can be permanently deleted.', 400);
|
||||
}
|
||||
|
||||
const archivedIds = deletedUsers.map((u) => u.user_id);
|
||||
const bannerBlockedIds = await getBannerBlockedIds(archivedIds, t);
|
||||
const deletedIds = archivedIds.filter((id) => !bannerBlockedIds.includes(id));
|
||||
|
||||
if (!deletedIds.length) {
|
||||
await t.rollback();
|
||||
return R.error(res, 'Cannot delete: all selected users have banned other users, and ban records must keep their banner on file.', 400);
|
||||
}
|
||||
|
||||
await purgeUserDependents(deletedIds, t);
|
||||
await mdl_Users.destroy({ where: { user_id: deletedIds }, force: true, transaction: t });
|
||||
|
||||
await t.commit();
|
||||
|
||||
logActivity(req.user.user_id, 'bulk_permanently_delete_users', {
|
||||
entityType: 'user',
|
||||
details: { ids: deletedIds, count: deletedIds.length },
|
||||
});
|
||||
|
||||
return R.success(res, `${deletedIds.length} user(s) permanently deleted.`, {
|
||||
deleted_ids: deletedIds,
|
||||
skipped_ids: ids.filter((id) => !deletedIds.includes(id)),
|
||||
});
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
console.error('[ADMIN][BULK PERMANENT DELETE USERS]', err);
|
||||
return R.error(res, 'Could not permanently delete users.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── SESSIONS ─────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.getUserSessions = async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user