Open registration to guild members (#27) #36
Reference in New Issue
Block a user
Delete Branch "feat/open-registration-guild-members"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #27.
Guild membership is now the whole gate.
discordCallbackchecks membership(and
DISCORD_REQUIRED_ROLEwhen set), thenStore.EnsureReadercreates theReader on first sight and returns the same row on every later login. The
refusal returns before
EnsureReader, so a turned-away sign-in leaves no rowbehind.
OWNER_DISCORD_IDstill seeds the owner, but only as theadministrator — it no longer gates login.
The cutover grace path goes with it:
API_TOKEN,API_TOKEN_GRACE_UNTILandthe legacy branch in
httpmw.ResolveReaderare deleted, so a credentialauthenticates exactly one Reader or nothing.
userscript.Handlerdrops itsre-derivation too — the resolved path segment is already the credential.
New surfaces: an empty library offers both install links (behind the
tab-specific empty states, so "No favourites yet" still wins), and the owner
alone gets a Readers panel with
POST /readers/{id}/revoke. The owner's ownrow is not revocable — 404, not a self-logout.
Isolation is asserted from both directions for read, modify and delete, and
the shared-series invariant is pinned: two Readers on one series produce one
series row, two independent progresses, one poll per due cycle, and one
Reader's delete leaves the other's bookmark and the poll intact.
Verified:
go test ./...green; live smoke against a throwaway Postgres —empty-library state in both colour branches, roster rendering, a real revoke
through the panel (target 401s next request, owner untouched), owner
self-revoke refused 404, per-Reader
/u/<cred>and bearer auth both 200 with404 for an unknown credential.
Guild membership is now the whole gate: discordCallback checks membership (and DISCORD_REQUIRED_ROLE when set), then Store.EnsureReader creates the Reader on first sight and returns the same row on every later login. The refusal returns before EnsureReader, so nothing is created as a side effect of being turned away. OWNER_DISCORD_ID keeps seeding the owner, but only as the administrator — it no longer gates sign-in. The cutover grace path is gone with it: API_TOKEN, API_TOKEN_GRACE_UNTIL and the legacy branch in httpmw.ResolveReader are deleted, so a credential authenticates exactly one Reader or nothing. That also lets userscript.Handler drop the re-derivation — the resolved path segment is already the credential to substitute. New surfaces: an empty library offers both install links instead of describing a filter (listView.Fresh, which also hides the action key it has nothing to name), and the owner alone gets a Readers panel with POST /readers/{id}/revoke (404 for anyone else) to sign a Reader out everywhere. Isolation is asserted from both directions rather than by counting one Reader's rows, and the shared-series invariant is pinned: two Readers on one series produce one series row, two independent progresses, one poll per due cycle, and one Reader's delete leaves the other's bookmark and the poll intact.- The empty state is about an empty library, not a brand-new Reader: listView.Fresh becomes EmptyLibrary and moves behind the tab-specific branches, so "No favourites yet" is no longer shadowed for a Reader whose library happens to be empty. The action key stays put — hiding it was never asked for. - The owner is not a revocable Reader: their row offers no button and POST /readers/{owner}/revoke is a 404, so the one row where the control would sign out the tapping browser cannot be reached by a hand-rolled POST either. - Modify isolation is asserted in both directions, and the owner's own sign-in through the OAuth callback is pinned to the seeded row. - CUTOVER.md and REDEPLOY.md still grepped API_TOKEN out of .env for their smoke tests, which the last commit deleted; both now take the acting Reader's derived credential. - Roster type follows the machine-fact spec (500 10-11px mono, tracked), and PRODUCT.md names the Readers panel instead of claiming there is no owner surface at all.