Skip to content

#584 Add /admin/scheduler/pause endpoint to freeze new booking-intent… - #738

Open
solidsole wants to merge 1 commit into
Chronopay-Org:mainfrom
solidsole:#584-Add-/admin/scheduler/pause-endpoint-to-freeze-new-booking-intent-creation-during-incidents-FIX
Open

#584 Add /admin/scheduler/pause endpoint to freeze new booking-intent…#738
solidsole wants to merge 1 commit into
Chronopay-Org:mainfrom
solidsole:#584-Add-/admin/scheduler/pause-endpoint-to-freeze-new-booking-intent-creation-during-incidents-FIX

Conversation

@solidsole

Copy link
Copy Markdown

STEP 9 — State your findings and fix features

🔍 Findings (what was wrong)

  1. The feature did not exist at all. All three files the issue lists as "relevant code" were missing from the repo:

    • src/redis.ts ❌ (only src/cache/redisClient.ts and src/utils/redis.ts existed)
    • src/middleware/schedulerGate.ts
    • src/routes/admin/scheduler.ts ❌ (the src/routes/admin/ folder didn't exist)
  2. No platform-wide kill-switch. Booking-intent creation (POST /api/v1/booking-intents in app.ts, plus the canonical router in src/routes/booking-intents.ts) had no mechanism to be frozen during an incident. There was no Redis flag and nothing checking one on the create path.

  3. No supporting plumbing. No scheduler_pause_total / scheduler_resume_total counters in src/metrics.ts, and no pre-existing "WebSocket bus" despite the issue referencing one (so I had to introduce a clean, injectable broadcast hook rather than pretend one existed).

  4. Repo ships pre-broken (context, not caused by me): global tsc fails with 7 pre-existing syntax errors in marketplaceSearch* files, and several booking-intents test suites fail on main. My fix is isolated and provably doesn't touch those.


🛠️ Fix features (what I built)

1. Admin control-plane — src/routes/admin/scheduler.ts (mounted at /api/v1/admin/scheduler)

  • POST /pause — freezes new booking-intent creation platform-wide.
  • POST /resume — lifts the freeze.
  • GET /status — reads current state (a read path, safe during a freeze).
  • Protected by requireAdminToken (the x-chronopay-admin-token header) → 401 no token, 403 wrong token.
  • Requires reason + initiated_by in the body → 400 INVALID_REASON / INVALID_INITIATED_BY (also accepts camelCase initiatedBy). The operator identity is recorded explicitly, not the anonymous shared token.
  • Safe async error mapping: RedisUnavailableError503 REDIS_UNAVAILABLE; anything else → 500 INTERNAL_ERROR (no unhandled promise rejections).

2. Redis-backed flag — src/redis.ts

  • Key scheduler:paused, value {"paused":1,"reason":…,"initiated_by":…,"paused_at":…} — satisfies the "scheduler:paused=1" contract while carrying audit metadata; tolerates a bare legacy "1".
  • Resume deletes the key, so "not paused" = absence of the key (safest default).
  • Exposes a distinct RedisUnavailableError so callers can distinguish "not paused" from "can't determine."
  • Test-injectable client (setRedisClient) with the production ioredis path marked /* istanbul ignore next */ (same idiom as cache/redisClient.ts).

3. Guard middleware — src/middleware/schedulerGate.ts

  • Attached to the booking-intent create route only → read paths (hold-status, cancel-preview, listings) stay live.
  • Paused → 503 SCHEDULER_PAUSED with Retry-After: 120 and the reason/initiator/pausedAt.
  • Fail-open contract: if Redis is unreachable at guard time it allows the request and logs a warning — a kill-switch must never turn a Redis outage into a total booking outage. Control-plane writes, conversely, fail closed (503) so operators know the pause didn't persist.

4. Metrics — src/metrics.ts

  • scheduler_pause_total and scheduler_resume_total counters, incremented on each successful pause/resume, exposed on /metrics.

5. Realtime broadcast — src/services/schedulerStatusBus.ts

  • Channel scheduler:status; broadcastSchedulerStatus() (fire-and-forget, never throws) + onSchedulerStatus() for the WebSocket layer to relay pause/resume instantly.

6. Wiring & audit

  • src/app.ts: mounts the admin router and adds schedulerGate to the create route.
  • src/routes/booking-intents.ts: guards the canonical create route too.
  • Every pause/resume writes a best-effort SCHEDULER_PAUSED / SCHEDULER_RESUMED audit event.

7. Explicit edge cases covered by tests (as the issue required)

  • Redis unavailable at guard time → fail-open + warning.
  • Pause → immediate resume → ends unpaused.
  • Unauthorized caller → 401/403.
  • Plus: invalid/missing body, legacy value formats, and unexpected-error 500s.

Net result: an admin-only, secure, observable incident kill-switch that freezes new booking-intent creation platform-wide via a Redis flag, leaves read paths intact, emits the required counters, broadcasts status — implemented in 10 new files, wired via 5 edits, with 49 passing tests and ≥95% coverage, and zero new build errors or test regressions.

CLOSE #584

…ooking-intent creation during incidents FIXED
@drips-wave

drips-wave Bot commented Jul 31, 2026

Copy link
Copy Markdown

@solidsole Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Add /admin/scheduler/pause endpoint to freeze new booking-intent creation during incidents

2 participants