language-learning-app/api/app/outbound/postgres/entities/adventure_entities.py
wilson 8b687e9737
Some checks are pending
/ test (push) Waiting to run
feat: [api] Add choose your own adventure functionality
2026-05-03 17:17:47 +01:00

144 lines
6.3 KiB
Python

import uuid
from datetime import datetime, timezone
from sqlalchemy import DateTime, ForeignKey, Integer, Text, UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import Mapped, mapped_column
from ..database import Base
class AdventureEntity(Base):
__tablename__ = "choose_your_own_adventure"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
status: Mapped[str] = mapped_column(Text, nullable=False, default="awaiting_first_entry")
language: Mapped[str] = mapped_column(Text, nullable=False)
source_language: Mapped[str] = mapped_column(Text, nullable=False)
competencies: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
max_entry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=6)
entry_story_text_target_length: Mapped[dict] = mapped_column(
JSONB, nullable=False, default=lambda: {"min": 700, "max": 800}
)
title: Mapped[str] = mapped_column(Text, nullable=False, default="Untitled adventure")
description: Mapped[str | None] = mapped_column(Text, nullable=True)
plot_summary: Mapped[str | None] = mapped_column(Text, nullable=True)
genres: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
setting: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
vibes: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
protagonist: Mapped[list] = mapped_column(JSONB, nullable=False, default=list)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)
)
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class AdventureEntryEntity(Base):
__tablename__ = "choose_your_own_adventure_entry"
__table_args__ = (
UniqueConstraint("adventure_id", "entry_index", name="uq_cyoa_entry_adventure_index"),
)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
adventure_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("choose_your_own_adventure.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
generated_from_choice_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True),
ForeignKey(
"choose_your_own_adventure_entry_possible_choice.id", ondelete="SET NULL"
),
nullable=True,
)
status: Mapped[str] = mapped_column(Text, nullable=False, default="generating")
entry_index: Mapped[int] = mapped_column(Integer, nullable=False)
story_text: Mapped[str | None] = mapped_column(Text, nullable=True)
gamemaster_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
llm_data: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)
)
class AdventureEntryPossibleChoiceEntity(Base):
__tablename__ = "choose_your_own_adventure_entry_possible_choice"
__table_args__ = (
UniqueConstraint("entry_id", "index", name="uq_cyoa_choice_entry_index"),
)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
entry_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("choose_your_own_adventure_entry.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
index: Mapped[int] = mapped_column(Integer, nullable=False)
label: Mapped[str] = mapped_column(Text, nullable=False)
text: Mapped[str] = mapped_column(Text, nullable=False)
class AdventureEntryPossibleChoiceDecisionEntity(Base):
__tablename__ = "choose_your_own_adventure_entry_possible_choice_decision"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
choice_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey(
"choose_your_own_adventure_entry_possible_choice.id", ondelete="CASCADE"
),
nullable=False,
index=True,
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)
)
class AdventureEntryTranslationEntity(Base):
__tablename__ = "choose_your_own_adventure_entry_translation"
__table_args__ = (
UniqueConstraint(
"entry_id", "component_type", "target_language",
name="uq_cyoa_translation_entry_component_lang",
),
)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
entry_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("choose_your_own_adventure_entry.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
component_type: Mapped[str] = mapped_column(Text, nullable=False, default="story_text")
target_language: Mapped[str] = mapped_column(Text, nullable=False)
translated_text: Mapped[str] = mapped_column(Text, nullable=False)
class AdventureEntryAudioEntity(Base):
__tablename__ = "choose_your_own_adventure_entry_audio"
__table_args__ = (
UniqueConstraint("entry_id", "component_type", name="uq_cyoa_audio_entry_component"),
)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
entry_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("choose_your_own_adventure_entry.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
component_type: Mapped[str] = mapped_column(Text, nullable=False, default="story_text")
tts_provider: Mapped[str] = mapped_column(Text, nullable=False, default="google_gemini")
tts_options: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
file_name: Mapped[str] = mapped_column(Text, nullable=False)