import sys import os from pathlib import Path # Add project root to Python path for imports in alembic migrations project_root = Path(__file__).parent.absolute() sys.path.insert(0, str(project_root)) from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.api.routers import api_router from app.core.config import settings from app.db import init_db # Initialize the database on startup print("Starting database initialization...") try: # Get absolute path of the database file from pathlib import Path db_path = Path("/app/storage/db/db.sqlite").absolute() print(f"Database path: {db_path}") # Check directory permissions db_dir = Path("/app/storage/db") print(f"Database directory exists: {db_dir.exists()}") print(f"Database directory is writable: {os.access(db_dir, os.W_OK)}") # Initialize the database and create test task init_db.init_db() # Try to create a test task try: init_db.create_test_task() except Exception as e: print(f"Error creating test task: {e}") # Continue anyway except Exception as e: print(f"Error initializing database: {e}") import traceback print(f"Detailed error: {traceback.format_exc()}") # Continue with app startup even if DB init fails, to allow debugging app = FastAPI(title=settings.PROJECT_NAME) # Set all CORS enabled origins - Allow all origins app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Add exception handlers for better error reporting @app.exception_handler(Exception) async def validation_exception_handler(request: Request, exc: Exception): import traceback error_detail = { "detail": f"Internal Server Error: {str(exc)}", "type": str(type(exc).__name__), "traceback": traceback.format_exc().split("\n") } print(f"Error processing request: {error_detail}") return JSONResponse( status_code=500, content=error_detail, ) # Include the API router with the version prefix app.include_router(api_router, prefix=settings.API_V1_STR) # Add support for compatibility with non-versioned endpoints from fastapi import Request from fastapi.responses import RedirectResponse # Create a catch-all route for /tasks paths to redirect to versioned API @app.get("/tasks", include_in_schema=False) @app.post("/tasks", include_in_schema=False) @app.get("/tasks/{task_id:path}", include_in_schema=False) @app.put("/tasks/{task_id:path}", include_in_schema=False) @app.delete("/tasks/{task_id:path}", include_in_schema=False) @app.post("/tasks/{task_id:path}/complete", include_in_schema=False) async def redirect_to_versioned_api(request: Request, task_id: str = None): """ Redirect unversioned API requests to the versioned API path """ target_url = str(request.url) # Replace the /tasks part with /api/v1/tasks versioned_url = target_url.replace("/tasks", f"{settings.API_V1_STR}/tasks", 1) # Add debugging info print(f"Redirecting from {target_url} to {versioned_url}") # Use 307 to preserve the method and body return RedirectResponse(url=versioned_url, status_code=307) @app.get("/", tags=["info"]) def api_info(): """ API information endpoint with links to documentation and versioned endpoints """ return { "name": settings.PROJECT_NAME, "version": "1.0.0", "description": "A RESTful API for managing tasks", "endpoints": { "api": f"{settings.API_V1_STR}", "tasks": f"{settings.API_V1_STR}/tasks", "docs": "/docs", "redoc": "/redoc", "health": "/health", "db_test": "/db-test" } } @app.get("/health", tags=["health"]) def health_check(): """ Health check endpoint to verify the application is running correctly """ return {"status": "ok"} @app.get("/db-test", tags=["health"]) def test_db_connection(): """ Test database connection and table creation """ from sqlalchemy import text, inspect from app.db.session import engine import traceback, os, subprocess from app.core.config import DB_DIR try: # Check directory structure and permissions storage_info = { "app_dir_exists": os.path.exists("/app"), "app_dir_writable": os.access("/app", os.W_OK), "storage_dir_exists": os.path.exists("/app/storage"), "storage_dir_writable": os.access("/app/storage", os.W_OK) if os.path.exists("/app/storage") else False, "db_dir_exists": os.path.exists(str(DB_DIR)), "db_dir_writable": os.access(str(DB_DIR), os.W_OK) if os.path.exists(str(DB_DIR)) else False, } # Get disk usage information disk_usage = {} try: df_output = subprocess.check_output(["df", "-h", "/app/storage"]).decode() disk_usage["df_output"] = df_output.strip().split('\n') except Exception as e: disk_usage["error"] = str(e) # Try database connection connection_info = {} inspector = None try: with engine.connect() as conn: connection_info["connection"] = "successful" connection_info["test_query"] = conn.execute(text("SELECT 1")).scalar() inspector = inspect(engine) except Exception as e: connection_info["connection"] = "failed" connection_info["error"] = str(e) # Get table information if connection successful table_info = {} if inspector: tables = inspector.get_table_names() connection_info["tables"] = tables for table in tables: columns = inspector.get_columns(table) table_info[table] = [col['name'] for col in columns] # Try to query task table if it exists if 'task' in tables: with engine.connect() as conn: try: task_count = conn.execute(text("SELECT COUNT(*) FROM task")).scalar() connection_info["task_count"] = task_count except Exception as e: connection_info["task_query_error"] = str(e) # Database file information db_file = f"{DB_DIR}/db.sqlite" db_file_info = { "path": db_file, "exists": os.path.exists(db_file), "size_bytes": os.path.getsize(db_file) if os.path.exists(db_file) else 0, "writable": os.access(db_file, os.W_OK) if os.path.exists(db_file) else False, } # SQLAlchemy configuration from app.core.config import settings db_config = { "sqlalchemy_url": settings.SQLALCHEMY_DATABASE_URL, "connect_args": {"check_same_thread": False} } # Test file creation write_test = {} try: test_file = f"{DB_DIR}/test_db.txt" with open(test_file, 'w') as f: f.write("Test write access") write_test["success"] = True write_test["path"] = test_file os.remove(test_file) # Clean up except Exception as e: write_test["success"] = False write_test["error"] = str(e) return { "status": "ok", "storage_info": storage_info, "disk_usage": disk_usage, "connection_info": connection_info, "table_info": table_info, "db_file_info": db_file_info, "db_config": db_config, "write_test": write_test } except Exception as e: return { "status": "error", "global_error": str(e), "traceback": traceback.format_exc() }