Fix database migration error and add error handling

This commit is contained in:
Automated Action 2025-06-01 10:30:40 +00:00
parent d01f405afa
commit 04a64ff424
4 changed files with 142 additions and 15 deletions

View File

@ -35,6 +35,9 @@ script_location = migrations
# are written from script.py.mako
# output_encoding = utf-8
# Use absolute path for SQLite URL
sqlalchemy.url = sqlite:////app/storage/db/db.sqlite
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
@ -68,7 +71,4 @@ formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
# Use absolute path for SQLite URL
sqlalchemy.url = sqlite:////app/storage/db/db.sqlite
datefmt = %H:%M:%S

View File

@ -1,17 +1,93 @@
import logging
import os
from pathlib import Path
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from app.core.config import settings
from app.db.session import Base, engine
from app.models import Category, Item, User # Will be implemented later
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def ensure_db_dir_exists() -> None:
"""
Ensure the database directory exists.
"""
try:
settings.DB_DIR.mkdir(parents=True, exist_ok=True)
logger.info(f"Database directory ensured at {settings.DB_DIR}")
except PermissionError:
logger.error(f"Permission denied when creating directory {settings.DB_DIR}")
raise
except Exception as e:
logger.error(f"Error creating database directory: {str(e)}")
raise
def init_db() -> None:
"""
Initialize the database.
Creates all tables defined in the models.
"""
# Create tables
logger.info("Creating database tables")
Base.metadata.create_all(bind=engine)
logger.info("Database tables created")
try:
# Ensure the database directory exists
ensure_db_dir_exists()
# Check if we can access the database file path
db_file = Path(settings.SQLALCHEMY_DATABASE_URL.replace("sqlite:///", ""))
db_dir = db_file.parent
if not db_dir.exists():
logger.warning(f"Database directory does not exist: {db_dir}")
db_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"Created database directory: {db_dir}")
# Create tables
logger.info("Creating database tables")
Base.metadata.create_all(bind=engine)
logger.info("Database tables created")
except SQLAlchemyError as e:
logger.error(f"SQLAlchemy error during database initialization: {str(e)}")
raise
except Exception as e:
logger.error(f"Unexpected error during database initialization: {str(e)}")
raise
def create_first_superuser(db: Session) -> None:
"""
Create the first superuser if it doesn't exist.
This can be called after init_db() to ensure there's at least one admin user.
"""
from app.core.security import get_password_hash
from app.crud.user import user
from app.schemas.user import UserCreate
# Check if there are any users in the database
first_user = db.query(User).first()
if not first_user:
logger.info("No users found. Creating initial superuser...")
try:
# Get superuser credentials from environment variables or use defaults
admin_email = os.getenv("FIRST_SUPERUSER_EMAIL", "admin@example.com")
admin_password = os.getenv("FIRST_SUPERUSER_PASSWORD", "adminpassword")
# Create user
user_in = UserCreate(
email=admin_email,
password=admin_password,
is_superuser=True,
full_name="Admin"
)
user.create(db, obj_in=user_in)
logger.info(f"Initial superuser created with email: {admin_email}")
except Exception as e:
logger.error(f"Error creating initial superuser: {str(e)}")
raise
else:
logger.info("Users exist in the database. Skipping superuser creation.")

36
main.py
View File

@ -1,9 +1,15 @@
import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from app.api.v1.api import api_router
from app.core.config import settings
from app.db.init_db import ensure_db_dir_exists, init_db
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create FastAPI app
app = FastAPI(
@ -24,13 +30,41 @@ if settings.BACKEND_CORS_ORIGINS:
# Include API router
app.include_router(api_router, prefix=settings.API_V1_STR)
# Startup event to initialize database
@app.on_event("startup")
async def startup_db_client():
try:
logger.info("Ensuring database directory exists")
ensure_db_dir_exists()
logger.info("Database directory check completed")
except Exception as e:
logger.error(f"Failed to initialize database directory: {str(e)}")
# We don't want to crash the app if this fails, just log the error
# Health check endpoint
@app.get("/health", response_model=None)
async def health_check():
"""
Health check endpoint to verify the API is running.
"""
return JSONResponse(content={"status": "healthy"})
# Check database directory
try:
db_dir_exists = settings.DB_DIR.exists()
except Exception:
db_dir_exists = False
health_status = {
"status": "healthy",
"api": "ok",
"db_directory": "ok" if db_dir_exists else "missing",
}
if not db_dir_exists:
health_status["status"] = "degraded"
return JSONResponse(content=health_status)
if __name__ == "__main__":

View File

@ -9,6 +9,7 @@ from sqlalchemy import engine_from_config, pool
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Import the SQLAlchemy metadata object
from app.core.config import settings
from app.db.session import Base
from app.models import * # Import all models for Alembic to detect them
@ -16,6 +17,10 @@ from app.models import * # Import all models for Alembic to detect them
# access to the values within the .ini file in use.
config = context.config
# Set the sqlalchemy url from the app config if not already set
if not config.get_main_option("sqlalchemy.url"):
config.set_main_option("sqlalchemy.url", settings.SQLALCHEMY_DATABASE_URL)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
@ -24,11 +29,6 @@ fileConfig(config.config_file_name)
# for 'autogenerate' support
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
@ -43,6 +43,9 @@ def run_migrations_offline():
"""
url = config.get_main_option("sqlalchemy.url")
if not url:
raise ValueError("Database URL is not configured. Check alembic.ini or environment variables.")
context.configure(
url=url,
target_metadata=target_metadata,
@ -61,8 +64,22 @@ def run_migrations_online():
and associate a connection with the context.
"""
# Ensure we have a URL configured
url = config.get_main_option("sqlalchemy.url")
if not url:
raise ValueError("Database URL is not configured. Check alembic.ini or environment variables.")
# Build configuration dictionary
alembic_config = config.get_section(config.config_ini_section)
if not alembic_config:
alembic_config = {}
# Ensure sqlalchemy.url is in the config
alembic_config["sqlalchemy.url"] = url
# Create engine
connectable = engine_from_config(
config.get_section(config.config_ini_section),
alembic_config,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)