2026-07-23 10:25:32 +03:30
|
|
|
import {
|
|
|
|
|
BadGatewayException,
|
|
|
|
|
Injectable,
|
|
|
|
|
NotFoundException,
|
|
|
|
|
} from '@nestjs/common';
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
import { Prisma } from '@prisma/client';
|
|
|
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
|
import { AuditService } from '../audit/audit.service';
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class SettingsProxyService {
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly prisma: PrismaService,
|
|
|
|
|
private readonly audit: AuditService,
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
async getSettings(tenantId: string) {
|
|
|
|
|
const tenant = await this.requireTenant(tenantId);
|
|
|
|
|
|
|
|
|
|
if (!tenant.remoteApiToken) {
|
|
|
|
|
return {
|
|
|
|
|
stub: true,
|
|
|
|
|
message:
|
|
|
|
|
'remoteApiToken تنظیم نشده. فعلاً stub است تا endpoint سوپر روی Laravel اضافه شود.',
|
|
|
|
|
tenant: {
|
|
|
|
|
id: tenant.id,
|
|
|
|
|
slug: tenant.slug,
|
|
|
|
|
nodeServer: tenant.nodeServer,
|
|
|
|
|
},
|
|
|
|
|
settings: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
2026-08-11 16:05:04 +03:30
|
|
|
const { data } = await axios.get(this.resolveSettingsUrl(tenant.nodeServer), {
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${tenant.remoteApiToken}`,
|
|
|
|
|
Accept: 'application/json',
|
2026-07-23 10:25:32 +03:30
|
|
|
},
|
2026-08-11 16:05:04 +03:30
|
|
|
timeout: 15000,
|
|
|
|
|
});
|
2026-07-23 10:25:32 +03:30
|
|
|
return { stub: false, settings: data };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new BadGatewayException(
|
|
|
|
|
this.axiosMessage(error, 'خواندن تنظیمات از سرور نسخه ناموفق بود'),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async updateSettings(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
payload: Record<string, unknown>,
|
|
|
|
|
userId?: string,
|
|
|
|
|
) {
|
|
|
|
|
const tenant = await this.requireTenant(tenantId);
|
|
|
|
|
|
|
|
|
|
if (!tenant.remoteApiToken) {
|
|
|
|
|
await this.audit.log({
|
|
|
|
|
action: 'settings.proxy.stub_update',
|
|
|
|
|
entityType: 'tenant_settings',
|
|
|
|
|
entityId: tenantId,
|
|
|
|
|
tenantId,
|
|
|
|
|
userId,
|
|
|
|
|
after: payload as Prisma.InputJsonValue,
|
|
|
|
|
meta: { stub: true },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
stub: true,
|
|
|
|
|
message:
|
|
|
|
|
'remoteApiToken تنظیم نشده؛ تغییر فقط در audit ثبت شد (stub).',
|
|
|
|
|
accepted: payload,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const { data } = await axios.post(
|
2026-08-11 16:05:04 +03:30
|
|
|
this.resolveSettingsUrl(tenant.nodeServer),
|
2026-07-23 10:25:32 +03:30
|
|
|
payload,
|
|
|
|
|
{
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${tenant.remoteApiToken}`,
|
|
|
|
|
Accept: 'application/json',
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
timeout: 20000,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await this.audit.log({
|
|
|
|
|
action: 'settings.proxy.update',
|
|
|
|
|
entityType: 'tenant_settings',
|
|
|
|
|
entityId: tenantId,
|
|
|
|
|
tenantId,
|
|
|
|
|
userId,
|
|
|
|
|
after: payload as Prisma.InputJsonValue,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { stub: false, result: data };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw new BadGatewayException(
|
|
|
|
|
this.axiosMessage(error, 'ذخیره تنظیمات روی سرور نسخه ناموفق بود'),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async requireTenant(tenantId: string) {
|
|
|
|
|
const tenant = await this.prisma.tenant.findUnique({
|
|
|
|
|
where: { id: tenantId },
|
|
|
|
|
});
|
|
|
|
|
if (!tenant) {
|
|
|
|
|
throw new NotFoundException('نسخه یافت نشد');
|
|
|
|
|
}
|
|
|
|
|
return tenant;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private joinUrl(base: string, path: string) {
|
|
|
|
|
return `${base.replace(/\/$/, '')}${path}`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 16:05:04 +03:30
|
|
|
/**
|
|
|
|
|
* nodeServer may be `https://host` or `https://host/api`.
|
|
|
|
|
* Laravel exposes both `/super/settings` (web) and `/api/super/settings`.
|
|
|
|
|
*/
|
|
|
|
|
private resolveSettingsUrl(nodeServer: string): string {
|
|
|
|
|
const base = String(nodeServer || '').replace(/\/$/, '');
|
|
|
|
|
if (/\/api$/i.test(base)) {
|
|
|
|
|
return this.joinUrl(base, '/super/settings');
|
|
|
|
|
}
|
|
|
|
|
return this.joinUrl(base, '/api/super/settings');
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-23 10:25:32 +03:30
|
|
|
private axiosMessage(error: unknown, fallback: string) {
|
|
|
|
|
if (axios.isAxiosError(error)) {
|
|
|
|
|
const status = error.response?.status;
|
|
|
|
|
const body = error.response?.data;
|
|
|
|
|
return `${fallback}${status ? ` (HTTP ${status})` : ''}${
|
|
|
|
|
body ? `: ${JSON.stringify(body)}` : ''
|
|
|
|
|
}`;
|
|
|
|
|
}
|
|
|
|
|
return fallback;
|
|
|
|
|
}
|
|
|
|
|
}
|