Skip to content

Add POST and GET /internal/listener-requests (BS#2662) - #3729

Open
jakebromberg wants to merge 1 commit into
mainfrom
orchestrator/bs-1-create-and-list-listener-requests-post-and-ge
Open

jakebromberg wants to merge 1 commit into
mainfrom
orchestrator/bs-1-create-and-list-listener-requests-post-and-ge

Conversation

@jakebromberg

Copy link
Copy Markdown
Member

Closes #2662

Adds POST and GET /internal/listener-requests as a sibling router under /internal, with the X-Internal-Key / ROM_INTERNAL_KEY gate, per-IP limiter, UUID regex and length caps copied from internal-bans.route.ts. Adds LISTENER_REPLY_WINDOW_MINUTES (default 30) and its docs/env-vars.md entry. The route ships dark: nothing calls it until request-o-matic's client lands behind its own flag, so no merge here needs an ops step.

  • POST creates the row and returns { id, expires_at, replyable } with 201. replyable is anonymous_user_id !== null, this service's verdict. SQLSTATE 23503 on anonymous_user_id maps to 400.
  • GET returns { items }, newest first, hard-capped at 20 with no limit parameter. Both audiences scope to slack_channel_id, require anonymous_user_id IS NOT NULL, and exclude requests with an active reply (retracted_at IS NULL). on_air filters on expires_at > now() and status = 'posted'; moderator on created_at > now() - interval '24 hours' and status in ('posted','hidden').
  • LISTENER_REPLY_WINDOW_MINUTES is read per request through requirePositiveInt, so a bad value fails that POST with a 500 and no write rather than stopping the API at boot. It is not in set-ec2-env-var.yml, dev_env/docker-compose.yml or test.yml.

Acceptance criteria and how each is tested

Unit tests in tests/unit/routes/internal-listener-requests.route.test.ts (29 cases), written first and seen failing with module-not-found before the route existed:

  • POST 401 with no key and with a wrong key; 400 for bad fingerprint, missing/non-string/empty/over-64 slack_channel_id, non-string/empty/over-255 anonymous_user_id (one parameterized table, each asserting the error names the field and no insert happened).
  • POST 201 with replyable false (omitted and null) and true; response keys are exactly expires_at, id, replyable.
  • POST expires_at is stamped from LISTENER_REPLY_WINDOW_MINUTES (default 30, and an override of 5); an invalid value gives 500 with no insert.
  • POST 400 on 23503, both drizzle-wrapped and bare; 500 on other DB errors.
  • GET 401; 400 for absent/repeated slack_channel_id, absent replyable_for, and replyable_for=nobody; 200 { items } for both audiences; limit(20) even when ?limit=500 is passed; 500 on DB error.
  • The grep criteria were run: two internalListenerRequestsRoute lines in app.ts, no created_at-derived window, zero hits for the env var in set-ec2-env-var.yml, zero hits for the path in app.yaml.

Mutation check: reverting each of the cap, the replyable expression, the 23503 arm, the default window, the env read, the replyable_for validation and the key gate made at least one test fail.

Not pinned by a unit test: the GET WHERE predicates themselves (channel scope, anonymous_user_id IS NOT NULL, no active reply, the per-audience window and status filters). The unit suite's @wxyc/database double stores columns as strings, so an assertion on them would compare the mock to itself. I rendered the real query against the real schema with drizzle's toSQL() and checked the SQL by eye, but it has not run against a live Postgres, and I did not add an integration spec.

Local checks: npm run test:unit (760 suites, all passing), typecheck, format:check, lint:migrations, check:db-mock-sync, and eslint on the touched files (0 errors). The full npm run lint was not run. No migration is added.

Mirrors: none found. The route is deliberately not in wxyc-shared/api.yaml or apps/backend/app.yaml, matching the two existing internal routers.

…S#2662)

request-o-matic creates the listener_requests row before posting to Slack and reads replyable off the response (anonymous_user_id IS NOT NULL, this service's verdict). GET returns the pre-filtered, newest-first list behind /reply, hard-capped at 20, with the on-air window read from the stored expires_at. Adds LISTENER_REPLY_WINDOW_MINUTES (default 30).
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.

BS-1 — Create and list listener requests: POST and GET /internal/listener-requests

1 participant