feat(settings): store Khanegi keys in settings tables instead of tenant JSON

Template defaults seed from deploy_settings_const; new tenants copy them, import decides per key, and /get_settings reads the per-tenant rows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-17 20:14:17 +03:30
co-authored by Cursor
parent 8f9988eb15
commit d57ce0a3cb
15 changed files with 2659 additions and 354 deletions
@@ -1,4 +1,15 @@
import { IsString, MaxLength, MinLength } from 'class-validator';
import { Allow, IsArray, IsIn, IsOptional, IsString, MaxLength, MinLength, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class ImportDecisionDto {
@IsString()
@MinLength(1)
@MaxLength(50)
key!: string;
@IsIn(['none', 'this', 'all'])
scope!: 'none' | 'this' | 'all';
}
export class ImportProgramSettingsDto {
@IsString()
@@ -10,4 +21,15 @@ export class ImportProgramSettingsDto {
@MinLength(1)
@MaxLength(120)
password!: string;
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => ImportDecisionDto)
decisions?: ImportDecisionDto[];
/** اگر true فقط پیش‌نمایش برگردد */
@IsOptional()
@Allow()
preview?: boolean;
}
@@ -0,0 +1,14 @@
import { Allow, IsArray, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';
export class AddSettingKeysDto {
@IsArray()
@Allow()
items!: Array<Record<string, unknown>>;
}
export class DeleteSettingKeyDto {
@IsString()
@MinLength(1)
@MaxLength(50)
key!: string;
}
@@ -35,10 +35,14 @@ export class ProgramSettingsController {
@Body() body: ImportProgramSettingsDto,
@CurrentUser() user: { id: string },
) {
return this.programSettings.importFromKhanegi(
if (body.preview) {
return this.programSettings.previewImport(tenantId, body.mobile, body.password);
}
return this.programSettings.applyImport(
tenantId,
body.mobile,
body.password,
body.decisions || [],
user.id,
);
}
@@ -5,6 +5,7 @@ import { ProgramSettingsController } from './program-settings.controller';
import { PublicProgramSettingsController } from './public-program-settings.controller';
import { ApiPublicProgramSettingsController } from './api-public-program-settings.controller';
import { RemoteProgramSettingsController } from './remote-program-settings.controller';
import { SettingTemplatesController } from './setting-templates.controller';
import { ProgramSettingsService } from './program-settings.service';
@Module({
@@ -14,6 +15,7 @@ import { ProgramSettingsService } from './program-settings.service';
PublicProgramSettingsController,
ApiPublicProgramSettingsController,
RemoteProgramSettingsController,
SettingTemplatesController,
],
providers: [ProgramSettingsService],
exports: [ProgramSettingsService],
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
import { Body, Controller, Delete, Get, Post, Query, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { PermissionsGuard } from '../auth/permissions.guard';
import { RequirePermissions } from '../auth/permissions.decorator';
import { CurrentUser } from '../auth/current-user.decorator';
import { AddSettingKeysDto, DeleteSettingKeyDto } from './dto/setting-keys.dto';
import { ProgramSettingsService } from './program-settings.service';
@Controller('setting-templates')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class SettingTemplatesController {
constructor(private readonly programSettings: ProgramSettingsService) {}
@Get()
@RequirePermissions('khanegi:read')
list() {
return this.programSettings.listTemplates();
}
@Post('keys')
@RequirePermissions('khanegi:write')
addKeys(@Body() body: AddSettingKeysDto, @CurrentUser() user: { id: string }) {
return this.programSettings.addKeys(body.items || [], user.id);
}
@Delete('keys')
@RequirePermissions('khanegi:write')
removeKey(
@Query('key') keyQuery: string | undefined,
@Body() body: DeleteSettingKeyDto | undefined,
@CurrentUser() user: { id: string },
) {
const key = body?.key || keyQuery || '';
return this.programSettings.deleteKey(key, user.id);
}
@Post('seed')
@RequirePermissions('khanegi:write')
seed() {
return this.programSettings.ensureTemplatesSeeded();
}
}