218 lines
6.8 KiB
Python
218 lines
6.8 KiB
Python
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ...auth import verify_token
|
|
from ...domain.services.dictionary_lookup_service import DictionaryLookupService, TokenLookupResult
|
|
from ...domain.services.vocab_service import VocabService
|
|
from ...outbound.postgres.database import get_db
|
|
from ...outbound.postgres.repositories.dictionary_repository import PostgresDictionaryRepository
|
|
from ...outbound.postgres.repositories.vocab_repository import PostgresVocabRepository
|
|
|
|
router = APIRouter(prefix="/vocab", tags=["vocab"])
|
|
|
|
|
|
class AddWordRequest(BaseModel):
|
|
language_pair_id: str
|
|
surface_text: str
|
|
entry_pathway: str = "manual"
|
|
is_phrase: bool = False
|
|
source_article_id: str | None = None
|
|
|
|
|
|
class AddFromTokenRequest(BaseModel):
|
|
language_pair_id: str
|
|
surface: str
|
|
spacy_lemma: str
|
|
pos_ud: str
|
|
language: str
|
|
source_article_id: str | None = None
|
|
|
|
|
|
class SenseCandidateResponse(BaseModel):
|
|
id: str
|
|
gloss: str
|
|
topics: list[str]
|
|
tags: list[str]
|
|
|
|
|
|
class FromTokenResponse(BaseModel):
|
|
entry: "WordBankEntryResponse"
|
|
sense_candidates: list[SenseCandidateResponse]
|
|
matched_via: str
|
|
|
|
|
|
class SetSenseRequest(BaseModel):
|
|
sense_id: str
|
|
|
|
|
|
class WordBankEntryResponse(BaseModel):
|
|
id: str
|
|
user_id: str
|
|
language_pair_id: str
|
|
sense_id: str | None
|
|
wordform_id: str | None
|
|
surface_text: str
|
|
is_phrase: bool
|
|
entry_pathway: str
|
|
source_article_id: str | None
|
|
disambiguation_status: str
|
|
created_at: str
|
|
|
|
|
|
def _service(db: AsyncSession) -> VocabService:
|
|
return VocabService(
|
|
vocab_repo=PostgresVocabRepository(db),
|
|
dict_repo=PostgresDictionaryRepository(db),
|
|
)
|
|
|
|
|
|
@router.post("", response_model=WordBankEntryResponse, status_code=201)
|
|
async def add_word(
|
|
request: AddWordRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
token_data: dict = Depends(verify_token),
|
|
) -> WordBankEntryResponse:
|
|
user_id = uuid.UUID(token_data["sub"])
|
|
try:
|
|
language_pair_id = uuid.UUID(request.language_pair_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid language_pair_id")
|
|
|
|
source_article_id = None
|
|
if request.source_article_id:
|
|
try:
|
|
source_article_id = uuid.UUID(request.source_article_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid source_article_id")
|
|
|
|
try:
|
|
entry = await _service(db).add_word_to_bank(
|
|
user_id=user_id,
|
|
surface_text=request.surface_text.strip(),
|
|
language_pair_id=language_pair_id,
|
|
pathway=request.entry_pathway,
|
|
is_phrase=request.is_phrase,
|
|
source_article_id=source_article_id,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc))
|
|
|
|
return _to_response(entry)
|
|
|
|
|
|
@router.post("/from-token", response_model=FromTokenResponse, status_code=201)
|
|
async def add_from_token(
|
|
request: AddFromTokenRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
token_data: dict = Depends(verify_token),
|
|
) -> FromTokenResponse:
|
|
user_id = uuid.UUID(token_data["sub"])
|
|
try:
|
|
language_pair_id = uuid.UUID(request.language_pair_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid language_pair_id")
|
|
|
|
source_article_id = None
|
|
if request.source_article_id:
|
|
try:
|
|
source_article_id = uuid.UUID(request.source_article_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid source_article_id")
|
|
|
|
lookup_service = DictionaryLookupService(PostgresDictionaryRepository(db))
|
|
result: TokenLookupResult = await lookup_service.lookup_token(
|
|
surface=request.surface,
|
|
spacy_lemma=request.spacy_lemma,
|
|
pos_ud=request.pos_ud,
|
|
language=request.language,
|
|
)
|
|
|
|
wordform_id = uuid.UUID(result.wordform_id) if result.wordform_id else None
|
|
|
|
try:
|
|
entry = await _service(db).add_token_to_bank(
|
|
user_id=user_id,
|
|
surface_text=request.surface,
|
|
language_pair_id=language_pair_id,
|
|
senses=result.senses,
|
|
wordform_id=wordform_id,
|
|
source_article_id=source_article_id,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc))
|
|
|
|
candidates = [
|
|
SenseCandidateResponse(id=s.id, gloss=s.gloss, topics=s.topics, tags=s.tags)
|
|
for s in result.senses
|
|
]
|
|
return FromTokenResponse(
|
|
entry=_to_response(entry),
|
|
sense_candidates=candidates,
|
|
matched_via=result.matched_via,
|
|
)
|
|
|
|
|
|
@router.get("", response_model=list[WordBankEntryResponse])
|
|
async def list_entries(
|
|
language_pair_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
token_data: dict = Depends(verify_token),
|
|
) -> list[WordBankEntryResponse]:
|
|
user_id = uuid.UUID(token_data["sub"])
|
|
try:
|
|
pair_id = uuid.UUID(language_pair_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid language_pair_id")
|
|
|
|
entries = await PostgresVocabRepository(db).get_entries_for_user(user_id, pair_id)
|
|
return [_to_response(e) for e in entries]
|
|
|
|
|
|
@router.get("/pending-disambiguation", response_model=list[WordBankEntryResponse])
|
|
async def pending_disambiguation(
|
|
db: AsyncSession = Depends(get_db),
|
|
token_data: dict = Depends(verify_token),
|
|
) -> list[WordBankEntryResponse]:
|
|
user_id = uuid.UUID(token_data["sub"])
|
|
entries = await PostgresVocabRepository(db).get_pending_disambiguation(user_id)
|
|
return [_to_response(e) for e in entries]
|
|
|
|
|
|
@router.patch("/{entry_id}/sense", response_model=WordBankEntryResponse)
|
|
async def resolve_sense(
|
|
entry_id: str,
|
|
request: SetSenseRequest,
|
|
db: AsyncSession = Depends(get_db),
|
|
token_data: dict = Depends(verify_token),
|
|
) -> WordBankEntryResponse:
|
|
try:
|
|
eid = uuid.UUID(entry_id)
|
|
sid = uuid.UUID(request.sense_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid UUID")
|
|
|
|
try:
|
|
entry = await _service(db).resolve_disambiguation(eid, sid)
|
|
except Exception:
|
|
raise HTTPException(status_code=404, detail="Entry not found")
|
|
|
|
return _to_response(entry)
|
|
|
|
|
|
def _to_response(entry) -> WordBankEntryResponse:
|
|
return WordBankEntryResponse(
|
|
id=entry.id,
|
|
user_id=entry.user_id,
|
|
language_pair_id=entry.language_pair_id,
|
|
sense_id=entry.sense_id,
|
|
wordform_id=entry.wordform_id,
|
|
surface_text=entry.surface_text,
|
|
is_phrase=entry.is_phrase,
|
|
entry_pathway=entry.entry_pathway,
|
|
source_article_id=entry.source_article_id,
|
|
disambiguation_status=entry.disambiguation_status,
|
|
created_at=entry.created_at.isoformat(),
|
|
)
|