Problem
invites keeps every row forever. An accepted invite stays, with its token and
the invited email address, and so does one that nobody ever opened.
Nothing reads that history. Every reader filters it back out:
src/worker/routes/orgs.ts:307 filters !r.acceptedBy && r.expiresAt > ts for the list.
orgs.ts:324 filters accepted_by is null when checking for a duplicate invite.
orgs.ts:583 and orgs.ts:994 both re-check invite.acceptedBy || invite.expiresAt < Date.now().
So the column exists to be excluded four times, and the rows exist to be
skipped.
Why it matters
A spent invite token and the email it was sent to sit in D1 with no expiry.
That is retention nobody asked for, on the one table that holds an address
belonging to someone who may never have signed up.
Plan
- On accept (
inviteRoutes.post("/:token/accept"), orgs.ts:1002), insert the
membership and delete the invite in one D1 batch, instead of setting
accepted_by.
- Delete expired invites in the existing scheduled cleanup.
- Drop
invites.accepted_by in a migration, and the four filters with it.
- If invite history turns out to be wanted later, it belongs in
admin_actions
or an org activity log, not in a live-lookup table.
Acceptance criteria
Problem
inviteskeeps every row forever. An accepted invite stays, with its token andthe invited email address, and so does one that nobody ever opened.
Nothing reads that history. Every reader filters it back out:
src/worker/routes/orgs.ts:307filters!r.acceptedBy && r.expiresAt > tsfor the list.orgs.ts:324filtersaccepted_by is nullwhen checking for a duplicate invite.orgs.ts:583andorgs.ts:994both re-checkinvite.acceptedBy || invite.expiresAt < Date.now().So the column exists to be excluded four times, and the rows exist to be
skipped.
Why it matters
A spent invite token and the email it was sent to sit in D1 with no expiry.
That is retention nobody asked for, on the one table that holds an address
belonging to someone who may never have signed up.
Plan
inviteRoutes.post("/:token/accept"),orgs.ts:1002), insert themembership and delete the invite in one D1 batch, instead of setting
accepted_by.invites.accepted_byin a migration, and the four filters with it.admin_actionsor an org activity log, not in a live-lookup table.
Acceptance criteria
invitesrow.token, so nothing learns which tokens existed.
on
accepted_by.tests/e2e/.