Skip to content

Feat/dockerize - #87

Open
Harxhit wants to merge 2 commits into
mainfrom
feat/dockerize
Open

Feat/dockerize#87
Harxhit wants to merge 2 commits into
mainfrom
feat/dockerize

Conversation

@Harxhit

@Harxhit Harxhit commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Adds Docker Compose stacks for local development and production deployment, plus a Puppeteer load test that scales viewers to measure SFU capacity. Backend and frontend Dockerfiles are now multi-stage: dev targets hot reload, prod runs compiled TS and serves the static SPA through nginx, which also proxies /backend and /socket.io. The broadcaster, viewer, and dashboard routes moved out of ProtectedRoute so the load test can reach them without auth.

Docker

  • Two compose files orchestrate backend, frontend, MongoDB, a 6-node Redis cluster, and an nginx ingress, all on network_mode: host (Linux only).
  • The backend prod image runs node dist/index.js; the build copies rateLimit.lua into dist because tsc doesn't emit it.
  • deploy/redis/init-cluster.sh creates the cluster idempotently; deploy/env.docker.template and README.docker.md document the required env vars and caveats.
  • Compose force-overrides Mongo/Redis endpoints so the containerized infra is always used.

Load test

  • load-test/sfu-capacity.js launches a real broadcaster, adds viewers in batches, and writes join and first-frame latency percentiles to CSV.
  • Frontend exposes __csRoomId, __csLiveAt, __csJoinedAt, __csFirstFrameAt, and __csSocket timing globals and wires socket connect/disconnect in Broadcaster.
  • Making the three routes public removes auth from production; re-add ProtectedRoute if route security is still required.
  • Socket disconnects now log reason and details at error level.

Written for commit cef912b. Summary will update on new commits.

Review in cubic

Harxhit and others added 2 commits August 27, 2026 12:01
- add load-test script and results CSV for SFU capacity testing
- expose window globals for test timing (__csSocket, __csRoomId,
  __csLiveAt, __csJoinedAt, __csFirstFrameAt)
- wire socket connect/disconnect lifecycle in Broadcaster; use
  connectSocket in ViewerPage
- unprotect broadcaster/viewer/dashboard routes for load testing
- log socket disconnect reason and details at error level

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add two top-level compose files that orchestrate the app plus its core
dependencies (backend, frontend, 6-node Redis cluster, MongoDB, nginx
ingress), all host-networked to match infra/. Coturn/Envoy/HAProxy stay
in infra/.

- backend/Dockerfile: multi-stage (dev via ts-node/nodemon; prod compiles
  TS -> dist and runs node). Add build/start scripts; build copies
  src/scripts (rateLimit.lua) into dist since tsc does not emit it.
- frontend/Dockerfile: multi-stage (dev Vite server; prod nginx serving
  the SPA and proxying /backend + /socket.io -> :3000 as the ingress).
- docker-compose.local.yml: hot-reload dev with bind-mounted source.
- docker-compose.prod.yml: optimized images, restart policies, Mongo in
  a named volume, VITE_* baked in at build time.
- deploy/: local nginx ingress conf, idempotent Redis cluster init,
  env.docker.template documenting all required vars.
- README.docker.md: run instructions and caveats.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 551f5a21-903c-459c-badd-a7259c6f2706


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

16 issues found across 20 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/router/index.tsx">

<violation number="1" location="frontend/src/router/index.tsx:19">
P1: These routes now bypass `ProtectedRoute`, allowing unauthenticated users to open the dashboard, broadcaster, and viewer pages. Keep these routes nested under `ProtectedRoute` so the existing authentication and socket readiness checks still run.</violation>
</file>

<file name="backend/src/utils/socket.util.ts">

<violation number="1" location="backend/src/utils/socket.util.ts:143">
P2: Disconnect is a normal Socket.IO lifecycle event (tab close, navigation, network blip), not an error. Logging every disconnect with logger.error fills logs/error.log (the winston File transport at level 'error') and drowns genuine failures. Keep the previous logger.info level if you want these logs, or use logger.warn at most.</violation>
</file>

<file name="backend/Dockerfile">

<violation number="1" location="backend/Dockerfile:32">
P1: The production build fails at `npm run build` because `NODE_ENV=production` makes `npm ci` omit the `typescript` dev dependency. Install build dependencies explicitly in this stage, then prune them after compilation.</violation>
</file>

<file name="frontend/nginx.conf">

<violation number="1" location="frontend/nginx.conf:18">
P1: When production sets `HOST_PUBLIC_IP` as documented, the backend binds to that public address rather than `127.0.0.1`, so this proxy target refuses the API requests and the analogous Socket.IO target fails too. Bind the backend listener to loopback/all interfaces independently of the advertised public IP, or make both ingress targets use the backend's actual bind address.</violation>
</file>

<file name="load-test/sfu-capacity.js">

<violation number="1" location="load-test/sfu-capacity.js:13">
P1: This commits a plaintext test-account password to the repository, and the environment-variable guard can never take effect. Read both values from `process.env` and keep credentials out of source control.</violation>

<violation number="2" location="load-test/sfu-capacity.js:24">
P2: A non-positive `--batchSize` makes the main loop non-terminating. Reject non-positive and non-finite parsed values before entering the viewer loop.</violation>

<violation number="3" location="load-test/sfu-capacity.js:678">
P2: When `maxViewers` is not divisible by `batchSize`, the test silently measures fewer viewers than requested. Continue until `MAX_VIEWERS` and size the final batch to the remaining viewer count.</violation>
</file>

<file name="deploy/redis/init-cluster.sh">

<violation number="1" location="deploy/redis/init-cluster.sh:21">
P2: When a persisted topology has slot coverage but is missing nodes or replicas, this check exits successfully without creating or repairing the promised 3-master/3-replica cluster. Validate `cluster nodes` or `cluster slots` and require six connected nodes, three masters, and three replicas before returning.</violation>
</file>

<file name="README.docker.md">

<violation number="1" location="README.docker.md:62">
P3: The documented output for `/backend/db/__ping` is wrong. After nginx strips the `/backend/` prefix the request hits `dbReadinessCheck`, which returns JSON `{success:true, message:"Database Up"}`, not the literal `PING OK` shown. Update the comment so a user following the doc isn't confused by the different output.</violation>
</file>

<file name="load-test/sfu-capacity-results.csv">

<violation number="1" location="load-test/sfu-capacity-results.csv:2">
P2: Every one of the 100 rows has joinP50 == joinP99 and firstFrameP50 == firstFrameP99 (identical value). The generator records one row per batch and computes percentile() over only that batch's samples, so the committed file was produced with batchSize=1 where each batch holds a single sample and percentile() degenerates to that one value for both P50 and P99. As a committed capacity-test artifact the P50/P99 columns therefore do not represent real percentiles or a latency distribution — each value is just one viewer's latency — which misrepresents the SFU capacity the file claims to document. Re-run with a real batch size (e.g., 5-10) so the percentiles are meaningful, or drop/relabel these columns and state the per-row sample size.</violation>
</file>

<file name="deploy/nginx/local.conf">

<violation number="1" location="deploy/nginx/local.conf:14">
P2: The `location /` and `location /socket.io/` blocks force `Connection: upgrade` on every request, including plain HTTP and Socket.IO long-polling requests that never send an `Upgrade` header. This is the classic nginx WebSocket anti-pattern: nginx can no longer reuse the upstream keep-alive connection, so it opens a new TCP connection to Vite/backend for each request and emits a bogus `Connection: upgrade` header on non-upgrade traffic. Use a `map` so upgrade is only negotiated when the client actually requests it.</violation>
</file>

<file name="backend/package.json">

<violation number="1" location="backend/package.json:8">
P3: Running `npm run build` twice nests the copy: once `dist/scripts` exists, `cp -r src/scripts dist/scripts` creates `dist/scripts/scripts/rateLimit.lua` instead of refreshing the file in place. The runtime still reads `dist/scripts/rateLimit.lua` so nothing breaks functionally, but the build is not idempotent and accumulates stale nested copies with each rebuild. Copy the directory contents instead, e.g. `mkdir -p dist/scripts && cp -r src/scripts/. dist/scripts/`.</violation>
</file>

<file name="deploy/env.docker.template">

<violation number="1" location="deploy/env.docker.template:59">
P2: The comment claims leaving TURN blank falls back to STUN only, but no STUN server is configured anywhere and the frontend builds its iceServers purely from the VITE_TURN_* URL vars, which are empty when blank. With the template values, remote clients behind NAT have no STUN or TURN, so only host candidates are gathered and they will not connect. Either set up a STUN server or correct the comment to state that blank TURN means no NAT traversal.</violation>
</file>

<file name="docker-compose.local.yml">

<violation number="1" location="docker-compose.local.yml:129">
P2: The `- /app/node_modules` anonymous volumes are created once and reused across `up` runs, so after you add a dependency and `docker compose up --build`, the rebuilt image's node_modules (baked by the dev-stage `npm install`) stays hidden behind the stale volume. Docker only seeds that volume from the image on its first creation. The usual fix, `docker compose down -v`, also wipes the named Mongo/Redis data volumes. Name the volumes (e.g. a `backend-node-modules` named volume managed per-service, or document the `down -v`/`volume rm` step) so dependency changes take effect without nuking database data.</violation>
</file>

<file name="docker-compose.prod.yml">

<violation number="1" location="docker-compose.prod.yml:108">
P2: `redis-init` is a one-shot job with `restart: "no"`, and the backend starts only after it exits successfully (`depends_on ... service_completed_successfully`). If cluster creation fails transiently — a Redis node crashes or the `--cluster create` against an inconsistent state errors — the init container exits non-zero and nothing retries it, so the backend (and the whole stack) stays down until someone manually runs `docker compose restart redis-init`. Give the init a bounded retry policy (e.g. `restart: on-failure`), or have the backend not hard-gate on it, so a transient init failure self-heals.</violation>
</file>

<file name="frontend/Dockerfile">

<violation number="1" location="frontend/Dockerfile:24">
P2: VITE_TURN_USERNAME and VITE_TURN_CREDENTIAL build args are dead config: the frontend never reads them (src/utils/iceServer.util.ts obtains username/credential at runtime from the backend GET /turn/credentials). A real TURN secret passed here would be copied into the build-stage image layer and is reachable via `docker history`, yet serve no purpose. Remove these two ARG/ENV lines (and the matching args in docker-compose.prod.yml) so a shared secret can't be baked into the build.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

<Route path="/signin" element={<SignInPage />} />
<Route path="/signup" element={<SignUpPage />} />

<Route path="/broadcaster" element={<BroadcasterPage />} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: These routes now bypass ProtectedRoute, allowing unauthenticated users to open the dashboard, broadcaster, and viewer pages. Keep these routes nested under ProtectedRoute so the existing authentication and socket readiness checks still run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/router/index.tsx, line 19:

<comment>These routes now bypass `ProtectedRoute`, allowing unauthenticated users to open the dashboard, broadcaster, and viewer pages. Keep these routes nested under `ProtectedRoute` so the existing authentication and socket readiness checks still run.</comment>

<file context>
@@ -16,10 +16,10 @@ export default function Router() {
         <Route path="/signin" element={<SignInPage />} />
         <Route path="/signup" element={<SignUpPage />} />
 
+        <Route path="/broadcaster" element={<BroadcasterPage />} />
+        <Route path="/viewer" element={<ViewerPage />} />
+        <Route path="/dashboard" element={<DashboardPage />} />
</file context>

Comment thread backend/Dockerfile
FROM base AS build
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The production build fails at npm run build because NODE_ENV=production makes npm ci omit the typescript dev dependency. Install build dependencies explicitly in this stage, then prune them after compilation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/Dockerfile, line 32:

<comment>The production build fails at `npm run build` because `NODE_ENV=production` makes `npm ci` omit the `typescript` dev dependency. Install build dependencies explicitly in this stage, then prune them after compilation.</comment>

<file context>
@@ -1,20 +1,50 @@
+FROM base AS build
+ENV NODE_ENV=production
+COPY package*.json ./
+RUN npm ci
 COPY . .
+# `npm run build` = tsc + `cp -r src/scripts dist/scripts` (rateLimit.lua is read
</file context>
Suggested change
RUN npm ci
RUN npm ci --include=dev

Comment thread frontend/nginx.conf
# REST API. Trailing slash strips the /backend/ prefix:
# /backend/api/v1/... -> /api/v1/... ; /backend/db/__ping -> /db/__ping
location /backend/ {
proxy_pass http://127.0.0.1:3000/;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When production sets HOST_PUBLIC_IP as documented, the backend binds to that public address rather than 127.0.0.1, so this proxy target refuses the API requests and the analogous Socket.IO target fails too. Bind the backend listener to loopback/all interfaces independently of the advertised public IP, or make both ingress targets use the backend's actual bind address.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/nginx.conf, line 18:

<comment>When production sets `HOST_PUBLIC_IP` as documented, the backend binds to that public address rather than `127.0.0.1`, so this proxy target refuses the API requests and the analogous Socket.IO target fails too. Bind the backend listener to loopback/all interfaces independently of the advertised public IP, or make both ingress targets use the backend's actual bind address.</comment>

<file context>
@@ -0,0 +1,38 @@
+    # REST API. Trailing slash strips the /backend/ prefix:
+    #   /backend/api/v1/... -> /api/v1/... ; /backend/db/__ping -> /db/__ping
+    location /backend/ {
+        proxy_pass http://127.0.0.1:3000/;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
</file context>

Comment thread load-test/sfu-capacity.js
Comment on lines +13 to +14
const TEST_EMAIL = "harsxit04@gmail.com"
const TEST_PASSWORD = "@Harshit1308"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This commits a plaintext test-account password to the repository, and the environment-variable guard can never take effect. Read both values from process.env and keep credentials out of source control.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At load-test/sfu-capacity.js, line 13:

<comment>This commits a plaintext test-account password to the repository, and the environment-variable guard can never take effect. Read both values from `process.env` and keep credentials out of source control.</comment>

<file context>
@@ -0,0 +1,854 @@
+const BASE_URL = arg("baseUrl", "http://localhost");
+// const TOKEN = arg("token", null);
+
+const TEST_EMAIL = "harsxit04@gmail.com"
+const TEST_PASSWORD = "@Harshit1308"
+
</file context>
Suggested change
const TEST_EMAIL = "harsxit04@gmail.com"
const TEST_PASSWORD = "@Harshit1308"
const TEST_EMAIL = process.env.CROWDSTREAM_TEST_EMAIL;
const TEST_PASSWORD = process.env.CROWDSTREAM_TEST_PASSWORD;

Comment on lines +143 to 146
socket.on("disconnect", async (reason, details) => {
logger.error(`User disconnected, ${socket.id} reason: ${reason} details: ${details}`)
handleDisconnect(socket)
await stopFfmpegRecording(socket.id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Disconnect is a normal Socket.IO lifecycle event (tab close, navigation, network blip), not an error. Logging every disconnect with logger.error fills logs/error.log (the winston File transport at level 'error') and drowns genuine failures. Keep the previous logger.info level if you want these logs, or use logger.warn at most.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/src/utils/socket.util.ts, line 143:

<comment>Disconnect is a normal Socket.IO lifecycle event (tab close, navigation, network blip), not an error. Logging every disconnect with logger.error fills logs/error.log (the winston File transport at level 'error') and drowns genuine failures. Keep the previous logger.info level if you want these logs, or use logger.warn at most.</comment>

<file context>
@@ -140,8 +140,8 @@ io.on("connection", (socket) => {
 
-  socket.on("disconnect", async (reason) => {
-    logger.info(`User disconnected ${socket.id} beacuse of ${reason}`)
+  socket.on("disconnect", async (reason, details) => {
+    logger.error(`User disconnected, ${socket.id} reason: ${reason} details: ${details}`)
     handleDisconnect(socket)
</file context>
Suggested change
socket.on("disconnect", async (reason, details) => {
logger.error(`User disconnected, ${socket.id} reason: ${reason} details: ${details}`)
handleDisconnect(socket)
await stopFfmpegRecording(socket.id)
socket.on("disconnect", async (reason, details) => {
logger.info(`User disconnected, ${socket.id} reason: ${reason} details: ${details}`)
handleDisconnect(socket)
await stopFfmpegRecording(socket.id)
});

Comment thread docker-compose.local.yml
RECORDING_IP: "127.0.0.1"
volumes:
- ./backend:/app
- /app/node_modules

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The - /app/node_modules anonymous volumes are created once and reused across up runs, so after you add a dependency and docker compose up --build, the rebuilt image's node_modules (baked by the dev-stage npm install) stays hidden behind the stale volume. Docker only seeds that volume from the image on its first creation. The usual fix, docker compose down -v, also wipes the named Mongo/Redis data volumes. Name the volumes (e.g. a backend-node-modules named volume managed per-service, or document the down -v/volume rm step) so dependency changes take effect without nuking database data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.local.yml, line 129:

<comment>The `- /app/node_modules` anonymous volumes are created once and reused across `up` runs, so after you add a dependency and `docker compose up --build`, the rebuilt image's node_modules (baked by the dev-stage `npm install`) stays hidden behind the stale volume. Docker only seeds that volume from the image on its first creation. The usual fix, `docker compose down -v`, also wipes the named Mongo/Redis data volumes. Name the volumes (e.g. a `backend-node-modules` named volume managed per-service, or document the `down -v`/`volume rm` step) so dependency changes take effect without nuking database data.</comment>

<file context>
@@ -0,0 +1,166 @@
+      RECORDING_IP: "127.0.0.1"
+    volumes:
+      - ./backend:/app
+      - /app/node_modules
+    depends_on:
+      mongo:
</file context>

Comment thread docker-compose.prod.yml
volumes:
- ./deploy/redis/init-cluster.sh:/init-cluster.sh:ro
entrypoint: ["sh", "/init-cluster.sh"]
restart: "no"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: redis-init is a one-shot job with restart: "no", and the backend starts only after it exits successfully (depends_on ... service_completed_successfully). If cluster creation fails transiently — a Redis node crashes or the --cluster create against an inconsistent state errors — the init container exits non-zero and nothing retries it, so the backend (and the whole stack) stays down until someone manually runs docker compose restart redis-init. Give the init a bounded retry policy (e.g. restart: on-failure), or have the backend not hard-gate on it, so a transient init failure self-heals.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docker-compose.prod.yml, line 108:

<comment>`redis-init` is a one-shot job with `restart: "no"`, and the backend starts only after it exits successfully (`depends_on ... service_completed_successfully`). If cluster creation fails transiently — a Redis node crashes or the `--cluster create` against an inconsistent state errors — the init container exits non-zero and nothing retries it, so the backend (and the whole stack) stays down until someone manually runs `docker compose restart redis-init`. Give the init a bounded retry policy (e.g. `restart: on-failure`), or have the backend not hard-gate on it, so a transient init failure self-heals.</comment>

<file context>
@@ -0,0 +1,166 @@
+    volumes:
+      - ./deploy/redis/init-cluster.sh:/init-cluster.sh:ro
+    entrypoint: ["sh", "/init-cluster.sh"]
+    restart: "no"
+
+  backend:
</file context>

Comment thread frontend/Dockerfile
WORKDIR /app
ARG VITE_API_URL=
ARG VITE_SIGNALING_URL=
ARG VITE_TURN_USERNAME=

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: VITE_TURN_USERNAME and VITE_TURN_CREDENTIAL build args are dead config: the frontend never reads them (src/utils/iceServer.util.ts obtains username/credential at runtime from the backend GET /turn/credentials). A real TURN secret passed here would be copied into the build-stage image layer and is reachable via docker history, yet serve no purpose. Remove these two ARG/ENV lines (and the matching args in docker-compose.prod.yml) so a shared secret can't be baked into the build.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/Dockerfile, line 24:

<comment>VITE_TURN_USERNAME and VITE_TURN_CREDENTIAL build args are dead config: the frontend never reads them (src/utils/iceServer.util.ts obtains username/credential at runtime from the backend GET /turn/credentials). A real TURN secret passed here would be copied into the build-stage image layer and is reachable via `docker history`, yet serve no purpose. Remove these two ARG/ENV lines (and the matching args in docker-compose.prod.yml) so a shared secret can't be baked into the build.</comment>

<file context>
@@ -1,18 +1,46 @@
+WORKDIR /app
+ARG VITE_API_URL=
+ARG VITE_SIGNALING_URL=
+ARG VITE_TURN_USERNAME=
+ARG VITE_TURN_CREDENTIAL=
+ARG VITE_TURN_UDP_URL=
</file context>

Comment thread README.docker.md
Health checks:

```bash
curl http://localhost/backend/db/__ping # -> PING OK

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The documented output for /backend/db/__ping is wrong. After nginx strips the /backend/ prefix the request hits dbReadinessCheck, which returns JSON {success:true, message:"Database Up"}, not the literal PING OK shown. Update the comment so a user following the doc isn't confused by the different output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.docker.md, line 62:

<comment>The documented output for `/backend/db/__ping` is wrong. After nginx strips the `/backend/` prefix the request hits `dbReadinessCheck`, which returns JSON `{success:true, message:"Database Up"}`, not the literal `PING OK` shown. Update the comment so a user following the doc isn't confused by the different output.</comment>

<file context>
@@ -0,0 +1,96 @@
+Health checks:
+
+```bash
+curl http://localhost/backend/db/__ping   # -> PING OK
+curl http://localhost/backend/health      # -> HEALTH OK
+```
</file context>
Suggested change
curl http://localhost/backend/db/__ping # -> PING OK
curl http://localhost/backend/db/__ping # -> {"success":true,"message":"Database Up"}

Comment thread backend/package.json
"scripts": {
"dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts"
"dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts",
"build": "tsc -p tsconfig.json && cp -r src/scripts dist/scripts",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Running npm run build twice nests the copy: once dist/scripts exists, cp -r src/scripts dist/scripts creates dist/scripts/scripts/rateLimit.lua instead of refreshing the file in place. The runtime still reads dist/scripts/rateLimit.lua so nothing breaks functionally, but the build is not idempotent and accumulates stale nested copies with each rebuild. Copy the directory contents instead, e.g. mkdir -p dist/scripts && cp -r src/scripts/. dist/scripts/.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/package.json, line 8:

<comment>Running `npm run build` twice nests the copy: once `dist/scripts` exists, `cp -r src/scripts dist/scripts` creates `dist/scripts/scripts/rateLimit.lua` instead of refreshing the file in place. The runtime still reads `dist/scripts/rateLimit.lua` so nothing breaks functionally, but the build is not idempotent and accumulates stale nested copies with each rebuild. Copy the directory contents instead, e.g. `mkdir -p dist/scripts && cp -r src/scripts/. dist/scripts/`.</comment>

<file context>
@@ -4,7 +4,9 @@
   "scripts": {
-    "dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts"
+    "dev": "nodemon --watch src --ext ts,d.ts --exec ts-node --files src/index.ts",
+    "build": "tsc -p tsconfig.json && cp -r src/scripts dist/scripts",
+    "start": "node dist/index.js"
   },
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant