Skip to content

feat: add audit logging to avatar, calendar, workflow, task status & channel routes - #778

Closed
2witstudios wants to merge 1 commit into
masterfrom
pu/audit-features
Closed

2witstudios wants to merge 1 commit into
masterfrom
pu/audit-features

Conversation

@2witstudios

@2witstudios 2witstudios commented Mar 14, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Add fire-and-forget audit logging (getActorInfo().then() pattern) across 10 API route files covering avatar, calendar events, calendar attendees, workflows, task statuses, channel messages, and reactions
  • Extend activity resource enum with workflow and calendar_event types (schema + migration)
  • Eliminate redundant db.query.pages.findFirst() calls in task status audit logging, using driveId: null instead (pageId already provides sufficient audit context)

Test plan

  • pnpm typecheck — full build passes with 0 errors
  • Verify avatar upload/delete logs activity
  • Verify calendar event CRUD logs activity
  • Verify workflow create/update/delete/run logs activity
  • Verify task status create/update/delete logs activity
  • Verify channel message send and reaction add/remove logs activity

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Enhanced activity tracking infrastructure with expanded audit logging across calendar events, workflows, messages, reactions, and task management operations to improve audit trail capabilities and system observability.

…nd channel routes

Add fire-and-forget activity logging across 10 API route files using the
getActorInfo().then() pattern to avoid blocking responses. Extend the
activity resource enum with 'workflow' and 'calendar_event' types.
Eliminate redundant page.driveId DB queries in task status audit logging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 14, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds comprehensive fire-and-forget audit logging to API routes handling avatar updates, calendar events, attendees, channels messages, reactions, workflows, and task statuses. It extends the activity logging system to support 'workflow' and 'calendar_event' resource types via enum additions in the database schema and activity-logger types.

Changes

Cohort / File(s) Summary
Calendar Events Audit Logging
apps/web/src/app/api/calendar/events/route.ts, apps/web/src/app/api/calendar/events/[eventId]/route.ts, apps/web/src/app/api/calendar/events/[eventId]/attendees/route.ts
Added fire-and-forget audit logging for create, update, delete, and attendee operations on calendar events, including metadata capture for allDay, visibility, recurrence, attendee changes, and RSVP updates.
Workflow Audit Logging
apps/web/src/app/api/workflows/route.ts, apps/web/src/app/api/workflows/[workflowId]/route.ts, apps/web/src/app/api/workflows/[workflowId]/run/route.ts
Added fire-and-forget audit logging for workflow creation, updates, deletions, and manual run execution with operation and metadata tracking (e.g., manual_run, success, durationMs).
Channel Messages & Reactions Audit Logging
apps/web/src/app/api/channels/[pageId]/messages/route.ts, apps/web/src/app/api/channels/[pageId]/messages/[messageId]/reactions/route.ts
Added fire-and-forget audit logging for message creation and emoji reaction add/delete operations.
Avatar & Task Status Audit Logging
apps/web/src/app/api/account/avatar/route.ts, apps/web/src/app/api/pages/[pageId]/tasks/statuses/route.ts
Added fire-and-forget audit logging for avatar updates/deletions and task status create/update/delete operations with updatedFields tracking.
Activity Logging Type Definitions
packages/lib/src/monitoring/activity-logger.ts
Extended ActivityResourceType union to include 'workflow' and 'calendar_event' resource types.
Database Schema & Migrations
packages/db/drizzle/0091_ambitious_eternity.sql, packages/db/drizzle/meta/_journal.json, packages/db/src/schema/monitoring.ts
Added 'workflow' and 'calendar_event' enum values to activity_resource PostgreSQL type and updated corresponding Drizzle schema definition.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hop, hop, logging all the way!
Workflows, calendars—audit every day,
Fire-and-forget, no errors to dismay,
The audit trails grow greener in their way! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding audit logging across multiple API routes (avatar, calendar, workflow, task status, and channel).
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pu/audit-features
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3d2bcf7ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

logMessageActivity(userId, 'create', {
id: createdMessage.id,
pageId,
driveId: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Attach drive ID to channel activity entries

This new audit entry is recorded with driveId: null, so channel message activity is detached from its actual drive. Drive-scoped activity queries filter on activityLogs.driveId (apps/web/src/app/api/activities/route.ts uses eq(activityLogs.driveId, params.driveId) in drive context), which means these records are omitted from drive audit/history views; the same driveId: null pattern added in the reactions route has the same effect. It also suppresses event-triggered workflows because emitWorkflowEvent returns early when event.driveId is missing (apps/web/src/lib/workflows/event-trigger.ts).

Useful? React with 👍 / 👎.

operation: 'create',
resourceType: 'page',
resourceId: pageId,
driveId: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve drive context in task status audit logs

These task-status audit logs are also written with driveId: null, which makes them invisible in drive-level audit/history endpoints that filter strictly by drive ID (apps/web/src/app/api/activities/route.ts drive context). Because workflow event dispatch drops events without a drive (apps/web/src/lib/workflows/event-trigger.ts), these operations also cannot trigger any drive-scoped event workflows, even though they are page changes inside a drive.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/web/src/app/api/channels/[pageId]/messages/[messageId]/reactions/route.ts (1)

94-106: Consider semantic clarity of resourceType for reactions.

Using resourceType: 'message' with operation: 'create' for adding a reaction may cause confusion in audit logs—the message itself isn't being created. The metadata.action: 'reaction_added' disambiguates, but consider whether a dedicated resourceType: 'reaction' would improve audit query clarity.

This is a minor consistency consideration; the current approach works functionally.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/src/app/api/channels/`[pageId]/messages/[messageId]/reactions/route.ts
around lines 94 - 106, Update the audit log entry for reaction events to use a
clearer resource type: when calling getActorInfo(...).then(...) and invoking
logActivity({ userId, ...actorInfo, operation: 'create', resourceType:
'message', resourceId: messageId, pageId, metadata: { action: 'reaction_added',
emoji } }), change resourceType to 'reaction' (and consider changing resourceId
to a reaction identifier if available, otherwise keep messageId but document
that resourceId references the parent message) so audit queries clearly reflect
that a reaction was created; adjust only the logActivity payload in the
reactions route where those symbols are used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/api/channels/`[pageId]/messages/route.ts:
- Around line 171-179: The audit log currently hardcodes driveId: null when
calling logMessageActivity after getActorInfo; change this to pass the real
drive context by ensuring you have the channel/page driveId before
logging—either move the getActorInfo.then(...) block to after the channel lookup
(where channel.driveId is available) or perform a small pages lookup to fetch
the page's driveId and use that value instead of null when constructing the
payload for logMessageActivity (referencing createdMessage.id, pageId, and the
resolved driveId).

In `@apps/web/src/app/api/workflows/`[workflowId]/run/route.ts:
- Around line 78-90: The audit logging currently runs only after successful
executeWorkflow; update the handler so audit entries are written even when
executeWorkflow throws by invoking getActorInfo(...) and logActivity(...) in a
finally-like path: wrap the call to executeWorkflow(...) in try/catch and always
call getActorInfo(auth.userId).then(actorInfo => logActivity({...})) in both
success and error cases (setting metadata.action='manual_run',
metadata.success=true/false, include error message/stack when failed), and
ensure any promise rejections from getActorInfo or logActivity are caught (e.g.,
.catch(() => {})) so auditing is fire-and-forget and does not alter the response
flow; reference executeWorkflow, getActorInfo, logActivity, workflowId, and
workflow.name when making the audit payload.

---

Nitpick comments:
In
`@apps/web/src/app/api/channels/`[pageId]/messages/[messageId]/reactions/route.ts:
- Around line 94-106: Update the audit log entry for reaction events to use a
clearer resource type: when calling getActorInfo(...).then(...) and invoking
logActivity({ userId, ...actorInfo, operation: 'create', resourceType:
'message', resourceId: messageId, pageId, metadata: { action: 'reaction_added',
emoji } }), change resourceType to 'reaction' (and consider changing resourceId
to a reaction identifier if available, otherwise keep messageId but document
that resourceId references the parent message) so audit queries clearly reflect
that a reaction was created; adjust only the logActivity payload in the
reactions route where those symbols are used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc5f8329-087c-40cf-907d-fbe41baea101

📥 Commits

Reviewing files that changed from the base of the PR and between 318ad7a and f3d2bcf.

📒 Files selected for processing (15)
  • apps/web/src/app/api/account/avatar/route.ts
  • apps/web/src/app/api/calendar/events/[eventId]/attendees/route.ts
  • apps/web/src/app/api/calendar/events/[eventId]/route.ts
  • apps/web/src/app/api/calendar/events/route.ts
  • apps/web/src/app/api/channels/[pageId]/messages/[messageId]/reactions/route.ts
  • apps/web/src/app/api/channels/[pageId]/messages/route.ts
  • apps/web/src/app/api/pages/[pageId]/tasks/statuses/route.ts
  • apps/web/src/app/api/workflows/[workflowId]/route.ts
  • apps/web/src/app/api/workflows/[workflowId]/run/route.ts
  • apps/web/src/app/api/workflows/route.ts
  • packages/db/drizzle/0091_ambitious_eternity.sql
  • packages/db/drizzle/meta/0091_snapshot.json
  • packages/db/drizzle/meta/_journal.json
  • packages/db/src/schema/monitoring.ts
  • packages/lib/src/monitoring/activity-logger.ts

Comment on lines +171 to +179
// Audit logging (fire-and-forget)
getActorInfo(userId).then(actorInfo => {
logMessageActivity(userId, 'create', {
id: createdMessage.id,
pageId,
driveId: null,
conversationType: 'channel',
}, actorInfo);
}).catch(() => {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pass the real drive context in message audit logs.

At Line 176, driveId is hardcoded to null even for channel messages. This drops drive-level context for audit queries and indexing. Please pass the actual channel/page driveId instead.

💡 Suggested fix
-  // Audit logging (fire-and-forget)
+  // Audit logging (fire-and-forget) - include actual drive context
   getActorInfo(userId).then(actorInfo => {
     logMessageActivity(userId, 'create', {
       id: createdMessage.id,
       pageId,
-      driveId: null,
+      driveId: channel?.driveId ?? null,
       conversationType: 'channel',
     }, actorInfo);
   }).catch(() => {});

If channel is only loaded later, move this block to run after the existing channel lookup (Line 237+) or do a small pages lookup before logging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/channels/`[pageId]/messages/route.ts around lines 171 -
179, The audit log currently hardcodes driveId: null when calling
logMessageActivity after getActorInfo; change this to pass the real drive
context by ensuring you have the channel/page driveId before logging—either move
the getActorInfo.then(...) block to after the channel lookup (where
channel.driveId is available) or perform a small pages lookup to fetch the
page's driveId and use that value instead of null when constructing the payload
for logMessageActivity (referencing createdMessage.id, pageId, and the resolved
driveId).

Comment on lines +78 to +90
// Audit logging (fire-and-forget)
getActorInfo(auth.userId).then(actorInfo => {
logActivity({
userId: auth.userId,
...actorInfo,
operation: 'update',
resourceType: 'workflow',
resourceId: workflowId,
resourceTitle: workflow.name,
driveId: workflow.driveId,
metadata: { action: 'manual_run', success: result.success, durationMs: result.durationMs },
}).catch(() => {});
}).catch(() => {});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Manual-run failures are not audited when execution throws.

If executeWorkflow throws (Line 53-Line 60), the handler returns before this new logging block runs, so failed run attempts are missing from audit history.

💡 Suggested fix
+  const fireRunAuditLog = (success: boolean, durationMs?: number, error?: string) => {
+    getActorInfo(auth.userId).then(actorInfo => {
+      logActivity({
+        userId: auth.userId,
+        ...actorInfo,
+        operation: 'update',
+        resourceType: 'workflow',
+        resourceId: workflowId,
+        resourceTitle: workflow.name,
+        driveId: workflow.driveId,
+        metadata: { action: 'manual_run', success, durationMs, error },
+      }).catch(() => {});
+    }).catch(() => {});
+  };
+
   try {
     result = await executeWorkflow(workflow);
   } catch (error) {
     const errorMsg = error instanceof Error ? error.message : String(error);
+    fireRunAuditLog(false, undefined, errorMsg);
     await db
       .update(workflows)
       .set({ lastRunStatus: 'error', lastRunError: errorMsg })
       .where(eq(workflows.id, workflowId));
     return NextResponse.json({ success: false, error: errorMsg }, { status: 500 });
   }

-  // Audit logging (fire-and-forget)
-  getActorInfo(auth.userId).then(actorInfo => {
-    logActivity({
-      userId: auth.userId,
-      ...actorInfo,
-      operation: 'update',
-      resourceType: 'workflow',
-      resourceId: workflowId,
-      resourceTitle: workflow.name,
-      driveId: workflow.driveId,
-      metadata: { action: 'manual_run', success: result.success, durationMs: result.durationMs },
-    }).catch(() => {});
-  }).catch(() => {});
+  fireRunAuditLog(result.success, result.durationMs, result.error ?? undefined);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/api/workflows/`[workflowId]/run/route.ts around lines 78 -
90, The audit logging currently runs only after successful executeWorkflow;
update the handler so audit entries are written even when executeWorkflow throws
by invoking getActorInfo(...) and logActivity(...) in a finally-like path: wrap
the call to executeWorkflow(...) in try/catch and always call
getActorInfo(auth.userId).then(actorInfo => logActivity({...})) in both success
and error cases (setting metadata.action='manual_run',
metadata.success=true/false, include error message/stack when failed), and
ensure any promise rejections from getActorInfo or logActivity are caught (e.g.,
.catch(() => {})) so auditing is fire-and-forget and does not alter the response
flow; reference executeWorkflow, getActorInfo, logActivity, workflowId, and
workflow.name when making the audit payload.

@2witstudios
2witstudios deleted the pu/audit-features branch March 15, 2026 13:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant