"""Create the concierge advisor domain. Revision ID: 0016_concierge_advisors Revises: 0015_wanfa """ from datetime import datetime import json from alembic import context, op import sqlalchemy as sa from sqlalchemy import inspect from app.migration_compat import baseline_table_names revision = "0016_concierge_advisors" down_revision = "0015_wanfa" branch_labels = None depends_on = None SEED_ADVISORS = ( { "id": "advisor-amanda", "avatar": "https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240", "name": "Amanda", "role": "SENIOR TRAVEL ADVISOR", "details": [ {"icon": "calendar", "label": "服务经验:8年"}, {"icon": "navigate", "label": "擅长领域:瀑降 / 探洞"}, ], "qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge", "isActive": True, "sortOrder": 0, }, { "id": "advisor-mia", "avatar": "https://images.unsplash.com/photo-1531123897727-8f129e1688ce?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240", "name": "Mia", "role": "LIFESTYLE TRAVEL ADVISOR", "details": [ {"icon": "calendar", "label": "服务经验:6年"}, {"icon": "navigate", "label": "擅长领域:溯溪 / 人文探索"}, ], "qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge", "isActive": True, "sortOrder": 1, }, { "id": "advisor-leo", "avatar": "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&fm=jpg&q=80&w=240&h=240", "name": "Leo", "role": "WILD TRAVEL ADVISOR", "details": [ {"icon": "calendar", "label": "服务经验:9年"}, {"icon": "navigate", "label": "擅长领域:洞穴探险 / 山地徒步"}, ], "qrImage": "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=WonderQ%20Concierge", "isActive": True, "sortOrder": 2, }, ) def is_offline_mode() -> bool: try: return context.is_offline_mode() except (NameError, AttributeError): return False def _seed_advisors() -> None: offline = is_offline_mode() details_type = sa.Text() if offline else sa.JSON() table = sa.table( "ConciergeAdvisor", sa.column("id", sa.String()), sa.column("avatar", sa.String()), sa.column("name", sa.String()), sa.column("role", sa.String()), sa.column("details", details_type), sa.column("qrImage", sa.String()), sa.column("isActive", sa.Boolean()), sa.column("sortOrder", sa.Integer()), sa.column("createdAt", sa.DateTime()), sa.column("updatedAt", sa.DateTime()), ) timestamp = datetime(2026, 1, 1) rows = [{**advisor, "createdAt": timestamp, "updatedAt": timestamp} for advisor in SEED_ADVISORS] if offline: rows = [{**advisor, "details": json.dumps(advisor["details"], ensure_ascii=False)} for advisor in rows] op.bulk_insert(table, rows) def upgrade() -> None: if is_offline_mode(): existing_tables = baseline_table_names() else: bind = op.get_bind() existing_tables = set(inspect(bind).get_table_names()) if "ConciergeAdvisor" not in existing_tables: op.create_table( "ConciergeAdvisor", sa.Column("id", sa.String(), nullable=False), sa.Column("avatar", sa.String(), nullable=False), sa.Column("name", sa.String(), nullable=False), sa.Column("role", sa.String(), nullable=False), sa.Column("details", sa.JSON(), nullable=False), sa.Column("qrImage", sa.String(), nullable=False), sa.Column("isActive", sa.Boolean(), nullable=False, server_default=sa.true()), sa.Column("sortOrder", sa.Integer(), nullable=False, server_default="0"), sa.Column("createdAt", sa.DateTime(), nullable=False), sa.Column("updatedAt", sa.DateTime(), nullable=False), sa.PrimaryKeyConstraint("id"), ) op.create_index("ix_ConciergeAdvisor_isActive", "ConciergeAdvisor", ["isActive"]) op.create_index("ix_ConciergeAdvisor_sortOrder", "ConciergeAdvisor", ["sortOrder"]) if is_offline_mode(): _seed_advisors() return advisor_table = sa.table("ConciergeAdvisor", sa.column("id", sa.String())) if bind.execute(sa.select(advisor_table.c.id).limit(1)).first() is None: _seed_advisors() def downgrade() -> None: bind = op.get_bind() existing_tables = set(inspect(bind).get_table_names()) if "ConciergeAdvisor" in existing_tables: op.drop_table("ConciergeAdvisor")