Source: utils/activityLogger.js

/**
 * @module utils/activityLogger
 * @description Shared activity logging helper. Records user/system actions
 * so the Activity page shows a complete timeline.
 *
 * ### Type convention (dot-separated: `<resource>.<action>`)
 * `project.create` · `crawl.start` · `crawl.complete` · `crawl.fail` ·
 * `test_run.start` · `test_run.complete` · `test_run.fail` ·
 * `test.create` · `test.generate` · `test.edit` · `test.delete` ·
 * `test.approve` · `test.reject` · `test.restore` ·
 * `test.bulk_approve` · `test.bulk_reject` · `test.bulk_restore` ·
 * `settings.update`
 */

import { generateActivityId } from "./idGenerator.js";
import * as activityRepo from "../database/repositories/activityRepo.js";
import { formatLogLine } from "./logFormatter.js";

/**
 * Log an activity entry to the database.
 *
 * @param {Object}      opts
 * @param {string}      opts.type        - Activity type (e.g. `"test.approve"`).
 * @param {string}      [opts.projectId] - Associated project ID.
 * @param {string}      [opts.projectName] - Project name for display.
 * @param {string}      [opts.testId]    - Associated test ID.
 * @param {string}      [opts.testName]  - Test name for display.
 * @param {string}      [opts.detail]    - Human-readable description.
 * @param {string}      [opts.status="completed"] - Activity status.
 * @param {string}      [opts.userId]    - ID of the user who triggered the action (from req.authUser.sub).
 * @param {string}      [opts.userName]  - Display name of the user (from req.authUser.name or email).
 * @param {string}      [opts.workspaceId] - Workspace ID for multi-tenancy scoping (ACL-001).
 * @returns {Object}    The created activity record.
 */
export function logActivity({ type, projectId, projectName, testId, testName, detail, status, userId, userName, workspaceId }) {
  // Warn when a project-scoped activity is logged without a workspaceId.
  // Activities with workspaceId=NULL become orphaned — invisible to
  // workspace-scoped queries (/api/activities, /api/data/activities).
  if (projectId && !workspaceId) {
    console.warn(formatLogLine("warn", null,
      `[activity] Activity "${type}" for project ${projectId} logged without workspaceId — row will be orphaned`));
  }

  const id = generateActivityId();
  const activity = {
    id,
    type,
    projectId: projectId || null,
    projectName: projectName || null,
    testId: testId || null,
    testName: testName || null,
    detail: detail || null,
    status: status || "completed",
    createdAt: new Date().toISOString(),
    userId: userId || null,
    userName: userName || null,
    workspaceId: workspaceId || null,
  };
  activityRepo.create(activity);
  return activity;
}