Profile photos (Supabase Storage)¶
Monaco stores profile photos in the hosted monaco-dev Supabase project (komoccjaeypsrgocrnht). Local Postgres stays on Docker Compose; only object storage uses Supabase.
Environment¶
Backend reads (from .env.local via scripts/with-dotenv-local.sh):
SUPABASE_URL— e.g.https://komoccjaeypsrgocrnht.supabase.coSUPABASE_SERVICE_ROLE_KEY— service role key from Project Settings → API
Do not commit secrets. Do not point DATABASE_URL at hosted Supabase.
Storage bucket¶
Bucket name: avatars (public read for display URLs).
Create once on the dev project (dashboard or API):
- Storage → New bucket →
avatars - Enable Public bucket so clients can load
{SUPABASE_URL}/storage/v1/object/public/avatars/... - Uploads go through the Go API with the service role key (mobile never holds storage secrets)
Object keys: {user_id}/{random}.{jpg|png|webp}.
Limits¶
- Multipart field
photo, at most 2MB, jpeg/png/webp (checked by magic bytes, not the declared type). The app downscales and re-encodes to JPEG before upload. - Per-user rate limit: 3 uploads at once, then one per 20 s. Past that the API returns 429 with
Retry-Afterand nothing is written to storage. - Each upload writes a new object; older objects for the user are not deleted yet.
Verify¶
GET /v1/me,PATCH /v1/me, andPOST /v1/auth/sessionincludeprofilePhotoUrl(null when unset) andcreatedAtPOST /v1/me/profile-photomultipart fieldphoto, max ~2MB, jpeg/png/webp- Profile tab (or Settings → Profile photo) → tap the avatar, pick a photo, toast "Profile photo updated."
- Home leaderboard and cabal member boards show the photo after the app refreshes