Skip to main content

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

LayerTechnologyStatus
Mobile AppExpo SDK 54, expo-router, NativeWindActive — primary product
Web InterimNext.js 14 (App Router), Tailwind CSSActive — supports mobile
Web AppNext.js 14 (App Router), React 18, shadcn/uiPhase 2 — scaffolded, not yet built
DatabasePostgreSQL via SupabaseActive
AuthSupabase Auth (email/password)Active
StorageSupabase Storage (4 public buckets)Active
SecuritySupabase Row Level Security (RLS)Active — primary security boundary
Shared PackageTypeScript types, Zod schemas, constantsActive
EmailLoops.so (transactional + audience)Active

Monorepo Structure

recipe-room-monorepo/
├── apps/
│   ├── mobile/        # React Native (Expo) iOS app
│   ├── web-interim/   # Interim web app — auth callbacks, deep links, landing page
│   └── web/           # Full Next.js web client (Phase 2 — scaffolded, not active)
├── packages/
│   └── shared/        # Shared types, Zod validation, constants, API utilities
├── supabase/          # Database migrations
└── docs/              # This documentation (Mintlify)
The monorepo is managed with pnpm workspaces. The @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

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.
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
This is NOT the full web client. It has no content CRUD, no API routes for data mutations, and no dependency on @recipe-room/shared. It’s a separate, lightweight app with its own package.json and node_modules.
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).
BucketPublicPurposeUpload Policy
avatarsYesProfile photosOwner only (auth.uid() = folder[1])
cover-photosYesProfile cover imagesOwner only
recipe-imagesYesRecipe photos (up to 10 per recipe)Any authenticated user*
post-imagesYesPost 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:
PatternTablesSELECTINSERTUPDATEDELETE
Public read, owner writerecipes, posts, collectionsis_deleted = false OR ownerauth.uid() = user_idauth.uid() = user_idauth.uid() = user_id
Public read, owner writefollows, recipe_favorites, post_favorites, recipe_reposts, post_repoststrueauth.uid() = user_idauth.uid() = user_id
Public read, parent-owner writerecipe_images, recipe_tags, post_images, post_tags, recipe_tagged_users, post_tagged_users, lightroom_settingstrue (or parent not deleted)Owner of parent recordOwner of parentOwner of parent
Public read, parent-owner writerecipe_comments, post_commentsParent is_deleted = falseauth.uid() = user_idauth.uid() = user_id
Public read, parent-owner writerecipe_comment_mentions, post_comment_mentionstrueComment authorComment author
Private to ownersaved_itemsuser_id = auth.uid()user_id = auth.uid()user_id = auth.uid()
Private to ownernotificationsrecipient_id = auth.uid()actor_id = auth.uid()recipient_id = auth.uid()recipient_id = auth.uid()
Owner-scoped via parentcollection_saved_itemstrueOwner of collection AND saved itemOwner of collection
Public read, self onlyprofilestrueauth.uid() = idauth.uid() = id
There are NO database views. The project rules reference recipe_with_stats and collection_with_stats — these do not exist. All stats (favorite_count, comment_count, repost_count) are denormalized directly on the parent tables. Author info is joined inline by RPC functions.

Storage Policies (Detail)

All four buckets are publicly readable. Write operations are restricted:
BucketSELECTINSERTUPDATEDELETE
avatarsPublicauth.uid() = folder[1]auth.uid() = folder[1]auth.uid() = folder[1]
cover-photosPublicauth.uid() = folder[1]auth.uid() = folder[1]auth.uid() = folder[1]
recipe-imagesPublicauth.role() = 'authenticated'auth.uid() = folder[1]auth.uid() = folder[1]
post-imagesPublicauth.uid() = folder[1]auth.uid() = folder[1]auth.uid() = folder[1]
The folder structure is {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.