"""initial schema (squashed) 单一迁移:pgvector + 当前全部 ORM 表(含 conversations.deleted_at 软删除);并补充 models 未声明的 story_image_intents.asset_id → assets 外键,以及每个 story 仅一条 primary intent 的唯一索引。 chapters 含 story 物化字段:markdown_compose_dirty、markdown_composed_at、reading_segments_json (阅读片段快照,随 ORM 一并 create_all)。 已并入原 0002(stories-first:无 chapter_sections / memoir_images.section_id)与原 0003(segments.tts_audio_urls) 的语义:新库仅由当前 ORM 建表即可,无需后续 ALTER。 conversation_messages(会话轮次 durable log)由 app.features.conversation.models.ConversationMessage 一并 create_all。 segments.audio_duration_seconds(语音条时长秒数,历史 API / Redis 回填)由 ORM 一并 create_all,无独立迁移。 story_image_intents 无 source_span(主图回填在正文末尾,意图仅存 caption / prompt_brief 等)。 新库 / 删库重来:`alembic upgrade head`。 Revision ID: 0001_initial Revises: """ from typing import Sequence, Union from alembic import op from sqlalchemy import text revision: str = "0001_initial" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def _import_all_models() -> None: from app.features.asset import models as _asset_models # noqa: F401 from app.features.auth import models as _auth_models # noqa: F401 from app.features.conversation import models as _conv_models # noqa: F401 from app.features.memory import models as _memory_models # noqa: F401 from app.features.memoir import models as _memoir_models # noqa: F401 from app.features.payment import models as _payment_models # noqa: F401 from app.features.story import models as _story_models # noqa: F401 from app.features.user import models as _user_models # noqa: F401 def upgrade() -> None: conn = op.get_bind() conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector")) from app.core.db import Base _import_all_models() Base.metadata.create_all(bind=conn) op.create_foreign_key( "fk_story_image_intents_asset_id_assets", "story_image_intents", "assets", ["asset_id"], ["id"], ondelete="SET NULL", ) op.execute( """ CREATE UNIQUE INDEX IF NOT EXISTS uq_story_primary_image_intent ON story_image_intents (story_id) WHERE intent_role = 'primary' """ ) def downgrade() -> None: conn = op.get_bind() from app.core.db import Base op.execute("DROP INDEX IF EXISTS uq_story_primary_image_intent") op.drop_constraint( "fk_story_image_intents_asset_id_assets", "story_image_intents", type_="foreignkey", ) _import_all_models() Base.metadata.drop_all(bind=conn) conn.execute(text("DROP EXTENSION IF EXISTS vector"))