Files
Audiolib/backend/app/routers/auth.py
Audiolib 6bb07ff873 Case-insensitive login, auto-matching after scan, per-library sources
- auth: username lookup is now case-insensitive via func.lower()
- scanner: trigger match_audiobook for each newly found item after scan
- matcher: read match_sources from library settings; refactored to loop
  over configured sources in priority order instead of hardcoded sequence
- schemas/routers: expose matchSources in LibraryOut API response
- Admin UI: pill-toggle for MusicBrainz/Open Library/Google Books per library

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 15:01:56 +02:00

103 lines
3.3 KiB
Python

from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from ..database import AsyncSessionLocal
from ..dependencies import get_db, get_current_user
from ..models.user import User
from ..models.library import Library
from ..models.session import ServerSetting
from ..services.auth import verify_password, create_token
from ..schemas.auth import LoginRequest, LoginResponse, AuthorizeResponse
from ..schemas.user import UserOut, UserSettings, ServerSettingsOut
router = APIRouter(tags=["auth"])
def _build_user_out(user: User) -> UserOut:
raw_settings = user.settings or {}
settings = UserSettings(**{k: v for k, v in raw_settings.items() if k in UserSettings.model_fields})
return UserOut(
id=user.id,
username=user.username,
email=user.email,
is_admin=user.is_admin,
is_active=user.is_active,
last_seen=user.last_seen,
created_at=user.created_at,
token=user.token,
settings=settings,
type="root" if user.is_admin else "user",
permissions={
"download": True,
"update": user.is_admin,
"delete": user.is_admin,
"upload": user.is_admin,
"access_all_libraries": True,
"access_explicit_content": True,
},
)
@router.post("/login", response_model=LoginResponse)
async def login(body: LoginRequest, db: AsyncSession = Depends(get_db)):
result = await db.execute(
select(User).where(func.lower(User.username) == body.username.lower(), User.is_active == True)
)
user = result.scalar_one_or_none()
if not user or not verify_password(body.password, user.password_hash):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials")
token = create_token(user.id)
user.token = token
user.last_seen = datetime.utcnow()
await db.commit()
# Erste Library als Default zurückgeben
lib_result = await db.execute(select(Library).limit(1))
first_lib = lib_result.scalar_one_or_none()
return LoginResponse(
user=_build_user_out(user),
user_default_library_id=first_lib.id if first_lib else None,
server_settings=ServerSettingsOut(),
)
@router.post("/logout")
async def logout(current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
current_user.token = None
await db.commit()
return {"success": True}
@router.get("/api/authorize", response_model=AuthorizeResponse)
async def authorize(current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
current_user.last_seen = datetime.utcnow()
await db.commit()
lib_result = await db.execute(select(Library))
libraries = lib_result.scalars().all()
from ..routers.libraries import _library_to_out
libs_out = [_library_to_out(lib) for lib in libraries]
first_lib_id = libraries[0].id if libraries else None
return AuthorizeResponse(
user=_build_user_out(current_user),
libraries=libs_out,
user_default_library_id=first_lib_id,
server_settings=ServerSettingsOut(),
)
@router.get("/ping")
async def ping():
return {"success": True}
@router.get("/health")
async def health():
return {"status": "ok"}