mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
chore: relocate backend into apps/api ahead of monorepo merge
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* 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 { resolveAvatarUrl } = require('../../utils/resolveAvatar.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) {
|
||||
// `to` arrives as a date-only string (e.g. "2026-06-04"), which parses
|
||||
// to that day's UTC midnight — an Op.lte against midnight excludes
|
||||
// every event that happened later the same day. Push it to the last
|
||||
// instant of that calendar day instead.
|
||||
const to = new Date(query.to);
|
||||
to.setUTCHours(23, 59, 59, 999);
|
||||
where.created_at[Op.lte] = 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 = await Promise.all(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 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
async function formatRow(row) {
|
||||
const r = row.toJSON();
|
||||
const info = r.user?.personal_info;
|
||||
const avatar = await resolveAvatarUrl(info?.avatar);
|
||||
return {
|
||||
activity_id: r.activity_id,
|
||||
user_id: r.user_id,
|
||||
email: r.user?.email ?? null,
|
||||
full_name: info?.name?.full_name ?? null,
|
||||
avatar_stream_token: avatar?.stream_token ?? 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user