feat(admin, miniapp): 优化数据库迁移逻辑与小程序图片资源处理

- 为后端Admin新增migration_compat工具,支持离线迁移时的基线资源检查,重构全部alembic迁移脚本适配离线模式,避免重复创建已存在的数据库表和字段
- 完善初始数据库迁移脚本,添加默认部门种子数据,新增ConciergeAdvisor详情字段从JSON到JSONB的类型转换迁移
- 小程序端新增图片资源解析工具resolveNetworkImage,实现本地静态资源到CDN的自动映射,添加图片加载容错降级机制
- 完善HeroSlide类型定义,新增actionLabel字段,优化首页轮播组件的文本渲染与错误处理逻辑
- 新增对应测试用例验证迁移路径正确性与前端数据处理逻辑
- 新增临时 brainstorm UI对比草稿与开发临时状态文件
This commit is contained in:
duanshuwen committed 2026-08-27 11:37:03 +08:00
1 parent 96bcc7c032
commit 4e04feda5b
27 files changed
+2695 -377

No files matched your search

@@ -5,7 +5,10 @@ Revises:
Create Date: 2026-06-30
"""
from datetime import datetime, timezone
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from app.database import Base
from app import models # noqa: F401
@@ -16,6 +19,41 @@ down_revision = None
branch_labels = None
depends_on = None
DEFAULT_DEPT_ID = "00000000-0000-0000-0000-000000000001"
def _seed_default_department(bind) -> None:
department_table = sa.table(
"AdminDepartment",
sa.column("id", sa.String()),
sa.column("name", sa.String()),
sa.column("sortOrder", sa.Integer()),
sa.column("isActive", sa.Boolean()),
sa.column("createdAt", sa.DateTime()),
sa.column("updatedAt", sa.DateTime()),
)
if context.is_offline_mode():
should_seed = True
else:
should_seed = bind.execute(
sa.text('SELECT 1 FROM "AdminDepartment" LIMIT 1')
).first() is None
if should_seed:
timestamp = datetime.now(timezone.utc).replace(tzinfo=None)
op.bulk_insert(
department_table,
[
{
"id": DEFAULT_DEPT_ID,
"name": "默认部门",
"sortOrder": 0,
"isActive": True,
"createdAt": timestamp,
"updatedAt": timestamp,
}
],
)
def upgrade() -> None:
bind = op.get_bind()
@@ -26,6 +64,8 @@ def upgrade() -> None:
for table in Base.metadata.sorted_tables:
if table.name not in existing_tables:
table.create(bind)
if "AdminDepartment" in Base.metadata.tables:
_seed_default_department(bind)
def downgrade() -> None:
+7 -2
View File
@@ -10,6 +10,8 @@ from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from app.migration_compat import baseline_table_names
revision = "0015_wanfa"
down_revision = "0001_initial_schema"
@@ -99,8 +101,11 @@ def _seed_wanfa() -> None:
def upgrade() -> None:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if context.is_offline_mode():
existing_tables = baseline_table_names()
else:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
if "WanfaCategory" not in existing_tables:
op.create_table(
@@ -5,11 +5,14 @@ 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"
@@ -60,14 +63,23 @@ SEED_ADVISORS = (
)
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", sa.JSON()),
sa.column("details", details_type),
sa.column("qrImage", sa.String()),
sa.column("isActive", sa.Boolean()),
sa.column("sortOrder", sa.Integer()),
@@ -75,15 +87,18 @@ def _seed_advisors() -> None:
sa.column("updatedAt", sa.DateTime()),
)
timestamp = datetime(2026, 1, 1)
op.bulk_insert(
table,
[{**advisor, "createdAt": timestamp, "updatedAt": timestamp} for advisor in SEED_ADVISORS],
)
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:
bind = op.get_bind()
existing_tables = set(inspect(bind).get_table_names())
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",
@@ -102,7 +117,7 @@ def upgrade() -> None:
op.create_index("ix_ConciergeAdvisor_isActive", "ConciergeAdvisor", ["isActive"])
op.create_index("ix_ConciergeAdvisor_sortOrder", "ConciergeAdvisor", ["sortOrder"])
if context.is_offline_mode():
if is_offline_mode():
_seed_advisors()
return
@@ -5,12 +5,15 @@ Revises: 0016_concierge_advisors
"""
from datetime import datetime
import json
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql
from app.migration_compat import baseline_column_names, baseline_table_names
revision = "0017_home_content"
down_revision = "0016_concierge_advisors"
@@ -142,6 +145,13 @@ SEED_WILD_ARCHIVES = (
def _create_table(table_name: str, columns: list[sa.Column]) -> None:
if is_offline_mode():
if table_name in baseline_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"])
return
bind = op.get_bind()
if table_name in set(inspect(bind).get_table_names()):
return
@@ -178,6 +188,11 @@ def _seed_table(table_name: str, columns: list[sa.Column], rows: tuple[dict, ...
column_types = {column.name: column.type for column in columns}
if is_offline_mode():
available_columns = set(column_types)
if table_name in baseline_table_names():
available_columns.update(baseline_column_names(table_name))
for name, column_type in OPTIONAL_SEED_COLUMN_TYPES.items():
if name in available_columns:
column_types.setdefault(name, column_type)
else:
bind = op.get_bind()
current_columns = inspect(bind).get_columns(table_name)
@@ -200,7 +215,31 @@ def _seed_table(table_name: str, columns: list[sa.Column], rows: tuple[dict, ...
],
)
timestamp = datetime(2026, 1, 1)
op.bulk_insert(table, prepare_seed_rows(rows, seed_column_names, timestamp))
seed_rows = prepare_seed_rows(rows, seed_column_names, timestamp)
if is_offline_mode():
seed_rows = [
{
**row,
**{
name: json.dumps(row[name], ensure_ascii=False)
for name in ("detailParagraphs", "images")
if name in row
},
}
for row in seed_rows
]
for name in ("detailParagraphs", "images"):
if name in column_types:
column_types[name] = sa.Text()
table = sa.table(
table_name,
*[
sa.column(name, column_types[name])
for name in column_types
if name in seed_column_names
],
)
op.bulk_insert(table, seed_rows)
def upgrade() -> None:
@@ -4,10 +4,12 @@ Revision ID: 0018_home_wanfa_recommendations
Revises: 0017_home_content
"""
from alembic import op
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from app.migration_compat import baseline_table_names
revision = "0018_home_wanfa_recommendations"
down_revision = "0017_home_content"
@@ -15,7 +17,16 @@ branch_labels = None
depends_on = None
def is_offline_mode() -> bool:
try:
return context.is_offline_mode()
except (NameError, AttributeError):
return False
def upgrade() -> None:
if "HomeWanfaRecommendation" in baseline_table_names() and is_offline_mode():
return
bind = op.get_bind()
if "HomeWanfaRecommendation" in set(inspect(bind).get_table_names()):
return
@@ -9,6 +9,8 @@ import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql
from app.migration_compat import baseline_column_names
revision = "0019_home_wild_archive_images"
down_revision = "0018_home_wanfa_recommendations"
@@ -29,7 +31,7 @@ def is_offline_mode() -> bool:
def upgrade() -> None:
if is_offline_mode():
add_images_column = True
add_images_column = "images" not in baseline_column_names("HomeWildArchive")
else:
bind = op.get_bind()
existing_columns = {column["name"] for column in inspect(bind).get_columns("HomeWildArchive")}
@@ -9,6 +9,8 @@ import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql
from app.migration_compat import baseline_column_names
revision = "0020_home_team_building_details"
down_revision = "0019_home_wild_archive_images"
@@ -32,7 +34,9 @@ def is_offline_mode() -> bool:
def upgrade() -> None:
if is_offline_mode():
columns_to_add = DETAIL_COLUMNS
columns_to_add = missing_detail_columns(
baseline_column_names("HomeTeamBuilding")
)
else:
bind = op.get_bind()
existing_columns = {column["name"] for column in inspect(bind).get_columns("HomeTeamBuilding")}
@@ -11,6 +11,8 @@ import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql
from app.migration_compat import baseline_table_names
revision = "0021_detail_records"
down_revision = "0020_home_team_building_details"
@@ -86,7 +88,7 @@ def _create_detail_table() -> None:
def upgrade() -> None:
if is_offline_mode():
table_exists = False
table_exists = "DetailRecord" in baseline_table_names()
existing_index_names = set()
else:
bind = op.get_bind()
@@ -8,6 +8,8 @@ from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from app.migration_compat import baseline_column_names
revision = "0023_detail_concierge_advisor"
down_revision = "0022_opaque_ids"
@@ -28,7 +30,7 @@ def is_offline_mode() -> bool:
def upgrade() -> None:
if is_offline_mode():
should_add = True
should_add = "conciergeAdvisorId" not in baseline_column_names("DetailRecord")
else:
existing_columns = {
column["name"]
@@ -5,6 +5,13 @@ import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql
from app.migration_compat import (
baseline_column_names,
baseline_foreign_keys,
baseline_index_names,
baseline_table_names,
)
revision = "0024_vehicle_demand"
down_revision = "0023_detail_concierge_advisor"
@@ -55,10 +62,10 @@ def is_offline_mode() -> bool:
def upgrade() -> None:
if is_offline_mode():
existing_lead_columns = set()
existing_lead_indexes = set()
existing_lead_foreign_keys = []
vehicle_service_table_exists = False
existing_lead_columns = baseline_column_names("Lead")
existing_lead_indexes = baseline_index_names("Lead")
existing_lead_foreign_keys = baseline_foreign_keys("Lead")
vehicle_service_table_exists = "VehicleServiceConfig" in baseline_table_names()
existing_vehicle_service_indexes = set()
else:
inspector = inspect(op.get_bind())
@@ -2,8 +2,9 @@
from datetime import datetime, timezone
from alembic import op
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0025_admin_rbac"
@@ -37,10 +38,40 @@ MENU_ROWS = (
)
def is_offline_mode() -> bool:
try:
return context.is_offline_mode()
except (NameError, AttributeError):
return False
def _existing_table_names() -> set[str]:
if is_offline_mode():
# The initial baseline creates the current RBAC tables before this
# revision runs, but an offline connection cannot inspect them.
return set(RBAC_TABLES)
return set(inspect(op.get_bind()).get_table_names())
def _create_table_if_missing(existing_tables: set[str], table_name: str, *elements) -> None:
if table_name in existing_tables:
return
op.create_table(table_name, *elements)
existing_tables.add(table_name)
def _table_has_rows(table_name: str) -> bool:
if is_offline_mode():
return False
return op.get_bind().execute(sa.text(f'SELECT 1 FROM "{table_name}" LIMIT 1')).first() is not None
def upgrade() -> None:
now = datetime.now(timezone.utc).replace(tzinfo=None)
existing_tables = _existing_table_names()
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminRole",
sa.Column("id", sa.String(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
@@ -53,7 +84,8 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("code", name="uq_AdminRole_code"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminDepartment",
sa.Column("id", sa.String(), nullable=False),
sa.Column("parentId", sa.String(), nullable=True),
@@ -65,7 +97,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["parentId"], ["AdminDepartment.id"], ondelete="SET NULL"),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminMenu",
sa.Column("id", sa.String(), nullable=False),
sa.Column("parentId", sa.String(), nullable=True),
@@ -84,7 +117,8 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("permissionCode", name="uq_AdminMenu_permissionCode"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminUserRole",
sa.Column("userId", sa.String(), nullable=False),
sa.Column("roleId", sa.String(), nullable=False),
@@ -93,7 +127,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["userId"], ["AdminUser.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("userId", "roleId"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminRoleMenu",
sa.Column("roleId", sa.String(), nullable=False),
sa.Column("menuId", sa.String(), nullable=False),
@@ -102,7 +137,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["roleId"], ["AdminRole.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("roleId", "menuId"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminUserDepartment",
sa.Column("userId", sa.String(), nullable=False),
sa.Column("deptId", sa.String(), nullable=False),
@@ -111,7 +147,8 @@ def upgrade() -> None:
sa.ForeignKeyConstraint(["userId"], ["AdminUser.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("userId", "deptId"),
)
op.create_table(
_create_table_if_missing(
existing_tables,
"AdminRoleDepartment",
sa.Column("roleId", sa.String(), nullable=False),
sa.Column("deptId", sa.String(), nullable=False),
@@ -124,15 +161,18 @@ def upgrade() -> None:
role_table = sa.table("AdminRole", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("code", sa.String), sa.column("dataScope", sa.String), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
dept_table = sa.table("AdminDepartment", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("sortOrder", sa.Integer), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
menu_table = sa.table("AdminMenu", sa.column("id", sa.String), sa.column("name", sa.String), sa.column("type", sa.String), sa.column("path", sa.String), sa.column("componentKey", sa.String), sa.column("permissionCode", sa.String), sa.column("sortOrder", sa.Integer), sa.column("isVisible", sa.Boolean), sa.column("isActive", sa.Boolean), sa.column("createdAt", sa.DateTime), sa.column("updatedAt", sa.DateTime))
op.bulk_insert(role_table, [{"id": SUPER_ROLE_ID, "name": "超级管理员", "code": "super_admin", "dataScope": "all", "isActive": True, "createdAt": now, "updatedAt": now}])
op.bulk_insert(dept_table, [{"id": DEFAULT_DEPT_ID, "name": "默认部门", "sortOrder": 0, "isActive": True, "createdAt": now, "updatedAt": now}])
op.bulk_insert(
menu_table,
[
{"id": menu_id, "name": name, "type": menu_type, "path": path, "componentKey": component, "permissionCode": permission, "sortOrder": sort_order, "isVisible": True, "isActive": True, "createdAt": now, "updatedAt": now}
for menu_id, name, menu_type, path, component, permission, sort_order in MENU_ROWS
],
)
if not _table_has_rows("AdminRole"):
op.bulk_insert(role_table, [{"id": SUPER_ROLE_ID, "name": "超级管理员", "code": "super_admin", "dataScope": "all", "isActive": True, "createdAt": now, "updatedAt": now}])
if not _table_has_rows("AdminDepartment"):
op.bulk_insert(dept_table, [{"id": DEFAULT_DEPT_ID, "name": "默认部门", "sortOrder": 0, "isActive": True, "createdAt": now, "updatedAt": now}])
if not _table_has_rows("AdminMenu"):
op.bulk_insert(
menu_table,
[
{"id": menu_id, "name": name, "type": menu_type, "path": path, "componentKey": component, "permissionCode": permission, "sortOrder": sort_order, "isVisible": True, "isActive": True, "createdAt": now, "updatedAt": now}
for menu_id, name, menu_type, path, component, permission, sort_order in MENU_ROWS
],
)
op.execute(sa.text('INSERT INTO "AdminRoleMenu" ("roleId", "menuId", "createdAt") SELECT :role_id, "id", :created_at FROM "AdminMenu" ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, created_at=now))
op.execute(sa.text('INSERT INTO "AdminRoleDepartment" ("roleId", "deptId", "createdAt") VALUES (:role_id, :dept_id, :created_at) ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, dept_id=DEFAULT_DEPT_ID, created_at=now))
op.execute(sa.text('INSERT INTO "AdminUserRole" ("userId", "roleId", "createdAt") SELECT "id", :role_id, :created_at FROM "AdminUser" ON CONFLICT DO NOTHING').bindparams(role_id=SUPER_ROLE_ID, created_at=now))
@@ -1,7 +1,8 @@
"""Add department and creator ownership to operator-managed resources."""
from alembic import op
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
revision = "0026_admin_ownership"
@@ -31,56 +32,140 @@ OWNED_TABLES = (
)
def is_offline_mode() -> bool:
try:
return context.is_offline_mode()
except (NameError, AttributeError):
return False
def _schema_snapshot() -> tuple[set[str], dict[str, dict[str, dict]], dict[str, set[str]], dict[str, list[dict]]]:
if is_offline_mode():
return set(), {}, {}, {}
inspector = inspect(op.get_bind())
table_names = set(inspector.get_table_names())
columns = {
table_name: {column["name"]: column for column in inspector.get_columns(table_name)}
for table_name in table_names
}
indexes = {
table_name: {
index["name"]
for index in inspector.get_indexes(table_name)
if index.get("name")
}
for table_name in table_names
}
foreign_keys = {
table_name: inspector.get_foreign_keys(table_name)
for table_name in table_names
}
return table_names, columns, indexes, foreign_keys
def _has_foreign_key(
foreign_keys: list[dict],
constrained_column: str,
referred_table: str,
) -> bool:
return any(
foreign_key.get("constrained_columns") == [constrained_column]
and foreign_key.get("referred_table") == referred_table
and foreign_key.get("referred_columns") == ["id"]
for foreign_key in foreign_keys
)
def upgrade() -> None:
table_names, columns, indexes, foreign_keys = _schema_snapshot()
for table_name in OWNED_TABLES:
op.add_column(
table_name,
sa.Column(
"deptId",
sa.String(),
nullable=True,
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
),
)
op.add_column(table_name, sa.Column("createdById", sa.String(), nullable=True))
op.create_index(f"ix_{table_name}_deptId", table_name, ["deptId"])
op.create_index(f"ix_{table_name}_createdById", table_name, ["createdById"])
op.create_foreign_key(
f"fk_{table_name}_deptId",
table_name,
"AdminDepartment",
["deptId"],
["id"],
ondelete="RESTRICT",
)
op.create_foreign_key(
f"fk_{table_name}_createdById",
table_name,
"AdminUser",
["createdById"],
["id"],
ondelete="SET NULL",
)
if table_name not in table_names:
continue
table_columns = columns[table_name]
added_dept_column = "deptId" not in table_columns
added_creator_column = "createdById" not in table_columns
if added_dept_column:
op.add_column(
table_name,
sa.Column(
"deptId",
sa.String(),
nullable=True,
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
),
)
table_columns["deptId"] = {"name": "deptId", "nullable": True}
if added_creator_column:
op.add_column(table_name, sa.Column("createdById", sa.String(), nullable=True))
table_columns["createdById"] = {"name": "createdById", "nullable": True}
table_indexes = indexes[table_name]
if f"ix_{table_name}_deptId" not in table_indexes:
op.create_index(f"ix_{table_name}_deptId", table_name, ["deptId"])
table_indexes.add(f"ix_{table_name}_deptId")
if f"ix_{table_name}_createdById" not in table_indexes:
op.create_index(f"ix_{table_name}_createdById", table_name, ["createdById"])
table_indexes.add(f"ix_{table_name}_createdById")
table_foreign_keys = foreign_keys[table_name]
if not _has_foreign_key(table_foreign_keys, "deptId", "AdminDepartment"):
op.create_foreign_key(
f"fk_{table_name}_deptId",
table_name,
"AdminDepartment",
["deptId"],
["id"],
ondelete="RESTRICT",
)
table_foreign_keys.append(
{
"constrained_columns": ["deptId"],
"referred_table": "AdminDepartment",
"referred_columns": ["id"],
}
)
if not _has_foreign_key(table_foreign_keys, "createdById", "AdminUser"):
op.create_foreign_key(
f"fk_{table_name}_createdById",
table_name,
"AdminUser",
["createdById"],
["id"],
ondelete="SET NULL",
)
table_foreign_keys.append(
{
"constrained_columns": ["createdById"],
"referred_table": "AdminUser",
"referred_columns": ["id"],
}
)
quoted_table = f'"{table_name}"'
op.execute(
sa.text(f'UPDATE {quoted_table} SET "deptId" = :dept_id WHERE "deptId" IS NULL').bindparams(
dept_id=DEFAULT_DEPT_ID
needs_dept_backfill = added_dept_column or table_columns["deptId"].get("nullable") is True
if needs_dept_backfill:
op.execute(
sa.text(f'UPDATE {quoted_table} SET "deptId" = :dept_id WHERE "deptId" IS NULL').bindparams(
dept_id=DEFAULT_DEPT_ID
)
)
)
op.alter_column(
table_name,
"deptId",
existing_type=sa.String(),
nullable=False,
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
)
op.execute(
sa.text(
f'UPDATE {quoted_table} SET "createdById" = '
'(SELECT "id" FROM "AdminUser" ORDER BY "createdAt" ASC LIMIT 1) '
'WHERE "createdById" IS NULL'
op.alter_column(
table_name,
"deptId",
existing_type=sa.String(),
nullable=False,
server_default=sa.text(f"'{DEFAULT_DEPT_ID}'"),
)
if added_creator_column:
op.execute(
sa.text(
f'UPDATE {quoted_table} SET "createdById" = '
'(SELECT "id" FROM "AdminUser" ORDER BY "createdAt" ASC LIMIT 1) '
'WHERE "createdById" IS NULL'
)
)
)
def downgrade() -> None:
@@ -1,9 +1,11 @@
"""Create the media asset table required by admin uploads."""
from alembic import op
from alembic import context, op
import sqlalchemy as sa
from sqlalchemy import inspect
from app.migration_compat import baseline_table_names
revision = "0034_media_assets"
down_revision = "0033_remove_site_versions"
@@ -13,7 +15,16 @@ depends_on = None
DEFAULT_DEPT_ID = "00000000-0000-0000-0000-000000000001"
def is_offline_mode() -> bool:
try:
return context.is_offline_mode()
except (NameError, AttributeError):
return False
def _table_names() -> set[str]:
if is_offline_mode():
return baseline_table_names()
return set(inspect(op.get_bind()).get_table_names())
@@ -0,0 +1,33 @@
"""Align the concierge details column with the PostgreSQL JSONB model type."""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = "0035_concierge_details_jsonb"
down_revision = "0034_media_assets"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.alter_column(
"ConciergeAdvisor",
"details",
existing_type=sa.JSON(),
type_=postgresql.JSONB(astext_type=sa.Text()),
existing_nullable=False,
postgresql_using='"details"::jsonb',
)
def downgrade() -> None:
op.alter_column(
"ConciergeAdvisor",
"details",
existing_type=postgresql.JSONB(astext_type=sa.Text()),
type_=sa.JSON(),
existing_nullable=False,
postgresql_using='"details"::json',
)