import uuid import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles import os from .database import init_db, AsyncSessionLocal from .config import get_settings from .models import User, Library from .services.auth import hash_password, create_token from .services.file_watcher import start_file_watcher, stop_file_watcher from sqlalchemy import select logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) logger = logging.getLogger(__name__) async def _seed_admin(): settings = get_settings() async with AsyncSessionLocal() as db: result = await db.execute(select(User).where(User.is_admin == True)) if result.scalar_one_or_none(): return # Admin existiert bereits logger.info(f"Lege Admin-User an: {settings.admin_username}") admin = User( id=str(uuid.uuid4()), username=settings.admin_username, email=settings.admin_email, password_hash=hash_password(settings.admin_password), is_admin=True, ) db.add(admin) await db.flush() # Token mit echter ID erstellen admin.token = create_token(admin.id) await db.commit() logger.info("Admin-User angelegt.") async def _seed_default_library(): settings = get_settings() async with AsyncSessionLocal() as db: result = await db.execute(select(Library)) if result.scalar_one_or_none(): return # Bereits eine Library vorhanden folder_id = str(uuid.uuid4()) lib = Library( id=str(uuid.uuid4()), name="Hörbücher", display_name="Hörbücher", folders=[{"id": folder_id, "fullPath": settings.audiofiles_path}], media_type="book", settings={"icon": "headphones", "provider": "google"}, ) db.add(lib) await db.commit() logger.info(f"Standard-Library angelegt: {settings.audiofiles_path}") @asynccontextmanager async def lifespan(app: FastAPI): # Startup settings = get_settings() os.makedirs(settings.hls_cache_dir, exist_ok=True) os.makedirs(settings.covers_dir, exist_ok=True) os.makedirs(settings.log_dir, exist_ok=True) await init_db() await _seed_admin() await _seed_default_library() await start_file_watcher() logger.info("Audiolib gestartet.") yield # Shutdown stop_file_watcher() logger.info("Audiolib gestoppt.") app = FastAPI( title="Audiolib", version="2.4.0", description="Selbst gehosteter Audiobook-Server, API-kompatibel mit Audiobookshelf", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Cover-Dateien direkt ausliefern settings = get_settings() if os.path.exists(settings.covers_dir): app.mount("/covers", StaticFiles(directory=settings.covers_dir), name="covers") # Router registrieren from .routers import auth, libraries, items, stream, me, users, settings as settings_router app.include_router(auth.router) app.include_router(libraries.router) app.include_router(items.router) app.include_router(stream.router) app.include_router(me.router) app.include_router(users.router) app.include_router(settings_router.router)