Skip to content

feat(api): enforce Full Moon premium server-side (ADR 0018)#115

Merged
miquelmatoses merged 2 commits into
mainfrom
feat/fullmoon-server-side-paywall
Jul 14, 2026
Merged

feat(api): enforce Full Moon premium server-side (ADR 0018)#115
miquelmatoses merged 2 commits into
mainfrom
feat/fullmoon-server-side-paywall

Conversation

@miquelmatoses

Copy link
Copy Markdown
Collaborator

Implements ADR 0018 (Option A): enforce premium on the server-dependent Full Moon value surfaces. Client-side scoring is untouched (privacy-by-design). Check authorizes premium OR is_beta so promo accounts keep access.

Reusable dependency (api/deps.py, mirrors require_admin):

async def require_premium(request, user=Depends(get_current_user)):
    ...
    if not row or not (row["premium"] or row["is_beta"]):
        raise HTTPException(status_code=403, detail="Forbidden")
    return user

Gated: POST /witness/sessions, GET /witness/my-sessions, GET /groups/{id}/report-data (on top of membership).

POST /results — inline, fullMoon branch only (free/anon stay open):

if body.instrument == "fullMoon":
    prow = await conn.fetchrow("SELECT premium, is_beta FROM profiles WHERE id = $1", user_id) if user_id else None
    if not prow or not (prow["premium"] or prow["is_beta"]):
        raise HTTPException(status_code=403, detail="Forbidden")

Left open by design: public witness submit, /witness/my-contributions, /me/results, POST /groups.

No migration (handler logic only). Tests: require_premium unit (403/401/pass), route wiring (gated vs non-targets), /results fullMoon (anon/non-premium 403; premium/beta persist; anon newMoon 200). Backend 185 passed, frontend 248 passed, build clean.

🤖 Generated with Claude Code

miquelmatoses and others added 2 commits July 14, 2026 16:53
The paywall was UI-only; POST /results accepted fullMoon from anyone. Gate the
server-dependent value surfaces on premium OR is_beta (promo accounts keep
access), leaving client-side scoring and free/public flows untouched.

- deps.require_premium: reusable dependency mirroring require_admin (401 no sub,
  403 not entitled), authorizes premium OR is_beta.
- Gated: POST /witness/sessions, GET /witness/my-sessions,
  GET /groups/{id}/report-data (on top of membership).
- POST /results: inline gate INSIDE the fullMoon branch only — free instruments
  and anonymous posts stay open; anon/non-entitled fullMoon persist -> 403.
- Left open by design: public witness submit, my-contributions, /me/results,
  POST /groups.

No migration (handler logic only). ADR 0018 Accepted; auth.md updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lated)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@miquelmatoses miquelmatoses merged commit 7432cda into main Jul 14, 2026
8 checks passed
@miquelmatoses miquelmatoses deleted the feat/fullmoon-server-side-paywall branch July 14, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant