Fix database migration error and add error handling
This commit is contained in:
parent
d01f405afa
commit
04a64ff424
@ -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
|
@ -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
36
main.py
@ -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__":
|
||||
|
@ -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,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user