feat: supporter-gated content with atprotofans validation (#637)
* feat: add supporter-gated content infrastructure
backend support for atprotofans content gating:
- create private R2 buckets (audio-private-dev/staging/prod)
- add R2_PRIVATE_BUCKET and PRESIGNED_URL_EXPIRY_SECONDS settings
- implement save_gated() and generate_presigned_url() in R2Storage
- add supportGate field to fm.plyr.track lexicon
- add support_gate JSONB column to tracks table
- add atprotofans validation helper (_internal/atprotofans.py)
- update audio endpoint to check supporter status for gated tracks
- 401 if not authenticated
- 402 if not a supporter
- presigned URL redirect if valid supporter
the supportGate object starts with type: "any" (any support unlocks),
with room to grow for tiers (recurring, minimum amounts, etc.)
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add support_gate parameter to upload flow
enable artists to upload supporter-gated tracks:
- add support_gate form parameter to upload endpoint
- validate support_gate JSON structure (must have type: "any")
- require atprotofans to be enabled in settings to use gating
- use save_gated() for gated tracks (private R2 bucket)
- store support_gate in Track model and ATProto record
- gated tracks use API endpoint URL instead of direct R2 URL
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(frontend): add UI for supporter-gated tracks
- TrackItem: show heart badge overlay on gated tracks with dimmed image
- Player: detect 401/402 on gated content, show toast with supporter CTA
- Upload: add "supporters only" toggle when artist has atprotofans enabled
- Types: add SupportGate interface and support_gate field to Track
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: complete supporter-gated content implementation
- fix presigned URL generation with SigV4 signature (was returning 401)
- add playback helper to check gated access BEFORE modifying queue state
(clicking locked track no longer interrupts current playback)
- add support_gate toggle to track edit UI in portal
- centralize atprotofans support URL generation in config.ts
- add 7 regression tests for gated content access control
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: migrate audio to private bucket when enabling support_gate
when enabling support_gate on an existing track, the audio file must be
migrated from the public bucket to the private bucket. otherwise the
original public r2_url remains accessible, bypassing the paywall.
- add R2Storage.migrate_to_private_bucket() - copies file then deletes original
- add migrate_track_to_private_bucket background task
- schedule migration in PATCH endpoint when support_gate is enabled on
a track that has an r2_url (indicating it's in the public bucket)
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: unify audio bucket migration to single move_audio method
- R2Storage.move_audio(file_id, extension, to_private) handles both directions
- move_track_audio background task replaces separate migrate_to_private/public
- PATCH endpoint schedules move when toggling support_gate in either direction:
- enabling gate on public track โ move to private
- disabling gate on private track โ move to public
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: sync ATProto record when toggling support_gate
- include support_gate changes in metadata_changed check
- pass support_gate to build_track_record
- use backend API URL for gated tracks (r2_url is None)
- fix upload page: link to /portal not /settings for atprotofans setup
๐ค Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
authored by
zzstoatzz.io
Claude Opus 4.5
and committed by
GitHub
99c02c16
17247f59