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>
This commit is contained in:
102
backend/app/routers/auth.py
Normal file
102
backend/app/routers/auth.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
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(User.username == body.username, 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"}
|
||||
Reference in New Issue
Block a user