added some feature for plugin
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { NotificationsModule } from '../notifications/notifications.module';
|
||||||
import { GetUserController } from './get-user.controller';
|
import { GetUserController } from './get-user.controller';
|
||||||
import { GetUserService } from './get-user.service';
|
import { GetUserService } from './get-user.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [NotificationsModule],
|
||||||
controllers: [GetUserController],
|
controllers: [GetUserController],
|
||||||
providers: [GetUserService],
|
providers: [GetUserService],
|
||||||
exports: [GetUserService],
|
exports: [GetUserService],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
import { applyKhanegiNavSample } from './khanegi-nav-sample';
|
import { applyKhanegiNavSample } from './khanegi-nav-sample';
|
||||||
import { rewriteLegacyUploadUrl } from '../uploads/uploads.util';
|
import { rewriteLegacyUploadUrl } from '../uploads/uploads.util';
|
||||||
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
|
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
|
||||||
|
import { RuntimeSettingsService } from '../notifications/runtime-settings.service';
|
||||||
import {
|
import {
|
||||||
findTenantForHost as matchTenantForHost,
|
findTenantForHost as matchTenantForHost,
|
||||||
normalizeTenantHost,
|
normalizeTenantHost,
|
||||||
@@ -29,7 +30,10 @@ type SubscriptionAlerts = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GetUserService {
|
export class GetUserService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly runtimeSettings: RuntimeSettingsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async resolveByHost(hostHeader?: string, domainQuery?: string) {
|
async resolveByHost(hostHeader?: string, domainQuery?: string) {
|
||||||
const host = normalizeTenantHost(domainQuery || hostHeader || '');
|
const host = normalizeTenantHost(domainQuery || hostHeader || '');
|
||||||
@@ -89,6 +93,8 @@ export class GetUserService {
|
|||||||
tenant.backgroundColor ||
|
tenant.backgroundColor ||
|
||||||
null
|
null
|
||||||
|
|
||||||
|
const khanegiDeposit = isKhanegi ? this.runtimeSettings.getKhanegiDeposit() : null;
|
||||||
|
|
||||||
const branding = {
|
const branding = {
|
||||||
id: tenant.id,
|
id: tenant.id,
|
||||||
name: tenant.nameEn || tenant.slug,
|
name: tenant.nameEn || tenant.slug,
|
||||||
@@ -120,6 +126,7 @@ export class GetUserService {
|
|||||||
plugin_store_enabled: isKhanegi
|
plugin_store_enabled: isKhanegi
|
||||||
? await this.isPluginStoreEnabled(tenant.id)
|
? await this.isPluginStoreEnabled(tenant.id)
|
||||||
: false,
|
: false,
|
||||||
|
...(khanegiDeposit || {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
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 { PermissionsGuard } from '../auth/permissions.guard';
|
||||||
import { RequirePermissions } from '../auth/permissions.decorator';
|
import { RequirePermissions } from '../auth/permissions.decorator';
|
||||||
import { UpdateAdminSmsDto } from './dto/update-admin-sms.dto';
|
import { UpdateAdminSmsDto } from './dto/update-admin-sms.dto';
|
||||||
|
import { UpdateKhanegiDepositDto } from './dto/update-khanegi-deposit.dto';
|
||||||
import { RuntimeSettingsService } from './runtime-settings.service';
|
import { RuntimeSettingsService } from './runtime-settings.service';
|
||||||
|
|
||||||
@Controller('settings')
|
@Controller('settings')
|
||||||
@@ -22,4 +23,16 @@ export class RuntimeSettingsController {
|
|||||||
this.settings.setAdminSmsEnabled(dto.enabled);
|
this.settings.setAdminSmsEnabled(dto.enabled);
|
||||||
return this.settings.getPublic();
|
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
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 = {
|
type RuntimeSettings = {
|
||||||
/** When false, SuperAdmin stops SMS alerts to managers */
|
/** When false, SuperAdmin stops SMS alerts to managers */
|
||||||
adminSmsEnabled: boolean;
|
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 = {
|
const DEFAULTS: RuntimeSettings = {
|
||||||
adminSmsEnabled: true,
|
adminSmsEnabled: true,
|
||||||
|
khanegiDeposit: { ...DEPOSIT_DEFAULTS },
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -28,6 +48,30 @@ export class RuntimeSettingsService {
|
|||||||
return next;
|
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() {
|
getPublic() {
|
||||||
try {
|
try {
|
||||||
return { adminSmsEnabled: this.getAdminSmsEnabled() };
|
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 {
|
private read(): RuntimeSettings {
|
||||||
if (this.cache) return this.cache;
|
if (this.cache) return this.cache;
|
||||||
try {
|
try {
|
||||||
if (!existsSync(this.filePath)) {
|
if (!existsSync(this.filePath)) {
|
||||||
this.cache = { ...DEFAULTS };
|
this.cache = {
|
||||||
|
adminSmsEnabled: DEFAULTS.adminSmsEnabled,
|
||||||
|
khanegiDeposit: { ...DEPOSIT_DEFAULTS },
|
||||||
|
};
|
||||||
return this.cache;
|
return this.cache;
|
||||||
}
|
}
|
||||||
const raw = JSON.parse(readFileSync(this.filePath, 'utf8')) as Partial<RuntimeSettings>;
|
const raw = JSON.parse(readFileSync(this.filePath, 'utf8')) as Partial<RuntimeSettings>;
|
||||||
this.cache = {
|
this.cache = {
|
||||||
adminSmsEnabled: raw.adminSmsEnabled !== false,
|
adminSmsEnabled: raw.adminSmsEnabled !== false,
|
||||||
|
khanegiDeposit: this.normalizeDeposit(raw.khanegiDeposit),
|
||||||
};
|
};
|
||||||
return this.cache;
|
return this.cache;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.warn(`Failed to read runtime settings: ${error?.message || error}`);
|
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;
|
return this.cache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
|||||||
import { AuditService } from '../audit/audit.service';
|
import { AuditService } from '../audit/audit.service';
|
||||||
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
||||||
import { CentralSmsService } from '../notifications/central-sms.service';
|
import { CentralSmsService } from '../notifications/central-sms.service';
|
||||||
|
import { RuntimeSettingsService } from '../notifications/runtime-settings.service';
|
||||||
import { ProgramSettingsService } from '../program-settings/program-settings.service';
|
import { ProgramSettingsService } from '../program-settings/program-settings.service';
|
||||||
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
|
import { PLUGIN_STORE_SETTING_KEY } from '../program-settings/runtime-setting-keys';
|
||||||
import { DEPLOY_SETTING_CATALOG_KEYS } from '../program-settings/deploy-setting-catalog-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 sms: CentralSmsService,
|
||||||
private readonly programSettings: ProgramSettingsService,
|
private readonly programSettings: ProgramSettingsService,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
|
private readonly runtime: RuntimeSettingsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
@@ -558,11 +560,14 @@ export class PluginStoreService implements OnModuleInit, OnModuleDestroy {
|
|||||||
async remoteListRequests(tenantId: string, product: string) {
|
async remoteListRequests(tenantId: string, product: string) {
|
||||||
await this.assertRemoteKhanegi(tenantId, product);
|
await this.assertRemoteKhanegi(tenantId, product);
|
||||||
try {
|
try {
|
||||||
return await this.buildRemoteRequestRows(tenantId);
|
return {
|
||||||
|
requests: await this.buildRemoteRequestRows(tenantId),
|
||||||
|
deposit: this.runtime.pluginStoreDeposit(),
|
||||||
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error instanceof ForbiddenException) throw error;
|
if (error instanceof ForbiddenException) throw error;
|
||||||
this.logger.warn(`remoteListRequests skipped: ${error?.message || 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 { PrismaService } from '../prisma/prisma.service';
|
||||||
import { AuditService } from '../audit/audit.service';
|
import { AuditService } from '../audit/audit.service';
|
||||||
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
import { AdminNotifyService } from '../notifications/admin-notify.service';
|
||||||
|
import { RuntimeSettingsService } from '../notifications/runtime-settings.service';
|
||||||
import { iranYmd } from '../common/iran-time.util';
|
import { iranYmd } from '../common/iran-time.util';
|
||||||
import {
|
import {
|
||||||
ChargeSmsWalletDto,
|
ChargeSmsWalletDto,
|
||||||
@@ -27,6 +28,7 @@ export class SmsService {
|
|||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly audit: AuditService,
|
private readonly audit: AuditService,
|
||||||
private readonly notify: AdminNotifyService,
|
private readonly notify: AdminNotifyService,
|
||||||
|
private readonly runtime: RuntimeSettingsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async listTenants() {
|
async listTenants() {
|
||||||
@@ -374,7 +376,7 @@ export class SmsService {
|
|||||||
async remoteOverview(tenantId: string) {
|
async remoteOverview(tenantId: string) {
|
||||||
const account = await this.ensureAccount(tenantId);
|
const account = await this.ensureAccount(tenantId);
|
||||||
const usage = await this.computeUsageStats(account.id);
|
const usage = await this.computeUsageStats(account.id);
|
||||||
const charge = this.chargeInfoOf(account);
|
const charge = await this.chargeInfoForRemote(tenantId, account);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wallet: {
|
wallet: {
|
||||||
@@ -417,7 +419,8 @@ export class SmsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async remoteChargeInfo(tenantId: string) {
|
async remoteChargeInfo(tenantId: string) {
|
||||||
const charge = await this.getChargeInfo(tenantId);
|
const account = await this.ensureAccount(tenantId);
|
||||||
|
const charge = await this.chargeInfoForRemote(tenantId, account);
|
||||||
return {
|
return {
|
||||||
card: charge.card,
|
card: charge.card,
|
||||||
iban: charge.iban,
|
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) {
|
private chargeInfoOf(account: SmsAccount) {
|
||||||
return {
|
return {
|
||||||
card: account.chargeCard || '',
|
card: account.chargeCard || '',
|
||||||
|
|||||||
Reference in New Issue
Block a user