/*********************************************************************************************************************************************************************** * File Name: user_activity.controller.js (admin) * Type of Program: Controller * Description: Admin-only view of the user_activity log. * * GET /api/admin/activity → global paginated activity feed (all users) * GET /api/admin/users/:id/activity → paginated activity for a single user * * Author: Kenneth Obsequio (@lash0000) * Date Created: Jun. 21, 2026 ***********************************************************************************************************************************************************************/ const { Op } = require('sequelize'); const mdl_UserActivity = require('../../models/users/user_activity.mdl'); const mdl_Users = require('../../models/users/users.mdl'); const R = require('../../utils/response.util'); const USER_ATTRS = [ 'user_id', 'email', 'acc_type', // full_name from JSONB — resolved in the association below ]; // ─── Shared query builder ───────────────────────────────────────────────────── function buildWhere(query, extraWhere = {}) { const where = { ...extraWhere }; if (query.action) where.action = query.action; if (query.from || query.to) { where.created_at = {}; if (query.from) where.created_at[Op.gte] = new Date(query.from); if (query.to) where.created_at[Op.lte] = new Date(query.to); } return where; } // ─── GET GLOBAL ACTIVITY FEED ───────────────────────────────────────────────── exports.getActivity = 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 where = buildWhere(req.query); const { count, rows } = await mdl_UserActivity.findAndCountAll({ where, include: [{ model: mdl_Users, as: 'user', attributes: ['user_id', 'email', 'acc_type', 'personal_info'], }], order: [['created_at', 'DESC']], limit, offset, }); const data = rows.map(formatRow); return R.success(res, 'Activity feed retrieved.', { total: count, page, totalPages: Math.ceil(count / limit), activities: data, }); } catch (err) { console.error('[ADMIN][GET ACTIVITY FEED]', err); return R.error(res, 'Could not retrieve activity feed.', 500); } }; // ─── GET PER-USER ACTIVITY ──────────────────────────────────────────────────── exports.getUserActivity = async (req, res) => { try { const { id } = req.params; if (!id || id === 'undefined') return R.error(res, 'Invalid User ID.', 400); const user = await mdl_Users.findByPk(id, { attributes: ['user_id'] }); if (!user) return R.error(res, 'User not found.', 404); 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 where = buildWhere(req.query, { user_id: id }); const { count, rows } = await mdl_UserActivity.findAndCountAll({ where, order: [['created_at', 'DESC']], limit, offset, }); return R.success(res, 'User activity retrieved.', { total: count, page, totalPages: Math.ceil(count / limit), activities: rows, }); } catch (err) { console.error('[ADMIN][GET USER ACTIVITY]', err); return R.error(res, 'Could not retrieve user activity.', 500); } }; // ─── Helpers ────────────────────────────────────────────────────────────────── function formatRow(row) { const r = row.toJSON(); const info = r.user?.personal_info; return { activity_id: r.activity_id, user_id: r.user_id, email: r.user?.email ?? null, full_name: info?.name?.full_name ?? null, avatar_url: info?.avatar?.url ?? null, acc_type: r.user?.acc_type ?? null, action: r.action, entity_type: r.entity_type, entity_id: r.entity_id, details: r.details, created_at: r.created_at, }; }