#!/usr/bin/env python3 """ Generate client merge SQL (settings + const) from: - goldAli catalog template (abshode_rahimi_merge_goldali_catalog.sql UPDATE blocks) - client JSON: { "settings": [...], "const": [...] } Usage: python generate_merge_sql.py abaeigold_client.json abaeigold """ from __future__ import annotations import json import re import sys from pathlib import Path SQL_DIR = Path(__file__).resolve().parent CATALOG_SQL = SQL_DIR / "abshode_rahimi_merge_goldali_catalog.sql" CONST_CATALOG: list[dict] = [ {"key": "limited_price", "title_fa": "سقف قیمت محدود", "default": ""}, {"key": "is_gateway_open", "title_fa": "باز بودن درگاه پرداخت", "default": ""}, {"key": "liman", "title_fa": "توکن لیمان", "default": ""}, {"key": "header_banner", "title_fa": "بنر بالای صفحه", "default": ""}, {"key": "app_closed", "title_fa": "بسته بودن اپ", "default": "false"}, {"key": "main_product", "title_fa": "محصول آبشده پیش‌فرض", "default": "13"}, {"key": "gram", "title_fa": "گرم ثبت‌نام", "default": ""}, {"key": "first_customer_wallet_value", "title_fa": "حداقل اولین خرید", "default": ""}, {"key": "jibit_counter", "title_fa": "شمارنده جیبیت", "default": "0"}, {"key": "price", "title_fa": "لیست قیمت TGJU (JSON)", "default": "[]"}, ] # Legacy client keys → catalog key (value only) SETTINGS_ALIASES = { "liman_token": "const:liman", } # Keys dropped from client settings (logs, legacy goldir in settings if any) SETTINGS_SKIP = { "montzar_order_response", "montzar_last_product_log", "liman_token", } CONST_SKIP_PREFIXES = ("goldir",) CONST_SKIP_KEYS = {"log", "last_order_log"} def normalize_key(key: str) -> str: return key.strip().replace("\r", "").replace("\n", "") def sql_str(value: str | None) -> str: if value is None: return "NULL" return "'" + str(value).replace("\\", "\\\\").replace("'", "''") + "'" def _parse_sql_field(line: str) -> str | None: line = line.strip().rstrip(",") if line.endswith("= NULL"): return None eq = line.index("=") raw = line[eq + 1 :].strip() if raw.startswith("'") and raw.endswith("'"): return raw[1:-1].replace("''", "'") return raw def parse_catalog_updates(text: str) -> dict[str, dict]: catalog: dict[str, dict] = {} blocks = re.split(r"(?=UPDATE `settings` SET)", text) for block in blocks: if not block.startswith("UPDATE `settings` SET"): continue key_m = re.search( r"`updated_at` = NOW\(\)\s+WHERE `key` = '([^']+)';", block ) if not key_m: continue key = key_m.group(1) fields: dict[str, str | None] = {} for line in block.splitlines(): line = line.strip() for col in ("title_fa", "can_edit", "type", "description"): if line.startswith(f"`{col}` ="): fields[col] = _parse_sql_field(line) catalog[key] = { "title_fa": fields.get("title_fa") or "", "can_edit": int(fields.get("can_edit") or 0), "type": fields.get("type"), "description": fields.get("description"), } # fallback: ensure full key list from DELETE block delete_m = re.search( r"DELETE FROM `settings`\s+WHERE `key` NOT IN \(\s*(.*?)\s*\);", text, re.S, ) if delete_m: for key in re.findall(r"'([^']+)'", delete_m.group(1)): catalog.setdefault( key, {"title_fa": "", "can_edit": 1, "type": "text", "description": None}, ) catalog.pop("price_method", None) return catalog def load_client_rows(path: Path) -> tuple[dict[str, str | None], dict[str, str | None]]: data = json.loads(path.read_text(encoding="utf-8")) settings: dict[str, str | None] = {} const: dict[str, str | None] = {} for row in data.get("settings", []): key = normalize_key(row["key"]) if key in SETTINGS_SKIP: continue val = row.get("value") if val is not None and isinstance(val, str): val = val.strip() settings[key] = val if val != "" else None for row in data.get("const", []): key = normalize_key(row["key"]) if key in CONST_SKIP_KEYS or key.startswith(CONST_SKIP_PREFIXES): continue val = row.get("value") if val is not None and isinstance(val, str): val = val.strip() const[key] = val if val != "" else None # liman_token in settings → const.liman if "liman_token" in {normalize_key(r["key"]) for r in data.get("settings", [])}: for row in data.get("settings", []): if normalize_key(row["key"]) == "liman_token" and row.get("value"): const["liman"] = row["value"] # montzar_price → price_provider mp = settings.get("montzar_price") if mp and str(mp).lower() in ("true", "1"): settings["price_provider"] = "montzar" elif settings.get("main_product_price") and str(settings.get("main_product_price")).lower() == "true": settings["price_provider"] = "main_product" elif not settings.get("price_provider"): settings.setdefault("price_provider", "montzar" if mp and str(mp).lower() in ("true", "1") else "") # main_product from const if missing in settings if const.get("main_product") and not settings.get("main_product"): settings["main_product"] = const["main_product"] return settings, const def is_active_value(value: str | None) -> int: if value is None or value == "": return 0 return 1 def generate_settings_sql( client_name: str, client_name_fa: str, catalog: dict[str, dict], client_settings: dict[str, str | None], ) -> str: lines: list[str] = [ "-- =============================================================================", f"-- {client_name_fa} — ادغام settings با کاتالوگ goldAliii", "--", f"-- منبع مقادیر: {client_name}_client.json", f"-- قالب کلیدها: goldali ({len(catalog)} کلید)", "--", "-- قوانین:", "-- • کلید مشترک → value از کلاینت، metadata از کاتالوگ", "-- • کلید کاتالوگ بدون مقدار در کلاینت → value=''، is_active=0", "-- • کلیدهای اضافی کلاینت → DELETE", "--", f"-- قبل از اجرا: USE دیتابیس {client_name_fa} + بکاپ", "-- =============================================================================", "", "START TRANSACTION;", "", "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;", "", "ALTER TABLE `settings`", " MODIFY COLUMN `description` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,", " MODIFY COLUMN `title_fa` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL,", " MODIFY COLUMN `type` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;", "", "-- 1) INSERT", "", ] catalog_keys = sorted(catalog.keys()) for key in catalog_keys: meta = catalog[key] val = client_settings.get(key) if val is None: val = "" active = is_active_value(val if val != "" else None) lines.append( "INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)" ) lines.append( f"SELECT {sql_str(key)}, {sql_str(val)}, {sql_str(meta['title_fa'])}, " f"{meta['can_edit']}, {active}, {sql_str(meta['description'])}, {sql_str(meta['type'])}" ) lines.append(f"WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = {sql_str(key)} LIMIT 1);") lines.extend(["", "-- 2) UPDATE", ""]) for key in catalog_keys: meta = catalog[key] val = client_settings.get(key) if val is None: val = "" active = is_active_value(val if val != "" else None) lines.append("UPDATE `settings` SET") lines.append(f" `title_fa` = {sql_str(meta['title_fa'])},") lines.append(f" `can_edit` = {meta['can_edit']},") lines.append(f" `type` = {sql_str(meta['type'])},") lines.append(f" `description` = {sql_str(meta['description'])},") lines.append(f" `value` = {sql_str(val)},") lines.append(f" `is_active` = {active},") lines.append(" `updated_at` = NOW()") lines.append(f"WHERE `key` = {sql_str(key)};") lines.extend([ "", "-- 3) logo_login normalize", "", "DELETE FROM `settings`", "WHERE `key` LIKE 'logo_login%' AND `key` <> 'logo_login'", " AND EXISTS (SELECT 1 FROM (SELECT `id`, `key` FROM `settings`) AS `s` WHERE `s`.`key` = 'logo_login');", "", "UPDATE `settings` SET", " `key` = 'logo_login',", " `value` = TRIM(REPLACE(REPLACE(`value`, CHAR(13), ''), CHAR(10), '')),", " `updated_at` = NOW()", "WHERE `key` LIKE 'logo_login%' AND `key` <> 'logo_login';", "", "-- 4) DELETE extras", "", "DELETE FROM `settings`", "WHERE `key` NOT IN (", ]) for i, key in enumerate(catalog_keys): comma = "," if i < len(catalog_keys) - 1 else "" lines.append(f" {sql_str(key)}{comma}") lines.append(");") lines.extend([ "", "DELETE FROM `settings` WHERE `key` = 'price_method';", "", "COMMIT;", "", ]) return "\n".join(lines) def generate_const_sql( client_name: str, client_name_fa: str, client_const: dict[str, str | None], ) -> str: lines: list[str] = [ "-- =============================================================================", f"-- {client_name_fa} — ادغام const با کاتالوگ goldAliii", "--", f"-- منبع مقادیر: {client_name}_client.json", "-- قالب: 10 کلید goldali", "-- =============================================================================", "", "START TRANSACTION;", "", "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;", "", "ALTER TABLE `const`", " MODIFY COLUMN `title_fa` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL;", "", "-- 1) INSERT", "", ] for item in CONST_CATALOG: key = item["key"] val = client_const.get(key) if val is None: val = item["default"] lines.append("INSERT INTO `const` (`key`, `value`, `title_fa`)") lines.append( f"SELECT {sql_str(key)}, {sql_str(val)}, {sql_str(item['title_fa'])}" ) lines.append(f"WHERE NOT EXISTS (SELECT 1 FROM `const` WHERE `key` = {sql_str(key)} LIMIT 1);") lines.extend(["", "-- 2) UPDATE", ""]) for item in CONST_CATALOG: key = item["key"] val = client_const.get(key) if val is None: val = item["default"] lines.append("UPDATE `const` SET") lines.append(f" `title_fa` = {sql_str(item['title_fa'])},") lines.append(f" `value` = {sql_str(val)},") lines.append(" `updated_at` = NOW()") lines.append(f"WHERE `key` = {sql_str(key)};") allowed = [item["key"] for item in CONST_CATALOG] lines.extend(["", "-- 3) DELETE extras", "", "DELETE FROM `const`", "WHERE `key` NOT IN ("]) for i, key in enumerate(allowed): comma = "," if i < len(allowed) - 1 else "" lines.append(f" {sql_str(key)}{comma}") lines.extend([");", "", "COMMIT;", ""]) return "\n".join(lines) def main() -> None: if len(sys.argv) < 3: print(__doc__) sys.exit(1) client_json = Path(sys.argv[1]) client_slug = sys.argv[2] client_name_fa = sys.argv[3] if len(sys.argv) > 3 else client_slug catalog_text = CATALOG_SQL.read_text(encoding="utf-8") catalog = parse_catalog_updates(catalog_text) if not catalog: print("ERROR: could not parse catalog from", CATALOG_SQL) sys.exit(1) client_settings, client_const = load_client_rows(client_json) settings_sql = generate_settings_sql(client_slug, client_name_fa, catalog, client_settings) const_sql = generate_const_sql(client_slug, client_name_fa, client_const) out_settings = SQL_DIR / f"{client_slug}_merge_goldali_catalog.sql" out_const = SQL_DIR / f"{client_slug}_merge_goldali_const.sql" out_settings.write_text(settings_sql, encoding="utf-8") out_const.write_text(const_sql, encoding="utf-8") print(f"Wrote {out_settings} ({len(catalog)} keys)") print(f"Wrote {out_const} ({len(CONST_CATALOG)} keys)") if __name__ == "__main__": main()