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:
@@ -17,7 +17,10 @@ import { AuditService } from '../audit/audit.service';
|
||||
import { ReplicaWatchService } from '../replica-watch/replica-watch.service';
|
||||
import { isSecretOrServerOnlySettingKey } from './secret-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 = {
|
||||
id?: string | number;
|
||||
@@ -67,6 +70,12 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
if (result.seeded) {
|
||||
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();
|
||||
} catch (error: any) {
|
||||
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 incoming = this.asSettingsArray(itemsRaw)
|
||||
.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) {
|
||||
throw new BadRequestException('هیچ تنظیم معتبری برای ذخیره ارسال نشده');
|
||||
@@ -292,6 +302,7 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
await this.ensureTemplateDefaultsForTenant(tx, tenantId);
|
||||
|
||||
for (const [key, row] of remoteByKey) {
|
||||
if (!this.isSystemProgramSettingKey(key)) continue;
|
||||
const inCatalog = catalogKeys.has(key);
|
||||
const scope = inCatalog ? 'this' : (decisionMap.get(key) ?? 'this');
|
||||
if (scope === 'none') continue;
|
||||
@@ -346,7 +357,10 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
async listTemplates() {
|
||||
await this.ensureTemplatesSeeded();
|
||||
const rows = await this.prisma.settingTemplate.findMany({
|
||||
where: { deletedAt: null },
|
||||
where: {
|
||||
deletedAt: null,
|
||||
key: { in: [...DEPLOY_SETTING_CATALOG_KEYS] },
|
||||
},
|
||||
orderBy: { key: 'asc' },
|
||||
});
|
||||
return {
|
||||
@@ -359,7 +373,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
async addKeys(itemsRaw: Array<Record<string, unknown>>, userId?: string) {
|
||||
const incoming = this.asSettingsArray(itemsRaw)
|
||||
.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) {
|
||||
throw new BadRequestException('هیچ کلید معتبری ارسال نشده');
|
||||
}
|
||||
@@ -440,7 +455,10 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
async seedTenantFromTemplates(tenantId: string) {
|
||||
await this.ensureTemplatesSeeded();
|
||||
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 };
|
||||
|
||||
@@ -479,6 +497,8 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
for (const row of raw) {
|
||||
const key = String(row.key || '').trim();
|
||||
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 } });
|
||||
if (existing) {
|
||||
if (existing.deletedAt) {
|
||||
@@ -521,17 +541,37 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* قالب ایمپورت = کلیدهای INSERT در deploy_settings_const.sql
|
||||
* (باندل + JSON همتراز همان فایل). جدول setting_templates منبع قالب نیست.
|
||||
*/
|
||||
/** فقط can_edit=0 نهایی deploy_settings_const — نه کل INSERTها */
|
||||
private deployCatalogKeySet() {
|
||||
const keys = 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 new Set<string>(DEPLOY_SETTING_CATALOG_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 keys;
|
||||
return result;
|
||||
}
|
||||
|
||||
private deployCatalogCanEditMap() {
|
||||
@@ -571,6 +611,7 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
});
|
||||
return rows
|
||||
.filter((row: Setting) => !isRuntimeProgramSettingKey(row.key))
|
||||
.filter((row: Setting) => this.isSystemProgramSettingKey(row.key))
|
||||
.map((row: Setting) => this.toApiRow(row));
|
||||
}
|
||||
|
||||
@@ -656,7 +697,12 @@ export class ProgramSettingsService implements OnModuleInit {
|
||||
tx: any,
|
||||
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) {
|
||||
const existing = await tx.setting.findUnique({
|
||||
where: { tenantId_key: { tenantId, key: tpl.key } },
|
||||
|
||||
Reference in New Issue
Block a user