feat(backend): implement all missing endpoints, real analytics queries, and WebSocket server

- Replace stub analytics.repository.ts with real PostgreSQL queries using pg.Pool
- Add /api/guilds, /api/config, /api/auth/login, /api/ui-state (GET/POST)
- Add /api/review, /api/recordings, /api/analysis/search
- Add /api/messages/:id/reanalyze endpoint
- Add /api/analytics/heatmap and /api/analytics/topics
- Implement media routes (stub responses, backend has no Discord voice client)
- Add WebSocket server at /ws with heartbeat and broadcast functions
- Fix analytics route paths to match frontend contract (dual paths for backward compat)
- Export getPool() from database module for raw SQL queries
- Register all new routers in app.ts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-02 00:11:29 +07:00
co-authored by Claude Opus 4.8
parent 5a094c926a
commit 9b41eb9c12
24 changed files with 1205 additions and 87 deletions
@@ -1,5 +1,8 @@
import type { Router } from "express";
import type { Request, Response, Router } from "express";
import express from "express";
import { getPool } from "../../shared/database/index.js";
import { createChildLogger } from "../../shared/logger/index.js";
import { asyncHandler } from "../../shared/middlewares/index.js";
import {
handleGetAttachmentsByChannel,
handleGetMessageById,
@@ -7,6 +10,8 @@ import {
handleListMessages,
} from "./messages.controller.js";
const logger = createChildLogger("messages.routes");
export function createMessagesRouter(): Router {
const router = express.Router();
@@ -22,5 +27,68 @@ export function createMessagesRouter(): Router {
// GET /api/messages/:id - Get single message by ID
router.get("/messages/:id", handleGetMessageById);
// POST /api/messages/:id/reanalyze - Mark message for re-analysis
router.post(
"/messages/:id/reanalyze",
asyncHandler(async (req: Request, res: Response) => {
const id = req.params.id;
if (!id) {
res.status(400).json({ error: "MISSING_ID" });
return;
}
const pool = getPool();
await pool.query(
`UPDATE messages SET ai_status = 'pending' WHERE id = $1`,
[id],
);
logger.debug({ id }, "Message marked for re-analysis");
res.status(200).json({ ok: true });
}),
);
// GET /api/review - Get flagged/warned messages for review
router.get(
"/review",
asyncHandler(async (req: Request, res: Response) => {
const limit = Number(req.query.limit) || 20;
const channelId = (req.query.channelId as string) || undefined;
const pool = getPool();
let sqlQuery: string;
let params: (string | number)[];
if (channelId) {
sqlQuery = `
SELECT id, guild_id, channel_id, user_id, username, avatar_url,
content, type, created_at, ai_status, ai_severity,
ai_confidence, ai_analysis
FROM messages
WHERE ai_status IN ('warn', 'flagged')
AND channel_id = $1
ORDER BY created_at DESC
LIMIT $2
`;
params = [channelId, limit];
} else {
sqlQuery = `
SELECT id, guild_id, channel_id, user_id, username, avatar_url,
content, type, created_at, ai_status, ai_severity,
ai_confidence, ai_analysis
FROM messages
WHERE ai_status IN ('warn', 'flagged')
ORDER BY created_at DESC
LIMIT $1
`;
params = [limit];
}
const { rows } = await pool.query(sqlQuery, params);
logger.debug({ limit, channelId }, "Review query executed");
res.json({ results: rows, limit, cursor: null });
}),
);
return router;
}