added some feature for plugin
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { GetUserController } from './get-user.controller';
|
||||
import { GetUserService } from './get-user.service';
|
||||
|
||||
@Module({
|
||||
imports: [NotificationsModule],
|
||||
controllers: [GetUserController],
|
||||
providers: [GetUserService],
|
||||
exports: [GetUserService],
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { applyKhanegiNavSample } from './khanegi-nav-sample';
|
||||
import { rewriteLegacyUploadUrl } from '../uploads/uploads.util';
|
||||
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
|
||||
import { RuntimeSettingsService } from '../notifications/runtime-settings.service';
|
||||
import {
|
||||
findTenantForHost as matchTenantForHost,
|
||||
normalizeTenantHost,
|
||||
@@ -29,7 +30,10 @@ type SubscriptionAlerts = {
|
||||
|
||||
@Injectable()
|
||||
export class GetUserService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly runtimeSettings: RuntimeSettingsService,
|
||||
) {}
|
||||
|
||||
async resolveByHost(hostHeader?: string, domainQuery?: string) {
|
||||
const host = normalizeTenantHost(domainQuery || hostHeader || '');
|
||||
@@ -89,6 +93,8 @@ export class GetUserService {
|
||||
tenant.backgroundColor ||
|
||||
null
|
||||
|
||||
const khanegiDeposit = isKhanegi ? this.runtimeSettings.getKhanegiDeposit() : null;
|
||||
|
||||
const branding = {
|
||||
id: tenant.id,
|
||||
name: tenant.nameEn || tenant.slug,
|
||||
@@ -120,6 +126,7 @@ export class GetUserService {
|
||||
plugin_store_enabled: isKhanegi
|
||||
? await this.isPluginStoreEnabled(tenant.id)
|
||||
: false,
|
||||
...(khanegiDeposit || {}),
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
export class UpdateKhanegiDepositDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
sms_charge_card_number?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(64)
|
||||
sms_charge_sheba?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
sms_charge_account_holder?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(4000)
|
||||
sms_charge_note?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(4000)
|
||||
plugin_charge_note?: string;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { PermissionsGuard } from '../auth/permissions.guard';
|
||||
import { RequirePermissions } from '../auth/permissions.decorator';
|
||||
import { UpdateAdminSmsDto } from './dto/update-admin-sms.dto';
|
||||
import { UpdateKhanegiDepositDto } from './dto/update-khanegi-deposit.dto';
|
||||
import { RuntimeSettingsService } from './runtime-settings.service';
|
||||
|
||||
@Controller('settings')
|
||||
@@ -22,4 +23,16 @@ export class RuntimeSettingsController {
|
||||
this.settings.setAdminSmsEnabled(dto.enabled);
|
||||
return this.settings.getPublic();
|
||||
}
|
||||
|
||||
@Get('khanegi-deposit')
|
||||
@RequirePermissions('khanegi:read')
|
||||
getKhanegiDeposit() {
|
||||
return this.settings.getKhanegiDeposit();
|
||||
}
|
||||
|
||||
@Patch('khanegi-deposit')
|
||||
@RequirePermissions('khanegi:write')
|
||||
setKhanegiDeposit(@Body() dto: UpdateKhanegiDepositDto) {
|
||||
return this.settings.setKhanegiDeposit(dto);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,33 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export type KhanegiDeposit = {
|
||||
sms_charge_card_number: string;
|
||||
sms_charge_sheba: string;
|
||||
sms_charge_account_holder: string;
|
||||
sms_charge_note: string;
|
||||
plugin_charge_note: string;
|
||||
};
|
||||
|
||||
type RuntimeSettings = {
|
||||
/** When false, SuperAdmin stops SMS alerts to managers */
|
||||
adminSmsEnabled: boolean;
|
||||
khanegiDeposit: KhanegiDeposit;
|
||||
};
|
||||
|
||||
const DEPOSIT_DEFAULTS: KhanegiDeposit = {
|
||||
sms_charge_card_number: '6219861961033817',
|
||||
sms_charge_sheba: 'IR080560611828005603604201',
|
||||
sms_charge_account_holder: 'حسین منتظر',
|
||||
sms_charge_note:
|
||||
'لطفا مبلغ مورد نظر خود جهت شارژ سامانه پیامکی را به شماره کارت یا حساب زیر واریز نمایید و تصویر فیش پرداختی را برای پشتیبانی بفرستید.',
|
||||
plugin_charge_note:
|
||||
'لطفا مبلغ افزونه را به شماره کارت یا حساب زیر واریز نمایید و تصویر فیش را از تب درخواستها ارسال کنید.',
|
||||
};
|
||||
|
||||
const DEFAULTS: RuntimeSettings = {
|
||||
adminSmsEnabled: true,
|
||||
khanegiDeposit: { ...DEPOSIT_DEFAULTS },
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -28,6 +48,30 @@ export class RuntimeSettingsService {
|
||||
return next;
|
||||
}
|
||||
|
||||
getKhanegiDeposit(): KhanegiDeposit {
|
||||
return { ...this.read().khanegiDeposit };
|
||||
}
|
||||
|
||||
setKhanegiDeposit(patch: Partial<KhanegiDeposit>) {
|
||||
const current = this.read();
|
||||
const next: RuntimeSettings = {
|
||||
...current,
|
||||
khanegiDeposit: this.normalizeDeposit({ ...current.khanegiDeposit, ...patch }),
|
||||
};
|
||||
this.write(next);
|
||||
return next.khanegiDeposit;
|
||||
}
|
||||
|
||||
pluginStoreDeposit() {
|
||||
const d = this.getKhanegiDeposit();
|
||||
return {
|
||||
card_number: d.sms_charge_card_number,
|
||||
sheba: d.sms_charge_sheba,
|
||||
account_holder: d.sms_charge_account_holder,
|
||||
note: d.plugin_charge_note || d.sms_charge_note,
|
||||
};
|
||||
}
|
||||
|
||||
getPublic() {
|
||||
try {
|
||||
return { adminSmsEnabled: this.getAdminSmsEnabled() };
|
||||
@@ -37,21 +81,42 @@ export class RuntimeSettingsService {
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeDeposit(raw?: Partial<KhanegiDeposit> | null): KhanegiDeposit {
|
||||
const pick = (key: keyof KhanegiDeposit) => {
|
||||
const value = String(raw?.[key] ?? '').trim();
|
||||
return value || DEPOSIT_DEFAULTS[key];
|
||||
};
|
||||
return {
|
||||
sms_charge_card_number: pick('sms_charge_card_number'),
|
||||
sms_charge_sheba: pick('sms_charge_sheba'),
|
||||
sms_charge_account_holder: pick('sms_charge_account_holder'),
|
||||
sms_charge_note: String(raw?.sms_charge_note ?? DEPOSIT_DEFAULTS.sms_charge_note),
|
||||
plugin_charge_note: String(raw?.plugin_charge_note ?? DEPOSIT_DEFAULTS.plugin_charge_note),
|
||||
};
|
||||
}
|
||||
|
||||
private read(): RuntimeSettings {
|
||||
if (this.cache) return this.cache;
|
||||
try {
|
||||
if (!existsSync(this.filePath)) {
|
||||
this.cache = { ...DEFAULTS };
|
||||
this.cache = {
|
||||
adminSmsEnabled: DEFAULTS.adminSmsEnabled,
|
||||
khanegiDeposit: { ...DEPOSIT_DEFAULTS },
|
||||
};
|
||||
return this.cache;
|
||||
}
|
||||
const raw = JSON.parse(readFileSync(this.filePath, 'utf8')) as Partial<RuntimeSettings>;
|
||||
this.cache = {
|
||||
adminSmsEnabled: raw.adminSmsEnabled !== false,
|
||||
khanegiDeposit: this.normalizeDeposit(raw.khanegiDeposit),
|
||||
};
|
||||
return this.cache;
|
||||
} catch (error: any) {
|
||||
this.logger.warn(`Failed to read runtime settings: ${error?.message || error}`);
|
||||
this.cache = { ...DEFAULTS };
|
||||
this.cache = {
|
||||
adminSmsEnabled: DEFAULTS.adminSmsEnabled,
|
||||
khanegiDeposit: { ...DEPOSIT_DEFAULTS },
|
||||
};
|
||||
return this.cache;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
||||
import { CentralSmsService } from '../notifications/central-sms.service';
|
||||
import { RuntimeSettingsService } from '../notifications/runtime-settings.service';
|
||||
import { ProgramSettingsService } from '../program-settings/program-settings.service';
|
||||
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
|
||||
import { DEPLOY_SETTING_CATALOG_KEYS } from '../program-settings/deploy-setting-catalog-keys';
|
||||
@@ -59,6 +60,7 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly sms: CentralSmsService,
|
||||
private readonly programSettings: ProgramSettingsService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly runtime: RuntimeSettingsService,
|
||||
) {}
|
||||
|
||||
onModuleInit() {
|
||||
@@ -558,11 +560,14 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
|
||||
async remoteListRequests(tenantId: string, product: string) {
|
||||
await this.assertRemoteKhanegi(tenantId, product);
|
||||
try {
|
||||
return await this.buildRemoteRequestRows(tenantId);
|
||||
return {
|
||||
requests: await this.buildRemoteRequestRows(tenantId),
|
||||
deposit: this.runtime.pluginStoreDeposit(),
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error instanceof ForbiddenException) throw error;
|
||||
this.logger.warn(`remoteListRequests skipped: ${error?.message || error}`);
|
||||
return [];
|
||||
return { requests: [], deposit: this.runtime.pluginStoreDeposit() };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-2
@@ -8,6 +8,7 @@ import { Prisma, SmsAccount, SmsLedgerType } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
||||
import { RuntimeSettingsService } from '../notifications/runtime-settings.service';
|
||||
import { iranYmd } from '../common/iran-time.util';
|
||||
import {
|
||||
ChargeSmsWalletDto,
|
||||
@@ -27,6 +28,7 @@ export class SmsService {
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly audit: AuditService,
|
||||
private readonly notify: AdminNotifyService,
|
||||
private readonly runtime: RuntimeSettingsService,
|
||||
) {}
|
||||
|
||||
async listTenants() {
|
||||
@@ -374,7 +376,7 @@ export class SmsService {
|
||||
async remoteOverview(tenantId: string) {
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const usage = await this.computeUsageStats(account.id);
|
||||
const charge = this.chargeInfoOf(account);
|
||||
const charge = await this.chargeInfoForRemote(tenantId, account);
|
||||
|
||||
return {
|
||||
wallet: {
|
||||
@@ -417,7 +419,8 @@ export class SmsService {
|
||||
}
|
||||
|
||||
async remoteChargeInfo(tenantId: string) {
|
||||
const charge = await this.getChargeInfo(tenantId);
|
||||
const account = await this.ensureAccount(tenantId);
|
||||
const charge = await this.chargeInfoForRemote(tenantId, account);
|
||||
return {
|
||||
card: charge.card,
|
||||
iban: charge.iban,
|
||||
@@ -894,6 +897,23 @@ export class SmsService {
|
||||
);
|
||||
}
|
||||
|
||||
private async chargeInfoForRemote(tenantId: string, account: SmsAccount) {
|
||||
const tenant = await this.prisma.tenant.findUnique({
|
||||
where: { id: tenantId },
|
||||
select: { product: true },
|
||||
});
|
||||
if (tenant?.product === 'khanegi') {
|
||||
const deposit = this.runtime.getKhanegiDeposit();
|
||||
return {
|
||||
card: deposit.sms_charge_card_number,
|
||||
iban: deposit.sms_charge_sheba,
|
||||
accountHolder: deposit.sms_charge_account_holder,
|
||||
instruction: deposit.sms_charge_note,
|
||||
};
|
||||
}
|
||||
return this.chargeInfoOf(account);
|
||||
}
|
||||
|
||||
private chargeInfoOf(account: SmsAccount) {
|
||||
return {
|
||||
card: account.chargeCard || '',
|
||||
|
||||
Reference in New Issue
Block a user