mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -14,9 +14,11 @@ const crypto = require('crypto');
|
||||
const mdl_Users = require('../../models/users/users.mdl');
|
||||
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 { sendEmail } = require('../../services/email.service');
|
||||
const { fmtDate } = require('../../utils/datetime.util');
|
||||
const logActivity = require('../../utils/logActivity.util');
|
||||
const R = require('../../utils/response.util');
|
||||
const { paginate } = require('../../utils/paginate.util');
|
||||
@@ -383,4 +385,288 @@ exports.getUserAchievements = async (req, res) => {
|
||||
console.error('[ADMIN][GET USER ACHIEVEMENTS]', err);
|
||||
return R.error(res, 'Could not retrieve achievements.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── BAN USER ─────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.banUser = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (Number(id) === req.user.user_id)
|
||||
return R.error(res, 'You cannot ban your own account.', 400);
|
||||
|
||||
const user = await mdl_Users.findByPk(id);
|
||||
if (!user) return R.error(res, 'User not found.', 404);
|
||||
if (user.is_banned) return R.error(res, 'User is already banned.', 400);
|
||||
|
||||
const { reason, ban_type, expires_at } = req.body;
|
||||
|
||||
if (!reason?.trim()) return R.error(res, 'Ban reason is required.', 400);
|
||||
if (!['temporary', 'permanent'].includes(ban_type))
|
||||
return R.error(res, 'Invalid ban type. Must be "temporary" or "permanent".', 400);
|
||||
if (ban_type === 'temporary' && !expires_at)
|
||||
return R.error(res, 'Expiry date is required for temporary bans.', 400);
|
||||
if (ban_type === 'temporary' && new Date(expires_at) <= new Date())
|
||||
return R.error(res, 'Expiry date must be in the future.', 400);
|
||||
|
||||
const banExpiresAt = ban_type === 'temporary' ? new Date(expires_at) : null;
|
||||
|
||||
await sequelize.transaction(async (t) => {
|
||||
await mdl_UserBans.create({
|
||||
user_id: id,
|
||||
banned_by: req.user.user_id,
|
||||
reason: reason.trim(),
|
||||
ban_type,
|
||||
banned_at: new Date(),
|
||||
expires_at: banExpiresAt,
|
||||
}, { transaction: t });
|
||||
|
||||
await user.update({ is_banned: true, ban_expires_at: banExpiresAt }, { transaction: t });
|
||||
|
||||
await mdl_UserSessions.update(
|
||||
{ is_active: false, logout_info: { date: new Date().toISOString(), forced_by: req.user.user_id } },
|
||||
{ where: { user_id: id }, transaction: t }
|
||||
);
|
||||
});
|
||||
|
||||
logActivity(req.user.user_id, 'ban_user', {
|
||||
entityType: 'user',
|
||||
entityId: Number(id),
|
||||
details: { reason: reason.trim(), ban_type, expires_at: banExpiresAt },
|
||||
});
|
||||
|
||||
sendEmail({
|
||||
to: user.email,
|
||||
type: 'BANNED',
|
||||
data: {
|
||||
name: user.personal_info?.name?.full_name ?? 'User',
|
||||
email: user.email,
|
||||
date: fmtDate(new Date()),
|
||||
reason: reason.trim(),
|
||||
ban_type,
|
||||
},
|
||||
}).catch((err) => console.error('[ADMIN][BAN USER] Email failed:', err));
|
||||
|
||||
return R.success(res, 'User banned successfully.');
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][BAN USER]', err);
|
||||
return R.error(res, 'Could not ban user.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── UNBAN USER ───────────────────────────────────────────────────────────────
|
||||
|
||||
exports.unbanUser = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const user = await mdl_Users.findByPk(id);
|
||||
if (!user) return R.error(res, 'User not found.', 404);
|
||||
if (!user.is_banned) return R.error(res, 'User is not currently banned.', 400);
|
||||
|
||||
const { lift_reason } = req.body;
|
||||
|
||||
const activeBan = await mdl_UserBans.findOne({
|
||||
where: { user_id: id, is_lifted: false },
|
||||
order: [['banned_at', 'DESC']],
|
||||
});
|
||||
|
||||
await sequelize.transaction(async (t) => {
|
||||
if (activeBan) {
|
||||
await activeBan.update({
|
||||
is_lifted: true,
|
||||
lifted_at: new Date(),
|
||||
lifted_by: req.user.user_id,
|
||||
lift_reason: lift_reason?.trim() || null,
|
||||
}, { transaction: t });
|
||||
}
|
||||
|
||||
await user.update({ is_banned: false, ban_expires_at: null }, { transaction: t });
|
||||
});
|
||||
|
||||
logActivity(req.user.user_id, 'unban_user', { entityType: 'user', entityId: Number(id) });
|
||||
|
||||
sendEmail({
|
||||
to: user.email,
|
||||
type: 'BAN_LIFTED',
|
||||
data: {
|
||||
name: user.personal_info?.name?.full_name ?? 'User',
|
||||
email: user.email,
|
||||
date: fmtDate(new Date()),
|
||||
},
|
||||
}).catch((err) => console.error('[ADMIN][UNBAN USER] Email failed:', err));
|
||||
|
||||
return R.success(res, 'User unbanned successfully.');
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][UNBAN USER]', err);
|
||||
return R.error(res, 'Could not unban user.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── BULK BAN ─────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.bulkBanUsers = async (req, res) => {
|
||||
try {
|
||||
const { ids, reason, ban_type, expires_at } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || !ids.length)
|
||||
return R.error(res, 'No user IDs provided.', 400);
|
||||
if (ids.includes(req.user.user_id))
|
||||
return R.error(res, 'You cannot ban your own account.', 400);
|
||||
if (!reason?.trim()) return R.error(res, 'Ban reason is required.', 400);
|
||||
if (!['temporary', 'permanent'].includes(ban_type))
|
||||
return R.error(res, 'Invalid ban type.', 400);
|
||||
if (ban_type === 'temporary' && !expires_at)
|
||||
return R.error(res, 'Expiry date is required for temporary bans.', 400);
|
||||
if (ban_type === 'temporary' && new Date(expires_at) <= new Date())
|
||||
return R.error(res, 'Expiry date must be in the future.', 400);
|
||||
|
||||
const users = await mdl_Users.findAll({ where: { user_id: ids } });
|
||||
if (!users.length) return R.error(res, 'No users found.', 404);
|
||||
|
||||
const unbannedUsers = users.filter((u) => !u.is_banned);
|
||||
if (!unbannedUsers.length)
|
||||
return R.error(res, 'All selected users are already banned.', 400);
|
||||
|
||||
const targetIds = unbannedUsers.map((u) => u.user_id);
|
||||
const banExpiresAt = ban_type === 'temporary' ? new Date(expires_at) : null;
|
||||
const now = new Date();
|
||||
|
||||
await sequelize.transaction(async (t) => {
|
||||
await mdl_UserBans.bulkCreate(
|
||||
targetIds.map((uid) => ({
|
||||
user_id: uid,
|
||||
banned_by: req.user.user_id,
|
||||
reason: reason.trim(),
|
||||
ban_type,
|
||||
banned_at: now,
|
||||
expires_at: banExpiresAt,
|
||||
})),
|
||||
{ transaction: t }
|
||||
);
|
||||
|
||||
await mdl_Users.update(
|
||||
{ is_banned: true, ban_expires_at: banExpiresAt },
|
||||
{ where: { user_id: targetIds }, transaction: t }
|
||||
);
|
||||
|
||||
await mdl_UserSessions.update(
|
||||
{ is_active: false, logout_info: { date: now.toISOString(), forced_by: req.user.user_id } },
|
||||
{ where: { user_id: targetIds }, transaction: t }
|
||||
);
|
||||
});
|
||||
|
||||
const dateStr = fmtDate(new Date());
|
||||
unbannedUsers.forEach((u) => {
|
||||
sendEmail({
|
||||
to: u.email,
|
||||
type: 'BANNED',
|
||||
data: {
|
||||
name: u.personal_info?.name?.full_name ?? 'User',
|
||||
email: u.email,
|
||||
date: dateStr,
|
||||
reason: reason.trim(),
|
||||
ban_type,
|
||||
},
|
||||
}).catch((err) => console.error('[ADMIN][BULK BAN] Email failed:', u.email, err));
|
||||
});
|
||||
|
||||
return R.success(res, `${targetIds.length} user(s) banned successfully.`, {
|
||||
banned_ids: targetIds,
|
||||
skipped_ids: ids.filter((id) => !targetIds.includes(Number(id))),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][BULK BAN USERS]', err);
|
||||
return R.error(res, 'Could not ban users.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── GET USER BAN HISTORY ─────────────────────────────────────────────────────
|
||||
|
||||
exports.getUserBans = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const user = await mdl_Users.findByPk(id, { attributes: ['user_id'] });
|
||||
if (!user) return R.error(res, 'User not found.', 404);
|
||||
|
||||
const bans = await mdl_UserBans.findAll({
|
||||
where: { user_id: id },
|
||||
include: [
|
||||
{ model: mdl_Users, as: 'banner', attributes: ['user_id', 'email', 'personal_info'] },
|
||||
{ model: mdl_Users, as: 'lifter', attributes: ['user_id', 'email', 'personal_info'] },
|
||||
],
|
||||
order: [['banned_at', 'DESC']],
|
||||
});
|
||||
|
||||
return R.success(res, 'Ban history retrieved.', bans);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][GET USER BANS]', err);
|
||||
return R.error(res, 'Could not retrieve ban history.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
// ─── BULK UNBAN ───────────────────────────────────────────────────────────────
|
||||
|
||||
exports.bulkUnbanUsers = async (req, res) => {
|
||||
try {
|
||||
const { ids, lift_reason } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || !ids.length)
|
||||
return R.error(res, 'No user IDs provided.', 400);
|
||||
|
||||
const users = await mdl_Users.findAll({ where: { user_id: ids } });
|
||||
if (!users.length) return R.error(res, 'No users found.', 404);
|
||||
|
||||
const bannedUsers = users.filter((u) => u.is_banned);
|
||||
if (!bannedUsers.length)
|
||||
return R.error(res, 'None of the selected users are currently banned.', 400);
|
||||
|
||||
const targetIds = bannedUsers.map((u) => u.user_id);
|
||||
const now = new Date();
|
||||
|
||||
await sequelize.transaction(async (t) => {
|
||||
await mdl_UserBans.update(
|
||||
{
|
||||
is_lifted: true,
|
||||
lifted_at: now,
|
||||
lifted_by: req.user.user_id,
|
||||
lift_reason: lift_reason?.trim() || null,
|
||||
},
|
||||
{ where: { user_id: targetIds, is_lifted: false }, transaction: t }
|
||||
);
|
||||
|
||||
await mdl_Users.update(
|
||||
{ is_banned: false, ban_expires_at: null },
|
||||
{ where: { user_id: targetIds }, transaction: t }
|
||||
);
|
||||
});
|
||||
|
||||
logActivity(req.user.user_id, 'bulk_unban_users', {
|
||||
entityType: 'user',
|
||||
details: { unban_ids: targetIds },
|
||||
});
|
||||
|
||||
const dateStr = fmtDate(now);
|
||||
bannedUsers.forEach((u) => {
|
||||
sendEmail({
|
||||
to: u.email,
|
||||
type: 'BAN_LIFTED',
|
||||
data: {
|
||||
name: u.personal_info?.name?.full_name ?? 'User',
|
||||
email: u.email,
|
||||
date: dateStr,
|
||||
},
|
||||
}).catch((err) => console.error('[ADMIN][BULK UNBAN] Email failed:', u.email, err));
|
||||
});
|
||||
|
||||
return R.success(res, `${targetIds.length} user(s) unbanned successfully.`, {
|
||||
unbanned_ids: targetIds,
|
||||
skipped_ids: ids.filter((id) => !targetIds.includes(Number(id))),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][BULK UNBAN USERS]', err);
|
||||
return R.error(res, 'Could not unban users.', 500);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user