2026-04-09 19:40:11 +00:00
|
|
|
import uuid
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
from typing import Protocol
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
from ..entities.flashcard_entities import FlashcardEntity, FlashcardEventEntity
|
|
|
|
|
from ....domain.models.flashcard import Flashcard, FlashcardEvent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FlashcardRepository(Protocol):
|
|
|
|
|
async def create_flashcard(
|
|
|
|
|
self,
|
|
|
|
|
user_id: uuid.UUID,
|
|
|
|
|
bank_entry_id: uuid.UUID,
|
|
|
|
|
source_lang: str,
|
|
|
|
|
target_lang: str,
|
|
|
|
|
prompt_text: str,
|
|
|
|
|
answer_text: str,
|
|
|
|
|
prompt_modality: str = "text",
|
|
|
|
|
prompt_context_text: str | None = None,
|
|
|
|
|
answer_context_text: str | None = None,
|
2026-04-14 09:17:33 +00:00
|
|
|
source_pack_flashcard_template_id: uuid.UUID | None = None,
|
2026-04-09 19:40:11 +00:00
|
|
|
) -> Flashcard: ...
|
|
|
|
|
|
|
|
|
|
async def get_flashcards_for_user(self, user_id: uuid.UUID) -> list[Flashcard]: ...
|
|
|
|
|
|
|
|
|
|
async def get_flashcards_for_entry(self, bank_entry_id: uuid.UUID) -> list[Flashcard]: ...
|
|
|
|
|
|
|
|
|
|
async def record_event(
|
|
|
|
|
self,
|
|
|
|
|
flashcard_id: uuid.UUID,
|
|
|
|
|
user_id: uuid.UUID,
|
|
|
|
|
event_type: str,
|
|
|
|
|
user_response: str | None = None,
|
|
|
|
|
) -> FlashcardEvent: ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _flashcard_to_model(entity: FlashcardEntity) -> Flashcard:
|
|
|
|
|
return Flashcard(
|
|
|
|
|
id=str(entity.id),
|
|
|
|
|
user_id=str(entity.user_id),
|
|
|
|
|
bank_entry_id=str(entity.bank_entry_id),
|
|
|
|
|
source_lang=entity.source_lang,
|
|
|
|
|
target_lang=entity.target_lang,
|
|
|
|
|
prompt_text=entity.prompt_text,
|
|
|
|
|
answer_text=entity.answer_text,
|
|
|
|
|
prompt_context_text=entity.prompt_context_text,
|
|
|
|
|
answer_context_text=entity.answer_context_text,
|
|
|
|
|
prompt_modality=entity.prompt_modality,
|
2026-04-14 09:17:33 +00:00
|
|
|
source_pack_flashcard_template_id=(
|
|
|
|
|
str(entity.source_pack_flashcard_template_id)
|
|
|
|
|
if entity.source_pack_flashcard_template_id
|
|
|
|
|
else None
|
|
|
|
|
),
|
2026-04-09 19:40:11 +00:00
|
|
|
created_at=entity.created_at,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _event_to_model(entity: FlashcardEventEntity) -> FlashcardEvent:
|
|
|
|
|
return FlashcardEvent(
|
|
|
|
|
id=str(entity.id),
|
|
|
|
|
flashcard_id=str(entity.flashcard_id),
|
|
|
|
|
user_id=str(entity.user_id),
|
|
|
|
|
event_type=entity.event_type,
|
|
|
|
|
user_response=entity.user_response,
|
|
|
|
|
created_at=entity.created_at,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PostgresFlashcardRepository:
|
|
|
|
|
def __init__(self, db: AsyncSession) -> None:
|
|
|
|
|
self.db = db
|
|
|
|
|
|
|
|
|
|
async def create_flashcard(
|
|
|
|
|
self,
|
|
|
|
|
user_id: uuid.UUID,
|
|
|
|
|
bank_entry_id: uuid.UUID,
|
|
|
|
|
source_lang: str,
|
|
|
|
|
target_lang: str,
|
|
|
|
|
prompt_text: str,
|
|
|
|
|
answer_text: str,
|
|
|
|
|
prompt_modality: str = "text",
|
|
|
|
|
prompt_context_text: str | None = None,
|
|
|
|
|
answer_context_text: str | None = None,
|
2026-04-14 09:17:33 +00:00
|
|
|
source_pack_flashcard_template_id: uuid.UUID | None = None,
|
2026-04-09 19:40:11 +00:00
|
|
|
) -> Flashcard:
|
|
|
|
|
entity = FlashcardEntity(
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
bank_entry_id=bank_entry_id,
|
|
|
|
|
source_lang=source_lang,
|
|
|
|
|
target_lang=target_lang,
|
|
|
|
|
prompt_text=prompt_text,
|
|
|
|
|
answer_text=answer_text,
|
|
|
|
|
prompt_context_text=prompt_context_text,
|
|
|
|
|
answer_context_text=answer_context_text,
|
|
|
|
|
prompt_modality=prompt_modality,
|
2026-04-14 09:17:33 +00:00
|
|
|
source_pack_flashcard_template_id=source_pack_flashcard_template_id,
|
2026-04-09 19:40:11 +00:00
|
|
|
created_at=datetime.now(timezone.utc),
|
|
|
|
|
)
|
|
|
|
|
self.db.add(entity)
|
|
|
|
|
await self.db.commit()
|
|
|
|
|
await self.db.refresh(entity)
|
|
|
|
|
return _flashcard_to_model(entity)
|
|
|
|
|
|
|
|
|
|
async def get_flashcards_for_user(self, user_id: uuid.UUID) -> list[Flashcard]:
|
|
|
|
|
result = await self.db.execute(
|
|
|
|
|
select(FlashcardEntity)
|
|
|
|
|
.where(FlashcardEntity.user_id == user_id)
|
|
|
|
|
.order_by(FlashcardEntity.created_at.desc())
|
|
|
|
|
)
|
|
|
|
|
return [_flashcard_to_model(e) for e in result.scalars().all()]
|
|
|
|
|
|
|
|
|
|
async def get_flashcards_for_entry(self, bank_entry_id: uuid.UUID) -> list[Flashcard]:
|
|
|
|
|
result = await self.db.execute(
|
|
|
|
|
select(FlashcardEntity)
|
|
|
|
|
.where(FlashcardEntity.bank_entry_id == bank_entry_id)
|
|
|
|
|
.order_by(FlashcardEntity.created_at.desc())
|
|
|
|
|
)
|
|
|
|
|
return [_flashcard_to_model(e) for e in result.scalars().all()]
|
|
|
|
|
|
|
|
|
|
async def record_event(
|
|
|
|
|
self,
|
|
|
|
|
flashcard_id: uuid.UUID,
|
|
|
|
|
user_id: uuid.UUID,
|
|
|
|
|
event_type: str,
|
|
|
|
|
user_response: str | None = None,
|
|
|
|
|
) -> FlashcardEvent:
|
|
|
|
|
entity = FlashcardEventEntity(
|
|
|
|
|
flashcard_id=flashcard_id,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
event_type=event_type,
|
|
|
|
|
user_response=user_response,
|
|
|
|
|
created_at=datetime.now(timezone.utc),
|
|
|
|
|
)
|
|
|
|
|
self.db.add(entity)
|
|
|
|
|
await self.db.commit()
|
|
|
|
|
await self.db.refresh(entity)
|
|
|
|
|
return _event_to_model(entity)
|