diff --git a/docs/plans/2026-08-19-allow-signup-env.md b/docs/plans/2026-08-19-allow-signup-env.md new file mode 100644 index 0000000..8a72793 --- /dev/null +++ b/docs/plans/2026-08-19-allow-signup-env.md @@ -0,0 +1,179 @@ +# Env-Var-Controlled Sign-Up Implementation Plan + +**Goal:** Gate sign-up and the sign-in "Sign Up" link on the `PODSTALK_ALLOW_SIGNUP` environment variable instead of the number of existing users. + +**Architecture:** Add an `AllowSignup bool` field to the `AuthHandler`. `main.go` reads `PODSTALK_ALLOW_SIGNUP` and sets it on the handler. The signup handler and the sign-in page link both consult this boolean. `store.UserCount()` stays as a general utility but is no longer used by authentication. + +**Tech Stack:** Go (net/http), `strconv.ParseBool` for env parsing. + +--- + +## File Structure + +- Modify: `main.go` — read env var, set `AllowSignup` on the `AuthHandler`. +- Modify: `handler/auth.go` — replace count-based gating with the `AllowSignup` flag. +- No changes to `store/store.go` (`UserCount` is left in place). +- No changes to templates (the `HasUser` key drives the nav link already). + +--- + +### Task 1: Add `AllowSignup` field and switch signup gating + +**Files:** +- Modify: `handler/auth.go` + +- [ ] **Step 1: Add the `AllowSignup` field to `AuthHandler` and replace the count-based redirect** + +Replace: + +```go +type AuthHandler struct { + Store *store.Store + Sessions *SessionStore + Tpl *Templates +} + +func (h *AuthHandler) ServeSignup(w http.ResponseWriter, r *http.Request) { + count, err := h.Store.UserCount() + if err == nil && count > 0 { + http.Redirect(w, r, "/signin", http.StatusSeeOther) + return + } +``` + +with: + +```go +type AuthHandler struct { + Store *store.Store + Sessions *SessionStore + Tpl *Templates + AllowSignup bool +} + +func (h *AuthHandler) ServeSignup(w http.ResponseWriter, r *http.Request) { + if !h.AllowSignup { + http.Redirect(w, r, "/signin", http.StatusSeeOther) + return + } +``` + +- [ ] **Step 2: Replace the `hasUsers()` helper with an `allowSignup()` helper** + +Replace: + +```go +func (h *AuthHandler) hasUsers() bool { + count, err := h.Store.UserCount() + return err == nil && count > 0 +} +``` + +with: + +```go +func (h *AuthHandler) allowSignup() bool { + return h.AllowSignup +} +``` + +- [ ] **Step 3: Update the three `HasUser` template renders to use `allowSignup()`** + +Replace every occurrence of `"HasUser": h.hasUsers()` with `"HasUser": h.allowSignup()`. There are three (the initial GET render and the two error paths in the POST handler). + +- [ ] **Step 4: Commit** + +```bash +git add handler/auth.go +git commit -m "Gate sign-up on AllowSignup field instead of user count" +``` + +--- + +### Task 2: Read the env var in `main.go` and wire it up + +**Files:** +- Modify: `main.go` + +- [ ] **Step 1: Add `strconv` to imports** + +In `main.go`, add `"strconv"` to the import block (after `"os"`). + +- [ ] **Step 2: Parse `PODSTALK_ALLOW_SIGNUP` and set it on the handler** + +After the existing `baseURL` block, add: + +```go + allowSignup := false + if v := os.Getenv("PODSTALK_ALLOW_SIGNUP"); v != "" { + allowSignup = envBool(v) + } +``` + +Change the `authH` literal to include the flag: + +```go + authH := &handler.AuthHandler{Store: st, Sessions: sessions, Tpl: tpl, AllowSignup: allowSignup} +``` + +At the bottom (near `envOrDefault`), add the `envBool` helper: + +```go +// envBool interprets a string as a truthy boolean. +func envBool(v string) bool { + if b, err := strconv.ParseBool(v); err == nil { + return b + } + switch strings.ToLower(strings.TrimSpace(v)) { + case "yes", "on": + return true + } + return false +} +``` + +Add `"strings"` to the imports if not already present (it is not present in `main.go`; add it). + +- [ ] **Step 3: Commit** + +```bash +git add main.go +git commit -m "Read PODSTALK_ALLOW_SIGNUP env var to control sign-up" +``` + +--- + +### Task 3: Verify + +- [ ] **Step 1: Run the build script per AGENTS.md** + +Run: `./scripts/build` +Expected: PASS (gofmt, go vet, go build, docker build all succeed). + +- [ ] **Step 2: Manual sanity check (sandbox permitting)** + +Run: `PODSTALK_ALLOW_SIGNUP=true go run . &` then `curl -s -o /dev/null -w "%{http_code}\n" -X GET localhost:8080/signup` → expect a 200 render. +Run: `PODSTALK_ALLOW_SIGNUP=false go run . &` then `curl -s -o /dev/null -w "%{http_code}\n" -X GET localhost:8080/signup` → expect 303 redirect to `/signin`. + +- [ ] **Step 3: Final commit (if the build produced no fixes) and push** + +```bash +git status +git push +``` + +--- + +## Self-Review + +**1. Spec coverage:** +- Env var `PODSTALK_ALLOW_SIGNUP` parsed in `main.go` → Task 2 Step 2. ✓ +- Default (unset) = disabled → Task 2 Step 2 (`allowSignup := false`, only overridden when set non-empty). ✓ +- Truthy values (`true`, `1`, `yes`, `on`) enabled → `ParseBool` covers `1`/`true`, the switch covers `yes`/`on`. ✓ +- Signup handler redirects when disabled → Task 1 Step 1. ✓ +- Sign-in "Sign Up" link driven by same flag → Task 1 Steps 2–3 (`HasUser`). ✓ +- `store.UserCount()` unchanged → noted in File Structure. ✓ + +**2. Placeholder scan:** No TBD/TODO; every task has concrete code. ✓ + +**3. Type consistency:** `AllowSignup` field, `allowSignup()` method, and `envBool` helper are named consistently throughout. ✓