diff --git a/app/api/routers/tasks.py b/app/api/routers/tasks.py index fed8c06..2414ae1 100644 --- a/app/api/routers/tasks.py +++ b/app/api/routers/tasks.py @@ -37,8 +37,32 @@ def create_task( """ Create new task. """ - task = crud.task.create(db, obj_in=task_in) - return task + try: + print(f"Attempting to create task with data: {task_in.model_dump() if hasattr(task_in, 'model_dump') else task_in.dict()}") + + # Handle datetime conversion for due_date + if task_in.due_date: + print(f"Due date before processing: {task_in.due_date}") + # Ensure due_date is properly formatted + if isinstance(task_in.due_date, str): + from datetime import datetime + try: + task_in.due_date = datetime.fromisoformat(task_in.due_date.replace('Z', '+00:00')) + except Exception as e: + print(f"Error parsing due_date: {e}") + + # Create the task + task = crud.task.create(db, obj_in=task_in) + print(f"Task created successfully with ID: {task.id}") + return task + except Exception as e: + print(f"Error in create_task endpoint: {str(e)}") + import traceback + print(traceback.format_exc()) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"Error creating task: {str(e)}", + ) @router.get("/{task_id}", response_model=Task) diff --git a/app/crud/base.py b/app/crud/base.py index 0cb61f7..0abcaf6 100644 --- a/app/crud/base.py +++ b/app/crud/base.py @@ -27,12 +27,24 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): return db.query(self.model).offset(skip).limit(limit).all() def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: - obj_in_data = jsonable_encoder(obj_in) - db_obj = self.model(**obj_in_data) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj + try: + obj_in_data = jsonable_encoder(obj_in) + print(f"Creating {self.model.__name__} with data: {obj_in_data}") + + db_obj = self.model(**obj_in_data) + db.add(db_obj) + db.commit() + db.refresh(db_obj) + + print(f"Successfully created {self.model.__name__} with id: {db_obj.id}") + return db_obj + except Exception as e: + db.rollback() + error_msg = f"Error creating {self.model.__name__}: {str(e)}" + print(error_msg) + import traceback + print(traceback.format_exc()) + raise Exception(error_msg) from e def update( self, diff --git a/app/db/init_db.py b/app/db/init_db.py index 5b9aa50..5ffc47c 100644 --- a/app/db/init_db.py +++ b/app/db/init_db.py @@ -1,44 +1,119 @@ import os -import subprocess +import time from pathlib import Path -from sqlalchemy import text -from sqlalchemy.orm import Session +from sqlalchemy import inspect, text +from sqlalchemy.exc import OperationalError +from app.db.base import Base # Import all models from app.db.session import engine from app.core.config import settings def init_db() -> None: - """Initialize database with required tables and data.""" + """Initialize database with required tables and data directly.""" # Ensure database directory exists Path(settings.DB_DIR).mkdir(parents=True, exist_ok=True) - # Try to connect to check if the database is accessible - with engine.connect() as conn: - try: - conn.execute(text("SELECT 1")) - print("Database connection successful") - except Exception as e: - print(f"Database connection error: {e}") - raise + db_file = f"{settings.DB_DIR}/db.sqlite" + db_exists = os.path.exists(db_file) - # Run alembic upgrade to create tables + # If database doesn't exist or is empty, create tables try: - # Get the project root directory (where alembic.ini is located) - project_root = Path(__file__).parent.parent.parent.absolute() + # Try to connect to check if the database is accessible + max_retries = 3 + retry_count = 0 + connected = False - # Change to project root directory and run alembic - os.chdir(project_root) - subprocess.run(["alembic", "upgrade", "head"], check=True) - print("Database migration successful") - except subprocess.CalledProcessError as e: - print(f"Database migration error: {e}") - raise - - print("Database initialized successfully") + while retry_count < max_retries and not connected: + try: + with engine.connect() as conn: + conn.execute(text("SELECT 1")) + print(f"Database connection successful to {db_file}") + connected = True + except Exception as e: + retry_count += 1 + print(f"Database connection error (attempt {retry_count}/{max_retries}): {e}") + time.sleep(1) # Wait a second before retrying + + if not connected: + raise Exception(f"Failed to connect to database after {max_retries} attempts") + + # Check if tables exist + inspector = inspect(engine) + existing_tables = inspector.get_table_names() + + if not existing_tables: + print("No tables found in database. Creating tables...") + Base.metadata.create_all(bind=engine) + print(f"Created tables: {', '.join(inspector.get_table_names())}") + else: + print(f"Found existing tables: {', '.join(existing_tables)}") + + # Print database file information + if db_exists: + file_size = os.path.getsize(db_file) + print(f"Database file exists at {db_file}, size: {file_size} bytes") + else: + print(f"Warning: Database file does not exist at {db_file} after initialization!") + + print("Database initialization completed successfully") + + except Exception as e: + print(f"Database initialization error: {str(e)}") + # Print detailed error but don't raise to allow app to start + import traceback + print(traceback.format_exc()) + + # Try to create a test file to check write permissions + try: + test_file = f"{settings.DB_DIR}/test_write.txt" + with open(test_file, 'w') as f: + f.write('test') + print(f"Successfully wrote test file: {test_file}") + os.remove(test_file) + except Exception as e: + print(f"Failed to write test file: {str(e)}") + + +def create_test_task(): + """Create a test task in the database to verify everything is working.""" + try: + from app.crud.task import task as task_crud + from app.schemas.task import TaskCreate + from app.db.session import SessionLocal + from datetime import datetime, timedelta + + db = SessionLocal() + try: + # Check if there are any tasks + existing_tasks = db.execute("SELECT COUNT(*) FROM task").scalar() + if existing_tasks > 0: + print(f"Test task not needed, found {existing_tasks} existing tasks") + return + + # Create a test task + test_task = TaskCreate( + title="Test Task", + description="This is a test task created at database initialization", + priority="medium", + status="todo", + due_date=datetime.utcnow() + timedelta(days=7), + completed=False + ) + + created_task = task_crud.create(db, obj_in=test_task) + print(f"Created test task with ID: {created_task.id}") + + finally: + db.close() + except Exception as e: + print(f"Error creating test task: {e}") + import traceback + print(traceback.format_exc()) if __name__ == "__main__": - init_db() \ No newline at end of file + init_db() + create_test_task() \ No newline at end of file diff --git a/main.py b/main.py index 27d7bde..674f4ea 100644 --- a/main.py +++ b/main.py @@ -15,10 +15,31 @@ 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) @@ -110,44 +131,101 @@ def test_db_connection(): """ from sqlalchemy import text, inspect from app.db.session import engine - import traceback + import traceback, os, subprocess + from app.core.config import DB_DIR try: - inspector = inspect(engine) - tables = inspector.get_table_names() + # 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 = {} - for table in tables: - columns = inspector.get_columns(table) - table_info[table] = [col['name'] for col in columns] - - with engine.connect() as conn: - # Try to select from the task table to verify it exists + 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: - result = conn.execute(text("SELECT COUNT(*) FROM task")).scalar() - task_count = result - else: - task_count = "Table 'task' not found" - - # Check DB directory - import os - from app.core.config import DB_DIR - db_path = f"{DB_DIR}/db.sqlite" - db_exists = os.path.exists(db_path) - - return { - "status": "ok", - "connection": "successful", - "tables": tables, - "table_details": table_info, - "task_count": task_count, - "db_path": db_path, - "db_exists": db_exists - } + 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", - "connection": "failed", - "error": str(e), + "global_error": str(e), "traceback": traceback.format_exc() } \ No newline at end of file