diff --git a/src/program-settings/deploy-setting-catalog-keys.ts b/src/program-settings/deploy-setting-catalog-keys.ts new file mode 100644 index 0000000..ce91707 --- /dev/null +++ b/src/program-settings/deploy-setting-catalog-keys.ts @@ -0,0 +1,199 @@ +/** کلیدهای deploy_settings_const — داخل باندل کامپایل می‌شود تا ایمپورت به فایل prisma وابسته نباشد. */ +export const DEPLOY_SETTING_CATALOG_KEYS = new Set([ + "accounting_provider", + "admin_url", + "apk_download_link", + "app_url", + "auto_active_customer", + "balance_report_exclude_customer_id", + "bale_bot_token", + "banner_enabled", + "buy_fee", + "change_mesghal", + "charge_notif", + "chart_id", + "chart_use_local_data", + "chat_api", + "chat_api_key", + "chat_download_url", + "chat_upload_url", + "chat_webhook_base_url", + "chat_webhook_secret", + "close_price", + "customer_direct_sms", + "customer_special_pricing", + "deposit_account_holder", + "deposit_account_number", + "deposit_card_number", + "deposit_method_card_to_card", + "deposit_method_gateway", + "deposit_method_identified", + "deposit_method_shetab", + "deposit_sheba", + "deposit_sms_support_number", + "deposit_support_message_text", + "down_price", + "enable_conditional_order", + "enable_customer_level_profit", + "enable_product_funds", + "endDay", + "external_order_api_enabled", + "external_payment_base_url", + "external_payment_client_id", + "external_payment_client_secret", + "external_payment_enabled", + "external_payment_gateway", + "external_payment_return_url", + "external_payment_webhook_url", + "fee_conditional_order", + "fee_deduct_from_gold", + "fee_free", + "fee_service_charge_percentage", + "gift_for_referral", + "gift_for_referrer", + "group_sms", + "has_chat", + "has_page_earn_money", + "has_page_gift_card", + "has_page_notification", + "has_page_price_trigger", + "invite_link_text", + "is_store", + "jibit_enabled", + "jibit_verification", + "logo_login", + "logo_register", + "main_metal", + "main_product", + "main_product_price", + "main_wallet_products", + "metal_type", + "min_deposit", + "min_first_buy", + "min_withdraw", + "minimum_sell", + "montify_name", + "montzar_base_url", + "montzar_closed", + "montzar_diff", + "montzar_last_product_log", + "montzar_live_price", + "montzar_mazaneh", + "montzar_mobile", + "montzar_open_buy", + "montzar_open_sell", + "montzar_order_response", + "montzar_password", + "montzar_product_id", + "montzar_token", + "montzar_truly_buy_mazaneh", + "montzar_truly_sell_mazaneh", + "name_fa", + "node_client_id", + "node_shared_secret", + "open_register", + "pargar_api_token", + "pargar_item_id_gold", + "pargar_item_id_rial", + "pargar_password", + "pargar_username", + "payping_fin_customer_id", + "payping_fin_sync_enabled", + "payping_fin_token", + "payping_fin_url", + "payping_gateway_items", + "payping_gateway_name", + "payping_round_toman", + "payping_start_url", + "payping_strict_failed_withdraw", + "payping_verify_url", + "permision__enabled", + "price_channel_access", + "price_channel_bale_to", + "price_channel_diff", + "price_channel_enabled", + "price_channel_footer_id", + "price_channel_footer_link", + "price_channel_footer_phone", + "price_channel_message_text", + "price_channel_messengers", + "price_channel_min_seconds", + "price_channel_rubika_to", + "price_channel_telegram_to", + "price_extreme_date", + "price_provider", + "products_site_link_enabled", + "products_site_link_text", + "products_site_link_url", + "profit_gold_api_enabled", + "referral_bonus_amount", + "referral_gift_buy_from_montzar", + "referral_gift_on_first_order", + "referral_id", + "referral_limit", + "referral_show", + "register_jibit_verify", + "register_referral_bonus_message", + "register_referral_sms_to_referrer", + "require_zero_main_gold_before_other_buy", + "rubika_bot_token", + "sell_fee", + "send_to_pargar", + "send_to_tahesab", + "service_charge_percentage", + "show_gold_ounce_price", + "show_home_price_when_closed", + "show_last_price", + "show_product_card_meta", + "show_product_details", + "show_product_trade_status", + "show_to_users_chat", + "sms_notif_bank_card_admin", + "sms_notif_bank_card_customer", + "sms_notif_fund_withdraw_admin", + "sms_notif_fund_withdraw_customer", + "sms_notif_gateway_charge_admin", + "sms_notif_jibit_admin", + "sms_notif_login_otp", + "sms_notif_order_admin", + "sms_notif_order_customer", + "sms_notif_price_trigger", + "sms_notif_register_admin", + "sms_notif_register_customer", + "sms_notif_support_admin", + "sms_notif_wallet_alert", + "sms_notif_withdraw_admin", + "sms_notif_withdraw_otp", + "sms_panel", + "sse_path", + "startDay", + "support_messenger", + "support_messenger_id", + "support_messenger_text", + "tahesab_bank_account", + "tahesab_db", + "tahesab_host", + "tahesab_port", + "tahesab_token", + "telegram_bot_token", + "text_about", + "text_rule", + "title_fav", + "title_pwa", + "tracking_systems", + "tracking_systems_enabled", + "unit", + "up_price", + "wallet_payment_provider", + "wss_enabled", + "zaarbox_api_url", + "zaarbox_price_key", + "zarin_api", + "zarin_gorohe_kar", + "zarin_name_g", + "zarin_noe_kar", + "zarin_noe_kar_tedadi", + "zarin_token", + "zarin_uid_prefix", +]); + diff --git a/src/program-settings/program-settings.service.ts b/src/program-settings/program-settings.service.ts index 665c6c3..74dad8c 100644 --- a/src/program-settings/program-settings.service.ts +++ b/src/program-settings/program-settings.service.ts @@ -16,6 +16,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { AuditService } from '../audit/audit.service'; import { isSecretOrServerOnlySettingKey } from './secret-setting-keys'; import { isRuntimeProgramSettingKey } from './runtime-setting-keys'; +import { DEPLOY_SETTING_CATALOG_KEYS } from './deploy-setting-catalog-keys'; export type ProgramSettingRow = { id?: string | number; @@ -187,8 +188,10 @@ export class ProgramSettingsService implements OnModuleInit { throw new BadRequestException('موبایل ادمین خانگی باید ۱۱ رقم باشد (مثلاً 0912…)'); } const { apiBase, token } = await this.loginKhanegiAdmin(tenant, mobileNorm, password); + await this.ensureTemplatesSeeded(); const remote = await this.fetchSystemSettings(apiBase, token); const local = await this.listTenantRows(tenantId); + const catalogKeys = await this.catalogKeySet(); const localByKey = new Map(local.map((row) => [String(row.key), row])); const remoteByKey = new Map( remote @@ -206,6 +209,7 @@ export class ProgramSettingsService implements OnModuleInit { }> = []; for (const [key, row] of remoteByKey) { + if (!this.isNewKhanegiCatalogKey(key, catalogKeys)) continue; const localRow = localByKey.get(key); decisionsNeeded.push({ key, @@ -216,15 +220,16 @@ export class ProgramSettingsService implements OnModuleInit { }); } - const onlySuperAdmin = [...localByKey.keys()].filter((key) => !remoteByKey.has(key)); + const onlySuperAdmin = [...catalogKeys].filter((key) => !remoteByKey.has(key)); return { tenant: this.tenantMeta(tenant), apiBase, decisionsNeeded, onlySuperAdminKeys: onlySuperAdmin, + skippedCatalogCount: [...remoteByKey.keys()].filter((key) => catalogKeys.has(key)).length, note: - 'برای هر کلید یکی از حالت‌ها را بفرستید: none | this | all. کلیدهایی که فقط در سوپرادمین‌اند با مقدار پیش‌فرض قالب روی این پروژه تکمیل می‌شوند.', + 'فقط کلیدهایی که در قالب سوپرادمین نیستند. کلیدهای deploy_settings_const از قبل در قالب‌اند و از خانگی بازنویسی نمی‌شوند. کلیدهای فقط-سوپرادمین روی این پروژه با پیش‌فرض قالب تکمیل می‌شوند.', }; } @@ -241,11 +246,14 @@ export class ProgramSettingsService implements OnModuleInit { throw new BadRequestException('موبایل ادمین خانگی باید ۱۱ رقم باشد (مثلاً 0912…)'); } const { apiBase, token } = await this.loginKhanegiAdmin(tenant, mobileNorm, password); + await this.ensureTemplatesSeeded(); const remote = await this.fetchSystemSettings(apiBase, token); + const catalogKeys = await this.catalogKeySet(); const remoteByKey = new Map( remote .map((row) => this.normalizeIncoming(row)) .filter((row) => row.key && !isRuntimeProgramSettingKey(row.key)) + .filter((row) => this.isNewKhanegiCatalogKey(row.key, catalogKeys)) .map((row) => [row.key, row]), ); @@ -443,25 +451,29 @@ export class ProgramSettingsService implements OnModuleInit { } async ensureTemplatesSeeded() { - const count = await this.prisma.settingTemplate.count(); - if (count > 0) return { seeded: false, count }; - - const seedPath = this.resolveSeedPath(); - if (!seedPath || !existsSync(seedPath)) { - return { seeded: false, count: 0, missingSeed: true }; - } - const raw = JSON.parse(readFileSync(seedPath, 'utf8')) as TemplateSeedRow[]; - if (!Array.isArray(raw) || !raw.length) { - return { seeded: false, count: 0 }; + const raw = this.readSeedRows(); + const before = await this.prisma.settingTemplate.count(); + if (!raw.length) { + return { seeded: false, count: before, missingSeed: true }; } + let inserted = 0; await this.prisma.$transaction(async (tx: any) => { for (const row of raw) { const key = String(row.key || '').trim(); if (!key || isRuntimeProgramSettingKey(key)) continue; - await tx.settingTemplate.upsert({ - where: { key }, - create: { + const existing = await tx.settingTemplate.findUnique({ where: { key } }); + if (existing) { + if (existing.deletedAt) { + await tx.settingTemplate.update({ + where: { key }, + data: { deletedAt: null }, + }); + } + continue; + } + await tx.settingTemplate.create({ + data: { key, titleFa: row.title_fa || key, value: row.value == null ? '' : String(row.value), @@ -472,13 +484,46 @@ export class ProgramSettingsService implements OnModuleInit { isActive: this.toSmallInt(row.is_active, 1), isExist: row.is_exist === 0 || row.is_exist === false ? false : true, }, - update: {}, }); + inserted += 1; } }); const after = await this.prisma.settingTemplate.count(); - return { seeded: true, count: after }; + return { seeded: inserted > 0 || before === 0, count: after }; + } + + private readSeedRows(): TemplateSeedRow[] { + const seedPath = this.resolveSeedPath(); + if (!seedPath || !existsSync(seedPath)) return []; + try { + const raw = JSON.parse(readFileSync(seedPath, 'utf8')) as TemplateSeedRow[]; + return Array.isArray(raw) ? raw : []; + } catch { + return []; + } + } + + /** کلیدهای deploy const (داخل باندل) + فایل seed + جدول قالب. */ + private async catalogKeySet() { + const keys = new Set(DEPLOY_SETTING_CATALOG_KEYS); + for (const row of this.readSeedRows()) { + const key = String(row.key || '').trim(); + if (key) keys.add(key); + } + const rows = await this.prisma.settingTemplate.findMany({ + where: { deletedAt: null }, + select: { key: true }, + }); + for (const row of rows as Array<{ key: string }>) keys.add(row.key); + return keys; + } + + private isNewKhanegiCatalogKey(key: string, catalogKeys: Set) { + if (!key || catalogKeys.has(key)) return false; + if (isRuntimeProgramSettingKey(key)) return false; + if (isSecretOrServerOnlySettingKey(key)) return false; + return true; } private async seedEmptyKhanegiTenants() {