diff --git a/apps/core/server/db/migrate.ts b/apps/core/server/db/migrate.ts index cb0cca9..c7cc7d8 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,23 @@ if (!process.env.DATABASE_URL) { process.exit(1); } +// Log migration info for debugging +logger.info( + { + migrationsFolder, + 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", +); + // Migration connection with NOTICE suppression const migrationClient = postgres(process.env.DATABASE_URL, { max: 1, @@ -34,7 +58,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 +73,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' - ) -);