All files / src/routes admin-logs.js

73.77% Statements 45/61
59.45% Branches 22/37
66.66% Functions 4/6
82.6% Lines 38/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109              1x 1x 1x 1x   1x 1x 1x     1x 3x 3x                       2x   2x 2x 2x   2x     2x 2x 2x 2x 2x     2x 2x     2x   2x   2x   4x     2x     2x                   2x   2x               1x 2x 2x 1x   1x             2x   1x                   1x  
/**
 * Admin log query routes — query and trace logs stored in Firestore.
 *
 * GET /admin/logs            → Query logs with filters (admin only)
 * GET /admin/logs/trace/:traceId → Get all logs for a session trace (admin only)
 */
 
const router = require('express').Router();
const { db } = require('../utils/firebase');
const { requireAdmin } = require('../middleware/auth');
const log = require('../utils/log');
 
const DEFAULT_LIMIT = 50;
const MAX_LIMIT = 200;
const MAX_TRACE_LIMIT = 500;
 
// GET /admin/logs — Query logs with filters
router.get('/admin/logs', async (req, res) => {
  try {
    if (requireAdmin(req, res)) return;
    const {
      level,
      source,
      userId,
      sessionTraceId,
      requestTraceId,
      route,
      keyword,
      startTime,
      endTime,
      cursor,
    } = req.query;
 
    let limit = Number.parseInt(req.query.limit, 10) || DEFAULT_LIMIT;
    Iif (limit < 1) limit = DEFAULT_LIMIT;
    Iif (limit > MAX_LIMIT) limit = MAX_LIMIT;
 
    let query = db.collection('logs').orderBy('timestamp', 'desc');
 
    // Apply Firestore .where() filters
    if (level) query = query.where('level', '==', level);
    if (source) query = query.where('source', '==', source);
    if (userId) query = query.where('userId', '==', userId);
    Iif (sessionTraceId) query = query.where('sessionTraceId', '==', sessionTraceId);
    Iif (requestTraceId) query = query.where('requestTraceId', '==', requestTraceId);
 
    // Time range filters
    Iif (startTime) query = query.where('timestamp', '>=', Number(startTime));
    Iif (endTime) query = query.where('timestamp', '<=', Number(endTime));
 
    // Pagination
    Iif (cursor) query = query.startAfter(Number(cursor));
 
    query = query.limit(limit);
 
    const snapshot = await query.get();
 
    let logs = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
 
    // Client-side filters (can't be compound-queried in Firestore)
    Iif (route) {
      logs = logs.filter((entry) => entry.context?.route === route || entry.route === route);
    }
    Iif (keyword) {
      const lowerKeyword = keyword.toLowerCase();
      logs = logs.filter(
        (entry) =>
          entry.message?.toLowerCase().includes(lowerKeyword) ||
          (entry.context && JSON.stringify(entry.context).toLowerCase().includes(lowerKeyword)),
      );
    }
 
    const nextCursor =
      snapshot.docs.length === limit ? snapshot.docs.at(-1).data().timestamp : null;
 
    res.json({ logs, nextCursor });
  } catch (err) {
    log.error('admin-logs', 'Error querying logs', { error: err.message });
    res.status(500).json({ error: 'Internal server error' });
  }
});
 
// GET /admin/logs/trace/:traceId — Get all logs for a session trace
router.get('/admin/logs/trace/:traceId', async (req, res) => {
  try {
    if (requireAdmin(req, res)) return;
    const { traceId } = req.params;
 
    const snapshot = await db
      .collection('logs')
      .where('sessionTraceId', '==', traceId)
      .orderBy('timestamp', 'asc')
      .limit(MAX_TRACE_LIMIT)
      .get();
 
    const logs = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
 
    res.json({ logs });
  } catch (err) {
    log.error('admin-logs', 'Error querying trace logs', {
      traceId: req.params.traceId,
      error: err.message,
    });
    res.status(500).json({ error: 'Internal server error' });
  }
});
 
module.exports = router;