| """ |
| API routes for SQLite fallback database management. |
| |
| Provides endpoints for monitoring, testing, and managing |
| the SQLite fallback database system. |
| """ |
|
|
| import logging |
| from typing import Dict, Any |
| from fastapi import APIRouter, HTTPException, status, Depends |
| from sqlalchemy.ext.asyncio import AsyncSession |
|
|
| from core.database import get_db |
| from core.fallback_database import ( |
| check_database_health, |
| get_current_database_type, |
| switch_to_primary, |
| switch_to_fallback |
| ) |
| from core.sqlite_fallback_manager import SQLiteFallbackManager, test_fallback_mechanism |
| from schemas.response_schema import SuccessResponse, ErrorResponse |
| from api.dependencies.auth import get_current_active_user |
| from db_models.user import User |
|
|
| logger = logging.getLogger(__name__) |
|
|
| router = APIRouter(prefix="/sqlite-fallback", tags=["sqlite-fallback"]) |
|
|
|
|
| @router.get("/status", response_model=SuccessResponse) |
| async def get_fallback_status( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Get SQLite fallback database status. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Fallback database status |
| |
| Raises: |
| HTTPException: If status check fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| manager = SQLiteFallbackManager() |
| status_info = await manager.get_fallback_status() |
| |
| |
| is_healthy, active_db_type = await check_database_health() |
| status_info.update({ |
| "current_active_database": active_db_type, |
| "overall_healthy": is_healthy |
| }) |
| |
| return SuccessResponse( |
| message="SQLite fallback status retrieved successfully", |
| data=status_info |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to get fallback status: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to retrieve fallback status" |
| ) |
|
|
|
|
| @router.post("/test", response_model=SuccessResponse) |
| async def test_fallback_system( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Test SQLite fallback mechanism. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Test results |
| |
| Raises: |
| HTTPException: If test fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| test_results = await test_fallback_mechanism() |
| |
| return SuccessResponse( |
| message="SQLite fallback test completed", |
| data=test_results |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to test fallback system: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to test fallback system" |
| ) |
|
|
|
|
| @router.post("/switch-to-primary", response_model=SuccessResponse) |
| async def force_switch_to_primary( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Force switch back to primary PostgreSQL database. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Switch operation result |
| |
| Raises: |
| HTTPException: If switch fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| success = await switch_to_primary() |
| |
| if success: |
| return SuccessResponse( |
| message="Successfully switched to primary PostgreSQL database", |
| data={"current_database": "primary"} |
| ) |
| else: |
| return SuccessResponse( |
| message="Failed to switch to primary database - database not available", |
| data={"current_database": get_current_database_type()} |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to switch to primary database: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to switch to primary database" |
| ) |
|
|
|
|
| @router.post("/switch-to-fallback", response_model=SuccessResponse) |
| async def force_switch_to_fallback( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Force switch to SQLite fallback database. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Switch operation result |
| |
| Raises: |
| HTTPException: If switch fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| success = await switch_to_fallback() |
| |
| if success: |
| return SuccessResponse( |
| message="Successfully switched to SQLite fallback database", |
| data={"current_database": "fallback"} |
| ) |
| else: |
| return SuccessResponse( |
| message="Failed to switch to fallback database - fallback not available", |
| data={"current_database": get_current_database_type()} |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to switch to fallback database: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to switch to fallback database" |
| ) |
|
|
|
|
| @router.post("/migrate-from-primary", response_model=SuccessResponse) |
| async def migrate_from_primary_database( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Migrate data from primary PostgreSQL to SQLite fallback. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Migration result |
| |
| Raises: |
| HTTPException: If migration fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| manager = SQLiteFallbackManager() |
| success = await manager.migrate_from_postgresql() |
| |
| if success: |
| return SuccessResponse( |
| message="Data migration from PostgreSQL to SQLite completed successfully", |
| data={"migration_status": "completed"} |
| ) |
| else: |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Data migration failed" |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to migrate from primary database: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to migrate from primary database" |
| ) |
|
|
|
|
| @router.post("/sync-to-primary", response_model=SuccessResponse) |
| async def sync_to_primary_database( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Sync data from SQLite fallback back to primary PostgreSQL. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Sync result |
| |
| Raises: |
| HTTPException: If sync fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| manager = SQLiteFallbackManager() |
| success = await manager.sync_to_postgresql() |
| |
| if success: |
| return SuccessResponse( |
| message="Data sync from SQLite to PostgreSQL completed successfully", |
| data={"sync_status": "completed"} |
| ) |
| else: |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Data sync failed" |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to sync to primary database: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to sync to primary database" |
| ) |
|
|
|
|
| @router.post("/backup", response_model=SuccessResponse) |
| async def backup_fallback_database( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Create backup of SQLite fallback database. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Backup result |
| |
| Raises: |
| HTTPException: If backup fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| manager = SQLiteFallbackManager() |
| success = await manager.backup_fallback_database() |
| |
| if success: |
| return SuccessResponse( |
| message="SQLite fallback database backup created successfully", |
| data={"backup_status": "completed"} |
| ) |
| else: |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Database backup failed" |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to backup fallback database: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to backup fallback database" |
| ) |
|
|
|
|
| @router.delete("/cleanup", response_model=SuccessResponse) |
| async def cleanup_fallback_database( |
| current_user: User = Depends(get_current_active_user), |
| db: AsyncSession = Depends(get_db) |
| ): |
| """ |
| Clean up (remove) SQLite fallback database file. |
| |
| Args: |
| current_user: Current authenticated user |
| db: Database session |
| |
| Returns: |
| SuccessResponse: Cleanup result |
| |
| Raises: |
| HTTPException: If cleanup fails |
| """ |
| try: |
| |
| if not current_user.is_superuser: |
| raise HTTPException( |
| status_code=status.HTTP_403_FORBIDDEN, |
| detail="Access denied. Superuser access required." |
| ) |
| |
| manager = SQLiteFallbackManager() |
| success = await manager.cleanup_fallback_database() |
| |
| if success: |
| return SuccessResponse( |
| message="SQLite fallback database cleaned up successfully", |
| data={"cleanup_status": "completed"} |
| ) |
| else: |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Database cleanup failed" |
| ) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Failed to cleanup fallback database: {e}") |
| raise HTTPException( |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| detail="Failed to cleanup fallback database" |
| ) |
|
|