|
@@ -16,10 +16,10 @@ Docker: `docker compose up --build`. Supabase Studio at `localhost:3000` (dev on
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
TMDB Proxy: /api/tmdb/search, /api/tmdb/* (server-side only — TMDB_API_KEY never NEXT_PUBLIC_)
|
|
TMDB Proxy: /api/tmdb/search, /api/tmdb/* (server-side only — TMDB_API_KEY never NEXT_PUBLIC_)
|
|
|
-Auth: Supabase GoTrue (signInAnonymously) — no custom auth routes
|
|
|
|
|
|
|
+Auth: Supabase GoTrue (signInAnonymously). /api/auth/recovery/{generate,claim}, /api/auth/signout (POST), /logout (GET/POST → redirect /)
|
|
|
Groups: /api/groups, /api/groups/join (rate-limited, server-side via service role key)
|
|
Groups: /api/groups, /api/groups/join (rate-limited, server-side via service role key)
|
|
|
Health: /api/health
|
|
Health: /api/health
|
|
|
-Admin: /admin (TOTP login, iron-session v8)
|
|
|
|
|
|
|
+Admin: /admin (TOTP login, iron-session v8) — /api/admin/logout is SEPARATE from user signout
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
All TMDB calls set `include_adult=false` and server-side filter by `adult` field.
|
|
All TMDB calls set `include_adult=false` and server-side filter by `adult` field.
|
|
@@ -40,7 +40,7 @@ Tables: `users`, `groups`, `group_members`, `movies`, `landing_reel_posters`
|
|
|
|
|
|
|
|
## Frontend (Next.js App Router)
|
|
## Frontend (Next.js App Router)
|
|
|
|
|
|
|
|
-- `@supabase/ssr` — `createBrowserClient` (browser) / `createServerClient` with `SUPABASE_INTERNAL_URL` (server)
|
|
|
|
|
|
|
+- `@supabase/ssr` — `createBrowserClient` (browser) / `createServerClient` with `SUPABASE_INTERNAL_URL` (server). All server-side `createServerClient` MUST set `cookieOptions: { name: getSupabaseCookieName() }` (`src/lib/supabase/cookie-name.ts`) so the cookie name is derived from `NEXT_PUBLIC_SUPABASE_URL`, not the internal URL — otherwise hostname-based project-ref derivation diverges from the browser and `getUser`/`signOut` silently no-op.
|
|
|
- TanStack Query with explicit `staleTime`. Offline: `persistQueryClient` + IndexedDB (3 packages: `react-query-persist-client`, `query-async-storage-persister`, `idb-keyval`).
|
|
- TanStack Query with explicit `staleTime`. Offline: `persistQueryClient` + IndexedDB (3 packages: `react-query-persist-client`, `query-async-storage-persister`, `idb-keyval`).
|
|
|
- TMDB posters: native sized URLs from TMDB CDN (w342 grid, w185 reel, w500 panel). No `next/image` for posters. `loading="lazy"` + meaningful `alt` text on all images. Reel posters `aria-hidden`.
|
|
- TMDB posters: native sized URLs from TMDB CDN (w342 grid, w185 reel, w500 panel). No `next/image` for posters. `loading="lazy"` + meaningful `alt` text on all images. Reel posters `aria-hidden`.
|
|
|
- Real-time: subscribe on mount, unsubscribe on unmount (one list at a time). Home page counts via polling.
|
|
- Real-time: subscribe on mount, unsubscribe on unmount (one list at a time). Home page counts via polling.
|
|
@@ -54,6 +54,7 @@ Tables: `users`, `groups`, `group_members`, `movies`, `landing_reel_posters`
|
|
|
## Auth
|
|
## Auth
|
|
|
|
|
|
|
|
- Users: Supabase Anonymous Sign-In → JWT via GoTrue → cookie-based sessions via `@supabase/ssr`
|
|
- Users: Supabase Anonymous Sign-In → JWT via GoTrue → cookie-based sessions via `@supabase/ssr`
|
|
|
|
|
+- Signout: `<SignOutButton />` in `(app)` header → `POST /api/auth/signout` → `queryClient.clear()` → hard nav to `/`. Linkable variant: `GET /logout` (redirects). No confirm dialog, even for users without a saved recovery code.
|
|
|
- Recovery: 24-char alphanumeric (128-bit entropy), Argon2id hashed, single-use, claim rate-limited (5/15min per IP)
|
|
- Recovery: 24-char alphanumeric (128-bit entropy), Argon2id hashed, single-use, claim rate-limited (5/15min per IP)
|
|
|
- Admin: username + TOTP (otplib), iron-session v8 (HttpOnly, Secure, SameSite=Strict, 8h expiry)
|
|
- Admin: username + TOTP (otplib), iron-session v8 (HttpOnly, Secure, SameSite=Strict, 8h expiry)
|
|
|
- GoTrue config: `GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=true`, all other auth methods disabled
|
|
- GoTrue config: `GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=true`, all other auth methods disabled
|