1
19import { Controller, Get, Inject, Query, Req } from "@nestjs/common";
20import type { Request } from "express";
21import { ApiError } from "../../common/errors.js";
22import { RedisService } from "../../common/redis.service.js";
23import { loadEnv } from "../../config/env.js";
24import { EVENTS_STREAM } from "../../constants.js";
25
26@Controller()
27export class DebugController {
28 constructor(@Inject(RedisService) private readonly redis: RedisService) {}
29
30 @Get("debug/events")
31 async events(
32 @Query("name") name: string | undefined,
33 @Query("limit") limit: string | undefined,
34 @Req() req: Request
35 ) {
36
37 if (process.env.NODE_ENV !== "test") throw ApiError.notFound("Not found.");
38
39 const expected = loadEnv().TEST_DEBUG_TOKEN;
40 const given = String(req.headers["x-debug-token"] ?? "");
41 if (!expected || given !== expected) {
42 throw ApiError.unauthorized("Invalid debug token.", [{ reason: "debug_token_invalid" }]);
43 }
44
45 const count = Math.min(Math.max(Number(limit ?? 50) || 50, 1), 200);
46
47
48
49
50 const raw = await this.redis
51 .getRedis()
52 .xrange(EVENTS_STREAM, "-", "+", "COUNT", 1000)
53 .catch((): [string, string[]][] => []);
54 const out: { event: string; payload: unknown; ts: string }[] = [];
55 for (const [, fields] of raw) {
56 const map: Record<string, string> = {};
57 for (let i = 0; i < fields.length; i += 2) map[fields[i]!] = fields[i + 1]!;
58 if (name && map["event"] !== name) continue;
59 let payload: unknown = {};
60 try {
61 payload = map["payload"] ? JSON.parse(map["payload"]) : {};
62 } catch {
63 payload = map["payload"] ?? {};
64 }
65 out.push({ event: map["event"] ?? "", payload, ts: map["ts"] ?? "" });
66 if (out.length >= count) break;
67 }
68 return { items: out };
69 }
70}
71