From 5a4252d8bbe66609801ed30788fac2b9071b2c72 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 00:39:41 +0000 Subject: [PATCH 1/2] fix: Fix database migration issues for Railway deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Changes 1. **Fix migration path resolution** - Changed from relative path `./server/db/migrations` to absolute path - Uses `fileURLToPath` and `dirname` for reliable path resolution - Ensures migrations work in production environments (Railway) 2. **Remove duplicate migration files** - Deleted `0030_fix_uuid_defaults.sql` (duplicate of 0030_far_silver_fox) - Deleted `0031_add_row_level_security.sql` (duplicate of 0031_stormy_black_panther) - Cleans up migration journal to prevent conflicts 3. **Improve migration logging** - Added detailed startup logging with migrations folder path - Added database URL logging (with password redacted) - Enhanced error logging with migration folder info for debugging ## Why This Fixes Railway Deployment The original script used a relative path that failed in production because: - Working directory may differ in containerized environments - Relative paths are unreliable during Railway builds - Absolute paths ensure the script finds migrations regardless of cwd ## Testing Verified migration script works correctly with proper error handling: - ✅ Modules load successfully (drizzle-orm, postgres) - ✅ Migrations folder resolves to absolute path - ✅ Database connection attempts correctly - ✅ Error logging provides debugging information --- apps/core/server/db/migrate.ts | 32 ++- .../db/migrations/0030_fix_uuid_defaults.sql | 42 ---- .../0031_add_row_level_security.sql | 194 ------------------ 3 files changed, 28 insertions(+), 240 deletions(-) delete mode 100644 apps/core/server/db/migrations/0030_fix_uuid_defaults.sql delete mode 100644 apps/core/server/db/migrations/0031_add_row_level_security.sql diff --git a/apps/core/server/db/migrate.ts b/apps/core/server/db/migrate.ts index cb0cca9..b71458e 100644 --- a/apps/core/server/db/migrate.ts +++ b/apps/core/server/db/migrate.ts @@ -1,13 +1,20 @@ /** * Database Migration Runner * Runs pending migrations against the database + * Note: Bun auto-loads .env files, no dotenv needed */ -import "dotenv/config"; import { drizzle } from "drizzle-orm/postgres-js"; -import { logger } from "../utils/logger"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { logger } from "../utils/logger"; + +// Get absolute path to migrations folder +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const migrationsFolder = join(__dirname, "migrations"); // Validate environment if (!process.env.DATABASE_URL) { @@ -22,6 +29,15 @@ if (!process.env.DATABASE_URL) { process.exit(1); } +// Log migration info for debugging +logger.info( + { + migrationsFolder, + databaseUrl: process.env.DATABASE_URL?.replace(/:[^:@]+@/, ":****@"), // Hide password + }, + "[Migrations] Starting migration process", +); + // Migration connection with NOTICE suppression const migrationClient = postgres(process.env.DATABASE_URL, { max: 1, @@ -34,7 +50,7 @@ async function main() { logger.info({}, "[Migrations] Running migrations..."); try { - await migrate(db, { migrationsFolder: "./server/db/migrations" }); + await migrate(db, { migrationsFolder }); logger.info({}, "[Migrations] ✓ Migrations completed successfully"); } catch (error: any) { // Check if it's a "relation already exists" error (PostgreSQL code 42P07) @@ -49,7 +65,15 @@ async function main() { logger.warn({}, "[Migrations] ⚠️ Some tables already exist - skipping"); logger.info({}, "[Migrations] ✓ Database schema is up to date"); } else { - logger.error({ err: error }, "[Migrations] ✗ Migration failed:"); + logger.error( + { + err: error, + code: errorCode, + message: errorMessage, + migrationsFolder, + }, + "[Migrations] ✗ Migration failed", + ); process.exit(1); } } diff --git a/apps/core/server/db/migrations/0030_fix_uuid_defaults.sql b/apps/core/server/db/migrations/0030_fix_uuid_defaults.sql deleted file mode 100644 index fb53b2e..0000000 --- a/apps/core/server/db/migrations/0030_fix_uuid_defaults.sql +++ /dev/null @@ -1,42 +0,0 @@ --- Fix UUID defaults for all tables --- Ensures all UUID ID columns have gen_random_uuid() as default --- This fixes any tables that might be missing UUID generation - --- Note: This migration is idempotent - it's safe to run multiple times --- ALTER COLUMN SET DEFAULT doesn't error if the default already exists - --- Core tables -ALTER TABLE IF EXISTS "users" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "projects" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "assets" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "media_assets" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- Content tables -ALTER TABLE IF EXISTS "content" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "content_relationships" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- Generation tables -ALTER TABLE IF EXISTS "generation_jobs" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "generation_pipelines" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- Asset management -ALTER TABLE IF EXISTS "static_assets" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "asset_variants" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "prompts" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "material_presets" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- API and monitoring -ALTER TABLE IF EXISTS "api_keys" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "api_errors" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "api_error_aggregations" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- User features -ALTER TABLE IF EXISTS "achievements" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "user_achievements" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- Activity and admin -ALTER TABLE IF EXISTS "activity_log" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -ALTER TABLE IF EXISTS "admin_whitelist" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); - --- World configuration -ALTER TABLE IF EXISTS "world_config" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); diff --git a/apps/core/server/db/migrations/0031_add_row_level_security.sql b/apps/core/server/db/migrations/0031_add_row_level_security.sql deleted file mode 100644 index a2d20fa..0000000 --- a/apps/core/server/db/migrations/0031_add_row_level_security.sql +++ /dev/null @@ -1,194 +0,0 @@ --- ===================================================== --- Row Level Security (RLS) Migration --- Ensures users can only access their own data --- ===================================================== - --- Enable RLS on tables with user ownership -ALTER TABLE generation_pipelines ENABLE ROW LEVEL SECURITY; -ALTER TABLE asset_variants ENABLE ROW LEVEL SECURITY; -ALTER TABLE api_errors ENABLE ROW LEVEL SECURITY; -ALTER TABLE assets ENABLE ROW LEVEL SECURITY; -ALTER TABLE activity_log ENABLE ROW LEVEL SECURITY; - --- ===================================================== --- generation_pipelines: Users can only see their own pipelines --- ===================================================== - --- SELECT policy: Users can view their own pipelines -CREATE POLICY "Users can view their own pipelines" -ON generation_pipelines -FOR SELECT -USING (user_id = current_setting('app.current_user_id', TRUE)::uuid); - --- INSERT policy: Users can create pipelines for themselves -CREATE POLICY "Users can create their own pipelines" -ON generation_pipelines -FOR INSERT -WITH CHECK (user_id = current_setting('app.current_user_id', TRUE)::uuid); - --- UPDATE policy: Users can update their own pipelines -CREATE POLICY "Users can update their own pipelines" -ON generation_pipelines -FOR UPDATE -USING (user_id = current_setting('app.current_user_id', TRUE)::uuid); - --- DELETE policy: Users can delete their own pipelines -CREATE POLICY "Users can delete their own pipelines" -ON generation_pipelines -FOR DELETE -USING (user_id = current_setting('app.current_user_id', TRUE)::uuid); - --- ===================================================== --- asset_variants: Users can only see their own variants --- ===================================================== - --- SELECT policy: Users can view their own variants -CREATE POLICY "Users can view their own variants" -ON asset_variants -FOR SELECT -USING (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- INSERT policy: Users can create variants for themselves -CREATE POLICY "Users can create their own variants" -ON asset_variants -FOR INSERT -WITH CHECK (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- UPDATE policy: Users can update their own variants -CREATE POLICY "Users can update their own variants" -ON asset_variants -FOR UPDATE -USING (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- DELETE policy: Users can delete their own variants -CREATE POLICY "Users can delete their own variants" -ON asset_variants -FOR DELETE -USING (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- ===================================================== --- api_errors: Users can only see their own errors --- ===================================================== - --- SELECT policy: Users can view their own errors -CREATE POLICY "Users can view their own errors" -ON api_errors -FOR SELECT -USING (user_id = current_setting('app.current_user_id', TRUE)::uuid OR user_id IS NULL); - --- INSERT policy: System can create errors for any user -CREATE POLICY "System can create errors" -ON api_errors -FOR INSERT -WITH CHECK (true); - --- ===================================================== --- assets: Users can see their own assets + public assets --- ===================================================== - --- SELECT policy: Users can view their own assets or public assets -CREATE POLICY "Users can view own or public assets" -ON assets -FOR SELECT -USING ( - owner_id = current_setting('app.current_user_id', TRUE)::uuid - OR is_public = true -); - --- INSERT policy: Users can create assets for themselves -CREATE POLICY "Users can create their own assets" -ON assets -FOR INSERT -WITH CHECK (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- UPDATE policy: Users can update their own assets -CREATE POLICY "Users can update their own assets" -ON assets -FOR UPDATE -USING (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- DELETE policy: Users can delete their own assets -CREATE POLICY "Users can delete their own assets" -ON assets -FOR DELETE -USING (owner_id = current_setting('app.current_user_id', TRUE)::uuid); - --- ===================================================== --- activity_log: Users can only see their own activity --- ===================================================== - --- SELECT policy: Users can view their own activity -CREATE POLICY "Users can view their own activity" -ON activity_log -FOR SELECT -USING (user_id = current_setting('app.current_user_id', TRUE)::uuid); - --- INSERT policy: System can create activity logs for any user -CREATE POLICY "System can create activity logs" -ON activity_log -FOR INSERT -WITH CHECK (true); - --- ===================================================== --- Helper function to set current user context --- (Called by application middleware before each request) --- ===================================================== - -CREATE OR REPLACE FUNCTION set_current_user(user_uuid uuid) -RETURNS void AS $$ -BEGIN - PERFORM set_config('app.current_user_id', user_uuid::text, false); -END; -$$ LANGUAGE plpgsql; - --- ===================================================== --- Admin bypass policies (for admin dashboard) --- ===================================================== - --- Admins can view all pipelines -CREATE POLICY "Admins can view all pipelines" -ON generation_pipelines -FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM users - WHERE users.id = current_setting('app.current_user_id', TRUE)::uuid - AND users.role = 'admin' - ) -); - --- Admins can view all assets -CREATE POLICY "Admins can view all assets" -ON assets -FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM users - WHERE users.id = current_setting('app.current_user_id', TRUE)::uuid - AND users.role = 'admin' - ) -); - --- Admins can view all errors -CREATE POLICY "Admins can view all errors" -ON api_errors -FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM users - WHERE users.id = current_setting('app.current_user_id', TRUE)::uuid - AND users.role = 'admin' - ) -); - --- Admins can view all activity -CREATE POLICY "Admins can view all activity" -ON activity_log -FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM users - WHERE users.id = current_setting('app.current_user_id', TRUE)::uuid - AND users.role = 'admin' - ) -); From c33dc2c4ef86208caf6585bc97e869a98c629df9 Mon Sep 17 00:00:00 2001 From: dEXploarer Date: Fri, 21 Nov 2025 20:17:47 -0500 Subject: [PATCH 2/2] Update apps/core/server/db/migrate.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- apps/core/server/db/migrate.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/core/server/db/migrate.ts b/apps/core/server/db/migrate.ts index b71458e..c7cc7d8 100644 --- a/apps/core/server/db/migrate.ts +++ b/apps/core/server/db/migrate.ts @@ -33,7 +33,15 @@ if (!process.env.DATABASE_URL) { logger.info( { migrationsFolder, - databaseUrl: process.env.DATABASE_URL?.replace(/:[^:@]+@/, ":****@"), // Hide password + databaseUrl: (() => { + try { + const url = new URL(process.env.DATABASE_URL!); + url.password = "****"; + return url.toString(); + } catch { + return process.env.DATABASE_URL!.replace(/:[^:@]+@/, ":****@"); + } + })(), // Hide password }, "[Migrations] Starting migration process", );