124 lines
5.1 KiB
Python
124 lines
5.1 KiB
Python
"""init push tables
|
||
|
||
Revision ID: 0002_init_push_tables
|
||
Revises: 0001_init_content_tables
|
||
Create Date: 2026-02-03
|
||
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from alembic import op
|
||
import sqlalchemy as sa
|
||
from sqlalchemy.dialects import mysql
|
||
|
||
|
||
# revision identifiers, used by Alembic.
|
||
revision = "0002_init_push_tables"
|
||
down_revision = "0001_init_content_tables"
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
|
||
def upgrade() -> None:
|
||
# push_tokens
|
||
op.create_table(
|
||
"push_tokens",
|
||
sa.Column(
|
||
"id",
|
||
mysql.BIGINT(unsigned=True),
|
||
primary_key=True,
|
||
autoincrement=True,
|
||
comment="主键",
|
||
),
|
||
sa.Column("client_user_id", sa.String(length=64), nullable=False, comment="客户端用户标识(UUID)"),
|
||
sa.Column(
|
||
"platform",
|
||
sa.Enum("ios", "android", name="push_platform"),
|
||
nullable=False,
|
||
comment="平台",
|
||
),
|
||
sa.Column("push_token", sa.String(length=255), nullable=False, comment="Expo Push Token"),
|
||
sa.Column("app_id", sa.String(length=255), nullable=False, comment="bundle id / package name(用于隔离)"),
|
||
sa.Column(
|
||
"env",
|
||
sa.Enum("dev", "prod", name="push_env"),
|
||
nullable=False,
|
||
comment="环境隔离",
|
||
),
|
||
sa.Column(
|
||
"is_active",
|
||
sa.Boolean(),
|
||
server_default=sa.text("1"),
|
||
nullable=False,
|
||
comment="是否有效(发送失败且不可恢复时置为 false)",
|
||
),
|
||
sa.Column("last_seen_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="最后一次上报时间"),
|
||
sa.UniqueConstraint("env", "app_id", "push_token", name="uniq_env_app_token"),
|
||
mysql_charset="utf8mb4",
|
||
)
|
||
op.create_index("idx_push_tokens_client_user_id", "push_tokens", ["client_user_id"], unique=False)
|
||
op.create_index("idx_push_tokens_is_active", "push_tokens", ["is_active"], unique=False)
|
||
|
||
# push_preferences
|
||
op.create_table(
|
||
"push_preferences",
|
||
sa.Column("client_user_id", sa.String(length=64), primary_key=True, comment="客户端用户标识(UUID)"),
|
||
sa.Column(
|
||
"enabled",
|
||
sa.Boolean(),
|
||
server_default=sa.text("0"),
|
||
nullable=False,
|
||
comment="是否开启每日提醒(enabled=false 或 times_per_day=0 均视为关闭)",
|
||
),
|
||
sa.Column(
|
||
"times_per_day",
|
||
sa.SmallInteger(),
|
||
server_default="0",
|
||
nullable=False,
|
||
comment="每天推送次数(0~5)",
|
||
),
|
||
sa.Column("timezone", sa.String(length=64), nullable=True, comment="IANA 时区(例如 Asia/Shanghai),来自客户端上报"),
|
||
sa.Column("locale", sa.String(length=32), nullable=True, comment="客户端语言(例如 zh-CN/en/zh-TW),用于文案语言选择"),
|
||
sa.Column("user_profile_json", sa.JSON(), nullable=True, comment="用户画像(V1.2;可选)。用于 Push 场景推荐文案生成。"),
|
||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="更新时间"),
|
||
mysql_charset="utf8mb4",
|
||
)
|
||
|
||
# push_send_log
|
||
op.create_table(
|
||
"push_send_log",
|
||
sa.Column(
|
||
"id",
|
||
mysql.BIGINT(unsigned=True),
|
||
primary_key=True,
|
||
autoincrement=True,
|
||
comment="主键",
|
||
),
|
||
sa.Column("client_user_id", sa.String(length=64), nullable=False, comment="客户端用户标识(UUID)"),
|
||
sa.Column("local_date", sa.Date(), nullable=False, comment="用户时区的本地日期(用于幂等)"),
|
||
sa.Column("slot_index", sa.SmallInteger(), nullable=False, comment="当天第几条(1..times_per_day)"),
|
||
sa.Column("scheduled_at", sa.DateTime(), nullable=False, comment="计划发送时间(UTC)"),
|
||
sa.Column("sent_at", sa.DateTime(), nullable=True, comment="实际发送时间(UTC)"),
|
||
sa.Column("status", sa.String(length=16), server_default="scheduled", nullable=False, comment="scheduled/sent/failed"),
|
||
sa.Column("error", sa.Text(), nullable=True, comment="失败原因(可选)"),
|
||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="创建时间"),
|
||
sa.UniqueConstraint("client_user_id", "local_date", "slot_index", name="uniq_user_date_slot"),
|
||
mysql_charset="utf8mb4",
|
||
)
|
||
op.create_index("idx_push_log_user_date", "push_send_log", ["client_user_id", "local_date"], unique=False)
|
||
op.create_index("idx_push_log_status", "push_send_log", ["status"], unique=False)
|
||
|
||
|
||
def downgrade() -> None:
|
||
op.drop_index("idx_push_log_status", table_name="push_send_log")
|
||
op.drop_index("idx_push_log_user_date", table_name="push_send_log")
|
||
op.drop_table("push_send_log")
|
||
|
||
op.drop_table("push_preferences")
|
||
|
||
op.drop_index("idx_push_tokens_is_active", table_name="push_tokens")
|
||
op.drop_index("idx_push_tokens_client_user_id", table_name="push_tokens")
|
||
op.drop_table("push_tokens")
|
||
|