fix(settings): restrict superadmin template to can_edit=0 keys only
Superadmin was seeding all deploy_settings_const INSERT keys including can_edit=1 entries with wrong defaults (e.g. montzar_product_id=25). Template/catalog now follow final can_edit=0 from SQL; editable keys are purged from setting_templates and tenant settings on boot/seed. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
-1
@@ -38,7 +38,8 @@
|
|||||||
"replicas:sync": "bash deploy/sync-replica-schema.sh && bash deploy/sync-replica-data.sh --repair-if-needed",
|
"replicas:sync": "bash deploy/sync-replica-schema.sh && bash deploy/sync-replica-data.sh --repair-if-needed",
|
||||||
"replicas:check": "bash deploy/sync-replica-schema.sh --check && bash deploy/sync-replica-data.sh --check",
|
"replicas:check": "bash deploy/sync-replica-schema.sh --check && bash deploy/sync-replica-data.sh --check",
|
||||||
"replicas:repair-data": "bash deploy/sync-replica-data.sh --repair",
|
"replicas:repair-data": "bash deploy/sync-replica-data.sh --repair",
|
||||||
"settings:seed-templates": "echo 'Templates seed on first GET /api/setting-templates or POST /api/setting-templates/seed'"
|
"settings:sync-template": "node scripts/sync-superadmin-setting-template.mjs",
|
||||||
|
"settings:seed-templates": "echo 'Templates seed on server boot or POST /api/setting-templates/seed'"
|
||||||
},
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"seed": "ts-node --transpile-only prisma/seed.ts"
|
"seed": "ts-node --transpile-only prisma/seed.ts"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* منبع حقیقت: khaneghi_server/database/sql/deploy_settings_const.sql
|
||||||
|
* خروجی:
|
||||||
|
* - prisma/setting-templates-from-deploy.json (فقط can_edit=0 نهایی)
|
||||||
|
* - src/program-settings/deploy-setting-catalog-keys.ts
|
||||||
|
*/
|
||||||
|
import { readFileSync, writeFileSync } from 'fs';
|
||||||
|
import { dirname, join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const root = join(__dirname, '..');
|
||||||
|
const sqlPath = join(
|
||||||
|
root,
|
||||||
|
'../khaneghi_server/database/sql/deploy_settings_const.sql',
|
||||||
|
);
|
||||||
|
|
||||||
|
const sql = readFileSync(sqlPath, 'utf8');
|
||||||
|
|
||||||
|
/** @type {Map<string, { key: string, value: string, title_fa: string, can_edit: number, is_active: number, description: string, type: string }>} */
|
||||||
|
const rows = new Map();
|
||||||
|
|
||||||
|
function unquote(s) {
|
||||||
|
return String(s ?? '')
|
||||||
|
.trim()
|
||||||
|
.replace(/^'/, '')
|
||||||
|
.replace(/'$/, '')
|
||||||
|
.replace(/\\'/g, "'")
|
||||||
|
.replace(/\\n/g, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSERT … SELECT 'key', 'value', …
|
||||||
|
for (const m of sql.matchAll(
|
||||||
|
/SELECT\s+'([^']+)',\s+'((?:\\'|[^'])*)',\s+'((?:\\'|[^'])*)',\s+(\d+),\s+(\d+)(?:,\s+(NULL|'(?:\\'|[^'])*'))(?:,\s+'((?:\\'|[^'])*)')?/g,
|
||||||
|
)) {
|
||||||
|
const key = m[1];
|
||||||
|
const value = unquote(`'${m[2]}'`);
|
||||||
|
const title_fa = unquote(`'${m[3]}'`);
|
||||||
|
const can_edit = Number(m[4]);
|
||||||
|
const is_active = Number(m[5]);
|
||||||
|
let description = '';
|
||||||
|
if (m[6] && m[6] !== 'NULL') description = unquote(m[6]);
|
||||||
|
const type = m[7] ? unquote(`'${m[7]}'`) : '';
|
||||||
|
rows.set(key, { key, value, title_fa, can_edit, is_active, description, type });
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCanEdit(block, value) {
|
||||||
|
const keys = [...block.matchAll(/'([^']+)'/g)].map((x) => x[1]);
|
||||||
|
for (const key of keys) {
|
||||||
|
const row = rows.get(key);
|
||||||
|
if (row) row.can_edit = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UPDATE can_edit = 0/1 batches
|
||||||
|
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,
|
||||||
|
)) {
|
||||||
|
applyCanEdit(m[2], Number(m[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const m of sql.matchAll(
|
||||||
|
/UPDATE\s+`settings`\s+SET\s+`can_edit`\s*=\s*(\d)\s+WHERE\s+`key`\s*=\s*'([^']+)'/g,
|
||||||
|
)) {
|
||||||
|
const row = rows.get(m[2]);
|
||||||
|
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)) {
|
||||||
|
for (const [key, row] of rows) {
|
||||||
|
if (key.startsWith('zarin_')) row.can_edit = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// type-only UPDATE batches — enrich type when present
|
||||||
|
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,
|
||||||
|
)) {
|
||||||
|
const type = m[1];
|
||||||
|
for (const key of [...m[2].matchAll(/'([^']+)'/g)].map((x) => x[1])) {
|
||||||
|
const row = rows.get(key);
|
||||||
|
if (row && type) row.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemRows = [...rows.values()]
|
||||||
|
.filter((r) => r.can_edit === 0)
|
||||||
|
.sort((a, b) => a.key.localeCompare(b.key));
|
||||||
|
|
||||||
|
const editableRows = [...rows.values()]
|
||||||
|
.filter((r) => r.can_edit === 1)
|
||||||
|
.sort((a, b) => a.key.localeCompare(b.key));
|
||||||
|
|
||||||
|
const jsonOut = systemRows.map((r) => ({
|
||||||
|
key: r.key,
|
||||||
|
value: r.value ?? '',
|
||||||
|
title_fa: r.title_fa ?? r.key,
|
||||||
|
can_edit: 0,
|
||||||
|
is_active: r.is_active ?? 1,
|
||||||
|
description: r.description ?? '',
|
||||||
|
type: r.type ?? '',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const tsKeys = `/**
|
||||||
|
* کلیدهای can_edit=0 در deploy_settings_const.sql — فقط همینها در قالب سوپرادمین.
|
||||||
|
* تولید خودکار: node scripts/sync-superadmin-setting-template.mjs
|
||||||
|
*/
|
||||||
|
export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
||||||
|
${systemRows.map((r) => ` ${JSON.stringify(r.key)},`).join('\n')}
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** کلیدهای can_edit=1 — نباید در قالب/جدول settings سوپرادمین باشند */
|
||||||
|
export const DEPLOY_SETTING_EDITABLE_KEYS = new Set<string>([
|
||||||
|
${editableRows.map((r) => ` ${JSON.stringify(r.key)},`).join('\n')}
|
||||||
|
]);
|
||||||
|
`;
|
||||||
|
|
||||||
|
writeFileSync(join(root, 'prisma/setting-templates-from-deploy.json'), `${JSON.stringify(jsonOut, null, 2)}\n`);
|
||||||
|
writeFileSync(join(root, 'src/program-settings/deploy-setting-catalog-keys.ts'), `${tsKeys}\n`);
|
||||||
|
|
||||||
|
console.log(`system keys (can_edit=0): ${systemRows.length}`);
|
||||||
|
console.log(`editable keys (can_edit=1): ${editableRows.length}`);
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* کلیدهای INSERT در khaneghi_server/database/sql/deploy_settings_const.sql
|
* کلیدهای can_edit=0 در deploy_settings_const.sql — فقط همینها در قالب سوپرادمین.
|
||||||
* منبع قالب ایمپورت سوپرادمین — داخل باندل تا به فایل prisma وابسته نباشد.
|
* تولید خودکار: node scripts/sync-superadmin-setting-template.mjs
|
||||||
* این ۱۹۵ کلید با SQL فعلی همتراز است؛ کلید داخل این مجموعه در مودال «تصمیم برای هر کلید» نمیآید.
|
|
||||||
*/
|
*/
|
||||||
export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
||||||
"accounting_provider",
|
"accounting_provider",
|
||||||
"admin_url",
|
"admin_url",
|
||||||
"apk_download_link",
|
"apk_download_link",
|
||||||
"app_url",
|
"app_url",
|
||||||
"auto_active_customer",
|
|
||||||
"balance_report_exclude_customer_id",
|
|
||||||
"bale_bot_token",
|
"bale_bot_token",
|
||||||
"banner_enabled",
|
|
||||||
"buy_fee",
|
|
||||||
"change_mesghal",
|
|
||||||
"charge_notif",
|
|
||||||
"chart_id",
|
"chart_id",
|
||||||
"chart_use_local_data",
|
"chart_use_local_data",
|
||||||
"chat_api",
|
"chat_api",
|
||||||
@@ -23,24 +16,12 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
|||||||
"chat_upload_url",
|
"chat_upload_url",
|
||||||
"chat_webhook_base_url",
|
"chat_webhook_base_url",
|
||||||
"chat_webhook_secret",
|
"chat_webhook_secret",
|
||||||
"close_price",
|
|
||||||
"customer_direct_sms",
|
"customer_direct_sms",
|
||||||
"customer_special_pricing",
|
"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",
|
"down_price",
|
||||||
"enable_conditional_order",
|
"enable_conditional_order",
|
||||||
"enable_customer_level_profit",
|
"enable_customer_level_profit",
|
||||||
"enable_product_funds",
|
"enable_product_funds",
|
||||||
"endDay",
|
|
||||||
"external_order_api_enabled",
|
"external_order_api_enabled",
|
||||||
"external_payment_base_url",
|
"external_payment_base_url",
|
||||||
"external_payment_client_id",
|
"external_payment_client_id",
|
||||||
@@ -49,33 +30,19 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
|||||||
"external_payment_gateway",
|
"external_payment_gateway",
|
||||||
"external_payment_return_url",
|
"external_payment_return_url",
|
||||||
"external_payment_webhook_url",
|
"external_payment_webhook_url",
|
||||||
"fee_conditional_order",
|
|
||||||
"fee_deduct_from_gold",
|
"fee_deduct_from_gold",
|
||||||
"fee_free",
|
|
||||||
"fee_service_charge_percentage",
|
|
||||||
"gift_for_referral",
|
|
||||||
"gift_for_referrer",
|
|
||||||
"group_sms",
|
"group_sms",
|
||||||
"has_chat",
|
|
||||||
"has_page_earn_money",
|
"has_page_earn_money",
|
||||||
"has_page_gift_card",
|
"has_page_gift_card",
|
||||||
"has_page_notification",
|
"has_page_notification",
|
||||||
"has_page_price_trigger",
|
"has_page_price_trigger",
|
||||||
"invite_link_text",
|
|
||||||
"is_store",
|
"is_store",
|
||||||
"jibit_enabled",
|
"jibit_enabled",
|
||||||
"jibit_verification",
|
|
||||||
"logo_login",
|
"logo_login",
|
||||||
"logo_register",
|
"logo_register",
|
||||||
"main_metal",
|
"main_metal",
|
||||||
"main_product",
|
|
||||||
"main_product_price",
|
|
||||||
"main_wallet_products",
|
"main_wallet_products",
|
||||||
"metal_type",
|
"metal_type",
|
||||||
"min_deposit",
|
|
||||||
"min_first_buy",
|
|
||||||
"min_withdraw",
|
|
||||||
"minimum_sell",
|
|
||||||
"montify_name",
|
"montify_name",
|
||||||
"montzar_base_url",
|
"montzar_base_url",
|
||||||
"montzar_closed",
|
"montzar_closed",
|
||||||
@@ -83,19 +50,12 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
|||||||
"montzar_last_product_log",
|
"montzar_last_product_log",
|
||||||
"montzar_live_price",
|
"montzar_live_price",
|
||||||
"montzar_mazaneh",
|
"montzar_mazaneh",
|
||||||
"montzar_mobile",
|
|
||||||
"montzar_open_buy",
|
|
||||||
"montzar_open_sell",
|
|
||||||
"montzar_order_response",
|
"montzar_order_response",
|
||||||
"montzar_password",
|
|
||||||
"montzar_product_id",
|
|
||||||
"montzar_token",
|
"montzar_token",
|
||||||
"montzar_truly_buy_mazaneh",
|
"montzar_truly_buy_mazaneh",
|
||||||
"montzar_truly_sell_mazaneh",
|
"montzar_truly_sell_mazaneh",
|
||||||
"name_fa",
|
|
||||||
"node_client_id",
|
"node_client_id",
|
||||||
"node_shared_secret",
|
"node_shared_secret",
|
||||||
"open_register",
|
|
||||||
"pargar_api_token",
|
"pargar_api_token",
|
||||||
"pargar_item_id_gold",
|
"pargar_item_id_gold",
|
||||||
"pargar_item_id_rial",
|
"pargar_item_id_rial",
|
||||||
@@ -114,23 +74,83 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
|||||||
"permision__enabled",
|
"permision__enabled",
|
||||||
"price_channel_access",
|
"price_channel_access",
|
||||||
"price_channel_bale_to",
|
"price_channel_bale_to",
|
||||||
|
"price_channel_rubika_to",
|
||||||
|
"price_channel_telegram_to",
|
||||||
|
"price_extreme_date",
|
||||||
|
"referral_bonus_amount",
|
||||||
|
"rubika_bot_token",
|
||||||
|
"send_to_pargar",
|
||||||
|
"send_to_tahesab",
|
||||||
|
"show_to_users_chat",
|
||||||
|
"sms_panel",
|
||||||
|
"sse_path",
|
||||||
|
"tahesab_bank_account",
|
||||||
|
"tahesab_db",
|
||||||
|
"tahesab_host",
|
||||||
|
"tahesab_port",
|
||||||
|
"tahesab_token",
|
||||||
|
"telegram_bot_token",
|
||||||
|
"unit",
|
||||||
|
"up_price",
|
||||||
|
"wallet_payment_provider",
|
||||||
|
"wss_enabled",
|
||||||
|
"zarin_api",
|
||||||
|
"zarin_gorohe_kar",
|
||||||
|
"zarin_name_g",
|
||||||
|
"zarin_noe_kar",
|
||||||
|
"zarin_noe_kar_tedadi",
|
||||||
|
"zarin_token",
|
||||||
|
"zarin_uid_prefix",
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** کلیدهای can_edit=1 — نباید در قالب/جدول settings سوپرادمین باشند */
|
||||||
|
export const DEPLOY_SETTING_EDITABLE_KEYS = new Set<string>([
|
||||||
|
"auto_active_customer",
|
||||||
|
"balance_report_exclude_customer_id",
|
||||||
|
"banner_enabled",
|
||||||
|
"buy_fee",
|
||||||
|
"change_mesghal",
|
||||||
|
"charge_notif",
|
||||||
|
"close_price",
|
||||||
|
"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",
|
||||||
|
"endDay",
|
||||||
|
"fee_conditional_order",
|
||||||
|
"fee_free",
|
||||||
|
"fee_service_charge_percentage",
|
||||||
|
"gift_for_referral",
|
||||||
|
"gift_for_referrer",
|
||||||
|
"has_chat",
|
||||||
|
"jibit_verification",
|
||||||
|
"main_product_price",
|
||||||
|
"min_deposit",
|
||||||
|
"min_first_buy",
|
||||||
|
"min_withdraw",
|
||||||
|
"minimum_sell",
|
||||||
|
"montzar_mobile",
|
||||||
|
"montzar_open_buy",
|
||||||
|
"montzar_open_sell",
|
||||||
|
"montzar_password",
|
||||||
|
"montzar_product_id",
|
||||||
|
"name_fa",
|
||||||
|
"open_register",
|
||||||
"price_channel_diff",
|
"price_channel_diff",
|
||||||
"price_channel_enabled",
|
"price_channel_enabled",
|
||||||
"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_channel_rubika_to",
|
|
||||||
"price_channel_telegram_to",
|
|
||||||
"price_extreme_date",
|
|
||||||
"price_provider",
|
|
||||||
"products_site_link_enabled",
|
"products_site_link_enabled",
|
||||||
"products_site_link_text",
|
"products_site_link_text",
|
||||||
"products_site_link_url",
|
"products_site_link_url",
|
||||||
"profit_gold_api_enabled",
|
|
||||||
"referral_bonus_amount",
|
|
||||||
"referral_gift_buy_from_montzar",
|
"referral_gift_buy_from_montzar",
|
||||||
"referral_gift_on_first_order",
|
"referral_gift_on_first_order",
|
||||||
"referral_id",
|
"referral_id",
|
||||||
@@ -140,18 +160,13 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
|||||||
"register_referral_bonus_message",
|
"register_referral_bonus_message",
|
||||||
"register_referral_sms_to_referrer",
|
"register_referral_sms_to_referrer",
|
||||||
"require_zero_main_gold_before_other_buy",
|
"require_zero_main_gold_before_other_buy",
|
||||||
"rubika_bot_token",
|
|
||||||
"sell_fee",
|
"sell_fee",
|
||||||
"send_to_pargar",
|
|
||||||
"send_to_tahesab",
|
|
||||||
"service_charge_percentage",
|
|
||||||
"show_gold_ounce_price",
|
"show_gold_ounce_price",
|
||||||
"show_home_price_when_closed",
|
"show_home_price_when_closed",
|
||||||
"show_last_price",
|
"show_last_price",
|
||||||
"show_product_card_meta",
|
"show_product_card_meta",
|
||||||
"show_product_details",
|
"show_product_details",
|
||||||
"show_product_trade_status",
|
"show_product_trade_status",
|
||||||
"show_to_users_chat",
|
|
||||||
"sms_notif_bank_card_admin",
|
"sms_notif_bank_card_admin",
|
||||||
"sms_notif_bank_card_customer",
|
"sms_notif_bank_card_customer",
|
||||||
"sms_notif_fund_withdraw_admin",
|
"sms_notif_fund_withdraw_admin",
|
||||||
@@ -168,36 +183,11 @@ export const DEPLOY_SETTING_CATALOG_KEYS = new Set<string>([
|
|||||||
"sms_notif_wallet_alert",
|
"sms_notif_wallet_alert",
|
||||||
"sms_notif_withdraw_admin",
|
"sms_notif_withdraw_admin",
|
||||||
"sms_notif_withdraw_otp",
|
"sms_notif_withdraw_otp",
|
||||||
"sms_panel",
|
|
||||||
"sse_path",
|
|
||||||
"startDay",
|
"startDay",
|
||||||
"support_messenger",
|
"support_messenger",
|
||||||
"support_messenger_id",
|
"support_messenger_id",
|
||||||
"support_messenger_text",
|
"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",
|
||||||
"tracking_systems_enabled",
|
"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",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ import { AuditService } from '../audit/audit.service';
|
|||||||
import { ReplicaWatchService } from '../replica-watch/replica-watch.service';
|
import { ReplicaWatchService } from '../replica-watch/replica-watch.service';
|
||||||
import { isSecretOrServerOnlySettingKey } from './secret-setting-keys';
|
import { isSecretOrServerOnlySettingKey } from './secret-setting-keys';
|
||||||
import { isRuntimeProgramSettingKey } from './runtime-setting-keys';
|
import { isRuntimeProgramSettingKey } from './runtime-setting-keys';
|
||||||
import { DEPLOY_SETTING_CATALOG_KEYS } from './deploy-setting-catalog-keys';
|
import {
|
||||||
|
DEPLOY_SETTING_CATALOG_KEYS,
|
||||||
|
DEPLOY_SETTING_EDITABLE_KEYS,
|
||||||
|
} from './deploy-setting-catalog-keys';
|
||||||
|
|
||||||
export type ProgramSettingRow = {
|
export type ProgramSettingRow = {
|
||||||
id?: string | number;
|
id?: string | number;
|
||||||
@@ -67,6 +70,12 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
if (result.seeded) {
|
if (result.seeded) {
|
||||||
this.logger.log(`setting_templates seeded (${result.count} keys)`);
|
this.logger.log(`setting_templates seeded (${result.count} keys)`);
|
||||||
}
|
}
|
||||||
|
const purged = await this.purgeEditableFromSuperAdmin();
|
||||||
|
if (purged.templates || purged.settings) {
|
||||||
|
this.logger.log(
|
||||||
|
`purged can_edit=1 from superadmin (templates=${purged.templates}, settings=${purged.settings})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
await this.seedEmptyKhanegiTenants();
|
await this.seedEmptyKhanegiTenants();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.warn(`setting_templates seed skipped: ${error?.message || error}`);
|
this.logger.warn(`setting_templates seed skipped: ${error?.message || error}`);
|
||||||
@@ -113,7 +122,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
const tenant = await this.requireKhanegiTenant(tenantId);
|
const tenant = await this.requireKhanegiTenant(tenantId);
|
||||||
const incoming = this.asSettingsArray(itemsRaw)
|
const incoming = this.asSettingsArray(itemsRaw)
|
||||||
.map((row) => this.normalizeIncoming(row))
|
.map((row) => this.normalizeIncoming(row))
|
||||||
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key));
|
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key))
|
||||||
|
.filter((row) => this.isSystemProgramSettingKey(row.key));
|
||||||
|
|
||||||
if (!incoming.length) {
|
if (!incoming.length) {
|
||||||
throw new BadRequestException('هیچ تنظیم معتبری برای ذخیره ارسال نشده');
|
throw new BadRequestException('هیچ تنظیم معتبری برای ذخیره ارسال نشده');
|
||||||
@@ -292,6 +302,7 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
await this.ensureTemplateDefaultsForTenant(tx, tenantId);
|
await this.ensureTemplateDefaultsForTenant(tx, tenantId);
|
||||||
|
|
||||||
for (const [key, row] of remoteByKey) {
|
for (const [key, row] of remoteByKey) {
|
||||||
|
if (!this.isSystemProgramSettingKey(key)) continue;
|
||||||
const inCatalog = catalogKeys.has(key);
|
const inCatalog = catalogKeys.has(key);
|
||||||
const scope = inCatalog ? 'this' : (decisionMap.get(key) ?? 'this');
|
const scope = inCatalog ? 'this' : (decisionMap.get(key) ?? 'this');
|
||||||
if (scope === 'none') continue;
|
if (scope === 'none') continue;
|
||||||
@@ -346,7 +357,10 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
async listTemplates() {
|
async listTemplates() {
|
||||||
await this.ensureTemplatesSeeded();
|
await this.ensureTemplatesSeeded();
|
||||||
const rows = await this.prisma.settingTemplate.findMany({
|
const rows = await this.prisma.settingTemplate.findMany({
|
||||||
where: { deletedAt: null },
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] },
|
||||||
|
},
|
||||||
orderBy: { key: 'asc' },
|
orderBy: { key: 'asc' },
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@@ -359,7 +373,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
async addKeys(itemsRaw: Array<Record<string, unknown>>, userId?: string) {
|
async addKeys(itemsRaw: Array<Record<string, unknown>>, userId?: string) {
|
||||||
const incoming = this.asSettingsArray(itemsRaw)
|
const incoming = this.asSettingsArray(itemsRaw)
|
||||||
.map((row) => this.normalizeIncoming(row))
|
.map((row) => this.normalizeIncoming(row))
|
||||||
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key));
|
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key))
|
||||||
|
.filter((row) => this.isSystemProgramSettingKey(row.key));
|
||||||
if (!incoming.length) {
|
if (!incoming.length) {
|
||||||
throw new BadRequestException('هیچ کلید معتبری ارسال نشده');
|
throw new BadRequestException('هیچ کلید معتبری ارسال نشده');
|
||||||
}
|
}
|
||||||
@@ -440,7 +455,10 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
async seedTenantFromTemplates(tenantId: string) {
|
async seedTenantFromTemplates(tenantId: string) {
|
||||||
await this.ensureTemplatesSeeded();
|
await this.ensureTemplatesSeeded();
|
||||||
const templates = await this.prisma.settingTemplate.findMany({
|
const templates = await this.prisma.settingTemplate.findMany({
|
||||||
where: { deletedAt: null },
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (!templates.length) return { count: 0 };
|
if (!templates.length) return { count: 0 };
|
||||||
|
|
||||||
@@ -479,6 +497,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
for (const row of raw) {
|
for (const row of raw) {
|
||||||
const key = String(row.key || '').trim();
|
const key = String(row.key || '').trim();
|
||||||
if (!key || isRuntimeProgramSettingKey(key)) continue;
|
if (!key || isRuntimeProgramSettingKey(key)) continue;
|
||||||
|
if (!this.isSystemProgramSettingKey(key)) continue;
|
||||||
|
if (this.toSmallInt(row.can_edit, 0) !== 0) continue;
|
||||||
const existing = await tx.settingTemplate.findUnique({ where: { key } });
|
const existing = await tx.settingTemplate.findUnique({ where: { key } });
|
||||||
if (existing) {
|
if (existing) {
|
||||||
if (existing.deletedAt) {
|
if (existing.deletedAt) {
|
||||||
@@ -521,17 +541,37 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** فقط can_edit=0 نهایی deploy_settings_const — نه کل INSERTها */
|
||||||
* قالب ایمپورت = کلیدهای INSERT در deploy_settings_const.sql
|
|
||||||
* (باندل + JSON همتراز همان فایل). جدول setting_templates منبع قالب نیست.
|
|
||||||
*/
|
|
||||||
private deployCatalogKeySet() {
|
private deployCatalogKeySet() {
|
||||||
const keys = new Set<string>(DEPLOY_SETTING_CATALOG_KEYS);
|
return new Set<string>(DEPLOY_SETTING_CATALOG_KEYS);
|
||||||
for (const row of this.readSeedRows()) {
|
|
||||||
const key = String(row.key || '').trim();
|
|
||||||
if (key) keys.add(key);
|
|
||||||
}
|
}
|
||||||
return keys;
|
|
||||||
|
private isSystemProgramSettingKey(key: string) {
|
||||||
|
const k = String(key || '').trim();
|
||||||
|
if (!k || isRuntimeProgramSettingKey(k)) return false;
|
||||||
|
if (DEPLOY_SETTING_EDITABLE_KEYS.has(k)) return false;
|
||||||
|
return DEPLOY_SETTING_CATALOG_KEYS.has(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** حذف can_edit=1 از قالب و همهٔ پروژههای خانگی در DB سوپرادمین */
|
||||||
|
async purgeEditableFromSuperAdmin() {
|
||||||
|
const keys = [...DEPLOY_SETTING_EDITABLE_KEYS];
|
||||||
|
if (!keys.length) return { templates: 0, settings: 0 };
|
||||||
|
|
||||||
|
const result = await this.prisma.$transaction(async (tx: any) => {
|
||||||
|
const templates = await tx.settingTemplate.deleteMany({
|
||||||
|
where: { key: { in: keys } },
|
||||||
|
});
|
||||||
|
const settings = await tx.setting.deleteMany({
|
||||||
|
where: { key: { in: keys } },
|
||||||
|
});
|
||||||
|
return { templates: templates.count, settings: settings.count };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.templates || result.settings) {
|
||||||
|
this.replicaWatch.kick();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private deployCatalogCanEditMap() {
|
private deployCatalogCanEditMap() {
|
||||||
@@ -571,6 +611,7 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
});
|
});
|
||||||
return rows
|
return rows
|
||||||
.filter((row: Setting) => !isRuntimeProgramSettingKey(row.key))
|
.filter((row: Setting) => !isRuntimeProgramSettingKey(row.key))
|
||||||
|
.filter((row: Setting) => this.isSystemProgramSettingKey(row.key))
|
||||||
.map((row: Setting) => this.toApiRow(row));
|
.map((row: Setting) => this.toApiRow(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +697,12 @@ export class ProgramSettingsService implements OnModuleInit {
|
|||||||
tx: any,
|
tx: any,
|
||||||
tenantId: string,
|
tenantId: string,
|
||||||
) {
|
) {
|
||||||
const templates = await tx.settingTemplate.findMany({ where: { deletedAt: null } });
|
const templates = await tx.settingTemplate.findMany({
|
||||||
|
where: {
|
||||||
|
deletedAt: null,
|
||||||
|
key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] },
|
||||||
|
},
|
||||||
|
});
|
||||||
for (const tpl of templates) {
|
for (const tpl of templates) {
|
||||||
const existing = await tx.setting.findUnique({
|
const existing = await tx.setting.findUnique({
|
||||||
where: { tenantId_key: { tenantId, key: tpl.key } },
|
where: { tenantId_key: { tenantId, key: tpl.key } },
|
||||||
|
|||||||
@@ -36,7 +36,15 @@ export class SettingTemplatesController {
|
|||||||
|
|
||||||
@Post('seed')
|
@Post('seed')
|
||||||
@RequirePermissions('khanegi:write')
|
@RequirePermissions('khanegi:write')
|
||||||
seed() {
|
async seed() {
|
||||||
return this.programSettings.ensureTemplatesSeeded();
|
const seeded = await this.programSettings.ensureTemplatesSeeded();
|
||||||
|
const purged = await this.programSettings.purgeEditableFromSuperAdmin();
|
||||||
|
return { ...seeded, purged };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('purge-editable')
|
||||||
|
@RequirePermissions('khanegi:write')
|
||||||
|
purgeEditable() {
|
||||||
|
return this.programSettings.purgeEditableFromSuperAdmin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user