What is Recipe Room?
Recipe Room is a social platform for sharing Fujifilm camera film simulation recipes. Users create, share, and discover custom film simulation settings for Fujifilm X-series cameras, organize them into collections, and engage with the community through posts, follows, and favorites. The platform is currently mobile-first — the iOS app is the primary product, connecting directly to Supabase for all data operations. A full web client is planned for Phase 2.Tech Stack
| Layer | Technology | Status |
|---|---|---|
| Mobile App | Expo SDK 54, expo-router, NativeWind | Active — primary product |
| Web Interim | Next.js 14 (App Router), Tailwind CSS | Active — supports mobile |
| Web App | Next.js 14 (App Router), React 18, shadcn/ui | Phase 2 — scaffolded, not yet built |
| Database | PostgreSQL via Supabase | Active |
| Auth | Supabase Auth (email/password) | Active |
| Storage | Supabase Storage (4 public buckets) | Active |
| Security | Supabase Row Level Security (RLS) | Active — primary security boundary |
| Shared Package | TypeScript types, Zod schemas, constants | Active |
| Loops.so (transactional + audience) | Active |
Monorepo Structure
@recipe-room/shared package provides types, Zod validation schemas, constants, and API utilities shared between all apps.
Apps — What They Are and Where They Sit
apps/mobile — iOS App (Active)
apps/mobile — iOS App (Active)
The primary product. A React Native app built with Expo SDK 54 and expo-router, deployed to the App Store via EAS Build.Connects directly to Supabase for everything — reads, writes, auth, storage. No web API layer in between. RLS policies are the security boundary.This is where all current development happens.
apps/web-interim — Interim Web App (Active)
apps/web-interim — Interim Web App (Active)
A standalone Next.js 14 app that supports the mobile app while we wait to build the full web client in Phase 2. It handles:
- Auth callbacks (email confirmation, password reset, email change)
- Deep link verification (AASA for iOS universal links, Android Asset Links)
- Content redirect pages (recipe/post/collection URLs → deep link into app or “app only” fallback)
- Public landing page with waitlist signup
@recipe-room/shared. It’s a separate, lightweight app with its own package.json and node_modules.apps/web — Full Web Client (Phase 2 — Not Active)
apps/web — Full Web Client (Phase 2 — Not Active)
The scaffolded Next.js 14 web application with App Router, shadcn/ui, and full API routes. This is where the web client will live when we build it out in Phase 2.It contains API route scaffolding (
/api/auth, /api/recipes, /api/posts, etc.), middleware for auth-based route protection, and the full component library. But it is not the current focus — the mobile app and web interim are what’s live today.When Phase 2 begins, this app will use the same Supabase backend and @recipe-room/shared package, but route mutations through server-side API routes (with the service role key) rather than direct Supabase calls.Supabase Infrastructure
The entire backend runs on Supabase. Here’s what’s set up and how it’s secured.Storage Buckets
Four public buckets for user-uploaded images. All buckets enforce ownership via storage policies — users can only upload, update, and delete files in their own folder (auth.uid() matches the first folder segment).
| Bucket | Public | Purpose | Upload Policy |
|---|---|---|---|
avatars | Yes | Profile photos | Owner only (auth.uid() = folder[1]) |
cover-photos | Yes | Profile cover images | Owner only |
recipe-images | Yes | Recipe photos (up to 10 per recipe) | Any authenticated user* |
post-images | Yes | Post photos (multi-image) | Owner only |
recipe-images has a slightly broader INSERT policy — any authenticated user can upload (not just folder-owner). This is because the RPC function create_recipe handles the folder path assignment. All other operations (UPDATE, DELETE) still require folder ownership.Row Level Security (RLS)
Every table has RLS enabled. This is the primary security boundary for the mobile app — since the mobile client talks to Supabase directly with the anon key + user JWT, RLS is what prevents unauthorized access. The general pattern:| Pattern | Tables | SELECT | INSERT | UPDATE | DELETE |
|---|---|---|---|---|---|
| Public read, owner write | recipes, posts, collections | is_deleted = false OR owner | auth.uid() = user_id | auth.uid() = user_id | auth.uid() = user_id |
| Public read, owner write | follows, recipe_favorites, post_favorites, recipe_reposts, post_reposts | true | auth.uid() = user_id | — | auth.uid() = user_id |
| Public read, parent-owner write | recipe_images, recipe_tags, post_images, post_tags, recipe_tagged_users, post_tagged_users, lightroom_settings | true (or parent not deleted) | Owner of parent record | Owner of parent | Owner of parent |
| Public read, parent-owner write | recipe_comments, post_comments | Parent is_deleted = false | auth.uid() = user_id | — | auth.uid() = user_id |
| Public read, parent-owner write | recipe_comment_mentions, post_comment_mentions | true | Comment author | — | Comment author |
| Private to owner | saved_items | user_id = auth.uid() | user_id = auth.uid() | — | user_id = auth.uid() |
| Private to owner | notifications | recipient_id = auth.uid() | actor_id = auth.uid() | recipient_id = auth.uid() | recipient_id = auth.uid() |
| Owner-scoped via parent | collection_saved_items | true | Owner of collection AND saved item | — | Owner of collection |
| Public read, self only | profiles | true | auth.uid() = id | auth.uid() = id | — |
Storage Policies (Detail)
All four buckets are publicly readable. Write operations are restricted:| Bucket | SELECT | INSERT | UPDATE | DELETE |
|---|---|---|---|---|
avatars | Public | auth.uid() = folder[1] | auth.uid() = folder[1] | auth.uid() = folder[1] |
cover-photos | Public | auth.uid() = folder[1] | auth.uid() = folder[1] | auth.uid() = folder[1] |
recipe-images | Public | auth.role() = 'authenticated' | auth.uid() = folder[1] | auth.uid() = folder[1] |
post-images | Public | auth.uid() = folder[1] | auth.uid() = folder[1] | auth.uid() = folder[1] |
{user_id}/{filename} — the first path segment is always the user’s UUID, which is what the policies check against auth.uid().
Core Concepts
Film Simulation Recipes
Complete Fujifilm X-series camera settings including film simulation, dynamic range, grain, white balance, tone curve, color, sharpness, noise reduction, and clarity.
Posts
Photo posts with multi-image support, tags, camera metadata, and optional recipe linking.
Collections
User-created groupings of saved recipes and posts, with public/private visibility.
Social
Follow creators, favorite content, comment with @mentions, and receive notifications.