"""Задел под титулы/ачивки: users.title_achievement_slug + таблица user_achievements. Сами определения ачивок хранятся файлами (settings.achievements_dir), в БД — только будущая выдача игрокам. Идемпотентна (как 0003/0004): на свежей БД (0001 = create_all из актуальных моделей) столбец/таблица уже есть → no-op; на существующей — добавляет. Revision ID: 0005_achievements Revises: 0004_user_profile Create Date: 2026-06-17 """ from typing import Sequence, Union import sqlalchemy as sa from sqlalchemy import inspect from alembic import op revision: str = "0005_achievements" down_revision: Union[str, None] = "0004_user_profile" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: bind = op.get_bind() insp = inspect(bind) cols = {c["name"] for c in insp.get_columns("users")} if "title_achievement_slug" not in cols: with op.batch_alter_table("users") as b: b.add_column(sa.Column("title_achievement_slug", sa.String(64), nullable=True)) if "user_achievements" not in insp.get_table_names(): op.create_table( "user_achievements", sa.Column("id", sa.Integer(), primary_key=True), sa.Column( "user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column("achievement_slug", sa.String(64), nullable=False), sa.Column("earned_at", sa.DateTime(), nullable=False), sa.UniqueConstraint("user_id", "achievement_slug", name="uq_user_achievement"), ) op.create_index( "ix_user_achievements_user_id", "user_achievements", ["user_id"] ) def downgrade() -> None: bind = op.get_bind() insp = inspect(bind) if "user_achievements" in insp.get_table_names(): op.drop_table("user_achievements") with op.batch_alter_table("users") as b: try: b.drop_column("title_achievement_slug") except Exception: # noqa: BLE001 pass