"""API Push 短新闻来源 新增字段: - articles.is_short_news BOOL NOT NULL DEFAULT false (索引) - articles.external_id VARCHAR(128) nullable (索引) - articles.source_ref VARCHAR(64) nullable (索引) - articles.content_hash VARCHAR(40) nullable UNIQUE (索引,内容去重核心 key) - articles.url TEXT nullable (放宽 — 短新闻可合成 url) - sources.kind ENUM 加 'api_push' 值 - api_tokens.purpose VARCHAR(16) NOT NULL DEFAULT 'mobile' (索引) 值域: mobile / ingest - api_tokens.source_id INTEGER NULL FK sources.id ON DELETE CASCADE (索引, ingest token 绑定的 source) Revision ID: 0008 Revises: 0007 Create Date: 2026-06-14 """ from __future__ import annotations from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "0008" down_revision: Union[str, None] = "0007" branch_labels = None depends_on = None def upgrade() -> None: # === 1) sources.kind 加 'api_push' === # PG enum 加 value 必须用 ALTER TYPE,alembic 没有原生 op,直接 execute op.execute("ALTER TYPE source_kind ADD VALUE IF NOT EXISTS 'api_push'") # === 2) articles:5 个字段 === # 2.1) is_short_news op.add_column( "articles", sa.Column( "is_short_news", sa.Boolean, nullable=False, server_default=sa.text("false"), ), ) op.create_index("ix_articles_is_short_news", "articles", ["is_short_news"]) # 2.2) external_id op.add_column( "articles", sa.Column("external_id", sa.String(128), nullable=True), ) op.create_index("ix_articles_external_id", "articles", ["external_id"]) # 2.3) source_ref op.add_column( "articles", sa.Column("source_ref", sa.String(64), nullable=True), ) op.create_index("ix_articles_source_ref", "articles", ["source_ref"]) # 2.4) content_hash (UNIQUE,核心去重 key) op.add_column( "articles", sa.Column("content_hash", sa.String(40), nullable=True), ) op.create_index("ix_articles_content_hash", "articles", ["content_hash"], unique=True) # 注:articles.url 保持 NOT NULL。短新闻入库时会合成 "api-push://source-slug/content-hash" # 作为占位,避免改动下游 schema(ArticleDetail.url 等)引出更大爆炸半径。 # === 3) api_tokens.purpose + source_id === op.add_column( "api_tokens", sa.Column( "purpose", sa.String(16), nullable=False, server_default="mobile", ), ) op.create_index("ix_api_tokens_purpose", "api_tokens", ["purpose"]) op.add_column( "api_tokens", sa.Column( "source_id", sa.Integer, sa.ForeignKey("sources.id", ondelete="CASCADE"), nullable=True, ), ) op.create_index("ix_api_tokens_source_id", "api_tokens", ["source_id"]) def downgrade() -> None: # === 反向顺序 === op.drop_index("ix_api_tokens_source_id", table_name="api_tokens") op.drop_column("api_tokens", "source_id") op.drop_index("ix_api_tokens_purpose", table_name="api_tokens") op.drop_column("api_tokens", "purpose") op.drop_index("ix_articles_content_hash", table_name="articles") op.drop_column("articles", "content_hash") op.drop_index("ix_articles_source_ref", table_name="articles") op.drop_column("articles", "source_ref") op.drop_index("ix_articles_external_id", table_name="articles") op.drop_column("articles", "external_id") op.drop_index("ix_articles_is_short_news", table_name="articles") op.drop_column("articles", "is_short_news") # PG enum remove value 没有原生支持,需要重建类型。 # 这里只保留注释,提醒运维:downgrade 后如需去掉 api_push enum 值, # 需手工 ALTER TYPE (CREATE TYPE ... + ALTER COLUMN + DROP TYPE)。