Files
Audiolib/backend/app/main.py
Audiolib 14ffee3051 Initial commit: Phase 1 – Projektstruktur, DB-Schema, Core-API
- FastAPI-Backend mit vollständiger ABS v2.x API-Kompatibilität
- SQLAlchemy-Models: User, Library, LibraryItem, BookFile, Chapter,
  Podcast, PodcastEpisode, MediaProgress, Bookmark, PlaybackSession
- Auth: JWT-Login (/login, /logout, /api/authorize)
- Library + Items Endpoints inkl. camelCase ABS-Response-Format
- HLS-Streaming via FFmpeg (POST /api/items/:id/play, Session-Sync)
- Me/Progress Endpoints + Lesezeichen
- User-Management + Server-Settings (Admin)
- Library-Scanner (MP3/WAV Discovery, Hintergrund-Task)
- File Watcher (watchdog, 30s Debounce)
- Matching-Skelett (MusicBrainz, OpenLibrary, Google Books – Phase 5)
- Docker-Setup: backend (Python 3.12+FFmpeg), frontend (React/Vite),
  nginx Reverse-Proxy auf Port 3000
- setup.sh: Installiert Docker auf Debian/Ubuntu, richtet .env ein

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:43:35 +02:00

116 lines
3.4 KiB
Python

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)