feat(gmw): public features #7-14 — scam domains, top channels, hourly heatmap, category drill-down, coverage stats, channel culture glossary, term KB, edit history

ALSO fixes: dashboard.repository still JOINed dropped user_reputations table (listUsers/getUserDetail crash).
This commit is contained in:
asepharyana
2026-08-18 20:45:12 +07:00
parent 2a8f6d9062
commit 00e8d68ce5
38 changed files with 1542 additions and 25 deletions
@@ -86,3 +86,16 @@ hour-of-day (023) over last 14 days. Return
- frontend: `lib/ws/*`, `hooks/use-moderation.ts`, `hooks/use-messages.ts`,
`lib/csv.ts`, `lib/types/*`, `app/(dashboard)/moderation/view.tsx`,
`app/(dashboard)/messages/view.tsx`, new components under `components/`
## Status: COMPLETE (deployed + verified)
- Commit 9b3134d: features #2#6 (live feed, trends, timeline, CSV export, heatmap)
- Commit 2a8f6d9: user reputation feature fully removed (643 deletions, no trace in src/tests)
- Migration 0016 applied: user_reputations DROPPED (DB verified: false)
- All 3 services active (gateway + backend restarted 18:29, frontend running)
- Gateway typecheck/lint/test(117 passed); backend typecheck/lint/build; FE lint/build — all GREEN
## Verification
- moderation/stats WS returns data (32 actions) → WS adapter works
- DB: user_reputations gone; moderation_actions explainability cols present
- Live Feed: gateway publishes discord:moderation:action → backend WS (same path as guild_member_*)
- Trends/Activity: backend router procedures registered (typecheck+tsc), same WS adapter
@@ -0,0 +1,119 @@
# 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 `getTrends` SQL style):
- `moderation.repository.ts`:
- `getTopFlaggedDomains(days)``regexp_matches(content,'https?://([^/\s]+)')` on
`moderation_actions WHERE created_at>=since`, group by host, COUNT, order DESC LIMIT 20.
- `getTopFlaggedChannels(days)` — join `moderation_actions a` LEFT JOIN `messages m`
ON `m.id=a.message_id`, group by `m.channel_id`, COUNT, order DESC LIMIT 15.
Channel name via `m.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 where `categories` contains
`category` (reuse `listActions` filter or new query), for drill-down #10.
- `getCoverage(days)` — from `ai_analysis_runs`: total runs, status breakdown
(clean/flagged/warn/error/pending), coverage % = (analyzed)/(captured in window).
- `dashboard.repository.ts` (or new `knowledge.repository.ts`):
- `listChannelCultures(limit, search?)``channel_cultures` rows (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_edits` join `messages` for
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 (follow `trends` shape):
- `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`: add `FlaggedDomain`, `FlaggedChannel`, `HourlyModeration`,
`ModerationCoverage` interfaces.
- `lib/types/index.ts` (+ message.ts): add `ChannelCultureRow`, `GlossaryRow`, `EditHistoryRow`.
- `lib/api/moderation.ts`: add `topDomains`, `topChannels`, `byHour`, `byCategory`, `coverage`.
- `lib/api/dashboard.ts` (or messages.ts): add `channelCultures`, `glossary`, `editHistory`.
- `lib/api/server.ts`: add SSR seed fetchers (follow `getModerationStats`).
- `hooks/use-moderation.ts`: add `useTopDomains`, `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)
and `app/(dashboard)/messages/view.tsx` (EditHistory panel) and new route pages
`app/(dashboard)/channels/page.tsx` + `app/(dashboard)/glossary/page.tsx` with
matching `view.tsx` (follow existing page→view SSR pattern; check `app/(dashboard)/dashboard/page.tsx`).
- Export CSV buttons reuse `lib/csv.ts` `downloadCsv` (client-side) for domains/channels/edits.
### Gateway (#15 Weekly Digest)
- Add a cron/interval in `services/discord-gateway` (check existing scheduler pattern —
search `setInterval`/`cron` in `src`). 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 existing `discordClient.channels.send` helper). 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_at` is bigint epoch-MS — compare with `<`/`>`, do NOT divide by 1000 in SQL.
- Pure SVG only — frontend has ZERO chart libs.
- `Badge` Tone = signal|amber|vermilion|neutral (no "rose").
- Frontend WS import is `@/lib/ws/context`; method `on` not `subscribe`.
- Commit author `asepharyana`, no Co-Authored-By.
- Rebuild `dist/` after gateway changes; apply drizzle migrations manually.
## Verification
- Per service: `pnpm typecheck && pnpm lint && pnpm build` green.
- Gateway: `pnpm test` (117+ pass).
- Live: `moderation/stats` WS returns data (proves adapter); new procedures registered
(typecheck = proof). `systemctl show` new ActiveEnterTimestamp after deploy.
- DB: confirm `channel_cultures`/`term_glossary_cache`/`message_edits`/`ai_analysis_runs`
have rows before relying on them (some may be empty → components handle empty state).
## Execution order
1. Bug fix dashboard.repository (reputation JOIN) — deploy-safe.
2. Backend repositories + service + router (#7,#8,#9,#14 dashboard; #11,#12; #13).
3. FE types + api + hooks + components + wire (#7,#8,#9,#10,#11,#12,#13,#14).
4. Gateway #15 digest (if scheduler exists) — verify via log, not UI.
5. Build/lint all 3 services; commit; push; monitor CI; apply migrations; verify live.