Files
WonderQ-Project/WonderQ-Admin/alembic/versions/0017_home_content.py
T
2026-08-25 12:06:42 +08:00

228 lines
8.7 KiB
Python

"""Create the homepage content domains.
Revision ID: 0017_home_content
Revises: 0016_concierge_advisors
"""
from datetime import datetime
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0017_home_content"
down_revision = "0016_concierge_advisors"
branch_labels = None
depends_on = None
SEED_EXPERIENCES = (
{
"id": "waterfall-descent",
"badge": "玩过推荐",
"category": "瀑降体验",
"title": "悬崖瀑降",
"englishTitle": "WATERFALL DESCENT",
"image": "https://genk.mediacdn.vn/139269124445442048/2024/4/27/10-23-sinkhole-1714189653945948438879.jpg",
"demandKeyword": "悬崖瀑降",
"isActive": True,
"sortOrder": 0,
},
{
"id": "cave-exploration",
"badge": "玩过推荐",
"category": "地心探险",
"title": "森林&探洞",
"englishTitle": "CAVE EXPLORATION",
"image": "https://www.zurnal24.si/media/img/5e/d5/9526a56dba168aa136f3.jpeg",
"demandKeyword": "地心探险",
"isActive": True,
"sortOrder": 1,
},
)
SEED_TEAM_BUILDINGS = (
{
"id": "wild-challenge",
"tag": "户外挑战",
"title": "山野挑战,共创极境",
"description": "洞穴、瀑降与协作,适合 10-30 人。",
"image": "https://dimg04.c-ctrip.com/images/1mh0412000njfr1ot9453_W_640_10000.jpg?proc=autoorient",
"demandKeyword": "户外团建",
"detailSubtitle": "洞穴、瀑降与协作,适合 10-30 人。",
"detailParagraphs": ["洞穴、瀑降与协作,适合 10-30 人。"],
"isActive": True,
"sortOrder": 0,
},
{
"id": "canyon-teamwork",
"tag": "团队协作",
"title": "峡谷溯溪,默契同行",
"description": "溯溪与分组协作,兼顾参与感与安全。",
"image": "https://dimg04.c-ctrip.com/images/0EQ5712000ca7t504EC0E_W_640_10000.jpg?proc=autoorient",
"demandKeyword": "峡谷团建",
"detailSubtitle": "溯溪与分组协作,兼顾参与感与安全。",
"detailParagraphs": ["溯溪与分组协作,兼顾参与感与安全。"],
"isActive": True,
"sortOrder": 1,
},
{
"id": "village-gathering",
"tag": "人文聚会",
"title": "苗寨共聚,认识彼此",
"description": "夜游、长桌宴与文化体验,适合团建收尾。",
"image": "https://p6.itc.cn/q_70/images03/20200918/df728d2b79d943da869333e2ea2c92c8.jpeg",
"demandKeyword": "贵州团建",
"detailSubtitle": "夜游、长桌宴与文化体验,适合团建收尾。",
"detailParagraphs": ["夜游、长桌宴与文化体验,适合团建收尾。"],
"isActive": True,
"sortOrder": 2,
},
)
SEED_WILD_ARCHIVES = (
{
"id": "hundred-meter-descent",
"title": "百米自降",
"image": "https://genk.mediacdn.vn/139269124445442048/2024/4/27/10-23-sinkhole-1714189653945948438879.jpg",
"images": ["https://genk.mediacdn.vn/139269124445442048/2024/4/27/10-23-sinkhole-1714189653945948438879.jpg"],
"demandKeyword": "悬崖瀑降",
"isActive": True,
"sortOrder": 0,
},
{
"id": "shilong-cave",
"title": "石龙洞",
"image": "https://www.zurnal24.si/media/img/5e/d5/9526a56dba168aa136f3.jpeg",
"images": ["https://www.zurnal24.si/media/img/5e/d5/9526a56dba168aa136f3.jpeg"],
"demandKeyword": "地心探险",
"isActive": True,
"sortOrder": 1,
},
{
"id": "cliff-current",
"title": "绝壁迎流",
"image": "https://q9.itc.cn/q_70/images03/20250810/b62f5afc191a4947a66e6b32721b0235.jpeg",
"images": ["https://q9.itc.cn/q_70/images03/20250810/b62f5afc191a4947a66e6b32721b0235.jpeg"],
"demandKeyword": "峡谷探险",
"isActive": True,
"sortOrder": 2,
},
{
"id": "canyon-streaming",
"title": "峡谷溯溪",
"image": "https://dimg04.c-ctrip.com/images/0EQ5712000ca7t504EC0E_W_640_10000.jpg?proc=autoorient",
"images": ["https://dimg04.c-ctrip.com/images/0EQ5712000ca7t504EC0E_W_640_10000.jpg?proc=autoorient"],
"demandKeyword": "峡谷溯溪",
"isActive": True,
"sortOrder": 3,
},
)
def _create_table(table_name: str, columns: list[sa.Column]) -> None:
bind = op.get_bind()
if table_name in set(inspect(bind).get_table_names()):
return
op.create_table(table_name, *columns, sa.PrimaryKeyConstraint("id"))
op.create_index(f"ix_{table_name}_isActive", table_name, ["isActive"])
op.create_index(f"ix_{table_name}_sortOrder", table_name, ["sortOrder"])
def _prepare_seed_rows(
rows: tuple[dict, ...],
column_names: set[str],
timestamp: datetime,
) -> list[dict]:
return [
{
key: value
for key, value in {**row, "createdAt": timestamp, "updatedAt": timestamp}.items()
if key in column_names
}
for row in rows
]
def _seed_table(table_name: str, columns: list[sa.Column], rows: tuple[dict, ...]) -> None:
if context.is_offline_mode():
seed_columns = [sa.column(column.name, column.type) for column in columns]
else:
seed_columns = [
sa.column(column["name"], column["type"])
for column in inspect(op.get_bind()).get_columns(table_name)
]
table = sa.table(table_name, *seed_columns)
timestamp = datetime(2026, 1, 1)
op.bulk_insert(
table,
_prepare_seed_rows(rows, {column.name for column in seed_columns}, timestamp),
)
def upgrade() -> None:
experience_columns = [
sa.Column("id", sa.String(), nullable=False),
sa.Column("badge", sa.String(), nullable=False),
sa.Column("category", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("englishTitle", sa.String(), nullable=False),
sa.Column("image", sa.String(), nullable=False),
sa.Column("demandKeyword", 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),
]
team_columns = [
sa.Column("id", sa.String(), nullable=False),
sa.Column("tag", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("image", sa.String(), nullable=False),
sa.Column("demandKeyword", 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),
]
archive_columns = [
sa.Column("id", sa.String(), nullable=False),
sa.Column("title", sa.String(), nullable=False),
sa.Column("image", sa.String(), nullable=False),
sa.Column("demandKeyword", 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),
]
_create_table("HomeExperience", experience_columns)
_create_table("HomeTeamBuilding", team_columns)
_create_table("HomeWildArchive", archive_columns)
if context.is_offline_mode():
_seed_table("HomeExperience", experience_columns, SEED_EXPERIENCES)
_seed_table("HomeTeamBuilding", team_columns, SEED_TEAM_BUILDINGS)
_seed_table("HomeWildArchive", archive_columns, SEED_WILD_ARCHIVES)
return
bind = op.get_bind()
for table_name, columns, rows in (
("HomeExperience", experience_columns, SEED_EXPERIENCES),
("HomeTeamBuilding", team_columns, SEED_TEAM_BUILDINGS),
("HomeWildArchive", archive_columns, SEED_WILD_ARCHIVES),
):
table = sa.table(table_name, sa.column("id", sa.String()))
if bind.execute(sa.select(table.c.id).limit(1)).first() is None:
_seed_table(table_name, columns, rows)
def downgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
for table_name in ("HomeWildArchive", "HomeTeamBuilding", "HomeExperience"):
if table_name in existing_tables:
op.drop_table(table_name)