Moderation dashboard: staff dashboard, user history, notes, event capture (Phase 6a + 6b) #42
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/moderation-dashboard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Surfaces the Discord bot's moderation data on the admin panel and captures the event streams needed to power it. This is the first half of Step 6 (dashboard + history + notes + event capture); appeals (6c) and the bot reversal callback (6d) will land as a separate feature branch.
Phase 6a — Dashboard, user history, notes, moderator role
users.roleENUM gainsmoderator(CREATE + idempotent ALTER); new server-ownedmod_notestable (staff_only/admin_onlyvisibility).model/moderationreads the bot'smod_actionsdirectly via the shared pool (a documented, read-only cross of the bot/server ownership boundary), correlates accounts throughuser_identities(provider=discord), and flags automated actions viastaff_user_id === bot_config.application_id. New/api/v1/admin/moderation/*routes gated byrequireRole('admin','moderator');admin_onlynote writes require admin./admin/moderationoverview (window tiles, type-filterable recent feed, user lookup) and/user/:discordIdhistory (tabs + notes).RoleGate+AdminLayoutconfine moderators to their section.Phase 6b — Bot event capture
member_events(join/leave + invite attribution),filter_hits(word/invite),spam_hits(rate_limit/mass_mention/mass_emoji).inviteTracker.jskeeps an invite-use cache via theGuildInvitesintent and diffs it on join); newguildMemberRemoverecords leaves;messageFilterrecords filter/spam hits alongside the existing warn/mute (additive —mod_actionsstill logs the action).summaryextended with joins/leaves/invite_joins/filter_hits/spam_hits per window; new feeds/moderation/{members,filter-hits,spam-hits}.Notes
member_events/filter_hits/spam_hitstables live in the same physical DB and are ensured by the main server on boot (same pattern as the existing bot tables).Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_019rao86n5cXpwAyjdBFEshV
Surface the Discord bot's moderation data on the admin panel: a read-only staff dashboard over the existing mod_actions log, per-user history, staff notes, and a new moderator role. No bot changes. Schema - users.role ENUM gains 'moderator' (CREATE + idempotent ALTER for existing DBs) - new server-owned mod_notes table (staff_only/admin_only visibility) Server - model/moderation: read mod_actions via the shared pool (documented read-only cross of the bot/server ownership boundary), correlate accounts through user_identities (provider='discord'), flag automated actions via staff_user_id === bot_config.application_id; pure reshaping helpers isolated in moderation.pure.js so they unit-test without opening a DB pool - model/modNotes: list/add with role-gated admin_only visibility - admin/moderation.controller + routes under /api/v1/admin/moderation/* gated by requireRole('admin','moderator'); admin_only note writes require admin - allow assigning 'moderator' in the user create/update validators Client - /admin/moderation overview (window tiles, type-filterable recent feed, user lookup) and /user/:discordId history (tabs + notes with add-note) - RoleGate; AdminLayout filters nav and confines moderators to their section - moderator badge + action-type/auto badges Deferred (see plan): 6b bot event capture (joins/leaves/filter/spam), 6c appeals (needs public accounts), 6d /internal/mod-reverse bot reversal callback. Verified: 116 server unit tests, client build, DB-backed model smoke, full HTTP/RBAC e2e, and a browser click-through of the dashboard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019rao86n5cXpwAyjdBFEshVLight up the moderation dashboard's previously-empty widgets by persisting the event streams the bot only reacted to in-memory before. Schema (bot-owned) - member_events: join/leave, with invite_code/inviter_* for best-effort invite attribution on joins - filter_hits: word / foreign-invite filter deletions (matched + action_taken) - spam_hits: rate_limit / mass_mention / mass_emoji detections Bot - new models memberEvents/filterHits/spamHits - guildMemberAdd records the join with invite attribution; new inviteTracker.js keeps an invite-use cache (GuildInvites intent + inviteCreate/inviteDelete) and diffs it on join to find which invite was used — best-effort, never blocks auto-role - new guildMemberRemove records leaves - messageFilter records filter/spam hits alongside the existing warn/mute; inviteFilter now returns the offending code; detectSpam identifies which spam rule tripped (preserving the rate-limit-first side-effect order) - mod_actions still logs the resulting warn/mute — the new tables are additive Server - summary extended with joins/leaves/invite_joins/filter_hits/spam_hits per window - new feeds: /api/v1/admin/moderation/{members,filter-hits,spam-hits} Client - overview now shows 8 tiles (mod actions + joins/leaves/filter/spam, joins tile notes "N via invite") plus an Events panel with Members/Filter/Spam tabs; removed the coming-soon note Verified: 119 server unit tests, client build, 14-check DB-backed smoke, and a browser click-through of every tile and events tab (incl. invite attribution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019rao86n5cXpwAyjdBFEshV