Files
gartenmanager/backend/app/api/v1/auth.py
Faultier314 834a3bf4d5 feat: Phase 1 complete – full working application
Backend (FastAPI):
- REST API: auth, plants, beds, plantings
- CRUD layer with CRUDBase
- Pydantic v2 schemas for all entities
- Alembic migration: complete schema + all enums
- Seed data: 28 global plants + 15 compatibilities

Frontend (Vue 3 + PrimeVue):
- Axios client with JWT interceptor + auto-refresh
- Pinia stores: auth, beds, plants
- Views: Login, Beds, BedDetail, PlantLibrary
- Components: AppLayout, BedForm, PlantingForm, PlantForm

Docker:
- docker-compose.yml (production)
- docker-compose.dev.yml (development with hot-reload)
- Nginx config with SPA fallback + API proxy
- Multi-stage frontend Dockerfile
- .env.example, .gitignore

Version: 1.0.0-alpha
2026-04-06 07:45:00 +02:00

69 lines
2.4 KiB
Python

from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from jose import JWTError
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import CurrentUser, get_session
from app.core.security import TOKEN_TYPE_REFRESH, create_access_token, create_refresh_token, decode_token
from app.crud.user import crud_user
from app.schemas.auth import AccessTokenResponse, LoginRequest, RefreshRequest, TokenResponse
from app.schemas.user import UserRead
router = APIRouter(prefix="/auth", tags=["Authentifizierung"])
@router.post("/login", response_model=TokenResponse)
async def login(
body: LoginRequest,
db: Annotated[AsyncSession, Depends(get_session)],
) -> TokenResponse:
user = await crud_user.authenticate(db, email=body.email, password=body.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="E-Mail oder Passwort falsch.",
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Benutzerkonto ist deaktiviert.",
)
tenants = await crud_user.get_tenants(db, user_id=user.id)
return TokenResponse(
access_token=create_access_token(str(user.id)),
refresh_token=create_refresh_token(str(user.id)),
user=UserRead.model_validate(user),
tenants=tenants,
)
@router.post("/refresh", response_model=AccessTokenResponse)
async def refresh_token(
body: RefreshRequest,
db: Annotated[AsyncSession, Depends(get_session)],
) -> AccessTokenResponse:
try:
payload = decode_token(body.refresh_token)
if payload.get("type") != TOKEN_TYPE_REFRESH:
raise JWTError("Falscher Token-Typ")
user_id: str = payload["sub"]
except JWTError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Ungültiger oder abgelaufener Refresh-Token.",
)
from uuid import UUID
user = await crud_user.get(db, id=UUID(user_id))
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Benutzer nicht gefunden oder deaktiviert.",
)
return AccessTokenResponse(access_token=create_access_token(user_id))
@router.get("/me", response_model=UserRead)
async def get_me(current_user: CurrentUser) -> UserRead:
return UserRead.model_validate(current_user)