feat(dashboard): audit log — track all admin actions with attribution#260
feat(dashboard): audit log — track all admin actions with attribution#260BillChirico merged 8 commits intomainfrom
Conversation
Creates the audit_logs table with columns for guild_id, user_id, user_tag, action, target_type, target_id, details (JSONB), ip_address, and created_at. Indexes: - idx_audit_logs_guild_created (guild_id, created_at DESC) — primary access pattern - idx_audit_logs_guild_user (guild_id, user_id) — admin filter Closes part of #123
…ditLogs (#123) Introduces a standalone audit logger module: - logAuditEvent(pool, event) — fire-and-forget DB insert into audit_logs. Gracefully handles null pool, missing required fields, and query failures (logs error, never rethrows) so audit logging never breaks callers. - purgeOldAuditLogs(pool, retentionDays) — purges entries older than the configured retention period. retentionDays=0 disables purging. Handles missing table (42P01) without throwing. Closes part of #123
…#123) - Import getConfig + purgeOldAuditLogs in dbMaintenance.js - Call purgeOldAuditLogs in runMaintenance() using auditLog.retentionDays from config (defaults to 90 days when not set) - Update dbMaintenance tests: mock auditLogger module and config, keep existing assertions accurate Closes part of #123
19 tests covering:
- logAuditEvent: successful insert, field mapping, JSON serialisation,
null pool graceful skip, missing required fields, DB failure (no rethrow)
- purgeOldAuditLogs: row count return, info logging, retentionDays=0 skip,
null pool skip, missing table (42P01), unexpected DB error
Closes part of #123
…123) - types/config.ts: add AuditLogConfig interface + auditLog field to BotConfig; add 'auditLog' to ConfigSection union - config-workspace/types.ts: add 'audit-log' to ConfigFeatureId union - config-workspace/config-categories.ts: add audit-log feature + search items to moderation-safety category; add FEATURE_LABELS entry - config-sections/AuditLogSection.tsx: new card component with enabled toggle and retention days input - config-editor.tsx: import AuditLogSection, add updateAuditLogField callback, render section when moderation-safety + audit-log visible, add 'auditLog' to knownSections for changed-section tracking Closes part of #123
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds audit logging: a DB migration creating an audit_logs table, a backend auditLogger module with purge logic, integration into DB maintenance, Express middleware/userTag propagation, frontend config types and UI for enabling/retention, and tests for logger and maintenance behavior. Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
| Filename | Overview |
|---|---|
| migrations/013_audit_log.cjs | Clean schema: adds audit_logs table with guild/user/action/IP columns, appropriate indexes for the dashboard's primary query pattern, and a safe down migration. |
| src/modules/auditLogger.js | Well-structured module: graceful null-pool handling, required-field validation, error logging without rethrowing, and correct parameterized queries throughout. |
| src/api/routes/auditLog.js | Two critical issues: user_tag is missing from both SELECT queries (paginated + export) so it will never reach the client despite being correctly inserted; endDate is parsed as UTC midnight which excludes same-day entries. |
| web/src/app/dashboard/audit-log/page.tsx | AuditEntry interface and table rendering omit user_tag, so the User column shows raw Discord snowflake IDs; abort controller + request-ID race condition handling is solid. |
| web/src/components/dashboard/config-sections/AuditLogSection.tsx | Renders a second "Enable Audit Logging" Switch that duplicates the toggle already provided by the parent SettingsFeatureCard header; retention-period input is correct. |
Sequence Diagram
sequenceDiagram
participant Admin as Dashboard Admin
participant Next as Next.js Proxy
participant Express as Express API
participant Middleware as auditLogMiddleware
participant DB as PostgreSQL
participant WS as WebSocket (auditStream)
Admin->>Next: POST/PUT/PATCH/DELETE /api/guilds/:id/*
Next->>Express: Proxied mutating request
Express->>Middleware: auditLogMiddleware intercepts
Middleware->>Middleware: deriveAction(), extractGuildId()
Middleware->>Middleware: capture beforeConfig (if config update)
Middleware->>Express: next() — request proceeds normally
Express-->>Middleware: res.on('finish') fires (2xx/3xx only)
Middleware->>Middleware: computeConfigDiff(), maskSensitiveFields()
Middleware->>DB: INSERT INTO audit_logs (guild_id, user_id, user_tag, action, ...)
DB-->>Middleware: RETURNING row (id, created_at, ...)
Middleware->>WS: broadcastAuditEntry(row)
Admin->>Next: GET /api/guilds/:id/audit-log?filters
Next->>Express: GET /api/v1/guilds/:id/audit-log
Express->>DB: SELECT COUNT(*) + SELECT columns FROM audit_logs WHERE ...
DB-->>Express: {total, entries[]}
Express-->>Next: {entries, total, limit, offset}
Next-->>Admin: Rendered audit log table
Note over DB: Nightly: runMaintenance()<br/>DELETE WHERE created_at < NOW() - interval(retentionDays)
Comments Outside Diff (2)
-
src/api/routes/auditLog.js, line 163-164 (link)user_tagomitted from SELECT — never returned to clientsThis PR correctly adds
user_tagto the migration schema and updates the middleware INSERT to populate it, but both SELECT queries in this route file exclude it. As a result, the API response will never containuser_tag, the frontendAuditEntrytype doesn't declare it, and the dashboard table will always render raw Discord snowflake IDs in the "User" column instead of human-readable names.The same omission exists in the export query on line 220 and in the
rowsToCsvheaders on line 117.All three need to be updated:
// Line 163 — paginated query SELECT id, guild_id, user_id, user_tag, action, target_type, target_id, details, ip_address, created_at // Line 220 — export query SELECT id, guild_id, user_id, user_tag, action, target_type, target_id, details, ip_address, created_at // Line 117-127 — rowsToCsv headers const headers = ['id', 'guild_id', 'user_id', 'user_tag', 'action', 'target_type', 'target_id', 'details', 'ip_address', 'created_at'];
The frontend
AuditEntryinterface inweb/src/app/dashboard/audit-log/page.tsx(line 27) and the table's User cell (line 430) should also be updated to include and displayuser_tag. -
src/api/routes/auditLog.js, line 81-88 (link)endDateparsed as UTC midnight, excluding same-day entriesnew Date('2024-03-07')resolves to2024-03-07T00:00:00.000Z(UTC midnight). The resulting filtercreated_at <= '2024-03-07T00:00:00.000Z'will exclude virtually all entries from March 7 — only those logged at exactly midnight UTC survive. A user selecting "March 7" as their end date expects to see all entries from that day.The end date should be normalized to the end of the day before constructing the ISO string:
if (typeof query.endDate === 'string') { const end = new Date(query.endDate); if (!Number.isNaN(end.getTime())) { end.setUTCHours(23, 59, 59, 999); conditions.push(`created_at <= ${paramIndex}`); params.push(end.toISOString()); paramIndex++; } }
Last reviewed commit: 6af57fd
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/utils/dbMaintenance.js`:
- Around line 143-151: The current maintenance uses
getConfig()?.auditLog?.retentionDays as a single global value and calls
purgeOldAuditLogs(pool, auditRetentionDays), which ignores per-guild overrides
and deletes across the whole audit_logs table; change this to resolve retention
per guild and purge per-guild. Specifically, iterate all guilds (or read
guild-scoped settings) to get each guild's auditLog.retentionDays, then call a
per-guild purge function (e.g., extend purgeOldAuditLogs to accept guildId and
retentionDays or create purgeOldAuditLogsForGuild(pool, guildId, retentionDays))
and make sure the deletion SQL in purgeOldAuditLogs includes WHERE guild_id = ?
so each guild's retention window is honored.
In `@tests/modules/auditLogger.test.js`:
- Line 8: Remove the unused beforeEach import from the test module's import list
so only actually used helpers are imported (leave afterEach, describe, expect,
it, vi intact); locate the import statement that currently includes beforeEach
and delete that symbol from the destructured imports to satisfy the
noUnusedImports lint rule.
In `@tests/utils/dbMaintenance.test.js`:
- Around line 10-19: The test suite mocks purgeOldAuditLogs but never asserts
that runMaintenance() calls it with the configured retention, so add an
assertion that the mocked purgeOldAuditLogs was invoked with the retentionDays
value from the mocked getConfig (90); locate the mock for purgeOldAuditLogs in
tests/utils/dbMaintenance.test.js and after invoking runMaintenance() assert
something like purgeOldAuditLogs was called once and with the numeric retention
(90) so the audit cleanup integration is covered (use the purgeOldAuditLogs mock
reference and the runMaintenance invocation already present in the test).
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 2040-2047: The AuditLogSection is rendered directly which breaks
the shared SettingsFeatureCard/search jump contract (no feature-audit-log anchor
and no advanced panel auto-open for audit-log-retention); replace the direct
render with the reusable SettingsFeatureCard pattern: wrap AuditLogSection
inside a SettingsFeatureCard (provide header, master toggle, and separate
Basic/Advanced blocks) so the card emits the expected DOM anchor
(feature-audit-log) and advanced region; ensure audit-log-retention is
registered as an advanced hit and that the SettingsFeatureCard receives props to
auto-open its advanced panel when a search hit targets audit-log-retention;
preserve existing props (draftConfig, saving) and keep updateAuditLogField calls
wired to the inner controls.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 48d97c86-abac-46b9-b0da-9b49c1046082
📒 Files selected for processing (10)
migrations/013_audit_log.cjssrc/modules/auditLogger.jssrc/utils/dbMaintenance.jstests/modules/auditLogger.test.jstests/utils/dbMaintenance.test.jsweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-workspace/types.tsweb/src/types/config.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/modules/auditLogger.jstests/modules/auditLogger.test.jsmigrations/013_audit_log.cjssrc/utils/dbMaintenance.jstests/utils/dbMaintenance.test.js
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
src/modules/auditLogger.jsweb/src/components/dashboard/config-workspace/types.tstests/modules/auditLogger.test.jsweb/src/components/dashboard/config-sections/AuditLogSection.tsxsrc/utils/dbMaintenance.jstests/utils/dbMaintenance.test.jsweb/src/types/config.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*methods
Use safe Discord message methods:safeReply(),safeSend(),safeEditReply()instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries
Files:
src/modules/auditLogger.jssrc/utils/dbMaintenance.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_CONFIG_KEYS
Files:
src/modules/auditLogger.js
{.env*,README.md,src/**/!(*.test).{js,ts}}
📄 CodeRabbit inference engine (CLAUDE.md)
Remove
GUILD_IDfrom shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag--guild-id <guild_id>
Files:
src/modules/auditLogger.jssrc/utils/dbMaintenance.js
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/types/config.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx}: Web dashboard config editor must use category workspace navigation with categories:AI & Automation,Onboarding & Growth,Moderation & Safety,Community Tools,Support & Integrationslocated inweb/src/components/dashboard/config-workspace/
Config editor must implement metadata-driven config search with cross-category quick jump, focus/scroll targeting, and auto-open advanced sections when search hits advanced controls
Refactor config feature presentation to use reusableSettingsFeatureCardpattern with structure: header + master toggle + Basic/Advanced blocks
Config editor save contract must maintain: global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/config-categories.ts
tests/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Maintain 80% code coverage threshold minimum, never lower this threshold
Files:
tests/modules/auditLogger.test.jstests/utils/dbMaintenance.test.js
migrations/**/*.cjs
📄 CodeRabbit inference engine (AGENTS.md)
Use
.cjsfile extension for database migrations, use sequential migration numbering (001, 002, etc.) with node-pg-migrate
Files:
migrations/013_audit_log.cjs
migrations/[0-9]*_*.cjs
📄 CodeRabbit inference engine (CLAUDE.md)
Database migrations must be sequentially numbered with non-conflicting IDs; rename conflicting migration files to resolve out-of-order execution errors (e.g.,
migrations/004_*.cjs→migrations/007_*.cjs, etc.)
Files:
migrations/013_audit_log.cjs
🧠 Learnings (7)
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Refactor config feature presentation to use reusable `SettingsFeatureCard` pattern with structure: header + master toggle + Basic/Advanced blocks
Applied to files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Web dashboard config editor must use category workspace navigation with categories: `AI & Automation`, `Onboarding & Growth`, `Moderation & Safety`, `Community Tools`, `Support & Integrations` located in `web/src/components/dashboard/config-workspace/`
Applied to files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor must implement metadata-driven config search with cross-category quick jump, focus/scroll targeting, and auto-open advanced sections when search hits advanced controls
Applied to files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.test.{js,jsx,ts,tsx} : Config editor tests must cover manual-save workspace behavior (not autosave assumptions), category switching, search functionality, and dirty badges
Applied to files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves
Applied to files:
web/src/components/dashboard/config-workspace/types.tsweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to migrations/**/*.cjs : Use `.cjs` file extension for database migrations, use sequential migration numbering (001, 002, etc.) with node-pg-migrate
Applied to files:
migrations/013_audit_log.cjs
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor save contract must maintain: global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-workspace/config-categories.tsweb/src/components/dashboard/config-editor.tsx
🪛 GitHub Actions: CI
src/modules/auditLogger.js
[warning] 28-28: assist/source/organizeImports: The imports/exports are not sorted. Safe fix suggested.
tests/modules/auditLogger.test.js
[error] 8-8: lint/correctness/noUnusedImports: Unused imports detected.
web/src/components/dashboard/config-sections/AuditLogSection.tsx
[error] 62-66: format: Formatter would have printed different content. Ensure code is formatted as expected.
src/utils/dbMaintenance.js
[warning] 11-11: assist/source/organizeImports: The imports/exports are not sorted. Safe fix suggested.
🔇 Additional comments (2)
src/modules/auditLogger.js (1)
74-89: ThelogAuditEvent()function insrc/modules/auditLogger.jsis fully implemented with parameterized SQL and is not actually called anywhere in the codebase. There is no auto-logging of request bodies in this PR—the routes that handle config updates (src/api/routes/config.js,src/api/routes/guilds.js) do not invokelogAuditEvent(). Therefore, the scenario described in the review (config edits storing plaintext API keys in audit logs) is not realized, and no redaction logic is needed at this time. If future changes add calls tologAuditEvent()with raw request payloads, redaction should be considered then.> Likely an incorrect or invalid review comment.web/src/components/dashboard/config-workspace/config-categories.ts (1)
334-351: The audit log search metadata is properly integrated into the config system.The search items
audit-log-enabledandaudit-log-retentioncorrectly map to theaudit-logfeature ID, which is included in themoderation-safetycategory. The config editor'sforceOpenAdvancedFeatureIdlogic (lines 308–319 in config-editor.tsx) detects when a search result targets an advanced control and auto-opens the Advanced block via theforceOpenAdvancedprop onSettingsFeatureCard. The retention control's placement inside the Advanced block and the section's conditional rendering on feature visibility are wired correctly, so search results will properly focus and auto-expand the retention setting.
- middleware: include user_tag in audit log inserts - middleware: pass userTag from session to insertAuditEntry - dbMaintenance: add comment clarifying global vs per-guild retention scope - auditLogger.test.js: remove unused beforeEach import - dbMaintenance.test.js: add assertion that runMaintenance calls purgeOldAuditLogs with retentionDays - config-editor: wrap AuditLogSection in SettingsFeatureCard for search/jump contract - audit-log/page.tsx: biome-ignore on skeleton index keys
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/api/middleware/auditLog.js (1)
103-120: 🛠️ Refactor suggestion | 🟠 MajorDeduplicate the
audit_logsinsert contract.This file now owns a second copy of the column list and parameter ordering that already exists in
src/modules/auditLogger.js.user_taghad to be patched in both places in this PR; the next schema change can easily update one path and miss the other. Route middleware writes through the shared logger or extract a common insert helper.Also applies to: 252-261
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/middleware/auditLog.js` around lines 103 - 120, The insert SQL/parameter contract for audit_logs is duplicated here; replace the inline pool.query in audit middleware with the shared insert helper from src/modules/auditLogger.js (e.g., import and call insertAuditLog or the existing logAudit function) so there’s a single canonical SQL/parameter ordering; ensure the helper handles guildId -> 'global' fallback, userTag nulling, JSON.stringify(details), and ipAddress nulling and update the other duplicate (lines ~252-261) to call the same helper instead of re-duplicating the INSERT and parameter list.
♻️ Duplicate comments (3)
src/modules/auditLogger.js (1)
113-124:⚠️ Potential issue | 🟠 MajorGuild-scoped retention cannot be enforced with this purge API.
purgeOldAuditLogs()only takesretentionDaysand deletes without aguild_idpredicate, so any caller applying a guild override will still purge every guild's rows older than that threshold. IfauditLog.retentionDaysis configurable per guild, this helper needs a guild-scoped variant; otherwise the setting should be global-only.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/modules/auditLogger.js` around lines 113 - 124, The purgeOldAuditLogs function currently deletes audit rows by retentionDays across all guilds (see purgeOldAuditLogs and its pool.query DELETE) which prevents enforcing per-guild retention; either implement a guild-scoped variant or make retentionDays global-only: add an optional guildId parameter (e.g., guildId) to purgeOldAuditLogs and update the SQL to include "AND guild_id = $2" (and pass guildId) so callers can purge only that guild, or if per-guild retention is unsupported, document and enforce global-only behavior by removing any guild-specific override code paths and keeping purgeOldAuditLogs strictly global.tests/utils/dbMaintenance.test.js (1)
21-21:⚠️ Potential issue | 🟠 MajorRe-import
auditLoggerinsidebeforeEach()after callingvi.resetModules().The top-level import at line 21 is captured before the module cache is reset. When
beforeEach()clears modules and re-importsdbMaintenance, the mock factory creates a freshauditLoggerinstance. The assertion at line 53 then checks a stale reference while the code under test calls the new one.Fix
-import * as auditLogger from '../../src/modules/auditLogger.js'; - describe('runMaintenance', () => { let runMaintenance; + let auditLogger; let mockPool; let logger; beforeEach(async () => { vi.resetModules(); vi.clearAllMocks(); logger = await import('../../src/logger.js'); + auditLogger = await import('../../src/modules/auditLogger.js'); const mod = await import('../../src/utils/dbMaintenance.js'); runMaintenance = mod.runMaintenance;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/utils/dbMaintenance.test.js` at line 21, The top-level import of auditLogger is captured before vi.resetModules(), causing the test to assert against a stale instance; remove the module-level import of auditLogger and instead import it inside beforeEach() after calling vi.resetModules() and re-importing dbMaintenance so the test uses the fresh mocked auditLogger instance (ensure any assertions referencing auditLogger use the re-imported variable and that dbMaintenance is imported after vi.resetModules()).web/src/components/dashboard/config-editor.tsx (1)
2040-2058:⚠️ Potential issue | 🟠 MajorRemove duplicate
onEnabledChange—SettingsFeatureCardalready handles the master toggle.Line 2046 wires
onEnabledChangeon theSettingsFeatureCard, which provides the master enable/disable toggle. Line 2053 passes the same callback toAuditLogSection, which renders a second toggle internally. This creates two UI controls for the same setting.Once
AuditLogSectionis refactored to remove its internalCard/Switch, drop theonEnabledChangeprop here as well:♻️ Proposed fix after AuditLogSection refactor
{activeCategoryId === 'moderation-safety' && visibleFeatureIds.has('audit-log') && ( <SettingsFeatureCard featureId="audit-log" title="Audit Log" description="Record admin actions taken via the dashboard (config changes, XP adjustments, warnings)." enabled={draftConfig.auditLog?.enabled ?? true} onEnabledChange={(v) => updateAuditLogField('enabled', v)} disabled={saving} forceOpenAdvanced={forceOpenAdvancedFeatureId === 'audit-log'} basicContent={ <AuditLogSection draftConfig={draftConfig ?? {}} saving={saving} - onEnabledChange={(v) => updateAuditLogField('enabled', v)} onRetentionDaysChange={(days) => updateAuditLogField('retentionDays', days)} /> } /> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/config-editor.tsx` around lines 2040 - 2058, The AuditLogSection toggle is being wired twice: SettingsFeatureCard already receives the master onEnabledChange via featureId="audit-log", so remove the duplicate onEnabledChange prop passed to AuditLogSection in this JSX block; keep other props (draftConfig, saving, onRetentionDaysChange) and continue using updateAuditLogField for retention and the enabled state (draftConfig.auditLog?.enabled) only where needed, ensuring SettingsFeatureCard remains responsible for the master toggle.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@node_modules`:
- Line 1: The repository accidentally tracks a placeholder file named
node_modules which prevents pnpm from creating the real node_modules directory;
remove that tracked file from git (git rm --cached node_modules or git rm
node_modules then commit), add node_modules to .gitignore to prevent re-adding,
and push the commit so CI/preview can create the real node_modules tree with
pnpm install.
In `@web/node_modules`:
- Line 1: Remove the tracked placeholder file named "web/node_modules" from
version control so the package manager can create an actual directory;
specifically, delete the tracked file (the placeholder "web/node_modules") and
commit the removal, and ensure your .gitignore allows the node_modules directory
to be untracked (verify that "web/node_modules/" is not accidentally ignored or
listed as a file), then push the commit to remove the placeholder from the repo.
In `@web/src/components/dashboard/config-sections/AuditLogSection.tsx`:
- Around line 34-91: AuditLogSection currently renders its own Card, CardHeader,
CardTitle, description and the enable Switch which duplicates the outer
SettingsFeatureCard; remove the outer <Card> wrapper, the
<CardHeader>/<CardTitle>/<CardDescription> block and the enable Switch (the
onEnabledChange prop is already handled by SettingsFeatureCard), and leave only
the inner controls: the retention Label/paragraph, the Input bound to
retentionDays with onRetentionDaysChange, the retention helper text and preserve
the disabled logic that uses saving || !enabled; keep the same props (enabled,
retentionDays, onRetentionDaysChange, saving) and ensure the component returns
just the CardContent-equivalent block (no extra wrapping Card) so it integrates
cleanly as basicContent in SettingsFeatureCard.
---
Outside diff comments:
In `@src/api/middleware/auditLog.js`:
- Around line 103-120: The insert SQL/parameter contract for audit_logs is
duplicated here; replace the inline pool.query in audit middleware with the
shared insert helper from src/modules/auditLogger.js (e.g., import and call
insertAuditLog or the existing logAudit function) so there’s a single canonical
SQL/parameter ordering; ensure the helper handles guildId -> 'global' fallback,
userTag nulling, JSON.stringify(details), and ipAddress nulling and update the
other duplicate (lines ~252-261) to call the same helper instead of
re-duplicating the INSERT and parameter list.
---
Duplicate comments:
In `@src/modules/auditLogger.js`:
- Around line 113-124: The purgeOldAuditLogs function currently deletes audit
rows by retentionDays across all guilds (see purgeOldAuditLogs and its
pool.query DELETE) which prevents enforcing per-guild retention; either
implement a guild-scoped variant or make retentionDays global-only: add an
optional guildId parameter (e.g., guildId) to purgeOldAuditLogs and update the
SQL to include "AND guild_id = $2" (and pass guildId) so callers can purge only
that guild, or if per-guild retention is unsupported, document and enforce
global-only behavior by removing any guild-specific override code paths and
keeping purgeOldAuditLogs strictly global.
In `@tests/utils/dbMaintenance.test.js`:
- Line 21: The top-level import of auditLogger is captured before
vi.resetModules(), causing the test to assert against a stale instance; remove
the module-level import of auditLogger and instead import it inside beforeEach()
after calling vi.resetModules() and re-importing dbMaintenance so the test uses
the fresh mocked auditLogger instance (ensure any assertions referencing
auditLogger use the re-imported variable and that dbMaintenance is imported
after vi.resetModules()).
In `@web/src/components/dashboard/config-editor.tsx`:
- Around line 2040-2058: The AuditLogSection toggle is being wired twice:
SettingsFeatureCard already receives the master onEnabledChange via
featureId="audit-log", so remove the duplicate onEnabledChange prop passed to
AuditLogSection in this JSX block; keep other props (draftConfig, saving,
onRetentionDaysChange) and continue using updateAuditLogField for retention and
the enabled state (draftConfig.auditLog?.enabled) only where needed, ensuring
SettingsFeatureCard remains responsible for the master toggle.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b09303f7-90c3-487d-be57-951a9c76a099
📒 Files selected for processing (11)
TASK.mdnode_modulessrc/api/middleware/auditLog.jssrc/modules/auditLogger.jssrc/utils/dbMaintenance.jstests/modules/auditLogger.test.jstests/utils/dbMaintenance.test.jsweb/node_modulesweb/src/app/dashboard/audit-log/page.tsxweb/src/components/dashboard/config-editor.tsxweb/src/components/dashboard/config-sections/AuditLogSection.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,mjs,jsx,ts,tsx}: Use single quotes for strings in code, double quotes only allowed in JSON files
Always end statements with semicolons
Use 2-space indentation, enforced by Biome
Files:
web/src/app/dashboard/audit-log/page.tsxsrc/utils/dbMaintenance.jsweb/src/components/dashboard/config-sections/AuditLogSection.tsxsrc/api/middleware/auditLog.jstests/utils/dbMaintenance.test.jssrc/modules/auditLogger.jstests/modules/auditLogger.test.jsweb/src/components/dashboard/config-editor.tsx
web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Next.js 16 web dashboard uses App Router with Discord OAuth2 authentication, dark/light theme support, and mobile-responsive design
Files:
web/src/app/dashboard/audit-log/page.tsxweb/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-editor.tsx
**/*.{js,cjs,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use ESM only with
import/exportsyntax, never CommonJS except in migration files (.cjs)
Files:
src/utils/dbMaintenance.jssrc/api/middleware/auditLog.jstests/utils/dbMaintenance.test.jssrc/modules/auditLogger.jstests/modules/auditLogger.test.js
src/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.{js,mjs,jsx,ts,tsx}: Usesrc/logger.jsWinston logger singleton, never useconsole.*methods
Use safe Discord message methods:safeReply(),safeSend(),safeEditReply()instead of direct Discord.js methods
Use parameterized SQL queries, never string interpolation for database queries
Files:
src/utils/dbMaintenance.jssrc/api/middleware/auditLog.jssrc/modules/auditLogger.js
{.env*,README.md,src/**/!(*.test).{js,ts}}
📄 CodeRabbit inference engine (CLAUDE.md)
Remove
GUILD_IDfrom shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag--guild-id <guild_id>
Files:
src/utils/dbMaintenance.jssrc/api/middleware/auditLog.jssrc/modules/auditLogger.js
tests/**/*.{js,mjs,jsx,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Maintain 80% code coverage threshold minimum, never lower this threshold
Files:
tests/utils/dbMaintenance.test.jstests/modules/auditLogger.test.js
src/modules/**/*.{js,mjs}
📄 CodeRabbit inference engine (AGENTS.md)
Create new modules for features with corresponding config sections in
config.jsonand entries inSAFE_CONFIG_KEYS
Files:
src/modules/auditLogger.js
🧠 Learnings (8)
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to {.env*,README.md,src/**/!(*.test).{js,ts}} : Remove `GUILD_ID` from shared environment variables in production/deployment configurations; preserve dev-only guild-scoped deploy support via CLI flag `--guild-id <guild_id>`
Applied to files:
src/utils/dbMaintenance.js
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor save contract must maintain: global save/discard, diff-modal confirmation, per-section PATCH batching, and partial-failure behavior
Applied to files:
web/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Refactor config feature presentation to use reusable `SettingsFeatureCard` pattern with structure: header + master toggle + Basic/Advanced blocks
Applied to files:
web/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Config editor must implement metadata-driven config search with cross-category quick jump, focus/scroll targeting, and auto-open advanced sections when search hits advanced controls
Applied to files:
web/src/components/dashboard/config-sections/AuditLogSection.tsxweb/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to tests/**/*.{js,mjs,jsx,ts,tsx} : Maintain 80% code coverage threshold minimum, never lower this threshold
Applied to files:
tests/utils/dbMaintenance.test.js
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.{js,jsx,ts,tsx} : Web dashboard config editor must use category workspace navigation with categories: `AI & Automation`, `Onboarding & Growth`, `Moderation & Safety`, `Community Tools`, `Support & Integrations` located in `web/src/components/dashboard/config-workspace/`
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-07T15:34:56.495Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-07T15:34:56.495Z
Learning: Applies to web/src/components/dashboard/config-workspace/**/*.test.{js,jsx,ts,tsx} : Config editor tests must cover manual-save workspace behavior (not autosave assumptions), category switching, search functionality, and dirty badges
Applied to files:
web/src/components/dashboard/config-editor.tsx
📚 Learning: 2026-03-05T18:07:15.752Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-05T18:07:15.752Z
Learning: Applies to src/api/utils/configAllowlist.js : Maintain `SAFE_CONFIG_KEYS` for writable config sections via API and `READABLE_CONFIG_KEYS` for read-only sections, add new config sections to SAFE to enable saves
Applied to files:
web/src/components/dashboard/config-editor.tsx
🪛 GitHub Actions: Railway Preview Deploy
node_modules
[error] 1-1: pnpm install --frozen-lockfile failed with ENOTDIR: not a directory when attempting to mkdir for 'node_modules' (exit code 236).
🪛 markdownlint-cli2 (0.21.0)
TASK.md
[warning] 3-3: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 24-24: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 29-29: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 35-35: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 40-40: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 47-47: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 54-54: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 58-58: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 62-62: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 66-66: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🔇 Additional comments (3)
TASK.md (1)
1-72: LGTM — task planning document is clear and comprehensive.The static analysis warnings about blank lines around headings are minor formatting nits and don't affect the document's utility. If you want to silence them, add a blank line before each
###heading (e.g., before lines 9, 24, 29, etc.).web/src/components/dashboard/config-editor.tsx (2)
117-117: LGTM —'auditLog'correctly added to known sections.The type guard now properly recognizes the new config section.
699-707: LGTM —updateAuditLogFieldfollows established patterns.The callback is consistent with other field updaters like
updateModerationFieldand correctly wires toupdateDraftConfig.
web/src/components/dashboard/config-sections/AuditLogSection.tsx
Outdated
Show resolved
Hide resolved
🧹 Preview Environment Cleaned UpThe Railway preview environment for this PR has been removed. Environment: |
Summary
Full-stack audit log for dashboard admin actions.
Changes
audit_logstable with guild/user/action/details/IP, indexed for fast paginationauditLogger.js:logAuditEvent()+purgeOldAuditLogs()with graceful DB error handlingGET /api/v1/guilds/:id/audit-log— paginated, filterable by user/action/daterunMaintenance()/api/guilds/[guildId]/audit-logauditLog.retentionDaysin dashboard under Moderation & SafetyCloses #123