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:
@@ -5,6 +5,8 @@
|
||||
* - prisma/setting-templates-from-deploy.json (فقط can_edit=0 نهایی)
|
||||
* - src/program-settings/deploy-setting-catalog-keys.ts
|
||||
* - ../controlpanel_user/src/config/superAdminSettingKeys.ts
|
||||
*
|
||||
* قالب value خالی است برای توکن/رمز/شناسه پروژه — هر پروژه در سوپرادمین مقدار خودش را دارد.
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
@@ -31,30 +33,103 @@ function unquote(s) {
|
||||
.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 parseSqlList(expr) {
|
||||
const out = [];
|
||||
let i = 0;
|
||||
const s = String(expr ?? '').trim();
|
||||
while (i < s.length) {
|
||||
while (i < s.length && /[\s,]/.test(s[i])) i += 1;
|
||||
if (i >= s.length) break;
|
||||
if (s[i] === "'") {
|
||||
i += 1;
|
||||
let buf = '';
|
||||
while (i < s.length) {
|
||||
if (s[i] === '\\' && i + 1 < s.length) {
|
||||
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) {
|
||||
const keys = [...block.matchAll(/'([^']+)'/g)].map((x) => x[1]);
|
||||
for (const key of keys) {
|
||||
const row = rows.get(key);
|
||||
const row = ensureRow(key);
|
||||
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(
|
||||
/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(
|
||||
/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]);
|
||||
}
|
||||
|
||||
// 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,
|
||||
)) {
|
||||
@@ -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()]
|
||||
.filter((r) => r.can_edit === 0)
|
||||
.sort((a, b) => a.key.localeCompare(b.key));
|
||||
@@ -96,7 +214,7 @@ const editableRows = [...rows.values()]
|
||||
|
||||
const jsonOut = systemRows.map((r) => ({
|
||||
key: r.key,
|
||||
value: r.value ?? '',
|
||||
value: templateValue(r),
|
||||
title_fa: r.title_fa ?? r.key,
|
||||
can_edit: 0,
|
||||
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(`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}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user