language-learning-app/api/app/routers/api/account.py

97 lines
3.3 KiB
Python

import uuid
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, field_validator
from sqlalchemy.ext.asyncio import AsyncSession
from ...auth import verify_token
from ...domain.services.account_service import AccountService
from ...languages import SUPPORTED_LANGUAGES, SUPPORTED_LEVELS
from ...outbound.postgres.database import get_db
router = APIRouter(prefix="/account", tags=["account"])
class AddLearnableLanguageRequest(BaseModel):
source_language: str
target_language: str
proficiencies: list[str]
@field_validator("proficiencies")
@classmethod
def validate_proficiencies(cls, v: list[str]) -> list[str]:
if not (1 <= len(v) <= 2):
raise ValueError("proficiencies must contain 1 or 2 levels")
invalid = [p for p in v if p not in SUPPORTED_LEVELS]
if invalid:
raise ValueError(f"Invalid proficiency levels: {invalid}. Supported: {sorted(SUPPORTED_LEVELS)}")
return v
class LearnableLanguageResponse(BaseModel):
id: str
source_language: str
target_language: str
proficiencies: list[str]
@router.post(
"/learnable-languages",
response_model=LearnableLanguageResponse,
status_code=status.HTTP_201_CREATED,
)
async def add_learnable_language(
body: AddLearnableLanguageRequest,
db: AsyncSession = Depends(get_db),
token_data: dict = Depends(verify_token),
) -> LearnableLanguageResponse:
if body.source_language not in SUPPORTED_LANGUAGES:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Unsupported source language '{body.source_language}'. Supported: {list(SUPPORTED_LANGUAGES)}",
)
if body.target_language not in SUPPORTED_LANGUAGES:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Unsupported target language '{body.target_language}'. Supported: {list(SUPPORTED_LANGUAGES)}",
)
if body.source_language == body.target_language:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="source_language and target_language must differ",
)
user_id = uuid.UUID(token_data["sub"])
lang = await AccountService(db).add_learnable_language(
user_id=user_id,
source_language=body.source_language,
target_language=body.target_language,
proficiencies=body.proficiencies,
)
return LearnableLanguageResponse(
id=lang.id,
source_language=lang.source_language,
target_language=lang.target_language,
proficiencies=lang.proficiencies,
)
@router.delete(
"/learnable-languages/{language_id}",
status_code=status.HTTP_204_NO_CONTENT,
)
async def remove_learnable_language(
language_id: str,
db: AsyncSession = Depends(get_db),
token_data: dict = Depends(verify_token),
) -> None:
try:
lid = uuid.UUID(language_id)
except ValueError:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid language_id")
user_id = uuid.UUID(token_data["sub"])
try:
await AccountService(db).remove_learnable_language(user_id=user_id, language_id=lid)
except ValueError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc))