mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 19:32:58 +00:00
**Fixed Railway Startup Issues:** - Reorder commands: migrate before backup_users - Make backup_users handle missing tables gracefully - Add proper error handling for unmigrated databases - Check for table existence before querying users **Added Railway Setup Guide:** - Complete PostgreSQL configuration instructions - Environment variables template - Database verification commands - Troubleshooting steps for persistent storage **Key Issue Resolution:** - Railway using SQLite (ephemeral) instead of PostgreSQL - Users disappear because SQLite resets on each deployment - Need to add PostgreSQL database addon in Railway **Next Steps:** 1. Add PostgreSQL database in Railway dashboard 2. Verify DATABASE_URL environment variable 3. Deploy with persistent database 4. Create admin user via Railway console 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
211 lines
8.1 KiB
Python
211 lines
8.1 KiB
Python
from django.core.management.base import BaseCommand
|
|
from django.contrib.auth import get_user_model
|
|
from wallet.models import WalletTransaction
|
|
import json
|
|
from decimal import Decimal
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Backup and restore user data for Railway deployments'
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
'--action',
|
|
choices=['backup', 'restore', 'info'],
|
|
default='info',
|
|
help='Action to perform: backup, restore, or info',
|
|
)
|
|
parser.add_argument(
|
|
'--file',
|
|
default='users_backup.json',
|
|
help='Backup file path',
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
action = options['action']
|
|
backup_file = options['file']
|
|
|
|
if action == 'info':
|
|
self.show_database_info()
|
|
elif action == 'backup':
|
|
self.backup_users(backup_file)
|
|
elif action == 'restore':
|
|
self.restore_users(backup_file)
|
|
|
|
def show_database_info(self):
|
|
"""Show current database state"""
|
|
self.stdout.write("=== DATABASE INFO ===")
|
|
|
|
# Database backend
|
|
from django.conf import settings
|
|
from django.db import connection
|
|
db_config = settings.DATABASES['default']
|
|
self.stdout.write(f"Database Engine: {db_config['ENGINE']}")
|
|
if 'NAME' in db_config:
|
|
self.stdout.write(f"Database Name: {db_config['NAME']}")
|
|
|
|
# Check if tables exist
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
|
tables = [row[0] for row in cursor.fetchall()]
|
|
self.stdout.write(f"Database tables: {len(tables)} found")
|
|
|
|
if 'authentication_user' not in tables:
|
|
self.stdout.write("⚠️ User table not found - database not yet migrated")
|
|
return
|
|
except Exception as e:
|
|
self.stdout.write(f"⚠️ Could not check database tables: {e}")
|
|
return
|
|
|
|
try:
|
|
# User counts
|
|
total_users = User.objects.count()
|
|
superusers = User.objects.filter(is_superuser=True).count()
|
|
regular_users = total_users - superusers
|
|
|
|
self.stdout.write(f"Total Users: {total_users}")
|
|
self.stdout.write(f"Superusers: {superusers}")
|
|
self.stdout.write(f"Regular Users: {regular_users}")
|
|
|
|
# List superusers
|
|
if superusers > 0:
|
|
self.stdout.write("\\nSuperusers:")
|
|
for user in User.objects.filter(is_superuser=True):
|
|
self.stdout.write(f" - {user.email} (username: {user.username})")
|
|
|
|
# Wallet info
|
|
total_transactions = WalletTransaction.objects.count()
|
|
self.stdout.write(f"\\nWallet Transactions: {total_transactions}")
|
|
|
|
# Users with positive balance
|
|
users_with_balance = User.objects.filter(wallet_balance__gt=0).count()
|
|
self.stdout.write(f"Users with balance: {users_with_balance}")
|
|
|
|
except Exception as e:
|
|
self.stdout.write(f"⚠️ Could not read user data: {e}")
|
|
self.stdout.write("Database may not be fully migrated yet")
|
|
|
|
def backup_users(self, backup_file):
|
|
"""Backup all users and their wallet data"""
|
|
self.stdout.write(f"Backing up users to {backup_file}...")
|
|
|
|
backup_data = {
|
|
'users': [],
|
|
'transactions': []
|
|
}
|
|
|
|
# Backup users
|
|
for user in User.objects.all():
|
|
user_data = {
|
|
'username': user.username,
|
|
'email': user.email,
|
|
'first_name': user.first_name,
|
|
'last_name': user.last_name,
|
|
'is_superuser': user.is_superuser,
|
|
'is_staff': user.is_staff,
|
|
'is_active': user.is_active,
|
|
'wallet_balance': str(user.wallet_balance),
|
|
'date_joined': user.date_joined.isoformat(),
|
|
}
|
|
backup_data['users'].append(user_data)
|
|
|
|
# Backup transactions
|
|
for transaction in WalletTransaction.objects.all():
|
|
transaction_data = {
|
|
'user_email': transaction.user.email,
|
|
'amount': str(transaction.amount),
|
|
'type': transaction.type,
|
|
'description': transaction.description,
|
|
'agent_slug': transaction.agent_slug,
|
|
'stripe_session_id': transaction.stripe_session_id,
|
|
'created_at': transaction.created_at.isoformat(),
|
|
}
|
|
backup_data['transactions'].append(transaction_data)
|
|
|
|
# Write to file
|
|
with open(backup_file, 'w') as f:
|
|
json.dump(backup_data, f, indent=2)
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Backed up {len(backup_data['users'])} users and "
|
|
f"{len(backup_data['transactions'])} transactions to {backup_file}"
|
|
)
|
|
)
|
|
|
|
def restore_users(self, backup_file):
|
|
"""Restore users from backup file"""
|
|
try:
|
|
with open(backup_file, 'r') as f:
|
|
backup_data = json.load(f)
|
|
except FileNotFoundError:
|
|
self.stdout.write(
|
|
self.style.ERROR(f"Backup file {backup_file} not found")
|
|
)
|
|
return
|
|
|
|
self.stdout.write(f"Restoring users from {backup_file}...")
|
|
|
|
users_created = 0
|
|
users_updated = 0
|
|
transactions_created = 0
|
|
|
|
# Restore users
|
|
for user_data in backup_data.get('users', []):
|
|
user, created = User.objects.get_or_create(
|
|
email=user_data['email'],
|
|
defaults={
|
|
'username': user_data['username'],
|
|
'first_name': user_data['first_name'],
|
|
'last_name': user_data['last_name'],
|
|
'is_superuser': user_data['is_superuser'],
|
|
'is_staff': user_data['is_staff'],
|
|
'is_active': user_data['is_active'],
|
|
'wallet_balance': Decimal(user_data['wallet_balance']),
|
|
}
|
|
)
|
|
|
|
if created:
|
|
users_created += 1
|
|
self.stdout.write(f"Created user: {user.email}")
|
|
else:
|
|
# Update wallet balance for existing users
|
|
user.wallet_balance = Decimal(user_data['wallet_balance'])
|
|
user.save()
|
|
users_updated += 1
|
|
self.stdout.write(f"Updated user: {user.email}")
|
|
|
|
# Restore transactions
|
|
for transaction_data in backup_data.get('transactions', []):
|
|
try:
|
|
user = User.objects.get(email=transaction_data['user_email'])
|
|
transaction, created = WalletTransaction.objects.get_or_create(
|
|
user=user,
|
|
amount=Decimal(transaction_data['amount']),
|
|
type=transaction_data['type'],
|
|
description=transaction_data['description'],
|
|
created_at=transaction_data['created_at'],
|
|
defaults={
|
|
'agent_slug': transaction_data.get('agent_slug', ''),
|
|
'stripe_session_id': transaction_data.get('stripe_session_id', ''),
|
|
}
|
|
)
|
|
|
|
if created:
|
|
transactions_created += 1
|
|
except User.DoesNotExist:
|
|
self.stdout.write(
|
|
self.style.WARNING(
|
|
f"User {transaction_data['user_email']} not found for transaction"
|
|
)
|
|
)
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Restore complete: {users_created} users created, "
|
|
f"{users_updated} users updated, {transactions_created} transactions created"
|
|
)
|
|
) |