forked from Yara724/api
Toggle External API
This commit is contained in:
46
src/system-settings/dto/system-settings.dto.ts
Normal file
46
src/system-settings/dto/system-settings.dto.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsOptional, ValidateNested } from "class-validator";
|
||||
import { Type } from "class-transformer";
|
||||
|
||||
export class ExternalApisSettingsDto {
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"When true, SandHub/Tejarat inquiry HTTP APIs are called. When false, mocks are used and validations pass.",
|
||||
example: false,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
sandHubUseLiveApi?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateSystemSettingsDto {
|
||||
@ApiPropertyOptional({ type: ExternalApisSettingsDto })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => ExternalApisSettingsDto)
|
||||
externalApis?: ExternalApisSettingsDto;
|
||||
}
|
||||
|
||||
export class ExternalApisSettingsViewDto {
|
||||
@ApiProperty({
|
||||
description:
|
||||
"Stored in `system_settings.externalApis.sandHubUseLiveApi` — controls SandHubService",
|
||||
example: false,
|
||||
})
|
||||
sandHubUseLiveApi: boolean;
|
||||
}
|
||||
|
||||
export class SystemSettingsResponseDto {
|
||||
@ApiProperty({ example: "global" })
|
||||
key: string;
|
||||
|
||||
@ApiProperty({ type: ExternalApisSettingsViewDto })
|
||||
externalApis: ExternalApisSettingsViewDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Human-readable mode for operators",
|
||||
example: "mock",
|
||||
enum: ["live", "mock"],
|
||||
})
|
||||
sandHubMode: "live" | "mock";
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectModel } from "@nestjs/mongoose";
|
||||
import { Model, UpdateQuery } from "mongoose";
|
||||
import {
|
||||
SYSTEM_SETTINGS_GLOBAL_KEY,
|
||||
SystemSettingsModel,
|
||||
} from "../schema/system-settings.schema";
|
||||
|
||||
@Injectable()
|
||||
export class SystemSettingsDbService {
|
||||
constructor(
|
||||
@InjectModel(SystemSettingsModel.name)
|
||||
private readonly model: Model<SystemSettingsModel>,
|
||||
) {}
|
||||
|
||||
findGlobal() {
|
||||
return this.model
|
||||
.findOne({ key: SYSTEM_SETTINGS_GLOBAL_KEY })
|
||||
.lean()
|
||||
.exec();
|
||||
}
|
||||
|
||||
async upsertGlobal(update: UpdateQuery<SystemSettingsModel>) {
|
||||
return this.model
|
||||
.findOneAndUpdate(
|
||||
{ key: SYSTEM_SETTINGS_GLOBAL_KEY },
|
||||
{
|
||||
$setOnInsert: { key: SYSTEM_SETTINGS_GLOBAL_KEY },
|
||||
...update,
|
||||
},
|
||||
{ new: true, upsert: true, lean: true },
|
||||
)
|
||||
.exec();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
||||
|
||||
/** Singleton document key — only `global` is used today. */
|
||||
export const SYSTEM_SETTINGS_GLOBAL_KEY = "global";
|
||||
|
||||
/**
|
||||
* Toggle outbound integrations (SandHub / Tejarat inquiry gateways, etc.).
|
||||
* When `sandHubUseLiveApi` is false, {@link SandHubService} skips HTTP and
|
||||
* returns permissive mocks (including default plate/insurance inquiry data).
|
||||
*/
|
||||
export class ExternalApisSettings {
|
||||
/**
|
||||
* `true` → live SandHub/Tejarat HTTP calls.
|
||||
* `false` → no external requests; mocks pass validation checks.
|
||||
*/
|
||||
@Prop({ type: Boolean, required: false, default: false })
|
||||
sandHubUseLiveApi?: boolean;
|
||||
}
|
||||
|
||||
@Schema({ collection: "system_settings", versionKey: false })
|
||||
export class SystemSettingsModel {
|
||||
@Prop({ required: true, unique: true, default: SYSTEM_SETTINGS_GLOBAL_KEY })
|
||||
key: string;
|
||||
|
||||
@Prop({ type: ExternalApisSettings, required: false, default: {} })
|
||||
externalApis?: ExternalApisSettings;
|
||||
}
|
||||
|
||||
export const SystemSettingsSchema =
|
||||
SchemaFactory.createForClass(SystemSettingsModel);
|
||||
51
src/system-settings/system-settings.controller.ts
Normal file
51
src/system-settings/system-settings.controller.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Body, Controller, Get, Patch, UseGuards } from "@nestjs/common";
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
import { SettingsJwtGuard } from "src/auth/guards/settings-jwt.guard";
|
||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import {
|
||||
SystemSettingsResponseDto,
|
||||
UpdateSystemSettingsDto,
|
||||
} from "./dto/system-settings.dto";
|
||||
import { SystemSettingsService } from "./system-settings.service";
|
||||
|
||||
@ApiTags("system-settings")
|
||||
@ApiBearerAuth()
|
||||
@Controller("system-settings")
|
||||
export class SystemSettingsController {
|
||||
constructor(private readonly systemSettingsService: SystemSettingsService) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(SettingsJwtGuard, RolesGuard)
|
||||
@Roles(RoleEnum.ADMIN, RoleEnum.COMPANY)
|
||||
@ApiOperation({
|
||||
summary: "Get global system settings (external API toggles)",
|
||||
description:
|
||||
"Shows whether SandHub/Tejarat live HTTP is enabled. When disabled, the API uses mocks so flows continue without external connectivity.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: SystemSettingsResponseDto })
|
||||
getSettings(): Promise<SystemSettingsResponseDto> {
|
||||
return this.systemSettingsService.getSettingsView();
|
||||
}
|
||||
|
||||
@Patch()
|
||||
@UseGuards(SettingsJwtGuard, RolesGuard)
|
||||
@Roles(RoleEnum.ADMIN)
|
||||
@ApiOperation({
|
||||
summary: "Update global system settings",
|
||||
description:
|
||||
"Set `externalApis.sandHubUseLiveApi` to true for live inquiries, false for mock/offline mode.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: SystemSettingsResponseDto })
|
||||
updateSettings(
|
||||
@Body() body: UpdateSystemSettingsDto,
|
||||
): Promise<SystemSettingsResponseDto> {
|
||||
return this.systemSettingsService.updateSettings(body);
|
||||
}
|
||||
}
|
||||
21
src/system-settings/system-settings.module.ts
Normal file
21
src/system-settings/system-settings.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { SystemSettingsDbService } from "./entities/db-service/system-settings.db.service";
|
||||
import {
|
||||
SystemSettingsModel,
|
||||
SystemSettingsSchema,
|
||||
} from "./entities/schema/system-settings.schema";
|
||||
import { SystemSettingsController } from "./system-settings.controller";
|
||||
import { SystemSettingsService } from "./system-settings.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MongooseModule.forFeature([
|
||||
{ name: SystemSettingsModel.name, schema: SystemSettingsSchema },
|
||||
]),
|
||||
],
|
||||
controllers: [SystemSettingsController],
|
||||
providers: [SystemSettingsService, SystemSettingsDbService],
|
||||
exports: [SystemSettingsService],
|
||||
})
|
||||
export class SystemSettingsModule {}
|
||||
31
src/system-settings/system-settings.service.spec.ts
Normal file
31
src/system-settings/system-settings.service.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { SystemSettingsService } from "./system-settings.service";
|
||||
|
||||
describe("SystemSettingsService", () => {
|
||||
const db = {
|
||||
findGlobal: jest.fn(),
|
||||
upsertGlobal: jest.fn(),
|
||||
};
|
||||
|
||||
let service: SystemSettingsService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
service = new SystemSettingsService(db as any);
|
||||
});
|
||||
|
||||
it("defaults to mock when DB field is false", async () => {
|
||||
db.findGlobal.mockResolvedValue({
|
||||
key: "global",
|
||||
externalApis: { sandHubUseLiveApi: false },
|
||||
});
|
||||
await expect(service.isSandHubLiveEnabled()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("uses live mode when DB field is true", async () => {
|
||||
db.findGlobal.mockResolvedValue({
|
||||
key: "global",
|
||||
externalApis: { sandHubUseLiveApi: true },
|
||||
});
|
||||
await expect(service.isSandHubLiveEnabled()).resolves.toBe(true);
|
||||
});
|
||||
});
|
||||
79
src/system-settings/system-settings.service.ts
Normal file
79
src/system-settings/system-settings.service.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import {
|
||||
SystemSettingsResponseDto,
|
||||
UpdateSystemSettingsDto,
|
||||
} from "./dto/system-settings.dto";
|
||||
import { SystemSettingsDbService } from "./entities/db-service/system-settings.db.service";
|
||||
|
||||
@Injectable()
|
||||
export class SystemSettingsService {
|
||||
private readonly logger = new Logger(SystemSettingsService.name);
|
||||
private cache: Record<string, unknown> | null = null;
|
||||
private cacheAt = 0;
|
||||
private readonly cacheTtlMs = 15_000;
|
||||
|
||||
constructor(private readonly db: SystemSettingsDbService) {}
|
||||
|
||||
private async loadGlobal(): Promise<Record<string, unknown>> {
|
||||
const now = Date.now();
|
||||
if (this.cache && now - this.cacheAt < this.cacheTtlMs) {
|
||||
return this.cache;
|
||||
}
|
||||
let doc = await this.db.findGlobal();
|
||||
if (!doc) {
|
||||
doc = await this.db.upsertGlobal({
|
||||
$set: { externalApis: { sandHubUseLiveApi: false } },
|
||||
});
|
||||
this.logger.log(
|
||||
"Created default system_settings document (sandHubUseLiveApi=false)",
|
||||
);
|
||||
}
|
||||
this.cache = doc;
|
||||
this.cacheAt = now;
|
||||
return doc;
|
||||
}
|
||||
|
||||
private invalidateCache(): void {
|
||||
this.cache = null;
|
||||
this.cacheAt = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether SandHub/Tejarat live HTTP should run (`system_settings` collection).
|
||||
*/
|
||||
async isSandHubLiveEnabled(): Promise<boolean> {
|
||||
const doc = await this.loadGlobal();
|
||||
const externalApis = doc.externalApis as
|
||||
| { sandHubUseLiveApi?: boolean }
|
||||
| undefined;
|
||||
return externalApis?.sandHubUseLiveApi === true;
|
||||
}
|
||||
|
||||
async getSettingsView(): Promise<SystemSettingsResponseDto> {
|
||||
const doc = await this.loadGlobal();
|
||||
const externalApis = doc.externalApis as
|
||||
| { sandHubUseLiveApi?: boolean }
|
||||
| undefined;
|
||||
const sandHubUseLiveApi = externalApis?.sandHubUseLiveApi === true;
|
||||
return {
|
||||
key: String(doc.key ?? "global"),
|
||||
externalApis: { sandHubUseLiveApi },
|
||||
sandHubMode: sandHubUseLiveApi ? "live" : "mock",
|
||||
};
|
||||
}
|
||||
|
||||
async updateSettings(
|
||||
body: UpdateSystemSettingsDto,
|
||||
): Promise<SystemSettingsResponseDto> {
|
||||
const $set: Record<string, unknown> = {};
|
||||
if (body.externalApis?.sandHubUseLiveApi !== undefined) {
|
||||
$set["externalApis.sandHubUseLiveApi"] = body.externalApis.sandHubUseLiveApi;
|
||||
}
|
||||
if (Object.keys($set).length > 0) {
|
||||
await this.db.upsertGlobal({ $set });
|
||||
this.invalidateCache();
|
||||
this.logger.log(`System settings updated: ${JSON.stringify($set)}`);
|
||||
}
|
||||
return this.getSettingsView();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user