Files

113 lines
4.9 KiB
JavaScript

/***********************************************************************************************************************************************************************
* File Name: users.controller.js (staff)
* Type of Program: Controller
* Description: Staff-level user management.
* Staff can VIEW any client/staff user and perform limited actions.
* Staff CANNOT modify admin accounts or assign/revoke admin roles.
*
* Endpoints (require authenticate → requireStaff()):
* GET /api/staff/users → paginated user list (non-admins)
* GET /api/staff/users/:id → view any non-admin user
* PUT /api/staff/users/:id/status → activate / deactivate user
* GET /api/staff/users/:id/sessions → view user sessions
*
* Author: rgrgogu
* Date Created: Oct. 6, 2025
***********************************************************************************************************************************************************************/
const { Op } = require('sequelize');
const mdl_Users = require('../../models/users/users.mdl');
const mdl_UserSessions = require('../../models/users/user_sessions.mdl');
const logActivity = require('../../utils/logActivity.util');
const R = require('../../utils/response.util');
const EXCLUDED = ['password', 'otp_code', 'otp_expires_at'];
// ─── GET paginated list (no admins) ────────────────────────────────────────────
exports.getUsers = async (req, res) => {
try {
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, parseInt(req.query.limit) || 20);
const offset = (page - 1) * limit;
const { count, rows } = await mdl_Users.findAndCountAll({
where: { acc_type: { [Op.ne]: 'admin' } },
attributes: { exclude: EXCLUDED },
limit,
offset,
order: [['createdAt', 'DESC']],
});
return R.success(res, 'Users retrieved.', {
total: count,
page,
totalPages: Math.ceil(count / limit),
users: rows,
});
} catch (err) {
console.error('[STAFF] getUsers error:', err);
return R.error(res, 'Could not retrieve users.', 500);
}
};
// ─── GET single non-admin user ─────────────────────────────────────────────────
exports.getUser = async (req, res) => {
try {
const user = await mdl_Users.findOne({
where: { user_id: req.params.id, acc_type: { [Op.ne]: 'admin' } },
attributes: { exclude: EXCLUDED },
});
if (!user) return R.error(res, 'User not found.', 404);
return R.success(res, 'User retrieved.', user);
} catch (err) {
return R.error(res, 'Could not retrieve user.', 500);
}
};
// ─── PUT activate / deactivate ─────────────────────────────────────────────────
exports.setUserStatus = async (req, res) => {
try {
const { is_active } = req.body;
if (typeof is_active !== 'boolean')
return R.error(res, 'is_active must be a boolean.', 400);
const user = await mdl_Users.findOne({
where: { user_id: req.params.id, acc_type: { [Op.ne]: 'admin' } },
});
if (!user) return R.error(res, 'User not found or operation not permitted.', 404);
await user.update({ is_active });
logActivity(req.user.user_id, 'set_user_status', {
entityType: 'user',
entityId: Number(req.params.id),
metadata: { is_active },
});
return R.success(res, `User ${is_active ? 'activated' : 'deactivated'}.`);
} catch (err) {
return R.error(res, 'Could not update user status.', 500);
}
};
// ─── GET user sessions ─────────────────────────────────────────────────────────
exports.getUserSessions = async (req, res) => {
try {
const user = await mdl_Users.findOne({
where: { user_id: req.params.id, acc_type: { [Op.ne]: 'admin' } },
});
if (!user) return R.error(res, 'User not found.', 404);
const sessions = await mdl_UserSessions.findAll({
where: { user_id: req.params.id },
attributes: { exclude: ['refresh_token_hash'] },
order: [['createdAt', 'DESC']],
});
return R.success(res, 'Sessions retrieved.', sessions);
} catch (err) {
return R.error(res, 'Could not retrieve sessions.', 500);
}
};
// ─── Staff profile (re-use client controller) ──────────────────────────────────
// Staff also manage their own profile through the same client endpoints.
// No additional staff-specific profile endpoints needed.