Overview
JCV Fitness uses Supabase PostgreSQL for data persistence with Row Level Security (RLS) to protect user data. This guide will walk you through setting up your database schema using migrations.Database Architecture
The database consists of the following tables:| Table | Purpose |
|---|---|
profiles | User profiles (extends auth.users) |
subscriptions | Active subscriptions with payment info |
wizard_data | Wizard form responses |
user_plans | Generated fitness plans (freemium system) |
plan_downloads | Download tracking and security |
Prerequisites
Create a Supabase project
- Go to Supabase
- Click New Project
- Fill in project details:
- Name:
jcv-fitness - Database Password: (save this securely)
- Region: Choose closest to your users
- Name:
- Wait for project to initialize (~2 minutes)
Get your database credentials
Once created, go to Settings → API and copy:
- Project URL →
NEXT_PUBLIC_SUPABASE_URL - anon public key →
NEXT_PUBLIC_SUPABASE_ANON_KEY - service_role key →
SUPABASE_SERVICE_ROLE_KEY
.env.local file (see Environment Variables).Run Database Migrations
The database schema is defined in SQL migration files located insupabase/migrations/.
Migration 1: Initial Schema
Run the initial schema migration
Copy the contents of
supabase/migrations/001_initial_schema.sql and paste into the SQL editor.This migration creates:- ✅
profilestable with RLS policies - ✅
subscriptionstable with RLS policies - ✅
wizard_datatable with RLS policies - ✅
plan_downloadstable with RLS policies - ✅ Trigger to auto-create profiles on user signup
- ✅ Functions for subscription management
- ✅ Automatic
updated_attriggers
Migration 2: User Plans (Freemium System)
Run the user plans migration
Copy the contents of
supabase/migrations/002_user_plans.sql and run it in the SQL editor.This migration adds:- ✅
user_planstable for storing generated fitness plans - ✅ Freemium logic (5-week free plans, 1-year paid plans)
- ✅ Functions:
can_create_plan(),get_active_plan(),create_user_plan() - ✅ Expiration automation with
expire_old_plans() - ✅ Download tracking for paid users
Row Level Security (RLS)
All tables have RLS enabled to ensure users can only access their own data.Profiles Table Policies
Subscriptions Table Policies
User Plans Table Policies
RLS policies are enforced at the database level, providing security even if your application code has bugs.
Database Functions
The migrations include several PostgreSQL functions for common operations:Subscription Functions
has_active_subscription(user_uuid)
has_active_subscription(user_uuid)
Purpose: Check if a user has an active subscriptionReturns:
BOOLEANExample:get_active_subscription(user_uuid)
get_active_subscription(user_uuid)
Purpose: Get details of user’s active subscriptionReturns: Table with
id, plan_type, end_date, days_remainingExample:expire_old_subscriptions()
expire_old_subscriptions()
Purpose: Mark expired subscriptions as expired (run by cron)Returns:
INTEGER (number of affected rows)Example:Plan Functions
can_create_plan(user_uuid)
can_create_plan(user_uuid)
Purpose: Check if user can create a new planReturns: Table with Possible reasons:
can_create (boolean) and reason (text)Example:already_has_plan: User has an active planfree_used: User already used their free planNULL: User can create a plan
get_active_plan(user_uuid)
get_active_plan(user_uuid)
Purpose: Get user’s active plan with metadataReturns: Table with plan details, expiration, and download countExample:
create_user_plan(user_uuid, plan_data, plan_type)
create_user_plan(user_uuid, plan_data, plan_type)
Purpose: Create a new plan for a userParameters:
user_uuid: User IDplan_data: JSONB containing wizard stateplan_type: ‘free’ or ‘paid’
Triggers
Auto-create Profile on Signup
When a user signs up via Supabase Auth, a profile is automatically created:Auto-update updated_at
All tables automatically update their updated_at timestamp on modification.
Cron Jobs (Optional)
Set up automated tasks to expire old subscriptions and plans:If you don’t have Supabase Pro, you can call these functions from your application code or use a service like GitHub Actions to run them periodically.
Verify Setup
Check that your tables were created successfully:View tables
In Supabase dashboard: Table EditorYou should see:
- ✅ profiles
- ✅ subscriptions
- ✅ wizard_data
- ✅ user_plans
- ✅ plan_downloads
Authentication Setup
Configure authentication providers
Go to Authentication → ProvidersEnable Email provider with:
- Magic Link enabled
- Email confirmation required (recommended)
Plan Types
The system supports three subscription plans:| Plan Type | Duration | Price | Features |
|---|---|---|---|
PLAN_BASICO | 1 month | Basic pricing | Basic features |
PLAN_PRO | 2 months | Mid-tier | Enhanced features |
PLAN_PREMIUM | 3 months | Premium | All features |
Freemium System
- Free plans: 5-week access, one per user
- Paid plans: 1-year access, unlimited regeneration
- Downloads: Only available for paid subscribers
Troubleshooting
Migration fails with syntax error
- Ensure you’re copying the entire SQL file content
- Check for any missing semicolons
- Run migrations in order (001, then 002)
RLS policies blocking queries
- Verify you’re authenticated when testing
- Use
SUPABASE_SERVICE_ROLE_KEYfor admin access (server-side only) - Check policy definitions match your use case
Profile not auto-created on signup
- Verify the trigger is installed:
SELECT * FROM pg_trigger WHERE tgname = 'on_auth_user_created'; - Check Supabase logs for trigger errors
- Ensure profile table has all required columns
Next Steps
Cloudflare Deployment
Deploy your application to Cloudflare Pages
API Reference
Learn about the Supabase API and how to query your database