Create 20260503_0016_add_choose_your_own_adventure.py

This commit is contained in:
wilson 2026-05-03 15:28:20 +01:00
parent 4ea67fda13
commit 24dd4e7053

View file

@ -0,0 +1,208 @@
"""add choose_your_own_adventure tables
Revision ID: 0016
Revises: 0015
Create Date: 2026-05-03
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision: str = "0016"
down_revision: Union[str, None] = "0015"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"choose_your_own_adventure",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"user_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("status", sa.Text(), nullable=False, server_default="awaiting_first_entry"),
sa.Column("language", sa.Text(), nullable=False),
sa.Column("source_language", sa.Text(), nullable=False),
sa.Column("competencies", postgresql.JSONB(), nullable=False, server_default="[]"),
sa.Column("max_entry_count", sa.Integer(), nullable=False, server_default="6"),
sa.Column(
"entry_story_text_target_length",
postgresql.JSONB(),
nullable=False,
server_default='{"min": 700, "max": 800}',
),
sa.Column("title", sa.Text(), nullable=False, server_default="Untitled adventure"),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("plot_summary", sa.Text(), nullable=True),
sa.Column("genres", postgresql.JSONB(), nullable=False, server_default="[]"),
sa.Column("setting", postgresql.JSONB(), nullable=False, server_default="[]"),
sa.Column("vibes", postgresql.JSONB(), nullable=False, server_default="[]"),
sa.Column("protagonist", postgresql.JSONB(), nullable=False, server_default="[]"),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.Column("deleted_at", sa.DateTime(timezone=True), nullable=True),
)
op.create_index("ix_cyoa_user_id", "choose_your_own_adventure", ["user_id"])
op.create_index("ix_cyoa_status", "choose_your_own_adventure", ["status"])
# Entry table — generated_from_choice_id FK added after possible_choice table is created
op.create_table(
"choose_your_own_adventure_entry",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"adventure_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("choose_your_own_adventure.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("generated_from_choice_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("status", sa.Text(), nullable=False, server_default="generating"),
sa.Column("entry_index", sa.Integer(), nullable=False),
sa.Column("story_text", sa.Text(), nullable=True),
sa.Column("gamemaster_notes", sa.Text(), nullable=True),
sa.Column("llm_data", postgresql.JSONB(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
sa.UniqueConstraint("adventure_id", "entry_index", name="uq_cyoa_entry_adventure_index"),
)
op.create_index(
"ix_cyoa_entry_adventure_id", "choose_your_own_adventure_entry", ["adventure_id"]
)
op.create_table(
"choose_your_own_adventure_entry_possible_choice",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"entry_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("choose_your_own_adventure_entry.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("index", sa.Integer(), nullable=False),
sa.Column("label", sa.Text(), nullable=False),
sa.Column("text", sa.Text(), nullable=False),
sa.UniqueConstraint("entry_id", "index", name="uq_cyoa_choice_entry_index"),
)
op.create_index(
"ix_cyoa_choice_entry_id",
"choose_your_own_adventure_entry_possible_choice",
["entry_id"],
)
# Resolve circular FK: entry → possible_choice
op.create_foreign_key(
"fk_cyoa_entry_generated_from_choice",
"choose_your_own_adventure_entry",
"choose_your_own_adventure_entry_possible_choice",
["generated_from_choice_id"],
["id"],
ondelete="SET NULL",
)
op.create_table(
"choose_your_own_adventure_entry_possible_choice_decision",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"choice_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey(
"choose_your_own_adventure_entry_possible_choice.id", ondelete="CASCADE"
),
nullable=False,
),
sa.Column(
"user_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("users.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.func.now(),
),
)
op.create_index(
"ix_cyoa_decision_choice_id",
"choose_your_own_adventure_entry_possible_choice_decision",
["choice_id"],
)
op.create_index(
"ix_cyoa_decision_user_id",
"choose_your_own_adventure_entry_possible_choice_decision",
["user_id"],
)
op.create_table(
"choose_your_own_adventure_entry_translation",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"entry_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("choose_your_own_adventure_entry.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("component_type", sa.Text(), nullable=False, server_default="story_text"),
sa.Column("target_language", sa.Text(), nullable=False),
sa.Column("translated_text", sa.Text(), nullable=False),
sa.UniqueConstraint(
"entry_id", "component_type", "target_language",
name="uq_cyoa_translation_entry_component_lang",
),
)
op.create_index(
"ix_cyoa_translation_entry_id",
"choose_your_own_adventure_entry_translation",
["entry_id"],
)
op.create_table(
"choose_your_own_adventure_entry_audio",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column(
"entry_id",
postgresql.UUID(as_uuid=True),
sa.ForeignKey("choose_your_own_adventure_entry.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column("component_type", sa.Text(), nullable=False, server_default="story_text"),
sa.Column("tts_provider", sa.Text(), nullable=False, server_default="google_gemini"),
sa.Column("tts_options", postgresql.JSONB(), nullable=True),
sa.Column("file_name", sa.Text(), nullable=False),
sa.UniqueConstraint("entry_id", "component_type", name="uq_cyoa_audio_entry_component"),
)
op.create_index(
"ix_cyoa_audio_entry_id",
"choose_your_own_adventure_entry_audio",
["entry_id"],
)
def downgrade() -> None:
op.drop_table("choose_your_own_adventure_entry_audio")
op.drop_table("choose_your_own_adventure_entry_translation")
op.drop_table("choose_your_own_adventure_entry_possible_choice_decision")
op.drop_constraint(
"fk_cyoa_entry_generated_from_choice",
"choose_your_own_adventure_entry",
type_="foreignkey",
)
op.drop_table("choose_your_own_adventure_entry_possible_choice")
op.drop_table("choose_your_own_adventure_entry")
op.drop_table("choose_your_own_adventure")