147 lines
4.9 KiB
Python
147 lines
4.9 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 ...outbound.postgres.database import get_db
|
|
from ...outbound.storage_client import get_storage_client
|
|
from ...outbound.postgres.repositories.adventure_repository import (
|
|
PostgresAdventureEntryAudioRepository,
|
|
PostgresAdventureEntryChoiceRepository,
|
|
PostgresAdventureEntryRepository,
|
|
PostgresAdventureEntryTranslationRepository,
|
|
PostgresAdventureRepository,
|
|
)
|
|
|
|
router = APIRouter(prefix="/adventure", tags=["bff", "adventures"])
|
|
|
|
|
|
class AdventureChoiceItem(BaseModel):
|
|
id: str
|
|
index: int
|
|
label: str
|
|
text: str
|
|
|
|
|
|
class AdventureEntryItem(BaseModel):
|
|
id: str
|
|
adventure_id: str
|
|
possible_choices: list[AdventureChoiceItem] | None
|
|
generated_from_choice_id: str | None
|
|
status: str
|
|
entry_index: int
|
|
story_text: str | None
|
|
story_text_linguistic_data: dict | None
|
|
translation: str | None
|
|
audio_url: str | None
|
|
created_at: str
|
|
|
|
|
|
class AdventureDetailResponse(BaseModel):
|
|
id: str
|
|
user_id: str
|
|
status: str
|
|
language: str
|
|
source_language: str
|
|
competencies: list[str]
|
|
max_entry_count: int
|
|
title: str
|
|
description: str | None
|
|
genres: list[str]
|
|
setting: list[str]
|
|
vibes: list[str]
|
|
protagonist: list[str]
|
|
created_at: str
|
|
entries: list[AdventureEntryItem]
|
|
current_entry_choices: list[AdventureChoiceItem]
|
|
|
|
|
|
def _audio_url(key: str | None) -> str | None:
|
|
if key is None:
|
|
return None
|
|
return get_storage_client().get_url(key)
|
|
|
|
|
|
@router.get("/{adventure_id}", response_model=AdventureDetailResponse, status_code=200)
|
|
async def get_adventure(
|
|
adventure_id: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
token_data: dict = Depends(verify_token),
|
|
) -> AdventureDetailResponse:
|
|
user_id = uuid.UUID(token_data["sub"])
|
|
|
|
try:
|
|
adv_id = uuid.UUID(adventure_id)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail="Invalid adventure_id")
|
|
|
|
adventure = await PostgresAdventureRepository(db).get_by_id(adv_id)
|
|
if adventure is None or adventure.user_id != str(user_id):
|
|
raise HTTPException(status_code=404, detail="Adventure not found")
|
|
|
|
entries = await PostgresAdventureEntryRepository(db).list_for_adventure(adv_id)
|
|
|
|
choices_repo = PostgresAdventureEntryChoiceRepository(db)
|
|
translation_repo = PostgresAdventureEntryTranslationRepository(db)
|
|
audio_repo = PostgresAdventureEntryAudioRepository(db)
|
|
|
|
entry_items = []
|
|
for entry in entries:
|
|
eid = uuid.UUID(entry.id)
|
|
translation = await translation_repo.get_for_entry(
|
|
entry_id=eid,
|
|
component_type="story_text",
|
|
target_language=adventure.source_language,
|
|
)
|
|
audio = await audio_repo.get_for_entry(entry_id=eid, component_type="story_text")
|
|
choices = await choices_repo.list_for_entry(eid)
|
|
entry_items.append(
|
|
AdventureEntryItem(
|
|
id=entry.id,
|
|
adventure_id=entry.adventure_id,
|
|
possible_choices=[
|
|
AdventureChoiceItem(id=c.id, index=c.index, label=c.label, text=c.text)
|
|
for c in choices ]
|
|
if choices else None,
|
|
generated_from_choice_id=entry.generated_from_choice_id,
|
|
status=entry.status,
|
|
entry_index=entry.entry_index,
|
|
story_text=entry.story_text,
|
|
story_text_linguistic_data=entry.story_text_linguistic_data,
|
|
translation=translation.translated_text if translation else None,
|
|
audio_url=_audio_url(audio.file_name if audio else None),
|
|
created_at=entry.created_at.isoformat(),
|
|
)
|
|
)
|
|
|
|
# Choices for the most recent entry, if it's complete (entries are ordered entry_index asc)
|
|
current_entry_choices: list[AdventureChoiceItem] = []
|
|
if entries and entries[-1].status == "complete":
|
|
choices = await PostgresAdventureEntryChoiceRepository(db).list_for_entry(
|
|
uuid.UUID(entries[-1].id)
|
|
)
|
|
current_entry_choices = [
|
|
AdventureChoiceItem(id=c.id, index=c.index, label=c.label, text=c.text)
|
|
for c in choices
|
|
]
|
|
|
|
return AdventureDetailResponse(
|
|
id=adventure.id,
|
|
user_id=adventure.user_id,
|
|
status=adventure.status,
|
|
language=adventure.language,
|
|
source_language=adventure.source_language,
|
|
competencies=adventure.competencies,
|
|
max_entry_count=adventure.max_entry_count,
|
|
title=adventure.title,
|
|
description=adventure.description,
|
|
genres=adventure.genres,
|
|
setting=adventure.setting,
|
|
vibes=adventure.vibes,
|
|
protagonist=adventure.protagonist,
|
|
created_at=adventure.created_at.isoformat(),
|
|
entries=entry_items,
|
|
current_entry_choices=current_entry_choices,
|
|
)
|