feat(backend,frontend): migrate data APIs from REST to native tRPC over WebSocket
Replace REST module routers with a single typed tRPC appRouter served over
/trpc (HTTP + WebSocket), and rewire the frontend to call it via
@trpc/client wsLink (browser) and httpLink (RSC data layer). Existing
/api/health + /api/metrics stay as plain Express for infra scraping.
Notable fixes surfaced by the live smoke test:
- Express 5 / path-to-regexp v8 rejects the /trpc/* wildcard route; use a
prefix middleware that computes opts.path from the URL instead.
- nodeHTTPRequestHandler treats opts.path as the literal procedure path, so
it is derived per-request from req.url.
- Two ws servers on one http.Server (the /ws voice socket + /trpc) collided
and returned 400 on upgrade; both now use noServer + a manually routed
server.on('upgrade') keyed by path.
Verified: BE tsc+biome+40 vitest green; FE tsc+biome green; live
HTTP and WebSocket calls returned real prod data.
Co-Authored-By: Claude Opus 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
d8552a9fb8
commit
2fa1827f17
@@ -1 +0,0 @@
|
||||
export { createMediaRouter } from "./media.routes.js";
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { Request, Response, Router } from "express";
|
||||
import express from "express";
|
||||
import { createChildLogger } from "@/shared/logger/index";
|
||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||
import { mediaLoopSchema, mediaQueueSchema } from "./media.schema.js";
|
||||
import { getStatus, queue, setLoop, skip, stop } from "./media.service.js";
|
||||
|
||||
const logger = createChildLogger("media.routes");
|
||||
|
||||
export function createMediaRouter(): Router {
|
||||
const router = express.Router();
|
||||
|
||||
// GET /api/media/status
|
||||
router.get(
|
||||
"/media/status",
|
||||
asyncHandler(async (_req: Request, res: Response) => {
|
||||
logger.debug("Media status requested");
|
||||
const status = await getStatus();
|
||||
res.json(status);
|
||||
}),
|
||||
);
|
||||
|
||||
// POST /api/media/queue
|
||||
router.post(
|
||||
"/media/queue",
|
||||
validateBody(mediaQueueSchema),
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const { source, mode } = req.body as {
|
||||
source: string;
|
||||
mode: "music" | "screen";
|
||||
};
|
||||
logger.debug({ source, mode }, "Media queue requested");
|
||||
const state = await queue(source, mode);
|
||||
res.json(state);
|
||||
}),
|
||||
);
|
||||
|
||||
// POST /api/media/skip
|
||||
router.post(
|
||||
"/media/skip",
|
||||
asyncHandler(async (_req: Request, res: Response) => {
|
||||
logger.debug("Media skip requested");
|
||||
const state = await skip();
|
||||
res.json(state);
|
||||
}),
|
||||
);
|
||||
|
||||
// POST /api/media/stop
|
||||
router.post(
|
||||
"/media/stop",
|
||||
asyncHandler(async (_req: Request, res: Response) => {
|
||||
logger.debug("Media stop requested");
|
||||
const state = await stop();
|
||||
res.json(state);
|
||||
}),
|
||||
);
|
||||
|
||||
// POST /api/media/loop
|
||||
router.post(
|
||||
"/media/loop",
|
||||
validateBody(mediaLoopSchema),
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const { loop } = req.body as { loop: boolean };
|
||||
logger.debug({ loop }, "Media loop requested");
|
||||
const state = await setLoop(loop);
|
||||
res.json(state);
|
||||
}),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
Reference in New Issue
Block a user