docs(api): document the scheduled sessions API and its invitee tables - #63
Merged
Conversation
The scheduled meetings feature shipped undocumented: docs/API.md covered neither the two tables nor any of the /api/scheduled-sessions endpoints. Adds the scheduled_sessions and scheduled_session_invitees schemas, the create/list/read/update/cancel endpoints, and the invitee-facing RSVP endpoints at /api/invite/[token]. Three things worth writing down rather than rediscovering: - PATCH takes `inviteeEmails` as the complete desired guest list, not a delta, and which email goes out depends on what the diff turns up. Omitting the field leaves the list alone; sending [] clears it. - Removing an invitee does not rotate the shared join_code, so it does not actually revoke access. Recorded as a known limitation instead of leaving removal to look like a lockout. - scheduled_session_invitees must not get a permissive anon policy — the dropped USING (true) pair leaked every invite_token to the browser. Notes why the table needs no browser-facing policy at all. Also corrects an assumption while documenting: scheduled_sessions.session_id is never written by any code path, so it stays NULL even after a meeting starts. The link to the live room is the join_code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ThreatCrush Security Scan170 finding(s) HIGH/CRITICAL: 16 | MEDIUM: 48 | LOW: 106
…and 120 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
Removing an invitee deleted their row and emailed them "your join code no
longer applies" — which was not true. The code is shared by the whole guest
list and was never rotated, so a removed invitee could still open the room
with the code from their original invitation. The email said one thing and
the system did another.
Now a PATCH that drops at least one invitee rotates the meeting's join_code
and emails the replacement to everyone still invited, which makes the
existing removal copy honest.
Notes on the edges:
- Rotation is skipped once the meeting has started. create_session() copies
the code onto the live sessions row, so rewriting the scheduled row would
not evict anyone — it would only claim a lockout that did not happen.
/api/sessions/{id}/regenerate-code is the lever for a running session.
- A failed rotation write does not fail the PATCH. The removal already
succeeded, and a stale code beats a half-applied edit; retained invitees
are simply not told the code changed.
- The update email hardcoded "Your join code (unchanged)", which would have
become a lie. It now switches on a codeChanged flag.
- Adding an invitee never rotates. Only removal revokes.
Because the code is shared, there is no way to revoke one person without
reissuing to everybody. That is inherent to a single shared code.
getUniqueJoinCode moved out of the create route into lib/join-code.ts so
both paths generate codes the same way, alongside a liveSessionExistsForCode
helper for the already-started check.
Also fixes the test service mock: it returned [] for unmatched tables, and
Boolean([]) is true, so the live-session probe would have read as "already
started" and silently suppressed every rotation under test.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
CI runs format:check across **/*.md and the new scheduled-sessions section did not match prettier's table and code-fence formatting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same CI format:check gate as the previous commit; the rotation tests were added without a prettier pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The scheduled meetings feature shipped undocumented —
docs/API.mdcovered neither the two tables nor any of the/api/scheduled-sessionsendpoints. This is docs-only; no code changes.What's added
scheduled_sessionsandscheduled_session_invitees, documented as they stand after the fix(db): stop the anon key reading and rewriting meeting invitees #60 RLS hardening.?filter=upcoming|past|all), read,PATCH, and soft-cancel.GET/POST /api/invite/[token]— the no-login RSVP path.Three things worth writing down
inviteeEmailsis the complete desired guest list, not a delta. Omitting it leaves the list alone; sending[]removes everyone. Included a table of which email goes out for which kind of change, since that is not obvious from the route.Removing an invitee does not revoke access. The
join_codeis shared and is not rotated on removal, so a removed invitee can still join with the code from their original email, and there is no endpoint to re-issue a scheduled meeting's code. Recorded as a known limitation rather than leaving removal to look like a lockout — worth a follow-up if it should actually lock people out.scheduled_session_inviteesmust not get a permissiveanonpolicy. Documents why the droppedUSING (true)pair was dangerous (RLS cannot restrict which columns an UPDATE touches, so it exposed everyinvite_token) and why the table needs no browser-facing policy at all.One correction made while writing
scheduled_sessions.session_idis never written by any code path — I verified no route or component sets it. It stays NULL even after a meeting starts, so the docs say that outright instead of implying it gets populated. The link to the live room is thejoin_code, whichhandleStartpasses toPOST /api/sessions→create_session(p_join_code => ...).Every claim was checked against the routes, the Zod schemas, and both migrations rather than inferred.
🤖 Generated with Claude Code