fix(settings): include all can_edit=0 keys in SuperAdmin catalog

Parse both INSERT column orders from deploy_settings_const.sql so
price_provider, zaarbox_api_url, main_product and similar keys are
in the template. Keep template values empty for tokens and per-project IDs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-19 21:47:43 +03:30
co-authored by Cursor
parent 19d28f2ed7
commit c0b6ec9262
3 changed files with 278 additions and 71 deletions
+106 -52
View File
@@ -5,7 +5,7 @@
"title_fa": "سیستم حسابداری", "title_fa": "سیستم حسابداری",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "none,pargar,tahesab,zarin", "description": "سیستم حسابداری: none=هیچکدام، pargar=API پرگار، tahesab=ارسال خودکار ته‌حساب، zarin=ارسال خودکار زرین.",
"type": "" "type": ""
}, },
{ {
@@ -14,7 +14,7 @@
"title_fa": "آدرس پنل ادمین", "title_fa": "آدرس پنل ادمین",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس کامل پنل مدیریت (با https). برای لینک‌های ادمین و تنظیمات CORS در صورت نیاز.",
"type": "text" "type": "text"
}, },
{ {
@@ -32,7 +32,7 @@
"title_fa": "آدرس اپ (کاربر)", "title_fa": "آدرس اپ (کاربر)",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس کامل وب‌اپ کاربر (با https). در لینک‌های پیامک، بازگشت از درگاه و ارجاعات داخلی استفاده می‌شود.",
"type": "text" "type": "text"
}, },
{ {
@@ -41,7 +41,7 @@
"title_fa": "توکن بات بله", "title_fa": "توکن بات بله",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "توکن ربات بله برای ارسال اعلان‌ها. محرمانه.",
"type": "text" "type": "text"
}, },
{ {
@@ -50,7 +50,7 @@
"title_fa": "UUID نمودار Zaarbox", "title_fa": "UUID نمودار Zaarbox",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "خالی = UUID پیش‌فرض سرویس", "description": "شناسه یکتا (UUID) نمودار در سرویس تابلو. اگر خالی باشد از شناسه پیش‌فرض سرویس استفاده می‌شود.",
"type": "text" "type": "text"
}, },
{ {
@@ -59,7 +59,7 @@
"title_fa": "نمودار از داده خود سایت", "title_fa": "نمودار از داده خود سایت",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "خاموش = Zaarbox (chart_id) | روشن = تاریخچه ذخیره‌شده prices", "description": "روشن: نمودار قیمت از تاریخچه ذخیره‌شده در دیتابیس خودتان. خاموش: از سرویس تابلو آنلاین (با شناسه chart_id) استفاده می‌شود.",
"type": "switch" "type": "switch"
}, },
{ {
@@ -122,7 +122,7 @@
"title_fa": "پیامک تکی به مشتری از لیست", "title_fa": "پیامک تکی به مشتری از لیست",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود. خاموش: مخفی است. فقط از صفحه دیپلوی قابل تغییر است.", "description": "روشن: در لیست مشتریان دکمه ارسال پیامک تکی نمایش داده می‌شود و ادمین می‌تواند برای هر مشتری متن دلخواه بفرستد. خاموش: دکمه مخفی است. فقط از صفحه دیپلوی تغییر می‌کند.",
"type": "switch" "type": "switch"
}, },
{ {
@@ -167,7 +167,7 @@
"title_fa": "فعال‌سازی API سفارش خارجی (get_wallet / order)", "title_fa": "فعال‌سازی API سفارش خارجی (get_wallet / order)",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "true + node_client_id + node_shared_secret", "description": "روشن: API دریافت کیف پول و ثبت سفارش از سرویس فروشگاه خارجی فعال است. نیاز به node_client_id و node_shared_secret دارد.",
"type": "switch" "type": "switch"
}, },
{ {
@@ -257,7 +257,7 @@
"title_fa": "صفحه کسب درآمد", "title_fa": "صفحه کسب درآمد",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "روشن: منوی «کسب درآمد / دعوت دوستان» در اپ کاربر نمایش داده می‌شود.",
"type": "switch" "type": "switch"
}, },
{ {
@@ -293,7 +293,7 @@
"title_fa": "حالت فروشگاهی", "title_fa": "حالت فروشگاهی",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "فعال = سبد خرید، دسته‌بندی، حواله، تخفیف", "description": "نوع چیدمان اپ: روشن = حالت فروشگاهی (سبد، دسته‌بندی، حواله و …). خاموش = حالت معامله آبشده/محصولات کلاسیک. فقط از دیپلوی تغییر می‌کند.",
"type": "switch" "type": "switch"
}, },
{ {
@@ -311,7 +311,7 @@
"title_fa": "لوگوی صفحه ورود", "title_fa": "لوگوی صفحه ورود",
"can_edit": 0, "can_edit": 0,
"is_active": 0, "is_active": 0,
"description": "", "description": "آدرس تصویر لوگو در صفحه ورود. معمولاً از طریق دیپلوی یا آپلود فایل تنظیم می‌شود؛ در پنل قابل ویرایش نیست.",
"type": "text" "type": "text"
}, },
{ {
@@ -320,7 +320,7 @@
"title_fa": "لوگوی ثبت‌نام", "title_fa": "لوگوی ثبت‌نام",
"can_edit": 0, "can_edit": 0,
"is_active": 0, "is_active": 0,
"description": "", "description": "آدرس تصویر لوگو در صفحه ثبت‌نام. معمولاً از طریق دیپلوی تنظیم می‌شود؛ در پنل قابل ویرایش نیست.",
"type": "text" "type": "text"
}, },
{ {
@@ -332,9 +332,18 @@
"description": "", "description": "",
"type": "text" "type": "text"
}, },
{
"key": "main_product",
"value": "",
"title_fa": "محصول آبشده",
"can_edit": 0,
"is_active": 1,
"description": "شناسه عددی یا نام reading_method محصول آبشده مرجع. وقتی منبع قیمت روی main_product باشد برای محاسبه استفاده می‌شود.",
"type": "text"
},
{ {
"key": "main_wallet_products", "key": "main_wallet_products",
"value": "13", "value": "",
"title_fa": "محصولات متصل به کیف اصلی", "title_fa": "محصولات متصل به کیف اصلی",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
@@ -347,7 +356,7 @@
"title_fa": "عنوان فلز در UI", "title_fa": "عنوان فلز در UI",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "عنوان فلز اصلی در رابط کاربر (مثلاً «طلای آبشده» یا «کاتد مس»). در دارایی، کیف پول و برچسب‌های معامله دیده می‌شود. فقط از دیپلوی/SQL تغییر می‌کند.",
"type": "text" "type": "text"
}, },
{ {
@@ -356,7 +365,7 @@
"title_fa": "نام فروشگاه در درگاه", "title_fa": "نام فروشگاه در درگاه",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "نام فروشگاه که در درگاه پرداخت و رسید بانکی به کاربر نمایش داده می‌شود.",
"type": "text" "type": "text"
}, },
{ {
@@ -365,7 +374,7 @@
"title_fa": "آدرس سرور همکار", "title_fa": "آدرس سرور همکار",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس پایه سرور API همکار (بدون اسلش انتهایی). مثال: https://example.com",
"type": "text" "type": "text"
}, },
{ {
@@ -374,7 +383,7 @@
"title_fa": "شناسه کلاینت API خارجی (X-Client-Id)", "title_fa": "شناسه کلاینت API خارجی (X-Client-Id)",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "شناسه کلاینت برای API سفارش خارجی (هدر X-Client-Id). باید با مقدار تنظیم‌شده در سرویس مقصد یکسان باشد.",
"type": "text" "type": "text"
}, },
{ {
@@ -383,7 +392,7 @@
"title_fa": "کلید امضای HMAC API خارجی", "title_fa": "کلید امضای HMAC API خارجی",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "کلید مشترک امضای HMAC برای احراز درخواست‌های API سفارش خارجی. محرمانه نگه دارید.",
"type": "text" "type": "text"
}, },
{ {
@@ -397,7 +406,7 @@
}, },
{ {
"key": "pargar_item_id_gold", "key": "pargar_item_id_gold",
"value": "11", "value": "",
"title_fa": "کد کالای طلا پرگار", "title_fa": "کد کالای طلا پرگار",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
@@ -406,7 +415,7 @@
}, },
{ {
"key": "pargar_item_id_rial", "key": "pargar_item_id_rial",
"value": "0", "value": "",
"title_fa": "کد کالای ریال پرگار", "title_fa": "کد کالای ریال پرگار",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
@@ -433,11 +442,11 @@
}, },
{ {
"key": "payping_fin_customer_id", "key": "payping_fin_customer_id",
"value": "23", "value": "",
"title_fa": "customer_id fin", "title_fa": "customer_id fin",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "شناسه مشتری/حساب در سامانه fin که تراکنش‌ها به نام آن ثبت می‌شوند.",
"type": "number" "type": "number"
}, },
{ {
@@ -446,7 +455,7 @@
"title_fa": "همگام‌سازی fin", "title_fa": "همگام‌سازی fin",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "روشن: پس از واریز/برداشت، تراکنش به سامانه حسابداری fin همگام‌سازی می‌شود.",
"type": "switch" "type": "switch"
}, },
{ {
@@ -455,16 +464,16 @@
"title_fa": "توکن fin", "title_fa": "توکن fin",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "توکن احراز هویت برای API سامانه fin.",
"type": "text" "type": "text"
}, },
{ {
"key": "payping_fin_url", "key": "payping_fin_url",
"value": "https://limangold.ir/kernel/admin/fin", "value": "",
"title_fa": "URL fin", "title_fa": "URL fin",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس کامل endpoint ثبت تراکنش در سامانه fin (POST).",
"type": "text" "type": "text"
}, },
{ {
@@ -473,7 +482,7 @@
"title_fa": "لیست item (چرخشی)", "title_fa": "لیست item (چرخشی)",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "لیست کدهای item درگاه (با کاما) برای چرخش بین چند merchant.",
"type": "text" "type": "text"
}, },
{ {
@@ -482,7 +491,7 @@
"title_fa": "نام gateway درگاه", "title_fa": "نام gateway درگاه",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "sepal,", "description": "نام درگاه پرداخت فعال که به سرویس واسط درگاه ارسال می‌شود|sepal,",
"type": "select" "type": "select"
}, },
{ {
@@ -491,34 +500,34 @@
"title_fa": "گرد کردن مبلغ به تومان", "title_fa": "گرد کردن مبلغ به تومان",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "روشن: مبلغ پرداخت به نزدیک‌ترین تومان گرد می‌شود. خاموش: مبلغ دقیق ریالی.",
"type": "switch" "type": "switch"
}, },
{ {
"key": "payping_start_url", "key": "payping_start_url",
"value": "https://payping.limangold.ir/payment/start", "value": "",
"title_fa": "آدرس شروع درگاه", "title_fa": "آدرس شروع درگاه",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس API شروع پرداخت (redirect کاربر به درگاه).",
"type": "text" "type": "text"
}, },
{ {
"key": "payping_strict_failed_withdraw", "key": "payping_strict_failed_withdraw",
"value": "false", "value": "false",
"title_fa": "اولویت تراکنش ناموفق", "title_fa": "برداشت ناموفق سخت‌گیرانه",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "روشن: در صورت وجود برداشت ناموفق قبلی، تسویه جدید با احتیاط بیشتر انجام می‌شود.",
"type": "switch" "type": "switch"
}, },
{ {
"key": "payping_verify_url", "key": "payping_verify_url",
"value": "https://payping.limangold.ir/payment/verify", "value": "",
"title_fa": "آدرس تأیید درگاه", "title_fa": "آدرس تأیید درگاه",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس API تأیید و نهایی‌سازی پرداخت پس از بازگشت از درگاه.",
"type": "text" "type": "text"
}, },
{ {
@@ -566,13 +575,31 @@
"description": "آیدی چنل/گروه تلگرام برای ارسال قیمت (چند مقصد با ویرگول).", "description": "آیدی چنل/گروه تلگرام برای ارسال قیمت (چند مقصد با ویرگول).",
"type": "text" "type": "text"
}, },
{
"key": "price_provider",
"value": "",
"title_fa": "منبع قیمت",
"can_edit": 0,
"is_active": 1,
"description": "منبع دریافت قیمت لحظه‌ای برای اپ کاربر و همگام‌سازی محصولات. مقدار montzar = قیمت از همکار (Montzar).|montzar,zaarbox,main_product",
"type": "select"
},
{
"key": "profit_gold_api_enabled",
"value": "false",
"title_fa": "API سود طلا",
"can_edit": 0,
"is_active": 0,
"description": "روشن: API محاسبه سود طلا فعال است. در اکثر نصب‌ها خاموش می‌ماند.",
"type": "switch"
},
{ {
"key": "referral_bonus_amount", "key": "referral_bonus_amount",
"value": "", "value": "",
"title_fa": "مقدار پاداش معرف", "title_fa": "مقدار پاداش معرف",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "مبلغ یا مقدار پاداش معرف (متن آزاد — بسته به پیاده‌سازی پیامک/اعتبار). برای نمایش در پیام‌ها.",
"type": "text" "type": "text"
}, },
{ {
@@ -596,10 +623,10 @@
{ {
"key": "send_to_tahesab", "key": "send_to_tahesab",
"value": "false", "value": "false",
"title_fa": "ارسال به ته‌حساب (قدیمی)", "title_fa": "ارسال به ته‌حساب",
"can_edit": 0, "can_edit": 0,
"is_active": 0, "is_active": 0,
"description": "", "description": "روشن: واریز و برداشت مشتریان به‌صورت خودکار به ته‌حساب ارسال می‌شود.",
"type": "" "type": ""
}, },
{ {
@@ -626,7 +653,7 @@
"title_fa": "مسیر SSE قیمت", "title_fa": "مسیر SSE قیمت",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "GET لاراول — استریم event:last و event:list با payload کامل", "description": "مسیر GET لاراول برای استریم قیمت — پیش‌فرض /api/price/stream",
"type": "text" "type": "text"
}, },
{ {
@@ -635,16 +662,16 @@
"title_fa": "حساب بانکی ته‌حساب", "title_fa": "حساب بانکی ته‌حساب",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "نام یا کد حساب بانکی در سند واریز/برداشت ته‌حساب.",
"type": "text" "type": "text"
}, },
{ {
"key": "tahesab_db", "key": "tahesab_db",
"value": "", "value": "",
"title_fa": "دیتابیس ته‌حساب", "title_fa": "نام دیتابیس ته‌حساب",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "نام دیتابیس یا شناسه پایگاه در ته‌حساب که سند به آن ارسال می‌شود.",
"type": "text" "type": "text"
}, },
{ {
@@ -653,7 +680,7 @@
"title_fa": "آدرس IP سرور ته‌حساب", "title_fa": "آدرس IP سرور ته‌حساب",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "آدرس IP یا hostname سرور نرم‌افزار ته‌حساب روی شبکه داخلی.",
"type": "text" "type": "text"
}, },
{ {
@@ -662,7 +689,7 @@
"title_fa": "پورت سرور ته‌حساب", "title_fa": "پورت سرور ته‌حساب",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "شماره پورت اتصال به API ته‌حساب (معمولاً 8080 یا مقدار مستندات ته‌حساب).",
"type": "number" "type": "number"
}, },
{ {
@@ -671,7 +698,7 @@
"title_fa": "توکن ته‌حساب", "title_fa": "توکن ته‌حساب",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "توکن/API Key اتصال به ته‌حساب. از پنل ته‌حساب دریافت می‌شود.",
"type": "text" "type": "text"
}, },
{ {
@@ -680,7 +707,25 @@
"title_fa": "توکن بات تلگرام", "title_fa": "توکن بات تلگرام",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "", "description": "توکن ربات تلگرام برای ارسال اعلان‌ها. از BotFather دریافت می‌شود. محرمانه.",
"type": "text"
},
{
"key": "title_fav",
"value": "",
"title_fa": "عنوان favicon",
"can_edit": 0,
"is_active": 0,
"description": "عنوان نمایش‌داده‌شده در تب مرورگر (favicon). از دیپلوی مدیریت می‌شود.",
"type": "text"
},
{
"key": "title_pwa",
"value": "",
"title_fa": "عنوان PWA",
"can_edit": 0,
"is_active": 0,
"description": "عنوان کوتاه برای نصب PWA روی موبایل. از دیپلوی مدیریت می‌شود.",
"type": "text" "type": "text"
}, },
{ {
@@ -689,7 +734,7 @@
"title_fa": "واحد وزن دارایی و معاملات", "title_fa": "واحد وزن دارایی و معاملات",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "واحد نمایش وزن فلز در دارایی‌ها، کیف پول و تراکنش‌ها|گرم,کیلوگرم", "description": "واحد نمایش وزن در دارایی‌ها، کیف پول و جزئیات معاملات|گرم,کیلوگرم",
"type": "select" "type": "select"
}, },
{ {
@@ -707,9 +752,18 @@
"title_fa": "قیمت لحظه‌ای (SSE)", "title_fa": "قیمت لحظه‌ای (SSE)",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
"description": "روشن: اپ به sse_path وصل می‌شود؛ خاموش: polling /api/price/last و /customer/product/list", "description": "روشن: اپ به SSE (sse_path) وصل می‌شود و last/list با payload می‌آید؛ خاموش: polling.",
"type": "switch" "type": "switch"
}, },
{
"key": "zaarbox_api_url",
"value": "",
"title_fa": "آدرس API تابلو Zaarbox",
"can_edit": 0,
"is_active": 1,
"description": "آدرس سرور(های) فید قیمت (/api/free). تا ۵ آدرس (JSON آرایه) پشتیبانی می‌شود؛ اگر آدرس اول پاسخ ندهد به‌ترتیب سراغ بعدی می‌رود. مقدار تکی قبلی حفظ می‌شود.",
"type": "text"
},
{ {
"key": "zarin_api", "key": "zarin_api",
"value": "", "value": "",
@@ -721,7 +775,7 @@
}, },
{ {
"key": "zarin_gorohe_kar", "key": "zarin_gorohe_kar",
"value": "52", "value": "",
"title_fa": "گروه کار پیش‌فرض زرین", "title_fa": "گروه کار پیش‌فرض زرین",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
@@ -730,7 +784,7 @@
}, },
{ {
"key": "zarin_name_g", "key": "zarin_name_g",
"value": "مشتری نقدی (خریدار)", "value": "",
"title_fa": "نام گروه حساب مشتریان زرین", "title_fa": "نام گروه حساب مشتریان زرین",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
@@ -739,7 +793,7 @@
}, },
{ {
"key": "zarin_noe_kar", "key": "zarin_noe_kar",
"value": "16", "value": "",
"title_fa": "نوع کار پیش‌فرض زرین", "title_fa": "نوع کار پیش‌فرض زرین",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
@@ -748,7 +802,7 @@
}, },
{ {
"key": "zarin_noe_kar_tedadi", "key": "zarin_noe_kar_tedadi",
"value": "17", "value": "",
"title_fa": "نوع کار تعدادی زرین", "title_fa": "نوع کار تعدادی زرین",
"can_edit": 0, "can_edit": 0,
"is_active": 1, "is_active": 1,
+156 -19
View File
@@ -5,6 +5,8 @@
* - prisma/setting-templates-from-deploy.json (فقط can_edit=0 نهایی) * - prisma/setting-templates-from-deploy.json (فقط can_edit=0 نهایی)
* - src/program-settings/deploy-setting-catalog-keys.ts * - src/program-settings/deploy-setting-catalog-keys.ts
* - ../controlpanel_user/src/config/superAdminSettingKeys.ts * - ../controlpanel_user/src/config/superAdminSettingKeys.ts
*
* قالب value خالی است برای توکن/رمز/شناسه پروژه — هر پروژه در سوپرادمین مقدار خودش را دارد.
*/ */
import { readFileSync, writeFileSync } from 'fs'; import { readFileSync, writeFileSync } from 'fs';
import { dirname, join } from 'path'; import { dirname, join } from 'path';
@@ -31,30 +33,103 @@ function unquote(s) {
.replace(/\\n/g, '\n'); .replace(/\\n/g, '\n');
} }
// INSERT … SELECT 'key', 'value', … function parseSqlList(expr) {
for (const m of sql.matchAll( const out = [];
/SELECT\s+'([^']+)',\s+'((?:\\'|[^'])*)',\s+'((?:\\'|[^'])*)',\s+(\d+),\s+(\d+)(?:,\s+(NULL|'(?:\\'|[^'])*'))(?:,\s+'((?:\\'|[^'])*)')?/g, let i = 0;
)) { const s = String(expr ?? '').trim();
const key = m[1]; while (i < s.length) {
const value = unquote(`'${m[2]}'`); while (i < s.length && /[\s,]/.test(s[i])) i += 1;
const title_fa = unquote(`'${m[3]}'`); if (i >= s.length) break;
const can_edit = Number(m[4]); if (s[i] === "'") {
const is_active = Number(m[5]); i += 1;
let description = ''; let buf = '';
if (m[6] && m[6] !== 'NULL') description = unquote(m[6]); while (i < s.length) {
const type = m[7] ? unquote(`'${m[7]}'`) : ''; if (s[i] === '\\' && i + 1 < s.length) {
rows.set(key, { key, value, title_fa, can_edit, is_active, description, type }); buf += s[i + 1];
i += 2;
continue;
}
if (s[i] === "'") {
if (s[i + 1] === "'") {
buf += "'";
i += 2;
continue;
}
i += 1;
break;
}
buf += s[i];
i += 1;
}
out.push(buf);
continue;
}
let buf = '';
while (i < s.length && s[i] !== ',') {
buf += s[i];
i += 1;
}
out.push(buf.trim());
}
return out;
}
function ensureRow(key) {
const k = String(key || '').trim();
if (!k) return null;
let row = rows.get(k);
if (!row) {
row = {
key: k,
value: '',
title_fa: k,
can_edit: 0,
is_active: 1,
description: '',
type: '',
};
rows.set(k, row);
}
return row;
} }
function applyCanEdit(block, value) { function applyCanEdit(block, value) {
const keys = [...block.matchAll(/'([^']+)'/g)].map((x) => x[1]); const keys = [...block.matchAll(/'([^']+)'/g)].map((x) => x[1]);
for (const key of keys) { for (const key of keys) {
const row = rows.get(key); const row = ensureRow(key);
if (row) row.can_edit = value; if (row) row.can_edit = value;
} }
} }
// UPDATE can_edit = 0/1 batches function isBlankSql(v) {
const t = String(v ?? '').trim();
return t === '' || t === 'NULL' || t.startsWith('@');
}
// INSERT INTO settings (cols) SELECT ...
for (const m of sql.matchAll(
/INSERT INTO `settings` \(([^)]+)\)\s*SELECT\s+([\s\S]*?)\nWHERE NOT EXISTS/g,
)) {
const cols = m[1].split(',').map((c) => c.replace(/[`\s]/g, ''));
const values = parseSqlList(m[2]);
const rec = {};
cols.forEach((col, i) => {
rec[col] = values[i];
});
const key = rec.key;
if (!key || isBlankSql(key)) continue;
const row = ensureRow(key);
if (!isBlankSql(rec.value) && rec.value !== undefined) row.value = unquote(rec.value);
if (!isBlankSql(rec.title_fa) && rec.title_fa !== undefined) row.title_fa = unquote(rec.title_fa);
if (rec.can_edit !== undefined && !isBlankSql(rec.can_edit)) row.can_edit = Number(rec.can_edit);
if (rec.is_active !== undefined && !isBlankSql(rec.is_active)) row.is_active = Number(rec.is_active);
if (!isBlankSql(rec.description) && rec.description !== undefined) {
row.description = unquote(rec.description);
}
if (!isBlankSql(rec.type) && rec.type !== undefined) row.type = unquote(rec.type);
}
// UPDATE can_edit = 0/1 batches (بعد از INSERT — منبع حقیقت نهایی)
for (const m of sql.matchAll( for (const m of sql.matchAll(
/UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)(?:,\s*`[^`]+`\s*=[^W]+)?\s+WHERE\s+`key`\s+IN\s*\(([\s\S]*?)\)\s*;/g, /UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)(?:,\s*`[^`]+`\s*=[^W]+)?\s+WHERE\s+`key`\s+IN\s*\(([\s\S]*?)\)\s*;/g,
)) { )) {
@@ -64,18 +139,16 @@ for (const m of sql.matchAll(
for (const m of sql.matchAll( for (const m of sql.matchAll(
/UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)\s+WHERE\s+`key`\s*=\s*'([^']+)'/g, /UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)\s+WHERE\s+`key`\s*=\s*'([^']+)'/g,
)) { )) {
const row = rows.get(m[2]); const row = ensureRow(m[2]);
if (row) row.can_edit = Number(m[1]); if (row) row.can_edit = Number(m[1]);
} }
// zarin_* wildcard
if (/UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*0,\s*`is_active`\s*=\s*1\s+WHERE\s+`key`\s+LIKE\s+'zarin_%'/.test(sql)) { if (/UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*0,\s*`is_active`\s*=\s*1\s+WHERE\s+`key`\s+LIKE\s+'zarin_%'/.test(sql)) {
for (const [key, row] of rows) { for (const [key, row] of rows) {
if (key.startsWith('zarin_')) row.can_edit = 0; if (key.startsWith('zarin_')) row.can_edit = 0;
} }
} }
// type-only UPDATE batches — enrich type when present
for (const m of sql.matchAll( for (const m of sql.matchAll(
/UPDATE\s+`settings`\s+SET\s+`type`\s*=\s*'([^']+)'\s+WHERE\s+`key`\s+IN\s*\(([\s\S]*?)\)\s*;/g, /UPDATE\s+`settings`\s+SET\s+`type`\s*=\s*'([^']+)'\s+WHERE\s+`key`\s+IN\s*\(([\s\S]*?)\)\s*;/g,
)) { )) {
@@ -86,6 +159,51 @@ for (const m of sql.matchAll(
} }
} }
for (const m of sql.matchAll(
/UPDATE\s+`settings`\s+SET\s+`title_fa`\s*=\s+'((?:\\'|[^'])*)'\s+WHERE\s+`key`\s*=\s*'([^']+)'/g,
)) {
const row = rows.get(m[2]);
if (row && m[1]) row.title_fa = unquote(`'${m[1]}'`);
}
for (const m of sql.matchAll(
/UPDATE\s+`settings`\s+SET\s+`description`\s*=\s+'((?:\\'|[^'])*)'\s+WHERE\s+`key`\s*=\s*'([^']+)'/g,
)) {
const row = rows.get(m[2]);
if (row && m[1]) row.description = unquote(`'${m[1]}'`);
}
const SECRET_OR_PROJECT_VALUE = /(token|password|secret|api_key|sheba|card_number|client_secret|client_id|username)$/i;
const EMPTY_TEMPLATE_VALUE_KEYS = new Set([
'main_product',
'main_wallet_products',
'payping_fin_customer_id',
'payping_fin_url',
'payping_start_url',
'payping_verify_url',
'price_provider',
'pargar_item_id_gold',
'pargar_item_id_rial',
'zarin_noe_kar',
'zarin_noe_kar_tedadi',
'zarin_gorohe_kar',
'zarin_name_g',
]);
function templateValue(row) {
const key = row.key;
if (SECRET_OR_PROJECT_VALUE.test(key) || EMPTY_TEMPLATE_VALUE_KEYS.has(key)) return '';
const v = String(row.value ?? '');
if (/^eyJ/.test(v) || v.startsWith('http') && /token|secret/i.test(key)) return '';
return v;
}
const EXTRA_EDITABLE = ['fee', 'referral_bonus_count'];
for (const key of EXTRA_EDITABLE) {
const row = ensureRow(key);
if (row) row.can_edit = 1;
}
const systemRows = [...rows.values()] const systemRows = [...rows.values()]
.filter((r) => r.can_edit === 0) .filter((r) => r.can_edit === 0)
.sort((a, b) => a.key.localeCompare(b.key)); .sort((a, b) => a.key.localeCompare(b.key));
@@ -96,7 +214,7 @@ const editableRows = [...rows.values()]
const jsonOut = systemRows.map((r) => ({ const jsonOut = systemRows.map((r) => ({
key: r.key, key: r.key,
value: r.value ?? '', value: templateValue(r),
title_fa: r.title_fa ?? r.key, title_fa: r.title_fa ?? r.key,
can_edit: 0, can_edit: 0,
is_active: r.is_active ?? 1, is_active: r.is_active ?? 1,
@@ -145,3 +263,22 @@ writeFileSync(join(root, '../controlpanel_user/src/config/superAdminSettingKeys.
console.log(`system keys (can_edit=0): ${systemRows.length}`); console.log(`system keys (can_edit=0): ${systemRows.length}`);
console.log(`editable keys (can_edit=1): ${editableRows.length}`); console.log(`editable keys (can_edit=1): ${editableRows.length}`);
const mustHave = [
'price_provider',
'zaarbox_api_url',
'main_product',
'profit_gold_api_enabled',
'title_pwa',
'title_fav',
'unit',
'main_wallet_products',
];
for (const key of mustHave) {
const ok = systemRows.some((r) => r.key === key);
console.log(` ${ok ? 'OK' : 'MISSING'} ${key}`);
}
const mustNot = ['montzar_token', 'montzar_diff', 'name_fa', 'buy_fee'];
for (const key of mustNot) {
const bad = systemRows.some((r) => r.key === key);
console.log(` ${bad ? 'LEAK' : 'OK'} system must not contain ${key}`);
}
@@ -40,6 +40,7 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
"logo_login", "logo_login",
"logo_register", "logo_register",
"main_metal", "main_metal",
"main_product",
"main_wallet_products", "main_wallet_products",
"metal_type", "metal_type",
"montify_name", "montify_name",
@@ -66,6 +67,8 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
"price_channel_bale_to", "price_channel_bale_to",
"price_channel_rubika_to", "price_channel_rubika_to",
"price_channel_telegram_to", "price_channel_telegram_to",
"price_provider",
"profit_gold_api_enabled",
"referral_bonus_amount", "referral_bonus_amount",
"rubika_bot_token", "rubika_bot_token",
"send_to_pargar", "send_to_pargar",
@@ -79,9 +82,12 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
"tahesab_port", "tahesab_port",
"tahesab_token", "tahesab_token",
"telegram_bot_token", "telegram_bot_token",
"title_fav",
"title_pwa",
"unit", "unit",
"wallet_payment_provider", "wallet_payment_provider",
"wss_enabled", "wss_enabled",
"zaarbox_api_url",
"zarin_api", "zarin_api",
"zarin_gorohe_kar", "zarin_gorohe_kar",
"zarin_name_g", "zarin_name_g",
@@ -109,14 +115,18 @@ export const DEPLOY_SETTING_EDITABLE_KEYS = new Set<string>([
"deposit_method_shetab", "deposit_method_shetab",
"deposit_sheba", "deposit_sheba",
"deposit_sms_support_number", "deposit_sms_support_number",
"deposit_support_message_text",
"do_send_liman",
"down_price", "down_price",
"endDay", "endDay",
"fee",
"fee_conditional_order", "fee_conditional_order",
"fee_free", "fee_free",
"fee_service_charge_percentage", "fee_service_charge_percentage",
"gift_for_referral", "gift_for_referral",
"gift_for_referrer", "gift_for_referrer",
"has_chat", "has_chat",
"invite_link_text",
"jibit_verification", "jibit_verification",
"main_product_price", "main_product_price",
"min_deposit", "min_deposit",
@@ -144,12 +154,14 @@ export const DEPLOY_SETTING_EDITABLE_KEYS = new Set<string>([
"price_channel_footer_id", "price_channel_footer_id",
"price_channel_footer_link", "price_channel_footer_link",
"price_channel_footer_phone", "price_channel_footer_phone",
"price_channel_message_text",
"price_channel_messengers", "price_channel_messengers",
"price_channel_min_seconds", "price_channel_min_seconds",
"price_extreme_date", "price_extreme_date",
"products_site_link_enabled", "products_site_link_enabled",
"products_site_link_text", "products_site_link_text",
"products_site_link_url", "products_site_link_url",
"referral_bonus_count",
"referral_gift_buy_from_montzar", "referral_gift_buy_from_montzar",
"referral_gift_on_first_order", "referral_gift_on_first_order",
"referral_id", "referral_id",
@@ -186,8 +198,12 @@ export const DEPLOY_SETTING_EDITABLE_KEYS = new Set<string>([
"support_messenger", "support_messenger",
"support_messenger_id", "support_messenger_id",
"support_messenger_text", "support_messenger_text",
"support_messengers",
"text_about",
"text_rule",
"tracking_systems", "tracking_systems",
"tracking_systems_enabled", "tracking_systems_enabled",
"up_price", "up_price",
"zaarbox_price_key",
]); ]);