feat(activity-log): integrate logging across all endpoints (Phase 3)

Added comprehensive activity logging to 14 integration points:

Auth Controller (4 actions):
- AUTH_REGISTER: User registration
- AUTH_LOGIN: User login
- AUTH_VERIFY_EMAIL: Email verification (token & code)
- AUTH_PASSWORD_RESET: Password reset

Events Routes (2 actions):
- EVENT_CHECKIN: User checks into event
- EVENT_LEAVE: User leaves event

Socket Handlers (3 actions):
- EVENT_JOIN_CHAT: User joins event chat room
- EVENT_LEAVE_CHAT: User leaves event chat room
- CHAT_JOIN_ROOM: User joins private match chat

Matches Routes (3 actions):
- MATCH_CREATE: Match request created
- MATCH_ACCEPT: Match request accepted
- MATCH_REJECT: Match request rejected/cancelled

Admin Routes (1 action + security):
- ADMIN_MATCHING_RUN: Admin runs matching algorithm
- Added requireAdmin middleware to all admin routes

All logs include:
- User ID and username (denormalized)
- IP address (X-Forwarded-For aware)
- Action type and resource ID
- HTTP method and path (or SOCKET for WebSocket)
- Contextual metadata (event slugs, match IDs, etc.)

Fire-and-forget pattern ensures logging never blocks requests.
This commit is contained in:
Radosław Gierwiało
2025-12-02 20:07:10 +01:00
parent 0466ef9e5c
commit d83e4163e5
5 changed files with 217 additions and 3 deletions

View File

@@ -1,13 +1,16 @@
const express = require('express');
const { prisma } = require('../utils/db');
const { authenticate } = require('../middleware/auth');
const { requireAdmin } = require('../middleware/admin');
const matchingService = require('../services/matching');
const { SUGGESTION_STATUS } = require('../constants');
const { ACTIONS, log: activityLog } = require('../services/activityLog');
const { getClientIP } = require('../utils/request');
const router = express.Router();
// POST /api/admin/events/:slug/run-now - Trigger matching immediately for an event
router.post('/events/:slug/run-now', authenticate, async (req, res, next) => {
router.post('/events/:slug/run-now', authenticate, requireAdmin, async (req, res, next) => {
try {
const { slug } = req.params;
@@ -47,6 +50,23 @@ router.post('/events/:slug/run-now', authenticate, async (req, res, next) => {
},
});
// Log admin matching run activity
activityLog({
userId: req.user.id,
username: req.user.username,
ipAddress: getClientIP(req),
action: ACTIONS.ADMIN_MATCHING_RUN,
resource: `event:${event.id}`,
method: req.method,
path: req.path,
metadata: {
eventSlug: event.slug,
runId: runRow.id,
matchedCount,
notFoundCount,
},
});
return res.json({
success: true,
data: {
@@ -74,7 +94,7 @@ router.post('/events/:slug/run-now', authenticate, async (req, res, next) => {
});
// GET /api/admin/events/:slug/matching-runs?limit=20 - List recent runs
router.get('/events/:slug/matching-runs', authenticate, async (req, res, next) => {
router.get('/events/:slug/matching-runs', authenticate, requireAdmin, async (req, res, next) => {
try {
const { slug } = req.params;
const limit = Math.min(parseInt(req.query.limit || '20', 10), 100);
@@ -136,7 +156,7 @@ router.get('/events/:slug/matching-runs', authenticate, async (req, res, next) =
module.exports = router;
// GET /api/admin/events/:slug/matching-runs/:runId/suggestions - List suggestions created in this run
router.get('/events/:slug/matching-runs/:runId/suggestions', authenticate, async (req, res, next) => {
router.get('/events/:slug/matching-runs/:runId/suggestions', authenticate, requireAdmin, async (req, res, next) => {
try {
const { slug, runId } = req.params;
const onlyAssigned = String(req.query.onlyAssigned || 'true') === 'true';

View File

@@ -4,6 +4,8 @@ const { authenticate } = require('../middleware/auth');
const { getIO } = require('../socket');
const matchingService = require('../services/matching');
const { SUGGESTION_STATUS, MATCH_STATUS } = require('../constants');
const { ACTIONS, log: activityLog } = require('../services/activityLog');
const { getClientIP } = require('../utils/request');
const router = express.Router();
@@ -319,6 +321,21 @@ router.post('/checkin/:token', authenticate, async (req, res, next) => {
},
});
// Log checkin activity
activityLog({
userId: req.user.id,
username: req.user.username,
ipAddress: getClientIP(req),
action: ACTIONS.EVENT_CHECKIN,
resource: `event:${event.id}`,
method: req.method,
path: req.path,
metadata: {
eventSlug: event.slug,
eventName: event.name,
},
});
res.json({
success: true,
alreadyCheckedIn: false,
@@ -491,6 +508,21 @@ router.delete('/:slug/leave', authenticate, async (req, res, next) => {
},
});
// Log leave event activity
activityLog({
userId: req.user.id,
username: req.user.username,
ipAddress: getClientIP(req),
action: ACTIONS.EVENT_LEAVE,
resource: `event:${event.id}`,
method: req.method,
path: req.path,
metadata: {
eventSlug: event.slug,
eventName: event.name,
},
});
res.json({
success: true,
message: 'Successfully left the event',

View File

@@ -5,6 +5,8 @@ const { authenticate } = require('../middleware/auth');
const { getIO } = require('../socket');
const { MATCH_STATUS } = require('../constants');
const matchingService = require('../services/matching');
const { ACTIONS, log: activityLog } = require('../services/activityLog');
const { getClientIP } = require('../utils/request');
const router = express.Router();
@@ -184,6 +186,22 @@ router.post('/', authenticate, matchRequestLimiter, async (req, res, next) => {
console.error('Failed to emit match request notification:', socketError);
}
// Log match creation activity
activityLog({
userId: req.user.id,
username: req.user.username,
ipAddress: getClientIP(req),
action: ACTIONS.MATCH_CREATE,
resource: `match:${match.id}`,
method: req.method,
path: req.path,
metadata: {
matchSlug: match.slug,
targetUserId: targetUserId,
eventSlug: eventSlug,
},
});
res.status(201).json({
success: true,
data: match,
@@ -778,6 +796,22 @@ router.put('/:slug/accept', authenticate, async (req, res, next) => {
const isUser1 = updatedMatch.user1Id === userId;
const partner = isUser1 ? updatedMatch.user2 : updatedMatch.user1;
// Log match acceptance activity
activityLog({
userId: req.user.id,
username: req.user.username,
ipAddress: getClientIP(req),
action: ACTIONS.MATCH_ACCEPT,
resource: `match:${updatedMatch.id}`,
method: req.method,
path: req.path,
metadata: {
matchSlug: updatedMatch.slug,
partnerId: partner.id,
eventSlug: updatedMatch.event.slug,
},
});
res.json({
success: true,
data: {
@@ -867,6 +901,22 @@ router.delete('/:slug', authenticate, async (req, res, next) => {
console.error('Failed to emit match cancelled notification:', socketError);
}
// Log match rejection activity
activityLog({
userId: req.user.id,
username: req.user.username,
ipAddress: getClientIP(req),
action: ACTIONS.MATCH_REJECT,
resource: `match:${match.id}`,
method: req.method,
path: req.path,
metadata: {
matchSlug: match.slug,
eventSlug: match.event.slug,
matchStatus: match.status,
},
});
res.json({
success: true,
message: 'Match deleted successfully',