Initial SuperAdmin API (NestJS + Prisma)

This commit is contained in:
SuperAdmin
2026-07-23 10:25:32 +03:30
commit 7fbc47766c
43 changed files with 12902 additions and 0 deletions
@@ -0,0 +1,24 @@
import { Body, Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { CurrentUser } from '../auth/current-user.decorator';
import { SettingsProxyService } from './settings-proxy.service';
@Controller('tenants/:tenantId/settings')
@UseGuards(JwtAuthGuard)
export class SettingsProxyController {
constructor(private readonly settingsProxy: SettingsProxyService) {}
@Get()
getSettings(@Param('tenantId') tenantId: string) {
return this.settingsProxy.getSettings(tenantId);
}
@Post()
updateSettings(
@Param('tenantId') tenantId: string,
@Body() body: Record<string, unknown>,
@CurrentUser() user: { id: string },
) {
return this.settingsProxy.updateSettings(tenantId, body, user.id);
}
}
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { SettingsProxyController } from './settings-proxy.controller';
import { SettingsProxyService } from './settings-proxy.service';
@Module({
controllers: [SettingsProxyController],
providers: [SettingsProxyService],
})
export class SettingsProxyModule {}
@@ -0,0 +1,135 @@
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 {
const { data } = await axios.get(
this.joinUrl(tenant.nodeServer, '/super/settings'),
{
headers: {
Authorization: `Bearer ${tenant.remoteApiToken}`,
Accept: 'application/json',
},
timeout: 15000,
},
);
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(
this.joinUrl(tenant.nodeServer, '/super/settings'),
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}`;
}
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;
}
}