🔧 chore(auth): improve migration safety and docker setup

- Add safe-db-push.mjs script for safer database migrations
- Update docker-entrypoint.sh with db:push fallback when migrations fail
- Add validate-migrations.mjs script for CI migration validation
- Update CI workflow to use migration validation
- Update drizzle.config.ts with improved configuration
This commit is contained in:
Wuesteon 2025-12-19 02:18:31 +01:00
parent 319ccd1a46
commit 9e771c9ae2
7 changed files with 353 additions and 5 deletions

View file

@ -1,9 +1,35 @@
#!/bin/sh
set -e
# Skip migrations in Docker - tables are managed via 'pnpm db:push' locally
# For fresh databases, run 'pnpm db:push' manually first
echo "📋 Skipping migrations (run 'pnpm db:push' locally if needed)"
# Run database migrations using proper migration files
# This is SAFE - only applies versioned migration files, never drops tables
echo "📋 Running database migrations..."
# Wait for PostgreSQL to be ready (up to 60 seconds)
MAX_RETRIES=30
RETRY_COUNT=0
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
# db:migrate uses tsx which needs node, so we run it via pnpm
if pnpm db:migrate 2>&1; then
echo "✅ Database migrations completed successfully"
break
else
EXIT_CODE=$?
RETRY_COUNT=$((RETRY_COUNT + 1))
# Check if it's a connection error (exit code is typically non-zero for connection issues)
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
echo "⏳ Database not ready or migration in progress, retrying in 2 seconds... ($RETRY_COUNT/$MAX_RETRIES)"
sleep 2
else
echo "❌ Failed to run database migrations after $MAX_RETRIES attempts"
echo " Exit code: $EXIT_CODE"
echo " Check database connectivity and migration files"
exit 1
fi
fi
done
# Start the application
echo "🚀 Starting Mana Core Auth..."

View file

@ -7,7 +7,7 @@ export default defineConfig({
dbCredentials: {
url: process.env.DATABASE_URL || 'postgresql://manacore:devpassword@localhost:5432/manacore',
},
schemaFilter: ['auth', 'credits', 'referrals', 'public'],
schemaFilter: ['auth', 'credits', 'error_logs', 'referrals', 'public'],
verbose: true,
strict: true,
});

View file

@ -15,7 +15,9 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json",
"db:push": "drizzle-kit push",
"db:push": "node scripts/safe-db-push.mjs",
"db:push:force": "node scripts/safe-db-push.mjs --force",
"db:push:unsafe": "drizzle-kit push",
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx src/db/migrate.ts",
"db:studio": "drizzle-kit studio"

View file

@ -0,0 +1,95 @@
#!/usr/bin/env node
/**
* Safe db:push wrapper
*
* This script wraps drizzle-kit push to prevent accidental execution
* in production or staging environments.
*
* Usage:
* pnpm db:push # Uses this wrapper
* pnpm db:push:force # Bypass safety check (for emergencies only)
*/
import { execSync } from 'child_process';
const NODE_ENV = process.env.NODE_ENV || 'development';
const DATABASE_URL = process.env.DATABASE_URL || '';
// Check for production/staging indicators
const BLOCKED_ENVS = ['production', 'staging', 'prod', 'stage'];
const PROD_HOST_PATTERNS = [
/\.railway\.app/,
/\.supabase\.co/,
/\.neon\.tech/,
/\.render\.com/,
/staging\./,
/prod\./,
/production\./,
];
function isProductionEnvironment() {
// Check NODE_ENV
if (BLOCKED_ENVS.includes(NODE_ENV.toLowerCase())) {
return { blocked: true, reason: `NODE_ENV is set to '${NODE_ENV}'` };
}
// Check DATABASE_URL for production patterns
for (const pattern of PROD_HOST_PATTERNS) {
if (pattern.test(DATABASE_URL)) {
return {
blocked: true,
reason: `DATABASE_URL contains production pattern: ${pattern}`,
};
}
}
// Check for CI/CD environment
if (process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true') {
return { blocked: true, reason: 'Running in CI/CD environment' };
}
return { blocked: false };
}
function main() {
const args = process.argv.slice(2);
const forceFlag = args.includes('--force') || args.includes('-f');
console.log('🔒 Safe db:push - Environment Check\n');
const check = isProductionEnvironment();
if (check.blocked && !forceFlag) {
console.log('❌ BLOCKED: db:push is not allowed in this environment\n');
console.log(` Reason: ${check.reason}\n`);
console.log(' db:push can cause data loss and should only be used in development.\n');
console.log(' For production/staging, use:');
console.log(' pnpm db:generate # Generate migration files');
console.log(' pnpm db:migrate # Apply migrations safely\n');
console.log(' If you absolutely need to run db:push (DANGEROUS):');
console.log(' pnpm db:push:force\n');
process.exit(1);
}
if (check.blocked && forceFlag) {
console.log('⚠️ WARNING: --force flag detected, bypassing safety check\n');
console.log(` Blocked reason was: ${check.reason}\n`);
console.log(' THIS MAY CAUSE DATA LOSS. Proceeding in 5 seconds...\n');
// Give user time to cancel
execSync('sleep 5');
}
console.log('✅ Environment check passed\n');
console.log('📤 Running drizzle-kit push...\n');
try {
// Pass through any additional args (except --force)
const drizzleArgs = args.filter((arg) => arg !== '--force' && arg !== '-f').join(' ');
execSync(`pnpm drizzle-kit push ${drizzleArgs}`, { stdio: 'inherit' });
} catch {
process.exit(1);
}
}
main();