Skip to content

feat(notifications): handle feedback.submitted from Room TBA - #8

Open
smmariquit wants to merge 2 commits into
mainfrom
feat/feedback-submitted-event
Open

feat(notifications): handle feedback.submitted from Room TBA#8
smmariquit wants to merge 2 commits into
mainfrom
feat/feedback-submitted-event

Conversation

@smmariquit

Copy link
Copy Markdown
Contributor

Closes #7.

Room TBA's in-app feedback box (uplbtools/room-tba#888) emits feedback.submitted through the existing notification gateway. The envelope enum did not list the type, so /notifications answered 400 and the submission only ever reached the server log. Nothing was lost — the feedback table in Room TBA is the system of record — but nobody was told a student wrote in.

Payload

Matched to the emit site on origin/feat/in-app-feedback (src/lib/notifications/types.ts, src/pages/api/feedback.ts), no invented fields:

Field Type
feedbackId number
message string
contact string | null
screen string | null
appVersion string | null
wasOnline boolean | null

Routes to #development rather than #contributors: #contributors is documented as map edit proposals, and a submission can carry a contact address. Easy to move if the team wants it elsewhere.

Rendering untrusted text

/api/feedback is public and unauthenticated, so the body is attacker-controlled. Room TBA validates and rate limits; this PR only renders safely.

  • Parsed, not cast. The handler runs feedbackSubmittedPayloadSchema.parse, unlike the proposal handlers which cast.
  • Markdown escaped, including masked links, so a submission cannot render [totally the admin panel](https://evil.example) into the channel.
  • Mentions neutralised with a zero-width space after every @@everyone, @here, <@&role> all stop resolving while the text stays readable. Belt and braces: allowedMentions: { parse: [] } now applies to every Discord send in this bot, not just this path.
  • Truncated with an ellipsis so an over-long body loses its tail instead of Discord rejecting the whole message.
  • Raw text is cut before escaping. escapeMarkdown is quadratic: 22k chars measured at ~7s of blocked event loop (20s for plain text). Bounding the input at Room TBA's own 2000-char cap keeps it at ~55ms. There is a test asserting this stays under 1s.

Short context values (screen, app version) take a lighter path: whitespace is collapsed so no line start remains for heading/list markdown, which also avoids rendering an app version as 2026\.08.01.

Verification

bun run check — biome, tsc --noEmit, bun test, tsc build. 65 pass, 0 fail. The one biome warning (github-repo.ts optional chain) is pre-existing and untouched.

New tests cover: schema accepts the real payload and both null-context and malformed variants, mention text is neutralised, over-long bodies truncate rather than throw, the quadratic path stays fast, and POST /notifications now accepts a feedback.submitted envelope end to end.

Not merged, not deployed — Heroku still needs git push heroku main after merge.

Room TBA emits `feedback.submitted` from its in-app feedback box
(uplbtools/room-tba#888). The envelope enum did not list the type, so the
gateway answered 400 and the submission only reached the server log.

- Add `feedbackSubmittedPayloadSchema` matching the emit site exactly:
  feedbackId, message, contact, screen, appVersion, wasOnline.
- Post to #development: message body, contact when given, and the context
  Room TBA attaches. The handler parses the payload rather than casting it,
  since `/api/feedback` is public and unauthenticated.
- Render untrusted text through `safeUserText`: markdown and masked links
  escaped, every `@` broken with a zero-width space so no submission can ping
  @everyone/@here/a role, and truncation with an ellipsis instead of letting
  Discord reject the message. Raw text is cut before escaping because
  `escapeMarkdown` is quadratic (22k chars is ~7s of blocked event loop).
- Deny mentions on every Discord send, not just this path.

Closes #7
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.

Handle feedback.submitted events from Room TBA

1 participant