Cabina uses Supabase migrations to version-control database schema changes. All migrations are stored in supabase/migrations/ as timestamped SQL files.
Creates the API key pool table for load balancing AI requests.
supabase/migrations/20260210_api_key_pool.sql
-- Tabla para el Pool de API Keys de KIE.AI (Load Balancer)CREATE TABLE IF NOT EXISTS public.api_key_pool ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), api_key TEXT NOT NULL UNIQUE, account_name TEXT, is_active BOOLEAN DEFAULT true, last_used_at TIMESTAMPTZ DEFAULT now(), usage_count INTEGER DEFAULT 0, created_at TIMESTAMPTZ DEFAULT now());-- Habilitar RLSALTER TABLE public.api_key_pool ENABLE ROW LEVEL SECURITY;-- Política de Seguridad: Solo el rol de servicio puede accederCREATE POLICY "Service role only" ON public.api_key_pool FOR ALL TO service_role USING (true) WITH CHECK (true);
This migration creates a load balancer that rotates between multiple AI API keys to distribute usage and avoid rate limits.
This creates: supabase/migrations/20260315_add_user_preferences.sql
2
Write Migration SQL
-- Add user preferences columnALTER TABLE profiles ADD COLUMN preferences JSONB DEFAULT '{}'::jsonb;-- Add index for JSONB queriesCREATE INDEX idx_profiles_preferences ON profiles USING GIN (preferences);
3
Test Locally
# Reset local database and apply all migrationssupabase db reset# Or apply new migrations onlysupabase migration up
4
Push to Production
# Link to your project (first time only)supabase link --project-ref your-project-ref# Push migrationssupabase db push
-- Add a new column with default valueALTER TABLE events ADD COLUMN max_guests INTEGER DEFAULT 100;-- Add column only if it doesn't existDO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'events' AND column_name = 'max_guests' ) THEN ALTER TABLE events ADD COLUMN max_guests INTEGER DEFAULT 100; END IF;END $$;
-- Create index if not existsCREATE INDEX IF NOT EXISTS idx_generations_created_at ON generations(created_at DESC);-- Partial index for active events onlyCREATE INDEX idx_events_active ON events(is_active) WHERE is_active = TRUE;
-- Function to auto-update timestampsCREATE OR REPLACE FUNCTION update_updated_at_column()RETURNS TRIGGER AS $$BEGIN NEW.updated_at = NOW(); RETURN NEW;END;$$ LANGUAGE plpgsql;-- Trigger to call functionCREATE TRIGGER update_events_updated_at BEFORE UPDATE ON events FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Update all existing recordsUPDATE profiles SET role = 'user' WHERE role IS NULL;-- Backfill computed columnUPDATE events SET credits_remaining = credits_allocated - credits_used;
-- Safe to run multiple timesBEGIN;-- Create table if not existsCREATE TABLE IF NOT EXISTS favorites ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, generation_id UUID NOT NULL REFERENCES generations(id) ON DELETE CASCADE, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(user_id, generation_id));-- Add column if not existsDO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'favorites' AND column_name = 'note' ) THEN ALTER TABLE favorites ADD COLUMN note TEXT; END IF;END $$;-- Enable RLSALTER TABLE favorites ENABLE ROW LEVEL SECURITY;-- Drop existing policy if existsDROP POLICY IF EXISTS "Users can manage their own favorites" ON favorites;-- Create policyCREATE POLICY "Users can manage their own favorites" ON favorites FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);COMMIT;