ALSO fixes: dashboard.repository still JOINed dropped user_reputations table (listUsers/getUserDetail crash).
7.9 KiB
GMW — Fitur Publik Lanjutan #7–#15 + Bug Fix Reputation Removal
For Hermes: Implement task-by-task. Build + lint + typecheck each service after its changes. Deploy via push to main (CI handles Nix build + systemd). Apply any new drizzle migration MANUALLY (systemd does NOT run migrations). Hard constraint (user): public read-only web, fully automatic, rules in code, NO admin endpoints, NO shadow mode, NO per-user reputation aggregation.
Bug fix discovered during planning (MUST do first)
services/backend/src/modules/dashboard/dashboard.repository.ts still references
pgUserReputationsTable (import line 8; JOINs at lines 173 + 457) — that table was
DROPPED in migration 0016. dashboard.listUsers / dashboard.userDetail will
crash at runtime (undefined table). Remove the import + the r.* join columns
(trust_score, clean_message_streak, total_infractions) from both queries.
This is a regression introduced by the reputation removal commit.
Features to implement (#7–#15)
All reuse existing infra: moderation_actions, messages, channel_cultures,
term_glossary_cache, ai_analysis_runs, message_edits, gateway cron (for #15),
WS (proven Live Feed pattern), oRPC over WS (proven), pure-SVG charts (no libs).
| # | Feature | Data source | Surface |
|---|---|---|---|
| 7 | Flagged Link / Scam Domain Reporter | regex URL from moderation_actions.content/evidence |
/moderation |
| 8 | Top Flagged Channels | join moderation_actions.message_id→messages.channel_id |
/moderation |
| 9 | Moderation Heatmap by Hour | moderation_actions.created_at hour-of-day |
/moderation |
| 10 | Flag Category Drill-down | moderation_actions.categories (reuse Trends) |
/moderation FE-only |
| 11 | Channel Culture Glossary | channel_cultures (exists) |
new /channels panel |
| 12 | Term Knowledge Base | term_glossary_cache (exists) |
new /glossary panel |
| 13 | Edit/Evasion Tracker | message_edits (exists) |
/messages |
| 14 | Auto-mod Coverage Stats | ai_analysis_runs (exists) |
/moderation metric tiles |
| 15 | Weekly Digest (auto, cron) | aggregate #7/#8/#9 → Discord via gateway cron | gateway cron + /moderation |
Architecture per layer
Backend (oRPC, services/backend/src)
- New repository methods (add to existing repos, follow
getTrendsSQL style):moderation.repository.ts:getTopFlaggedDomains(days)—regexp_matches(content,'https?://([^/\s]+)')onmoderation_actions WHERE created_at>=since, group by host, COUNT, order DESC LIMIT 20.getTopFlaggedChannels(days)— joinmoderation_actions aLEFT JOINmessages mONm.id=a.message_id, group bym.channel_id, COUNT, order DESC LIMIT 15. Channel name viam.metadata::jsonb->'channel'->>'channelName'.getHourlyModeration(days)—EXTRACT(HOUR FROM to_timestamp(created_at/1000))group by hour, COUNT, severity breakdown. (24 rows)getFlaggedByCategory(days, category)— list actions wherecategoriescontainscategory(reuselistActionsfilter or new query), for drill-down #10.getCoverage(days)— fromai_analysis_runs: total runs, status breakdown (clean/flagged/warn/error/pending), coverage % = (analyzed)/(captured in window).
dashboard.repository.ts(or newknowledge.repository.ts):listChannelCultures(limit, search?)—channel_culturesrows (channel_id, guild_id, channel_name from messages metadata, culture_summary, last_analyzed_at).listGlossary(limit, search?)—term_glossary_cache(term, definition, source_url, resolved_at, hit_count) order by hit_count DESC.
messages.repository.ts:getEditHistory(limit, channelId?)—message_editsjoinmessagesfor old_content + channel + username + edited_at, order DESC LIMIT.
moderation.service.ts/dashboard.service.ts/messages.service.ts: thin wrappers.orpc/router.ts: add procedures (followtrendsshape):moderation.topDomains,moderation.topChannels,moderation.byHour,moderation.byCategory(input{days,category}),moderation.coverage.dashboard.channelCultures,dashboard.glossary.messages.editHistory.
Frontend (services/frontend/src)
lib/types/moderation.ts: addFlaggedDomain,FlaggedChannel,HourlyModeration,ModerationCoverageinterfaces.lib/types/index.ts(+ message.ts): addChannelCultureRow,GlossaryRow,EditHistoryRow.lib/api/moderation.ts: addtopDomains,topChannels,byHour,byCategory,coverage.lib/api/dashboard.ts(or messages.ts): addchannelCultures,glossary,editHistory.lib/api/server.ts: add SSR seed fetchers (followgetModerationStats).hooks/use-moderation.ts: adduseTopDomains,useTopChannels,useHourlyModeration,useByCategory,useCoverage.hooks/use-dashboard.ts/use-messages.ts: add culture/glossary/edit hooks.hooks/index.ts: export all.- New components (pure SVG/CSS, reuse
GlassPanel/SectionHeader/Badge/Donut):components/ScamDomains.tsx,components/TopChannels.tsx,components/ModerationHeatmap.tsx,components/CoverageTiles.tsx,components/ChannelCultureGlossary.tsx,components/TermGlossary.tsx,components/EditHistory.tsx.
- Wire into
app/(dashboard)/moderation/view.tsx(grid col-span-2/3/5 as space allows) andapp/(dashboard)/messages/view.tsx(EditHistory panel) and new route pagesapp/(dashboard)/channels/page.tsx+app/(dashboard)/glossary/page.tsxwith matchingview.tsx(follow existing page→view SSR pattern; checkapp/(dashboard)/dashboard/page.tsx). - Export CSV buttons reuse
lib/csv.tsdownloadCsv(client-side) for domains/channels/edits.
Gateway (#15 Weekly Digest)
- Add a cron/interval in
services/discord-gateway(check existing scheduler pattern — searchsetInterval/croninsrc). On a 7-day cadence, query backend oRPC (dashboard.activity,moderation.trends,moderation.topChannels) — OR compute directly via a shared repository — and post a formatted summary to the monitor guild channel (via existingdiscordClient.channels.sendhelper). Fully automatic, no UI.
Files touched (summary)
- backend:
modules/moderation/{repository,service}.ts,modules/dashboard/{repository,service}.ts,modules/messages/{repository,service}.ts,orpc/router.ts,shared/index.ts(if new tables),lib/types/*(FE) - frontend:
lib/api/*,lib/types/*,hooks/*,components/*,app/(dashboard)/* - gateway: new digest scheduler + (none if reuse backend) maybe
shared/redis-channels.ts
Constraints / pitfalls (from gmw-ops skill)
created_atis bigint epoch-MS — compare with</>, do NOT divide by 1000 in SQL.- Pure SVG only — frontend has ZERO chart libs.
BadgeTone = signal|amber|vermilion|neutral (no "rose").- Frontend WS import is
@/lib/ws/context; methodonnotsubscribe. - Commit author
asepharyana, no Co-Authored-By. - Rebuild
dist/after gateway changes; apply drizzle migrations manually.
Verification
- Per service:
pnpm typecheck && pnpm lint && pnpm buildgreen. - Gateway:
pnpm test(117+ pass). - Live:
moderation/statsWS returns data (proves adapter); new procedures registered (typecheck = proof).systemctl shownew ActiveEnterTimestamp after deploy. - DB: confirm
channel_cultures/term_glossary_cache/message_edits/ai_analysis_runshave rows before relying on them (some may be empty → components handle empty state).
Execution order
- Bug fix dashboard.repository (reputation JOIN) — deploy-safe.
- Backend repositories + service + router (#7,#8,#9,#14 dashboard; #11,#12; #13).
- FE types + api + hooks + components + wire (#7,#8,#9,#10,#11,#12,#13,#14).
- Gateway #15 digest (if scheduler exists) — verify via log, not UI.
- Build/lint all 3 services; commit; push; monitor CI; apply migrations; verify live.