feat(program-settings): expose tenant settings APIs and merge PATCH by key
Sold apps can read settings from /get_settings; Super Admin saves only changed keys so huge runtime logs are not sent in the body. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,10 +12,14 @@ async function bootstrap() {
|
|||||||
ensureUploadsDirs();
|
ensureUploadsDirs();
|
||||||
app.useStaticAssets(UPLOADS_ROOT, { prefix: '/uploads/' });
|
app.useStaticAssets(UPLOADS_ROOT, { prefix: '/uploads/' });
|
||||||
|
|
||||||
|
app.useBodyParser('json', { limit: '2mb' });
|
||||||
|
app.useBodyParser('urlencoded', { limit: '2mb', extended: true });
|
||||||
|
|
||||||
app.setGlobalPrefix('api', {
|
app.setGlobalPrefix('api', {
|
||||||
exclude: [
|
exclude: [
|
||||||
{ path: 'get_user', method: RequestMethod.GET },
|
{ path: 'get_user', method: RequestMethod.GET },
|
||||||
{ path: 'get_version', method: RequestMethod.GET },
|
{ path: 'get_version', method: RequestMethod.GET },
|
||||||
|
{ path: 'get_settings', method: RequestMethod.GET },
|
||||||
{ path: 'manifest.json', method: RequestMethod.GET },
|
{ path: 'manifest.json', method: RequestMethod.GET },
|
||||||
{ path: 'uploads/(.*)', method: RequestMethod.GET },
|
{ path: 'uploads/(.*)', method: RequestMethod.GET },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Controller, Get, Headers, Header, Query } from '@nestjs/common';
|
||||||
|
import { ProgramSettingsService } from './program-settings.service';
|
||||||
|
|
||||||
|
@Controller('public/program-settings')
|
||||||
|
export class ApiPublicProgramSettingsController {
|
||||||
|
constructor(private readonly programSettings: ProgramSettingsService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Header('Cache-Control', 'public, max-age=30')
|
||||||
|
getSettings(
|
||||||
|
@Headers('host') host?: string,
|
||||||
|
@Headers('x-forwarded-host') forwardedHost?: string,
|
||||||
|
@Query('domain') domain?: string,
|
||||||
|
) {
|
||||||
|
return this.programSettings.getPublicByHost(domain || forwardedHost || host, domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Allow, IsArray } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateProgramSettingsDto {
|
||||||
|
@IsArray()
|
||||||
|
@Allow()
|
||||||
|
items!: Array<Record<string, unknown>>;
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
|
import { Body, Controller, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
import { PermissionsGuard } from '../auth/permissions.guard';
|
import { PermissionsGuard } from '../auth/permissions.guard';
|
||||||
import { RequirePermissions } from '../auth/permissions.decorator';
|
import { RequirePermissions } from '../auth/permissions.decorator';
|
||||||
import { CurrentUser } from '../auth/current-user.decorator';
|
import { CurrentUser } from '../auth/current-user.decorator';
|
||||||
import { ImportProgramSettingsDto } from './dto/import-program-settings.dto';
|
import { ImportProgramSettingsDto } from './dto/import-program-settings.dto';
|
||||||
|
import { UpdateProgramSettingsDto } from './dto/update-program-settings.dto';
|
||||||
import { ProgramSettingsService } from './program-settings.service';
|
import { ProgramSettingsService } from './program-settings.service';
|
||||||
|
|
||||||
@Controller('tenants/:tenantId/program-settings')
|
@Controller('tenants/:tenantId/program-settings')
|
||||||
@@ -17,6 +18,16 @@ export class ProgramSettingsController {
|
|||||||
return this.programSettings.get(tenantId);
|
return this.programSettings.get(tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Patch()
|
||||||
|
@RequirePermissions('khanegi:write')
|
||||||
|
update(
|
||||||
|
@Param('tenantId') tenantId: string,
|
||||||
|
@Body() body: UpdateProgramSettingsDto,
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
) {
|
||||||
|
return this.programSettings.updateItems(tenantId, body.items || [], user.id);
|
||||||
|
}
|
||||||
|
|
||||||
@Post('import')
|
@Post('import')
|
||||||
@RequirePermissions('khanegi:write')
|
@RequirePermissions('khanegi:write')
|
||||||
import(
|
import(
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { SmsModule } from '../sms/sms.module';
|
||||||
import { ProgramSettingsController } from './program-settings.controller';
|
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 { ProgramSettingsService } from './program-settings.service';
|
import { ProgramSettingsService } from './program-settings.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [AuthModule],
|
imports: [AuthModule, SmsModule],
|
||||||
controllers: [ProgramSettingsController],
|
controllers: [
|
||||||
|
ProgramSettingsController,
|
||||||
|
PublicProgramSettingsController,
|
||||||
|
ApiPublicProgramSettingsController,
|
||||||
|
RemoteProgramSettingsController,
|
||||||
|
],
|
||||||
providers: [ProgramSettingsService],
|
providers: [ProgramSettingsService],
|
||||||
exports: [ProgramSettingsService],
|
exports: [ProgramSettingsService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import axios from 'axios';
|
|||||||
import { Prisma, ProductType } from '@prisma/client';
|
import { Prisma, ProductType } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { AuditService } from '../audit/audit.service';
|
import { AuditService } from '../audit/audit.service';
|
||||||
|
import { isSecretOrServerOnlySettingKey } from './secret-setting-keys';
|
||||||
|
import { isRuntimeProgramSettingKey } from './runtime-setting-keys';
|
||||||
|
|
||||||
export type ProgramSettingRow = {
|
export type ProgramSettingRow = {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -24,7 +26,7 @@ export type ProgramSettingRow = {
|
|||||||
|
|
||||||
export type ProgramSettingsPayload = {
|
export type ProgramSettingsPayload = {
|
||||||
importedAt: string;
|
importedAt: string;
|
||||||
source: 'khanegi_admin';
|
source: 'khanegi_admin' | 'superadmin';
|
||||||
apiBase: string;
|
apiBase: string;
|
||||||
items: ProgramSettingRow[];
|
items: ProgramSettingRow[];
|
||||||
};
|
};
|
||||||
@@ -44,8 +46,103 @@ export class ProgramSettingsService {
|
|||||||
importedAt: stored?.importedAt ?? null,
|
importedAt: stored?.importedAt ?? null,
|
||||||
source: stored?.source ?? null,
|
source: stored?.source ?? null,
|
||||||
apiBase: stored?.apiBase ?? null,
|
apiBase: stored?.apiBase ?? null,
|
||||||
items: stored?.items ?? [],
|
items: this.omitRuntime(stored?.items ?? []),
|
||||||
count: stored?.items?.length ?? 0,
|
count: this.omitRuntime(stored?.items ?? []).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPublicByHost(hostHeader?: string, domainQuery?: string) {
|
||||||
|
const tenant = await this.findKhanegiTenantByHost(domainQuery || hostHeader || '');
|
||||||
|
const stored = this.readStored(tenant.setting);
|
||||||
|
const items = this.omitRuntime(stored?.items ?? []).filter(
|
||||||
|
(row) => !isSecretOrServerOnlySettingKey(String(row.key || '')),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
settings: items,
|
||||||
|
updatedAt: stored?.importedAt || null,
|
||||||
|
count: items.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRemoteFull(tenantId: string) {
|
||||||
|
const tenant = await this.requireKhanegiTenant(tenantId);
|
||||||
|
const stored = this.readStored(tenant.setting);
|
||||||
|
return {
|
||||||
|
settings: this.omitRuntime(stored?.items ?? []),
|
||||||
|
updatedAt: stored?.importedAt || null,
|
||||||
|
count: this.omitRuntime(stored?.items ?? []).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateItems(
|
||||||
|
tenantId: string,
|
||||||
|
itemsRaw: Array<Record<string, unknown>>,
|
||||||
|
userId?: string,
|
||||||
|
) {
|
||||||
|
const tenant = await this.requireKhanegiTenant(tenantId);
|
||||||
|
const incoming = this.asSettingsArray(itemsRaw)
|
||||||
|
.map((row) => ({
|
||||||
|
...row,
|
||||||
|
key: String(row.key || '').trim(),
|
||||||
|
value: row.value == null ? '' : String(row.value),
|
||||||
|
can_edit: 0 as const,
|
||||||
|
is_active:
|
||||||
|
row.is_active === 0 || row.is_active === false || row.is_active === '0' ? 0 : 1,
|
||||||
|
}))
|
||||||
|
.filter((row) => row.key && !isRuntimeProgramSettingKey(row.key));
|
||||||
|
|
||||||
|
if (!incoming.length) {
|
||||||
|
throw new BadRequestException('هیچ تنظیم معتبری برای ذخیره ارسال نشده');
|
||||||
|
}
|
||||||
|
|
||||||
|
const previous = this.readStored(tenant.setting);
|
||||||
|
const byKey = new Map<string, ProgramSettingRow>();
|
||||||
|
for (const row of previous?.items ?? []) {
|
||||||
|
const key = String(row.key || '').trim();
|
||||||
|
if (!key || isRuntimeProgramSettingKey(key)) continue;
|
||||||
|
byKey.set(key, row);
|
||||||
|
}
|
||||||
|
for (const row of incoming) {
|
||||||
|
const prev = byKey.get(row.key) || {};
|
||||||
|
byKey.set(row.key, { ...prev, ...row });
|
||||||
|
}
|
||||||
|
const items = [...byKey.values()];
|
||||||
|
|
||||||
|
const payload: ProgramSettingsPayload = {
|
||||||
|
importedAt: new Date().toISOString(),
|
||||||
|
source: 'superadmin',
|
||||||
|
apiBase: previous?.apiBase || '',
|
||||||
|
items,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.prisma.tenant.update({
|
||||||
|
where: { id: tenantId },
|
||||||
|
data: {
|
||||||
|
setting: payload as unknown as Prisma.InputJsonValue,
|
||||||
|
appVersion: this.bumpAppVersion(tenant.appVersion),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.audit.log({
|
||||||
|
action: 'tenant.program_settings.update',
|
||||||
|
entityType: 'tenant_program_settings',
|
||||||
|
entityId: tenantId,
|
||||||
|
tenantId,
|
||||||
|
userId,
|
||||||
|
after: {
|
||||||
|
importedAt: payload.importedAt,
|
||||||
|
count: incoming.length,
|
||||||
|
keys: incoming.map((row) => row.key),
|
||||||
|
} as Prisma.InputJsonValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tenant: this.tenantMeta(tenant),
|
||||||
|
importedAt: payload.importedAt,
|
||||||
|
source: payload.source,
|
||||||
|
apiBase: payload.apiBase,
|
||||||
|
items: this.omitRuntime(items),
|
||||||
|
count: this.omitRuntime(items).length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +172,10 @@ export class ProgramSettingsService {
|
|||||||
|
|
||||||
await this.prisma.tenant.update({
|
await this.prisma.tenant.update({
|
||||||
where: { id: tenantId },
|
where: { id: tenantId },
|
||||||
data: { setting: payload as unknown as Prisma.InputJsonValue },
|
data: {
|
||||||
|
setting: payload as unknown as Prisma.InputJsonValue,
|
||||||
|
appVersion: this.bumpAppVersion(tenant.appVersion),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.audit.log({
|
await this.audit.log({
|
||||||
@@ -155,12 +255,72 @@ export class ProgramSettingsService {
|
|||||||
const items = this.asSettingsArray(obj.items ?? obj);
|
const items = this.asSettingsArray(obj.items ?? obj);
|
||||||
return {
|
return {
|
||||||
importedAt: String(obj.importedAt || ''),
|
importedAt: String(obj.importedAt || ''),
|
||||||
source: 'khanegi_admin',
|
source: obj.source === 'superadmin' ? 'superadmin' : 'khanegi_admin',
|
||||||
apiBase: String(obj.apiBase || ''),
|
apiBase: String(obj.apiBase || ''),
|
||||||
items,
|
items,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async findKhanegiTenantByHost(hostRaw: string) {
|
||||||
|
const host = this.normalizeHost(hostRaw);
|
||||||
|
if (!host) {
|
||||||
|
throw new NotFoundException('domain required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenants = await this.prisma.tenant.findMany({
|
||||||
|
where: { isActive: true, product: ProductType.khanegi },
|
||||||
|
});
|
||||||
|
|
||||||
|
const hostNoWww = host.replace(/^www\./, '');
|
||||||
|
const primary = tenants.find((item) => {
|
||||||
|
const d = this.normalizeHost(item.domain);
|
||||||
|
return d === host || d.replace(/^www\./, '') === hostNoWww;
|
||||||
|
});
|
||||||
|
const tenant =
|
||||||
|
primary ||
|
||||||
|
tenants.find((item) => {
|
||||||
|
for (const value of this.readDomains(item.domains)) {
|
||||||
|
const normalized = this.normalizeHost(value);
|
||||||
|
if (!normalized) continue;
|
||||||
|
if (normalized === host || normalized.replace(/^www\./, '') === hostNoWww) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
|
throw new NotFoundException('tenant not found for domain');
|
||||||
|
}
|
||||||
|
return tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readDomains(raw: unknown): string[] {
|
||||||
|
if (!raw) return [];
|
||||||
|
if (Array.isArray(raw)) return raw.map(String);
|
||||||
|
if (typeof raw === 'string') {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return Array.isArray(parsed) ? parsed.map(String) : [];
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private bumpAppVersion(current?: string | null): string {
|
||||||
|
const v = String(current || '1').trim() || '1';
|
||||||
|
if (/^\d+$/.test(v)) {
|
||||||
|
return String(Number(v) + 1);
|
||||||
|
}
|
||||||
|
const semver = v.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
||||||
|
if (semver) {
|
||||||
|
return `${semver[1]}.${semver[2]}.${Number(semver[3]) + 1}`;
|
||||||
|
}
|
||||||
|
return `${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
private async loginKhanegiAdmin(
|
private async loginKhanegiAdmin(
|
||||||
tenant: {
|
tenant: {
|
||||||
nodeServer: string;
|
nodeServer: string;
|
||||||
@@ -251,7 +411,9 @@ export class ProgramSettingsService {
|
|||||||
headers,
|
headers,
|
||||||
timeout: 25000,
|
timeout: 25000,
|
||||||
});
|
});
|
||||||
const items = this.asSettingsArray(data).filter((row) => this.isSystemSetting(row));
|
const items = this.asSettingsArray(data)
|
||||||
|
.filter((row) => this.isSystemSetting(row))
|
||||||
|
.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')));
|
||||||
if (items.length) return items;
|
if (items.length) return items;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
|
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
|
||||||
@@ -264,7 +426,9 @@ export class ProgramSettingsService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(url, { headers, timeout: 25000 });
|
const { data } = await axios.get(url, { headers, timeout: 25000 });
|
||||||
return this.asSettingsArray(data).filter((row) => this.isSystemSetting(row));
|
return this.asSettingsArray(data)
|
||||||
|
.filter((row) => this.isSystemSetting(row))
|
||||||
|
.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new BadGatewayException(
|
throw new BadGatewayException(
|
||||||
this.axiosMessage(error, 'خواندن get_settings از اپ خانگی ناموفق بود'),
|
this.axiosMessage(error, 'خواندن get_settings از اپ خانگی ناموفق بود'),
|
||||||
@@ -313,6 +477,10 @@ export class ProgramSettingsService {
|
|||||||
return String(raw || '').trim();
|
return String(raw || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private omitRuntime(items: ProgramSettingRow[]): ProgramSettingRow[] {
|
||||||
|
return items.filter((row) => !isRuntimeProgramSettingKey(String(row.key || '')));
|
||||||
|
}
|
||||||
|
|
||||||
private asSettingsArray(data: unknown): ProgramSettingRow[] {
|
private asSettingsArray(data: unknown): ProgramSettingRow[] {
|
||||||
if (Array.isArray(data)) return data as ProgramSettingRow[];
|
if (Array.isArray(data)) return data as ProgramSettingRow[];
|
||||||
if (data && typeof data === 'object') {
|
if (data && typeof data === 'object') {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Controller, Get, Headers, Header, Query } from '@nestjs/common';
|
||||||
|
import { ProgramSettingsService } from './program-settings.service';
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
export class PublicProgramSettingsController {
|
||||||
|
constructor(private readonly programSettings: ProgramSettingsService) {}
|
||||||
|
|
||||||
|
@Get('get_settings')
|
||||||
|
@Header('Cache-Control', 'public, max-age=30')
|
||||||
|
getSettings(
|
||||||
|
@Headers('host') host?: string,
|
||||||
|
@Headers('x-forwarded-host') forwardedHost?: string,
|
||||||
|
@Query('domain') domain?: string,
|
||||||
|
) {
|
||||||
|
return this.programSettings.getPublicByHost(domain || forwardedHost || host, domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Controller, Get, Req, UseGuards } from '@nestjs/common';
|
||||||
|
import { RemoteSmsAuthGuard } from '../auth/remote-sms-auth.guard';
|
||||||
|
import { ProgramSettingsService } from './program-settings.service';
|
||||||
|
|
||||||
|
@Controller('remote/program-settings')
|
||||||
|
@UseGuards(RemoteSmsAuthGuard)
|
||||||
|
export class RemoteProgramSettingsController {
|
||||||
|
constructor(private readonly programSettings: ProgramSettingsService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
getFull(@Req() req: { remoteTenant?: { id: string } }) {
|
||||||
|
const tenantId = String(req.remoteTenant?.id || '');
|
||||||
|
return this.programSettings.getRemoteFull(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/** کلیدهای runtime / لاگ — مال سوپرادمین نیستند؛ در اپ خانگی توسط cron پر میشوند. */
|
||||||
|
export const RUNTIME_PROGRAM_SETTING_KEYS = new Set([
|
||||||
|
'montzar_last_product_log',
|
||||||
|
'montzar_order_response',
|
||||||
|
'montzar_live_price',
|
||||||
|
'montzar_mazaneh',
|
||||||
|
'montzar_diff',
|
||||||
|
'montzar_truly_buy_mazaneh',
|
||||||
|
'montzar_truly_sell_mazaneh',
|
||||||
|
'montzar_token',
|
||||||
|
'montzar_closed',
|
||||||
|
'up_price',
|
||||||
|
'down_price',
|
||||||
|
'price_extreme_date',
|
||||||
|
'do_send_liman',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function isRuntimeProgramSettingKey(keyRaw: string): boolean {
|
||||||
|
return RUNTIME_PROGRAM_SETTING_KEYS.has(String(keyRaw || '').trim());
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/** همتراز `Setting::isSecretOrServerOnlyKey` در سرور خانگی — برای پاسخ عمومی `/get_settings`. */
|
||||||
|
export function isSecretOrServerOnlySettingKey(keyRaw: string): boolean {
|
||||||
|
const key = String(keyRaw || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (!key) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
key.startsWith('external_payment_') ||
|
||||||
|
key === 'wallet_payment_provider' ||
|
||||||
|
key.startsWith('payping_')
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exact = new Set([
|
||||||
|
'node_client_id',
|
||||||
|
'node_shared_secret',
|
||||||
|
'montzar_password',
|
||||||
|
'montzar_token',
|
||||||
|
'montzar_mobile',
|
||||||
|
'bale_bot_token',
|
||||||
|
'telegram_bot_token',
|
||||||
|
'rubika_bot_token',
|
||||||
|
'tahesab_token',
|
||||||
|
'tahesab_host',
|
||||||
|
'tahesab_port',
|
||||||
|
'tahesab_db',
|
||||||
|
'pargar_password',
|
||||||
|
'pargar_api_token',
|
||||||
|
'pargar_username',
|
||||||
|
'zarin_api',
|
||||||
|
'zarin_token',
|
||||||
|
'chat_webhook_secret',
|
||||||
|
'chat_webhook_base_url',
|
||||||
|
'chat_api_key',
|
||||||
|
]);
|
||||||
|
if (exact.has(key)) return true;
|
||||||
|
|
||||||
|
return /(^|_)(secret|password|token|api_key|apikey|private)(_|$)/.test(key);
|
||||||
|
}
|
||||||
@@ -10,6 +10,6 @@ import { SmsService } from './sms.service';
|
|||||||
imports: [NotificationsModule, AuthModule],
|
imports: [NotificationsModule, AuthModule],
|
||||||
controllers: [SmsController, RemoteSmsController],
|
controllers: [SmsController, RemoteSmsController],
|
||||||
providers: [SmsService, RemoteSmsAuthGuard],
|
providers: [SmsService, RemoteSmsAuthGuard],
|
||||||
exports: [SmsService],
|
exports: [SmsService, RemoteSmsAuthGuard],
|
||||||
})
|
})
|
||||||
export class SmsModule {}
|
export class SmsModule {}
|
||||||
|
|||||||
Reference in New Issue
Block a user