From bb43a07234215c64846667c99359c8c679eed942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0pek?= Date: Thu, 26 Feb 2026 10:40:36 +0300 Subject: [PATCH] refactor(db): move rate-limit extras into Prisma migration and remove db-extras script --- AGENTS.md | 2 +- package.json | 3 +- .../migration.sql | 44 +++++++++++++++ prisma/migrations/migration_lock.toml | 3 + prisma/migrations/rate_limit.sql | 31 ---------- scripts/db-extras.ts | 56 ------------------- 6 files changed, 49 insertions(+), 90 deletions(-) create mode 100644 prisma/migrations/20260226110000_rate_limit_extras/migration.sql create mode 100644 prisma/migrations/migration_lock.toml delete mode 100644 prisma/migrations/rate_limit.sql delete mode 100644 scripts/db-extras.ts diff --git a/AGENTS.md b/AGENTS.md index 5285a85..4844073 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ - Keep API and UI imports on `@/` aliases when available. ## Important locations -- Custom SQL not managed by Prisma migrations: `prisma/migrations/*.sql` and runner `scripts/db-extras.ts`. +- Custom SQL managed by Prisma migrations: `prisma/migrations/*/migration.sql`. - Shared API response helpers: `lib/api-response.ts`. - Auth + access-control helpers: `lib/auth.ts`. diff --git a/package.json b/package.json index b8d96ef..2760287 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,7 @@ "db:push": "prisma db push", "db:migrate": "prisma migrate deploy", "db:seed": "prisma db seed", - "db:setup": "bun run db:generate && bun run db:push && bun run db:extras", - "db:extras": "bun run scripts/db-extras.ts", + "db:setup": "bun run db:generate && bun run db:push && bun run db:migrate", "r2:cleanup-orphans:dry": "bun run scripts/r2-orphan-cleanup.ts --dry-run", "r2:cleanup-orphans": "bun run scripts/r2-orphan-cleanup.ts", "bunny:cleanup-orphans:dry": "bun run scripts/bunny-orphan-cleanup.ts --dry-run", diff --git a/prisma/migrations/20260226110000_rate_limit_extras/migration.sql b/prisma/migrations/20260226110000_rate_limit_extras/migration.sql new file mode 100644 index 0000000..2a9ba62 --- /dev/null +++ b/prisma/migrations/20260226110000_rate_limit_extras/migration.sql @@ -0,0 +1,44 @@ +-- Rate limiting extras migration +-- Keeps compatibility with existing environments created via prisma db push. + +CREATE TABLE IF NOT EXISTS rate_limits ( + id SERIAL PRIMARY KEY, + key VARCHAR(255) NOT NULL, + action VARCHAR(50) NOT NULL, + count INTEGER NOT NULL DEFAULT 1, + window_start TIMESTAMP NOT NULL DEFAULT NOW() +); + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'rate_limits_key_action_key' + ) THEN + ALTER TABLE rate_limits + ADD CONSTRAINT rate_limits_key_action_key UNIQUE (key, action); + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_rate_limits_key_action ON rate_limits(key, action); +CREATE INDEX IF NOT EXISTS idx_rate_limits_window_start ON rate_limits(window_start); + +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM pg_class + WHERE relname = 'rate_limits' + AND relkind = 'r' + AND relpersistence <> 'u' + ) THEN + ALTER TABLE rate_limits SET UNLOGGED; + END IF; +END $$; + +CREATE OR REPLACE FUNCTION cleanup_rate_limits() RETURNS void AS $$ +BEGIN + DELETE FROM rate_limits WHERE window_start < NOW() - INTERVAL '1 hour'; +END; +$$ LANGUAGE plpgsql; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/migrations/rate_limit.sql b/prisma/migrations/rate_limit.sql deleted file mode 100644 index 5c7fb94..0000000 --- a/prisma/migrations/rate_limit.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Rate Limiting Table (UNLOGGED for performance) --- Run this migration manually: psql $DATABASE_URL -f prisma/migrations/rate_limit.sql - --- Drop if exists (for re-running) -DROP TABLE IF EXISTS rate_limits; - --- Create UNLOGGED table for rate limiting --- UNLOGGED = no WAL writes = faster, but data lost on crash (acceptable for rate limits) -CREATE UNLOGGED TABLE rate_limits ( - id SERIAL PRIMARY KEY, - key VARCHAR(255) NOT NULL, -- e.g., "register:192.168.1.1" or "login:user@example.com" - action VARCHAR(50) NOT NULL, -- e.g., "register", "login", "api" - count INTEGER NOT NULL DEFAULT 1, - window_start TIMESTAMP NOT NULL DEFAULT NOW(), - - -- Unique constraint for upsert operations - UNIQUE(key, action) -); - --- Index for fast lookups -CREATE INDEX idx_rate_limits_key_action ON rate_limits(key, action); - --- Index for cleanup operations -CREATE INDEX idx_rate_limits_window_start ON rate_limits(window_start); - --- Auto-cleanup function: removes expired entries -CREATE OR REPLACE FUNCTION cleanup_rate_limits() RETURNS void AS $$ -BEGIN - DELETE FROM rate_limits WHERE window_start < NOW() - INTERVAL '1 hour'; -END; -$$ LANGUAGE plpgsql; diff --git a/scripts/db-extras.ts b/scripts/db-extras.ts deleted file mode 100644 index 9716c1f..0000000 --- a/scripts/db-extras.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * db-extras.ts — Run all custom SQL migrations that Prisma doesn't manage. - * - * This script runs after `prisma db push` / `prisma migrate deploy` to set up - * tables and functions that need raw SQL (UNLOGGED tables, custom functions, etc.). - * - * Usage: bun run db:extras - * - * To add new custom migrations: - * 1. Create a .sql file in prisma/migrations/ - * 2. Add the filename to the EXTRAS array below - */ - -import { readFileSync } from 'fs'; -import { join } from 'path'; -import pg from 'pg'; - -const EXTRAS = [ - 'rate_limit.sql', - // Add future custom SQL files here -]; - -async function main() { - const url = process.env.DATABASE_URL; - if (!url) { - console.error('❌ DATABASE_URL is not set'); - process.exit(1); - } - - // Strip Prisma-specific query params (e.g. ?schema=public) that pg doesn't understand - const cleanUrl = url.split('?')[0]; - const client = new pg.Client({ connectionString: cleanUrl }); - - try { - await client.connect(); - console.log('✅ Connected to database\n'); - - for (const file of EXTRAS) { - const filePath = join(import.meta.dirname, '..', 'prisma', 'migrations', file); - const sql = readFileSync(filePath, 'utf-8'); - - console.log(`▸ Running ${file}...`); - await client.query(sql); - console.log(` ✓ ${file} applied\n`); - } - - console.log('✅ All database extras applied successfully'); - } catch (err) { - console.error('❌ Database extras failed:', err); - process.exit(1); - } finally { - await client.end(); - } -} - -main();