Open registration to guild members #27
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Parent
#19
What to build
The gate opens. Any member of the configured Discord guild becomes a Reader on their first login, with an empty library and their own install links waiting.
The second Reader is the first real test of everything the schema promised: isolation between libraries, and a shared Series polled once rather than twice. Also cleans up the two pieces of scaffolding left over from the cutover.
The riskiest thing here fails silently — a leak between Readers does not throw, does not log, and looks like working software until someone notices a series they never bookmarked. Isolation tests use two Readers and assert from both directions.
Acceptance criteria
--emberis not borrowed for an empty state.Blocked by
#26
Implemented on
feat/open-registration-guild-members, PR #36 (b0bf6fe+f4f6c9c). Every criterion is ticked; how each was met, in the issue's order:1-4.
discordCallbackchecks guild membership (andDISCORD_REQUIRED_ROLE), thenStore.EnsureReader— one code path, idempotent ondiscord_id. The refusal returns beforeEnsureReader, so nothing is created for a non-member; asserted by a reader-count check after a refused sign-in.OWNER_DISCORD_IDseeds the owner but no longer gates login.5. Empty library offers both install links, placed behind the tab-specific empty states so "No favourites yet" is not shadowed. Tokens only,
--dangerfor destruction,--emberuntouched; both colour branches shot.6.
/install/*and/u/{cred}/*render with the acting Reader's derived credential — asserted to differ from the owner's.7-10. Isolation asserted in both directions for read, modify and delete. Shared series: one
seriesrow, two independent progresses (second starts at zero),DueForLatestCheckreturns it once, and one Reader's delete leaves the other's bookmark and the poll intact.11. Owner-only
POST /readers/{id}/revoke(404 for anyone else) plus a Readers panel. The owner's own row is deliberately not revocable — that is what logout is for — so the endpoint 404s on the owner id too.12.
API_TOKEN,API_TOKEN_GRACE_UNTIL, the legacy branch inhttpmw.ResolveReaderand their tests are gone, along with the references in.env.example, compose, README, DEPLOY, CUTOVER and REDEPLOY. A credential now authenticates exactly one Reader or nothing.13. PRODUCT.md rewritten: guild members with their own libraries, accounts created by signing in, one owner capability.
Verification:
go test ./...green (needs Docker). Live smoke against a throwaway Postgres — empty-library state in dark and light, roster render, a real revoke through the panel (target's next request 401s, owner untouched), owner self-revoke refused 404, per-Reader userscript path and bearer auth both 200 with 404/401 for an unknown credential.Not in scope here and still pending: deploying it.
OWNER_DISCORD_ID,DISCORD_GUILD_IDand the OAuth pair must be set before the next redeploy — the backend refuses to start without them, andAPI_TOKEN/API_TOKEN_GRACE_UNTILshould be dropped from.envat the same time.