feat: Add PostgreSQL database infrastructure
- Add SQLAlchemy models for User, Translation, ApiKey, UsageLog, PaymentHistory - Add database connection management with PostgreSQL/SQLite support - Add repository layer for CRUD operations - Add Alembic migration setup with initial migration - Update auth_service to automatically use database when DATABASE_URL is set - Update docker-compose.yml with PostgreSQL service and Redis (non-optional) - Add database migration script (scripts/migrate_to_db.py) - Update .env.example with database configuration
This commit is contained in:
160
scripts/migrate_to_db.py
Normal file
160
scripts/migrate_to_db.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Migration script to move data from JSON files to database
|
||||
Run this once to migrate existing users to the new database system
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from database.connection import init_db, get_db_session
|
||||
from database.repositories import UserRepository
|
||||
from database.models import PlanType, SubscriptionStatus
|
||||
|
||||
|
||||
def migrate_users_from_json():
|
||||
"""Migrate users from JSON file to database"""
|
||||
json_path = Path("data/users.json")
|
||||
|
||||
if not json_path.exists():
|
||||
print("No users.json found, nothing to migrate")
|
||||
return 0
|
||||
|
||||
# Initialize database
|
||||
print("Initializing database tables...")
|
||||
init_db()
|
||||
|
||||
# Load JSON data
|
||||
with open(json_path, 'r') as f:
|
||||
users_data = json.load(f)
|
||||
|
||||
print(f"Found {len(users_data)} users to migrate")
|
||||
|
||||
migrated = 0
|
||||
skipped = 0
|
||||
errors = 0
|
||||
|
||||
with get_db_session() as db:
|
||||
repo = UserRepository(db)
|
||||
|
||||
for user_id, user_data in users_data.items():
|
||||
try:
|
||||
# Check if user already exists
|
||||
existing = repo.get_by_email(user_data.get('email', ''))
|
||||
if existing:
|
||||
print(f" Skipping {user_data.get('email')} - already exists")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Map plan string to enum
|
||||
plan_str = user_data.get('plan', 'free')
|
||||
try:
|
||||
plan = PlanType(plan_str)
|
||||
except ValueError:
|
||||
plan = PlanType.FREE
|
||||
|
||||
# Map subscription status
|
||||
status_str = user_data.get('subscription_status', 'active')
|
||||
try:
|
||||
status = SubscriptionStatus(status_str)
|
||||
except ValueError:
|
||||
status = SubscriptionStatus.ACTIVE
|
||||
|
||||
# Create user with original ID
|
||||
from database.models import User
|
||||
user = User(
|
||||
id=user_id,
|
||||
email=user_data.get('email', '').lower(),
|
||||
name=user_data.get('name', ''),
|
||||
password_hash=user_data.get('password_hash', ''),
|
||||
email_verified=user_data.get('email_verified', False),
|
||||
avatar_url=user_data.get('avatar_url'),
|
||||
plan=plan,
|
||||
subscription_status=status,
|
||||
stripe_customer_id=user_data.get('stripe_customer_id'),
|
||||
stripe_subscription_id=user_data.get('stripe_subscription_id'),
|
||||
docs_translated_this_month=user_data.get('docs_translated_this_month', 0),
|
||||
pages_translated_this_month=user_data.get('pages_translated_this_month', 0),
|
||||
api_calls_this_month=user_data.get('api_calls_this_month', 0),
|
||||
extra_credits=user_data.get('extra_credits', 0),
|
||||
)
|
||||
|
||||
# Parse dates
|
||||
if user_data.get('created_at'):
|
||||
try:
|
||||
user.created_at = datetime.fromisoformat(user_data['created_at'].replace('Z', '+00:00'))
|
||||
except:
|
||||
pass
|
||||
|
||||
if user_data.get('updated_at'):
|
||||
try:
|
||||
user.updated_at = datetime.fromisoformat(user_data['updated_at'].replace('Z', '+00:00'))
|
||||
except:
|
||||
pass
|
||||
|
||||
db.add(user)
|
||||
db.commit()
|
||||
|
||||
print(f" Migrated: {user.email}")
|
||||
migrated += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" Error migrating {user_data.get('email', user_id)}: {e}")
|
||||
errors += 1
|
||||
db.rollback()
|
||||
|
||||
print(f"\nMigration complete:")
|
||||
print(f" Migrated: {migrated}")
|
||||
print(f" Skipped: {skipped}")
|
||||
print(f" Errors: {errors}")
|
||||
|
||||
# Backup original file
|
||||
if migrated > 0:
|
||||
backup_path = json_path.with_suffix('.json.bak')
|
||||
os.rename(json_path, backup_path)
|
||||
print(f"\nOriginal file backed up to: {backup_path}")
|
||||
|
||||
return migrated
|
||||
|
||||
|
||||
def verify_migration():
|
||||
"""Verify the migration was successful"""
|
||||
from database.connection import get_db_session
|
||||
from database.repositories import UserRepository
|
||||
|
||||
with get_db_session() as db:
|
||||
repo = UserRepository(db)
|
||||
count = repo.count_users()
|
||||
print(f"\nDatabase now contains {count} users")
|
||||
|
||||
# List first 5 users
|
||||
users = repo.get_all_users(limit=5)
|
||||
if users:
|
||||
print("\nSample users:")
|
||||
for user in users:
|
||||
print(f" - {user.email} ({user.plan.value})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 50)
|
||||
print("JSON to Database Migration Script")
|
||||
print("=" * 50)
|
||||
|
||||
# Check environment
|
||||
db_url = os.getenv("DATABASE_URL", "")
|
||||
if db_url:
|
||||
print(f"Database: PostgreSQL")
|
||||
else:
|
||||
print(f"Database: SQLite (development)")
|
||||
|
||||
print()
|
||||
|
||||
# Run migration
|
||||
migrate_users_from_json()
|
||||
|
||||
# Verify
|
||||
verify_migration()
|
||||
Reference in New Issue
Block a user