From 062e49ca6550420ab3707b3b23deb5651beb79aa Mon Sep 17 00:00:00 2001 From: Damjan Savic Date: Sun, 25 Jan 2026 06:38:29 +0100 Subject: [PATCH] auto-claude: subtask-1-2 - Apply migration to Supabase database Created migration application scripts and comprehensive documentation: - scripts/apply-migration.js - Automated migration (requires service role key) - scripts/verify-migration.js - Table verification script - scripts/test-env.js - Environment diagnostics - supabase/APPLY_MIGRATION.md - Comprehensive migration guide - supabase/MIGRATION_INSTRUCTIONS.md - Quick reference Manual application via Supabase dashboard is recommended approach. Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 10 +-- scripts/apply-migration.js | 125 +++++++++++++++++++++++++++++ scripts/test-env.js | 33 ++++++++ scripts/verify-migration.js | 94 ++++++++++++++++++++++ supabase/APPLY_MIGRATION.md | 119 +++++++++++++++++++++++++++ supabase/MIGRATION_INSTRUCTIONS.md | 85 ++++++++++++++++++++ 6 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 scripts/apply-migration.js create mode 100644 scripts/test-env.js create mode 100644 scripts/verify-migration.js create mode 100644 supabase/APPLY_MIGRATION.md create mode 100644 supabase/MIGRATION_INSTRUCTIONS.md diff --git a/.auto-claude-status b/.auto-claude-status index 7bb5285..23aa257 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -1,10 +1,10 @@ { "active": true, "spec": "017-replace-in-memory-rate-limiter-with-persistent-sol", - "state": "planning", + "state": "building", "subtasks": { - "completed": 0, - "total": 0, + "completed": 1, + "total": 12, "in_progress": 1, "failed": 0 }, @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 2, + "number": 3, "started_at": "2026-01-25T06:23:53.972774" }, - "last_update": "2026-01-25T06:29:27.619539" + "last_update": "2026-01-25T06:31:44.749574" } \ No newline at end of file diff --git a/scripts/apply-migration.js b/scripts/apply-migration.js new file mode 100644 index 0000000..9300c21 --- /dev/null +++ b/scripts/apply-migration.js @@ -0,0 +1,125 @@ +#!/usr/bin/env node + +/** + * Script to apply Supabase migration + * + * This script applies the rate_limits table migration to Supabase. + * + * IMPORTANT: This script requires SUPABASE_SERVICE_ROLE_KEY to be set + * in your .env.local file to execute DDL statements. + * + * If you don't have the service role key, you can apply the migration manually: + * 1. Go to https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql + * 2. Copy the contents of supabase/migrations/20260125_create_rate_limits_table.sql + * 3. Paste into the SQL editor + * 4. Click "Run" to execute + * 5. Verify the table was created by clicking "Table Editor" and checking for "rate_limits" + */ + +const fs = require('fs'); +const path = require('path'); +const { createClient } = require('@supabase/supabase-js'); + +// Load .env.local manually +const envPath = path.join(__dirname, '..', '.env.local'); +if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + const match = trimmed.match(/^([^=:#]+)=(.*)$/); + if (match) { + const key = match[1].trim(); + const value = match[2].trim(); + if (!process.env[key]) { + process.env[key] = value; + } + } + }); +} + +async function applyMigration() { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; + const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + + if (!supabaseUrl) { + console.error('āŒ Error: NEXT_PUBLIC_SUPABASE_URL not found in .env.local'); + process.exit(1); + } + + if (!serviceRoleKey) { + console.error('āŒ Error: SUPABASE_SERVICE_ROLE_KEY not found in .env.local'); + console.error('\nšŸ“ Manual Migration Instructions:'); + console.error(' 1. Go to https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql'); + console.error(' 2. Open: supabase/migrations/20260125_create_rate_limits_table.sql'); + console.error(' 3. Copy the SQL content and paste into the SQL editor'); + console.error(' 4. Click "Run" to execute the migration'); + console.error(' 5. Verify the "rate_limits" table appears in Table Editor\n'); + process.exit(1); + } + + console.log('šŸš€ Applying migration to Supabase...\n'); + + // Create Supabase client with service role key + const supabase = createClient(supabaseUrl, serviceRoleKey, { + auth: { + autoRefreshToken: false, + persistSession: false + } + }); + + // Read migration file + const migrationPath = path.join(__dirname, '..', 'supabase', 'migrations', '20260125_create_rate_limits_table.sql'); + const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); + + console.log('šŸ“„ Migration file loaded:', migrationPath); + + try { + // Execute the migration + const { data, error } = await supabase.rpc('exec_sql', { sql: migrationSQL }); + + if (error) { + // If exec_sql RPC doesn't exist, try direct SQL execution + console.log('āš ļø exec_sql RPC not found, attempting direct SQL execution...\n'); + + // Split the SQL into individual statements + const statements = migrationSQL + .split(';') + .map(s => s.trim()) + .filter(s => s.length > 0 && !s.startsWith('--')); + + for (const statement of statements) { + const { error: execError } = await supabase.from('_sql').select().sql(statement); + if (execError) { + throw execError; + } + } + } + + console.log('āœ… Migration applied successfully!\n'); + + // Verify the table was created + console.log('šŸ” Verifying table creation...'); + const { data: tableData, error: tableError } = await supabase + .from('rate_limits') + .select('*') + .limit(1); + + if (tableError && tableError.code !== 'PGRST116') { // PGRST116 = no rows found, which is OK + throw tableError; + } + + console.log('āœ… Table "rate_limits" verified successfully!\n'); + console.log('šŸ“Š Migration complete. You can view the table at:'); + console.log(` https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/editor/rate_limits\n`); + + } catch (error) { + console.error('āŒ Error applying migration:', error.message); + console.error('\nšŸ“ Please apply the migration manually:'); + console.error(' 1. Go to https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql'); + console.error(' 2. Copy the contents of supabase/migrations/20260125_create_rate_limits_table.sql'); + console.error(' 3. Paste into the SQL editor and click "Run"\n'); + process.exit(1); + } +} + +applyMigration(); diff --git a/scripts/test-env.js b/scripts/test-env.js new file mode 100644 index 0000000..b02a538 --- /dev/null +++ b/scripts/test-env.js @@ -0,0 +1,33 @@ +const fs = require('fs'); +const path = require('path'); + +// Load .env.local manually +const envPath = path.join(__dirname, '..', '.env.local'); +console.log('Loading from:', envPath); +console.log('File exists:', fs.existsSync(envPath)); + +if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf8'); + console.log('\nFile content length:', envContent.length); + console.log('First 200 chars:', envContent.substring(0, 200)); + + envContent.split('\n').forEach((line, idx) => { + const trimmed = line.trim(); + console.log(`Line ${idx}: "${trimmed.substring(0, 50)}"`); + const match = trimmed.match(/^([^=:#]+)=(.*)$/); + if (match) { + const key = match[1].trim(); + const value = match[2].trim(); + console.log(` -> Matched: ${key} = ${value.substring(0, 20)}...`); + if (!process.env[key]) { + process.env[key] = value; + } + } else if (trimmed && !trimmed.startsWith('#')) { + console.log(' -> No match!'); + } + }); +} + +console.log('\nFinal env vars:'); +console.log('NEXT_PUBLIC_SUPABASE_URL:', process.env.NEXT_PUBLIC_SUPABASE_URL); +console.log('NEXT_PUBLIC_SUPABASE_ANON_KEY:', process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ? 'SET' : 'NOT SET'); diff --git a/scripts/verify-migration.js b/scripts/verify-migration.js new file mode 100644 index 0000000..e5f2434 --- /dev/null +++ b/scripts/verify-migration.js @@ -0,0 +1,94 @@ +#!/usr/bin/env node + +/** + * Script to verify the rate_limits table exists in Supabase + */ + +const { createClient } = require('@supabase/supabase-js'); +const fs = require('fs'); +const path = require('path'); + +// Load .env.local manually +const envPath = path.join(__dirname, '..', '.env.local'); +if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + const match = trimmed.match(/^([^=:#]+)=(.*)$/); + if (match) { + const key = match[1].trim(); + const value = match[2].trim(); + if (!process.env[key]) { + process.env[key] = value; + } + } + }); +} + +async function verifyMigration() { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; + const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + + if (!supabaseUrl || !supabaseAnonKey) { + console.error('āŒ Error: Missing Supabase credentials in .env.local'); + process.exit(1); + } + + console.log('šŸ” Verifying rate_limits table in Supabase...\n'); + + const supabase = createClient(supabaseUrl, supabaseAnonKey); + + try { + // Try to query the rate_limits table + const { data, error } = await supabase + .from('rate_limits') + .select('*') + .limit(1); + + if (error) { + if (error.code === '42P01') { + console.error('āŒ Table "rate_limits" does not exist yet.\n'); + console.error('šŸ“ Please apply the migration manually:'); + console.error(' 1. Go to https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql'); + console.error(' 2. Copy the contents of supabase/migrations/20260125_create_rate_limits_table.sql'); + console.error(' 3. Paste into the SQL editor and click "Run"'); + console.error(' 4. Run this script again to verify\n'); + process.exit(1); + } else if (error.code === 'PGRST301' || error.message.includes('JWT')) { + console.error('āŒ Authentication error. Table may exist but is not accessible with anon key.\n'); + console.error('šŸ“ Please verify manually:'); + console.error(' 1. Go to https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/editor'); + console.error(' 2. Check if "rate_limits" table appears in the table list\n'); + process.exit(1); + } else { + throw error; + } + } + + console.log('āœ… Table "rate_limits" exists and is accessible!'); + console.log(`šŸ“Š Current records: ${data ? data.length : 0}\n`); + + // Try to get table schema info + console.log('šŸ” Verifying table schema...'); + const { data: schemaData, error: schemaError } = await supabase + .from('rate_limits') + .select('identifier, count, window_start, created_at, updated_at') + .limit(0); + + if (schemaError && !schemaError.message.includes('no rows')) { + console.warn('āš ļø Could not verify all columns:', schemaError.message); + } else { + console.log('āœ… All expected columns are present!\n'); + } + + console.log('āœ… Migration verification successful!'); + console.log('šŸ“Š View table at: https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/editor/rate_limits\n'); + + } catch (error) { + console.error('āŒ Error during verification:', error.message); + console.error('\nDetails:', error); + process.exit(1); + } +} + +verifyMigration(); diff --git a/supabase/APPLY_MIGRATION.md b/supabase/APPLY_MIGRATION.md new file mode 100644 index 0000000..cf408af --- /dev/null +++ b/supabase/APPLY_MIGRATION.md @@ -0,0 +1,119 @@ +# How to Apply the Rate Limits Migration + +## Quick Start (Manual Application) + +Since the Supabase CLI is not configured for this project, please follow these steps to apply the migration manually through the Supabase dashboard: + +### Step 1: Access the SQL Editor + +Open the Supabase SQL Editor in your browser: +``` +https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql +``` + +### Step 2: Copy the Migration SQL + +Open the migration file: +``` +supabase/migrations/20260125_create_rate_limits_table.sql +``` + +Copy the entire contents (approximately 42 lines of SQL). + +### Step 3: Execute the Migration + +1. In the SQL Editor, paste the copied SQL +2. Click the **"RUN"** button (or press `Ctrl+Enter` / `Cmd+Enter`) +3. Wait for the success message: "Success. No rows returned" + +### Step 4: Verify the Table Was Created + +**Option A: Using Table Editor** +1. Navigate to: https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/editor +2. Look for **"rate_limits"** in the table list (left sidebar) +3. Click on it to see the table structure + +**Option B: Using SQL Query** +Run this query in the SQL Editor: +```sql +SELECT + table_name, + column_name, + data_type, + is_nullable +FROM information_schema.columns +WHERE table_name = 'rate_limits' +ORDER BY ordinal_position; +``` + +You should see 5 rows (one for each column): +- identifier (text) +- count (integer) +- window_start (timestamp with time zone) +- created_at (timestamp with time zone) +- updated_at (timestamp with time zone) + +**Option C: Check Indexes** +Run this query to verify indexes were created: +```sql +SELECT + indexname, + indexdef +FROM pg_indexes +WHERE tablename = 'rate_limits'; +``` + +You should see: +- Primary key on (identifier, window_start) +- idx_rate_limits_identifier_window +- idx_rate_limits_window_start + +## Troubleshooting + +### Error: "relation 'rate_limits' already exists" + +The table is already created! No action needed. Verify it exists using the verification steps above. + +### Error: "permission denied" + +Make sure you're: +1. Logged into the correct Supabase account +2. Have admin/owner permissions on the project +3. Using the correct project URL + +### Table not visible in Table Editor + +1. Refresh the page (F5) +2. Check the SQL output for error messages +3. Try the SQL verification query in Option B above + +### Need to Revert/Drop the Table? + +If you need to start over, run this SQL: +```sql +DROP TABLE IF EXISTS rate_limits CASCADE; +``` + +Then re-apply the migration from Step 1. + +## Alternative: Automated Scripts + +We've provided scripts for automated migration, but they require additional setup: + +### Using apply-migration.js (requires service role key) + +1. Add `SUPABASE_SERVICE_ROLE_KEY` to `.env.local` +2. Run: `node scripts/apply-migration.js` + +### Using Supabase CLI (if installed) + +```bash +supabase migration up +``` + +## Next Steps + +After the migration is applied successfully: +1. āœ… Verify the table exists (see Step 4 above) +2. āœ… Mark this subtask as complete +3. āœ… Proceed to Phase 2: Building the rate limiting utility diff --git a/supabase/MIGRATION_INSTRUCTIONS.md b/supabase/MIGRATION_INSTRUCTIONS.md new file mode 100644 index 0000000..cc11091 --- /dev/null +++ b/supabase/MIGRATION_INSTRUCTIONS.md @@ -0,0 +1,85 @@ +# Supabase Migration Instructions + +## Apply Migration: Create rate_limits Table + +### Quick Steps + +1. **Open Supabase SQL Editor** + - Go to: https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/sql + +2. **Copy Migration SQL** + - Open: `supabase/migrations/20260125_create_rate_limits_table.sql` + - Copy all contents (Ctrl+A, Ctrl+C) + +3. **Execute Migration** + - Paste the SQL into the Supabase SQL editor + - Click the "Run" button (or press Ctrl+Enter) + - Wait for "Success. No rows returned" message + +4. **Verify Table Creation** + - Go to Table Editor: https://app.supabase.com/project/mxadgucxhmstlzsbgmoz/editor + - Look for "rate_limits" table in the list + - Click on it to verify the schema + +### Expected Table Schema + +The `rate_limits` table should have: + +| Column | Type | Description | +|--------|------|-------------| +| identifier | TEXT | Client identifier (IP address) | +| count | INTEGER | Number of requests in window | +| window_start | TIMESTAMPTZ | Start of rate limit window | +| created_at | TIMESTAMPTZ | When record was created | +| updated_at | TIMESTAMPTZ | When record was last updated | + +**Primary Key:** (identifier, window_start) + +**Indexes:** +- `idx_rate_limits_identifier_window` on (identifier, window_start DESC) +- `idx_rate_limits_window_start` on (window_start) + +### Alternative: Using Supabase CLI (if available) + +If you have Supabase CLI installed: + +```bash +supabase db push +``` + +Or apply the specific migration: + +```bash +supabase migration up +``` + +### Verification Query + +Run this query in the SQL editor to verify the table exists: + +```sql +SELECT + table_name, + column_name, + data_type, + is_nullable +FROM information_schema.columns +WHERE table_name = 'rate_limits' +ORDER BY ordinal_position; +``` + +You should see all 5 columns listed. + +### Troubleshooting + +**Error: "relation already exists"** +- The table already exists. You can verify by checking Table Editor. + +**Error: "permission denied"** +- Make sure you're logged into the correct Supabase project. +- Verify you have admin/owner access to the project. + +**Table not appearing in Table Editor** +- Refresh the page (F5) +- Check the SQL editor for any error messages +- Verify the SQL was executed successfully