From 58d56c3200648bd1f3a8c88334083ac5aa0cb70e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Jul 2025 10:10:42 +0530 Subject: [PATCH] Implement smart database auto-detection for local/Railway compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Smart Database Configuration:** - Auto-detects environment: Railway vs Local development - Tests PostgreSQL connection before using it - Falls back to SQLite if PostgreSQL unavailable - Preserves Railway DATABASE_URL when present **Environment Detection Logic:** 1. Railway: Uses Railway's DATABASE_URL (PostgreSQL) 2. Local with DATABASE_URL: Uses specified database 3. Local with PostgreSQL running: Uses PostgreSQL 4. Local without PostgreSQL: Falls back to SQLite **New Management Command:** - `check_db`: Diagnose database configuration and connection - Shows current engine, connection status, table count - Provides troubleshooting tips for PostgreSQL setup **Solves the Compatibility Problem:** - Local development: Works without PostgreSQL setup (SQLite fallback) - Railway production: Always uses PostgreSQL (via DATABASE_URL) - No more "works locally but fails on Railway" issues - No more "needs PostgreSQL locally to test Railway compatibility" **Benefits:** - Seamless development experience - Production-development parity when PostgreSQL available - Graceful degradation to SQLite when needed - Zero configuration required for basic development šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agent_base/management/commands/check_db.py | 108 +++++++++++++++++++++ netcop_hub/settings.py | 64 ++++++++++-- 2 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 agent_base/management/commands/check_db.py diff --git a/agent_base/management/commands/check_db.py b/agent_base/management/commands/check_db.py new file mode 100644 index 0000000..cbdbcdd --- /dev/null +++ b/agent_base/management/commands/check_db.py @@ -0,0 +1,108 @@ +from django.core.management.base import BaseCommand +from django.conf import settings +from django.db import connection +import os + + +class Command(BaseCommand): + help = 'Check current database configuration and connection' + + def handle(self, *args, **options): + self.stdout.write("šŸ” Database Configuration Check") + self.stdout.write("=" * 40) + + # Environment detection + is_railway = bool(os.environ.get('RAILWAY_ENVIRONMENT')) + database_url = os.environ.get('DATABASE_URL', '') + + self.stdout.write(f"Environment: {'Railway' if is_railway else 'Local Development'}") + self.stdout.write(f"DATABASE_URL set: {'Yes' if database_url else 'No'}") + + if database_url: + # Mask password in URL for display + masked_url = database_url + if '@' in masked_url and '://' in masked_url: + parts = masked_url.split('://') + if len(parts) == 2: + scheme = parts[0] + rest = parts[1] + if '@' in rest: + auth_part, host_part = rest.split('@', 1) + if ':' in auth_part: + user, password = auth_part.split(':', 1) + masked_url = f"{scheme}://{user}:***@{host_part}" + self.stdout.write(f"DATABASE_URL: {masked_url}") + + # Current Django database configuration + db_config = settings.DATABASES['default'] + engine = db_config['ENGINE'] + + self.stdout.write(f"\\nCurrent Django Configuration:") + self.stdout.write(f"Engine: {engine}") + + if 'postgresql' in engine: + self.stdout.write(f"Database: {db_config.get('NAME', 'N/A')}") + self.stdout.write(f"Host: {db_config.get('HOST', 'N/A')}") + self.stdout.write(f"Port: {db_config.get('PORT', 'N/A')}") + self.stdout.write(f"User: {db_config.get('USER', 'N/A')}") + elif 'sqlite' in engine: + self.stdout.write(f"Database file: {db_config.get('NAME', 'N/A')}") + + # Test connection + self.stdout.write(f"\\nšŸ”Œ Testing Database Connection...") + try: + with connection.cursor() as cursor: + if 'postgresql' in engine: + cursor.execute("SELECT version();") + version = cursor.fetchone()[0] + self.stdout.write(f"āœ… PostgreSQL Connection: {version}") + elif 'sqlite' in engine: + cursor.execute("SELECT sqlite_version();") + version = cursor.fetchone()[0] + self.stdout.write(f"āœ… SQLite Connection: {version}") + + # Check if tables exist + if 'postgresql' in engine: + cursor.execute(""" + SELECT COUNT(*) FROM information_schema.tables + WHERE table_schema = 'public' + """) + else: + cursor.execute(""" + SELECT COUNT(*) FROM sqlite_master + WHERE type='table' AND name NOT LIKE 'sqlite_%' + """) + + table_count = cursor.fetchone()[0] + self.stdout.write(f"šŸ“Š Database tables: {table_count}") + + if table_count == 0: + self.stdout.write("āš ļø No tables found. Run: python manage.py migrate") + + except Exception as e: + self.stdout.write(f"āŒ Connection failed: {e}") + + if 'postgresql' in engine: + self.stdout.write("\\nšŸ’” PostgreSQL Connection Tips:") + self.stdout.write("1. Install PostgreSQL: brew install postgresql") + self.stdout.write("2. Start PostgreSQL: brew services start postgresql") + self.stdout.write("3. Create database: createdb netcop_hub") + self.stdout.write("4. Create user: createuser netcop_user -P") + self.stdout.write("5. Or use Docker: docker run --name netcop-postgres -e POSTGRES_DB=netcop_hub -e POSTGRES_USER=netcop_user -e POSTGRES_PASSWORD=netcop_pass -p 5432:5432 -d postgres:15") + + # Module availability check + self.stdout.write(f"\\nšŸ“¦ Module Availability:") + try: + import psycopg2 + self.stdout.write("āœ… psycopg2 (PostgreSQL driver) available") + except ImportError: + self.stdout.write("āŒ psycopg2 not available") + + try: + import sqlite3 + self.stdout.write("āœ… sqlite3 available") + except ImportError: + self.stdout.write("āŒ sqlite3 not available") + + self.stdout.write("\\n" + "=" * 40) + self.stdout.write("Database check complete!") \ No newline at end of file diff --git a/netcop_hub/settings.py b/netcop_hub/settings.py index 2689e48..57f85ee 100644 --- a/netcop_hub/settings.py +++ b/netcop_hub/settings.py @@ -89,18 +89,70 @@ WSGI_APPLICATION = 'netcop_hub.wsgi.application' import dj_database_url -# Use DATABASE_URL if available (Railway, Heroku, etc.) -if config('DATABASE_URL', default=''): +# Smart database configuration: Auto-detect environment +database_url = config('DATABASE_URL', default='') + +if database_url: + # Parse the provided DATABASE_URL DATABASES = { - 'default': dj_database_url.parse(config('DATABASE_URL')) + 'default': dj_database_url.parse(database_url) } -else: +elif config('RAILWAY_ENVIRONMENT', default=''): + # Railway environment without DATABASE_URL (shouldn't happen, but fallback) DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': config('PGDATABASE', default='railway'), + 'USER': config('PGUSER', default='postgres'), + 'PASSWORD': config('PGPASSWORD', default=''), + 'HOST': config('PGHOST', default='localhost'), + 'PORT': config('PGPORT', default='5432'), } } +else: + # Local development: Try PostgreSQL first, fallback to SQLite + try: + import psycopg2 + # Test if PostgreSQL is actually available + try: + # Quick connection test + test_conn = psycopg2.connect( + host='localhost', + database='netcop_hub', + user='netcop_user', + password='netcop_pass', + port='5432', + connect_timeout=3 + ) + test_conn.close() + + # PostgreSQL works - use it + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': 'netcop_hub', + 'USER': 'netcop_user', + 'PASSWORD': 'netcop_pass', + 'HOST': 'localhost', + 'PORT': '5432', + } + } + except (psycopg2.OperationalError, psycopg2.Error): + # PostgreSQL not available - fallback to SQLite + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } + except ImportError: + # psycopg2 not available - use SQLite + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } # Password validation