feat(gmw): public features #2-#6 — live moderation feed, toxic topic trends, channel timeline, CSV export, activity heatmap
- Live Moderation Feed: gateway publishes discord:moderation:action (Redis) → backend WS emits moderation_action → public web shows realtime stream. - Toxic Topic Trends: backend moderation.trends aggregates categories/severity/action_type (read-only) → SVG bar + donut. - Channel Timeline: messages view gets Feed/Timeline toggle with date-grouped separators. - CSV Export: client-side downloadCsv for moderation actions (no backend write scope). - Activity Heatmap: backend messages.activity (per-hour volume by channel) → pure-SVG grid. User reputation deliberately excluded — no such feature exists in the codebase. All read-only / public-facing / fully automatic per project rules.
This commit is contained in:
@@ -459,6 +459,31 @@ export class MessagesRepository {
|
||||
|
||||
return { data: trimmed, nextCursor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-hour message volume for the last `days` days, grouped by channel.
|
||||
* Powers the public Activity Heatmap (read-only, no write scope).
|
||||
* Returns a flat list of { channel_id, hour (0-23), count } buckets.
|
||||
*/
|
||||
async getActivity(days = 30) {
|
||||
const db = getDatabase();
|
||||
const since = Date.now() - days * 24 * 60 * 60 * 1000;
|
||||
const result = await db.execute(sql`
|
||||
SELECT channel_id,
|
||||
EXTRACT(HOUR FROM to_timestamp(created_at / 1000))::int AS hour,
|
||||
COUNT(*)::int AS c
|
||||
FROM messages
|
||||
WHERE created_at >= ${since}
|
||||
GROUP BY channel_id, hour
|
||||
ORDER BY channel_id, hour
|
||||
`);
|
||||
const rows = (result.rows as Record<string, unknown>[]) || [];
|
||||
return rows.map((r) => ({
|
||||
channelId: String(r.channel_id ?? "unknown"),
|
||||
hour: Number(r.hour ?? 0),
|
||||
count: Number(r.c ?? 0),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export const messagesRepository = new MessagesRepository();
|
||||
|
||||
Reference in New Issue
Block a user