-- Create subscriptions table to track user subscriptions CREATE TABLE IF NOT EXISTS subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, stripe_customer_id TEXT, stripe_subscription_id TEXT UNIQUE, stripe_price_id TEXT, status TEXT NOT NULL, current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, cancel_at_period_end BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Create index for faster lookups CREATE INDEX IF NOT EXISTS subscriptions_user_id_idx ON subscriptions(user_id); CREATE INDEX IF NOT EXISTS subscriptions_stripe_customer_id_idx ON subscriptions(stripe_customer_id); CREATE INDEX IF NOT EXISTS subscriptions_stripe_subscription_id_idx ON subscriptions(stripe_subscription_id); -- Enable Row Level Security ALTER TABLE subscriptions ENABLE ROW LEVEL SECURITY; -- Policy: Users can only see their own subscriptions DROP POLICY IF EXISTS "Users can view own subscriptions" ON subscriptions; CREATE POLICY "Users can view own subscriptions" ON subscriptions FOR SELECT USING (auth.uid() = user_id); -- Policy: Users can insert their own subscription records (for initial customer creation) -- But they can ONLY insert records with status='incomplete' to prevent fraud DROP POLICY IF EXISTS "Users can insert own subscriptions" ON subscriptions; CREATE POLICY "Users can insert own subscriptions" ON subscriptions FOR INSERT WITH CHECK ( auth.uid() = user_id AND status = 'incomplete' ); -- Users CANNOT update their own subscriptions directly -- All updates must come from webhooks (using service role) or validated server actions -- This prevents users from setting status='active' without paying DROP POLICY IF EXISTS "Users can update own subscriptions" ON subscriptions; -- Note: Service role (used by webhooks via admin client) bypasses RLS entirely -- The admin client uses SUPABASE_SERVICE_ROLE_KEY which automatically bypasses all RLS policies. -- No policy needed for service role since it bypasses RLS. -- Function to update updated_at timestamp CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- Trigger to automatically update updated_at DROP TRIGGER IF EXISTS update_subscriptions_updated_at ON subscriptions; CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON subscriptions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();