v1.4 - Login fixes, etc
This commit is contained in:
+106
-21
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import inspect, text
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from sqlalchemy import MetaData, inspect, text
|
||||
from sqlalchemy.engine import Engine
|
||||
|
||||
|
||||
@@ -20,6 +22,27 @@ TENANT_TABLES = {
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MigrationReport:
|
||||
created_tables: tuple[str, ...] = ()
|
||||
added_columns: tuple[str, ...] = ()
|
||||
synced_tenant_rows: dict[str, int] = field(default_factory=dict)
|
||||
|
||||
def has_changes(self) -> bool:
|
||||
return bool(self.created_tables or self.added_columns or self.synced_tenant_rows)
|
||||
|
||||
def summary(self) -> str:
|
||||
parts: list[str] = []
|
||||
if self.created_tables:
|
||||
parts.append(f"created tables: {', '.join(self.created_tables)}")
|
||||
if self.added_columns:
|
||||
parts.append(f"patched columns: {', '.join(self.added_columns)}")
|
||||
if self.synced_tenant_rows:
|
||||
counts = ", ".join(f"{table}={count}" for table, count in sorted(self.synced_tenant_rows.items()))
|
||||
parts.append(f"synced tenant rows: {counts}")
|
||||
return "; ".join(parts) if parts else "schema already up to date"
|
||||
|
||||
|
||||
def _has_column(engine: Engine, table_name: str, column_name: str) -> bool:
|
||||
inspector = inspect(engine)
|
||||
try:
|
||||
@@ -40,25 +63,41 @@ def _table_exists(engine: Engine, table_name: str) -> bool:
|
||||
return inspect(engine).has_table(table_name)
|
||||
|
||||
|
||||
def ensure_tenant_columns(engine: Engine) -> None:
|
||||
def ensure_metadata_tables(engine: Engine, metadata: MetaData) -> tuple[str, ...]:
|
||||
missing_tables = tuple(table.name for table in metadata.sorted_tables if not _table_exists(engine, table.name))
|
||||
if missing_tables:
|
||||
metadata.create_all(bind=engine)
|
||||
return missing_tables
|
||||
|
||||
|
||||
def ensure_tenant_columns(engine: Engine) -> tuple[str, ...]:
|
||||
added_columns: list[str] = []
|
||||
for table_name in TENANT_TABLES:
|
||||
if _table_exists(engine, table_name):
|
||||
_add_tenant_column(engine, table_name)
|
||||
if not _has_column(engine, table_name, "tenant_id"):
|
||||
_add_tenant_column(engine, table_name)
|
||||
added_columns.append(f"{table_name}.tenant_id")
|
||||
return tuple(added_columns)
|
||||
|
||||
|
||||
def sync_tenant_ids(engine: Engine) -> None:
|
||||
if not _table_exists(engine, "client_accounts"):
|
||||
return
|
||||
def sync_tenant_ids(engine: Engine) -> dict[str, int]:
|
||||
existing_tables = set(inspect(engine).get_table_names())
|
||||
|
||||
if "client_accounts" not in existing_tables:
|
||||
return {}
|
||||
|
||||
synced_rows: dict[str, int] = {}
|
||||
with engine.begin() as connection:
|
||||
default_tenant = connection.execute(
|
||||
text("SELECT tenant_id FROM client_accounts ORDER BY id LIMIT 1")
|
||||
).scalar_one_or_none()
|
||||
if not default_tenant:
|
||||
return
|
||||
return {}
|
||||
|
||||
statements = [
|
||||
text(
|
||||
(
|
||||
"client_users",
|
||||
text(
|
||||
"""
|
||||
UPDATE client_users
|
||||
SET tenant_id = (
|
||||
@@ -68,8 +107,11 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"client_feature_access",
|
||||
text(
|
||||
"""
|
||||
UPDATE client_feature_access
|
||||
SET tenant_id = (
|
||||
@@ -79,15 +121,21 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"raw_materials",
|
||||
text(
|
||||
"""
|
||||
UPDATE raw_materials
|
||||
SET tenant_id = :default_tenant
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"raw_material_price_versions",
|
||||
text(
|
||||
"""
|
||||
UPDATE raw_material_price_versions
|
||||
SET tenant_id = (
|
||||
@@ -97,8 +145,11 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"mixes",
|
||||
text(
|
||||
"""
|
||||
UPDATE mixes
|
||||
SET tenant_id = COALESCE(
|
||||
@@ -111,8 +162,11 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"mix_ingredients",
|
||||
text(
|
||||
"""
|
||||
UPDATE mix_ingredients
|
||||
SET tenant_id = (
|
||||
@@ -122,8 +176,11 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"products",
|
||||
text(
|
||||
"""
|
||||
UPDATE products
|
||||
SET tenant_id = COALESCE(
|
||||
@@ -141,15 +198,21 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"scenarios",
|
||||
text(
|
||||
"""
|
||||
UPDATE scenarios
|
||||
SET tenant_id = :default_tenant
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"costing_results",
|
||||
text(
|
||||
"""
|
||||
UPDATE costing_results
|
||||
SET tenant_id = COALESCE(
|
||||
@@ -167,29 +230,51 @@ def sync_tenant_ids(engine: Engine) -> None:
|
||||
)
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"process_cost_rules",
|
||||
text(
|
||||
"""
|
||||
UPDATE process_cost_rules
|
||||
SET tenant_id = :default_tenant
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"packaging_cost_rules",
|
||||
text(
|
||||
"""
|
||||
UPDATE packaging_cost_rules
|
||||
SET tenant_id = :default_tenant
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
text(
|
||||
(
|
||||
"freight_cost_rules",
|
||||
text(
|
||||
"""
|
||||
UPDATE freight_cost_rules
|
||||
SET tenant_id = :default_tenant
|
||||
WHERE tenant_id IS NULL OR tenant_id = '' OR tenant_id = 'default'
|
||||
"""
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
for statement in statements:
|
||||
connection.execute(statement, {"default_tenant": default_tenant})
|
||||
for table_name, statement in statements:
|
||||
if table_name not in existing_tables:
|
||||
continue
|
||||
result = connection.execute(statement, {"default_tenant": default_tenant})
|
||||
if result.rowcount and result.rowcount > 0:
|
||||
synced_rows[table_name] = result.rowcount
|
||||
|
||||
return synced_rows
|
||||
|
||||
|
||||
def bootstrap_schema(engine: Engine, metadata: MetaData) -> MigrationReport:
|
||||
created_tables = ensure_metadata_tables(engine, metadata)
|
||||
added_columns = ensure_tenant_columns(engine)
|
||||
return MigrationReport(created_tables=created_tables, added_columns=added_columns)
|
||||
|
||||
Reference in New Issue
Block a user