diff --git a/app/src/lib/database/supabase-types.ts b/app/src/lib/database/supabase-types.ts new file mode 100644 index 0000000..7eccf16 --- /dev/null +++ b/app/src/lib/database/supabase-types.ts @@ -0,0 +1,200 @@ +/** + * TypeScript definitions for Supabase database tables + */ + +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[]; + +export interface Database { + public: { + Tables: { + completed_exercises: { + Row: { + id: number; + exercise_id: string; + completed_at: string; + user_id: string; + metrics: { + sets?: number; + reps?: number; + weight?: number; + time?: number; + distance?: number; + resistance?: number; + speed?: number; + incline?: number; + resistance_type?: string; + calories?: number; + heart_rate?: number; + rpe?: number; + }; + }; + Insert: { + id?: number; + exercise_id: string; + completed_at: string; + user_id: string; + metrics: { + sets?: number; + reps?: number; + weight?: number; + time?: number; + distance?: number; + resistance?: number; + speed?: number; + incline?: number; + resistance_type?: string; + calories?: number; + heart_rate?: number; + rpe?: number; + }; + }; + Update: { + id?: number; + exercise_id?: string; + completed_at?: string; + user_id?: string; + metrics?: { + sets?: number; + reps?: number; + weight?: number; + time?: number; + distance?: number; + resistance?: number; + speed?: number; + incline?: number; + resistance_type?: string; + calories?: number; + heart_rate?: number; + rpe?: number; + }; + }; + }; + stripe_customers: { + Row: { + user_id: string; + stripe_customer_id: string; + created_at: string; + updated_at: string; + }; + Insert: { + user_id: string; + stripe_customer_id: string; + created_at?: string; + updated_at?: string; + }; + Update: { + user_id?: string; + stripe_customer_id?: string; + created_at?: string; + updated_at?: string; + }; + }; + subscriptions: { + Row: { + id: string; + user_id: string; + stripe_subscription_id: string | null; + stripe_price_id: string | null; + stripe_product_id: string | null; + status: string; + cancel_at_period_end: boolean; + current_period_start: string | null; + current_period_end: string | null; + created_at: string; + updated_at: string; + ended_at: string | null; + }; + Insert: { + id?: string; + user_id: string; + stripe_subscription_id?: string | null; + stripe_price_id?: string | null; + stripe_product_id?: string | null; + status: string; + cancel_at_period_end?: boolean; + current_period_start?: string | null; + current_period_end?: string | null; + created_at?: string; + updated_at?: string; + ended_at?: string | null; + }; + Update: { + id?: string; + user_id?: string; + stripe_subscription_id?: string | null; + stripe_price_id?: string | null; + stripe_product_id?: string | null; + status?: string; + cancel_at_period_end?: boolean; + current_period_start?: string | null; + current_period_end?: string | null; + created_at?: string; + updated_at?: string; + ended_at?: string | null; + }; + }; + pricing_plans: { + Row: { + id: string; + name: string; + description: string | null; + features: Json | null; + stripe_price_id: string | null; + stripe_product_id: string | null; + amount: number; + currency: string; + interval: string; + active: boolean; + created_at: string; + updated_at: string; + }; + Insert: { + id?: string; + name: string; + description?: string | null; + features?: Json | null; + stripe_price_id?: string | null; + stripe_product_id?: string | null; + amount: number; + currency?: string; + interval: string; + active?: boolean; + created_at?: string; + updated_at?: string; + }; + Update: { + id?: string; + name?: string; + description?: string | null; + features?: Json | null; + stripe_price_id?: string | null; + stripe_product_id?: string | null; + amount?: number; + currency?: string; + interval?: string; + active?: boolean; + created_at?: string; + updated_at?: string; + }; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; +} diff --git a/app/supabase/.gitignore b/app/supabase/.gitignore new file mode 100644 index 0000000..fd88507 --- /dev/null +++ b/app/supabase/.gitignore @@ -0,0 +1 @@ +.branches diff --git a/app/supabase/migrations/20250501000000_create_completed_exercises_table.sql b/app/supabase/migrations/20250501000000_create_completed_exercises_table.sql new file mode 100644 index 0000000..8a958b1 --- /dev/null +++ b/app/supabase/migrations/20250501000000_create_completed_exercises_table.sql @@ -0,0 +1,16 @@ +-- Create the completed_exercises table +CREATE TABLE completed_exercises ( + id SERIAL PRIMARY KEY, + exercise_id TEXT NOT NULL, + completed_at TIMESTAMPTZ NOT NULL, + user_id UUID NOT NULL REFERENCES auth.users(id), + metrics JSONB NOT NULL DEFAULT '{}' +); +-- Set up row-level security +ALTER TABLE completed_exercises ENABLE ROW LEVEL SECURITY; +-- Create policy for users to only see and modify their own data +CREATE POLICY "Users can only access their own data" ON completed_exercises USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); +-- Create indexes for better query performance +CREATE INDEX completed_exercises_user_id_idx ON completed_exercises (user_id); +CREATE INDEX completed_exercises_exercise_id_idx ON completed_exercises (exercise_id); +CREATE INDEX completed_exercises_completed_at_idx ON completed_exercises (completed_at); \ No newline at end of file diff --git a/app/supabase/migrations/20250501101855_create_stripe_subscription_tables.sql b/app/supabase/migrations/20250501101855_create_stripe_subscription_tables.sql new file mode 100644 index 0000000..746cd5d --- /dev/null +++ b/app/supabase/migrations/20250501101855_create_stripe_subscription_tables.sql @@ -0,0 +1,54 @@ +-- Create stripe customers table to link Supabase users to Stripe customers +CREATE TABLE IF NOT EXISTS stripe_customers ( + user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + stripe_customer_id TEXT NOT NULL UNIQUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); +-- Create subscriptions table to track user subscription status +CREATE TABLE IF NOT EXISTS subscriptions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + stripe_subscription_id TEXT UNIQUE, + stripe_price_id TEXT, + stripe_product_id TEXT, + status TEXT NOT NULL, + cancel_at_period_end BOOLEAN DEFAULT FALSE, + current_period_start TIMESTAMP WITH TIME ZONE, + current_period_end TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + ended_at TIMESTAMP WITH TIME ZONE +); +-- Create pricing plans table for subscription options +CREATE TABLE IF NOT EXISTS pricing_plans ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL, + description TEXT, + features JSONB, + stripe_price_id TEXT UNIQUE, + stripe_product_id TEXT, + amount INTEGER NOT NULL, + currency TEXT NOT NULL DEFAULT 'usd', + interval TEXT NOT NULL, + -- month, year, etc. + active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); +-- Set up RLS (Row Level Security) policies +ALTER TABLE stripe_customers ENABLE ROW LEVEL SECURITY; +ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY; +ALTER TABLE pricing_plans ENABLE ROW LEVEL SECURITY; +-- Allow users to read their own customer and subscription data +CREATE POLICY "Users can read their own Stripe customer data" ON stripe_customers FOR +SELECT USING (auth.uid() = user_id); +CREATE POLICY "Users can read their own subscription data" ON subscriptions FOR +SELECT USING (auth.uid() = user_id); +-- Allow everyone to read active pricing plans +CREATE POLICY "Anyone can read active pricing plans" ON pricing_plans FOR +SELECT USING (active = true); +-- Allow service role to manage all tables (for webhooks and admin operations) +CREATE POLICY "Service role can manage all Stripe customers" ON stripe_customers USING (auth.jwt()->>'role' = 'service_role'); +CREATE POLICY "Service role can manage all subscriptions" ON subscriptions USING (auth.jwt()->>'role' = 'service_role'); +CREATE POLICY "Service role can manage all pricing plans" ON pricing_plans USING (auth.jwt()->>'role' = 'service_role'); \ No newline at end of file