forked from Yara724/api
added offline inquiry in system setting also fix some bugs
This commit is contained in:
@@ -13,12 +13,38 @@ export class ExternalApisSettingsDto {
|
||||
sandHubUseLiveApi?: boolean;
|
||||
}
|
||||
|
||||
export class OfflineInquirySettingsDto {
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"Master switch for seeded offline plate inquiries. When false, `offlineInquiries` hits are skipped.",
|
||||
example: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export class UpdateSystemSettingsDto {
|
||||
@ApiPropertyOptional({ type: ExternalApisSettingsDto })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => ExternalApisSettingsDto)
|
||||
externalApis?: ExternalApisSettingsDto;
|
||||
|
||||
@ApiPropertyOptional({ type: OfflineInquirySettingsDto })
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => OfflineInquirySettingsDto)
|
||||
offlineInquiry?: OfflineInquirySettingsDto;
|
||||
}
|
||||
|
||||
export class UpdateOfflineInquirySettingsDto {
|
||||
@ApiProperty({
|
||||
description: "Enable or disable offline seeded plate inquiries globally",
|
||||
example: true,
|
||||
})
|
||||
@IsBoolean()
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export class ExternalApisSettingsViewDto {
|
||||
@@ -30,6 +56,15 @@ export class ExternalApisSettingsViewDto {
|
||||
sandHubUseLiveApi: boolean;
|
||||
}
|
||||
|
||||
export class OfflineInquirySettingsViewDto {
|
||||
@ApiProperty({
|
||||
description:
|
||||
"Stored in `system_settings.offlineInquiry.enabled` — when false, offline seeds are ignored",
|
||||
example: true,
|
||||
})
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export class SystemSettingsResponseDto {
|
||||
@ApiProperty({ example: "global" })
|
||||
key: string;
|
||||
@@ -37,6 +72,9 @@ export class SystemSettingsResponseDto {
|
||||
@ApiProperty({ type: ExternalApisSettingsViewDto })
|
||||
externalApis: ExternalApisSettingsViewDto;
|
||||
|
||||
@ApiProperty({ type: OfflineInquirySettingsViewDto })
|
||||
offlineInquiry: OfflineInquirySettingsViewDto;
|
||||
|
||||
@ApiProperty({
|
||||
description: "Human-readable mode for operators",
|
||||
example: "mock",
|
||||
|
||||
@@ -17,6 +17,16 @@ export class ExternalApisSettings {
|
||||
sandHubUseLiveApi?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Master switch for seeded offline plate inquiries (`offlineInquiries` collection).
|
||||
* When false, lookup is skipped and live/mock inquiry paths run as usual.
|
||||
* Default true when unset (seeds remain usable without an explicit migrate).
|
||||
*/
|
||||
export class OfflineInquirySettings {
|
||||
@Prop({ type: Boolean, required: false, default: true })
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
@Schema({ collection: "system_settings", versionKey: false })
|
||||
export class SystemSettingsModel {
|
||||
@Prop({ required: true, unique: true, default: SYSTEM_SETTINGS_GLOBAL_KEY })
|
||||
@@ -24,6 +34,9 @@ export class SystemSettingsModel {
|
||||
|
||||
@Prop({ type: ExternalApisSettings, required: false, default: {} })
|
||||
externalApis?: ExternalApisSettings;
|
||||
|
||||
@Prop({ type: OfflineInquirySettings, required: false, default: {} })
|
||||
offlineInquiry?: OfflineInquirySettings;
|
||||
}
|
||||
|
||||
export const SystemSettingsSchema =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Get, Patch, UseGuards } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Patch, Post, UseGuards } from "@nestjs/common";
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
@@ -10,11 +10,23 @@ import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import {
|
||||
OfflineInquirySettingsViewDto,
|
||||
SystemSettingsResponseDto,
|
||||
UpdateOfflineInquirySettingsDto,
|
||||
UpdateSystemSettingsDto,
|
||||
} from "./dto/system-settings.dto";
|
||||
import { SystemSettingsService } from "./system-settings.service";
|
||||
|
||||
const EXPERT_AND_ADMIN_ROLES = [
|
||||
RoleEnum.ADMIN,
|
||||
RoleEnum.SUPER_ADMIN,
|
||||
RoleEnum.EXPERT,
|
||||
RoleEnum.DAMAGE_EXPERT,
|
||||
RoleEnum.FIELD_EXPERT,
|
||||
RoleEnum.FILE_MAKER,
|
||||
RoleEnum.FILE_REVIEWER,
|
||||
] as const;
|
||||
|
||||
@ApiTags("system-settings")
|
||||
@ApiBearerAuth()
|
||||
@Controller("system-settings")
|
||||
@@ -27,7 +39,7 @@ export class SystemSettingsController {
|
||||
@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.",
|
||||
"Shows whether SandHub/Tejarat live HTTP is enabled, and whether offline seeded inquiries are enabled.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: SystemSettingsResponseDto })
|
||||
getSettings(): Promise<SystemSettingsResponseDto> {
|
||||
@@ -40,7 +52,7 @@ export class SystemSettingsController {
|
||||
@ApiOperation({
|
||||
summary: "Update global system settings",
|
||||
description:
|
||||
"Set `externalApis.sandHubUseLiveApi` to true for live inquiries, false for mock/offline mode.",
|
||||
"Set `externalApis.sandHubUseLiveApi` and/or `offlineInquiry.enabled`.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: SystemSettingsResponseDto })
|
||||
updateSettings(
|
||||
@@ -48,4 +60,55 @@ export class SystemSettingsController {
|
||||
): Promise<SystemSettingsResponseDto> {
|
||||
return this.systemSettingsService.updateSettings(body);
|
||||
}
|
||||
|
||||
@Post("fanavaran-config/reload")
|
||||
@UseGuards(SettingsJwtGuard, RolesGuard)
|
||||
@Roles(...EXPERT_AND_ADMIN_ROLES)
|
||||
@ApiOperation({
|
||||
summary: "Reload Fanavaran client config cache from Mongo",
|
||||
description:
|
||||
"Re-reads `fanavaranClientConfigs` into the runtime profile cache. If auth fields changed, cached Fanavaran tokens are invalidated so the next call re-logins. Allowed for admin and expert roles.",
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: "Cache reloaded",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
ok: { type: "boolean", example: true },
|
||||
message: { type: "string" },
|
||||
},
|
||||
},
|
||||
})
|
||||
reloadFanavaranConfigCache(): Promise<{ ok: true; message: string }> {
|
||||
return this.systemSettingsService.reloadFanavaranConfigCache();
|
||||
}
|
||||
|
||||
@Get("offline-inquiry")
|
||||
@UseGuards(SettingsJwtGuard, RolesGuard)
|
||||
@Roles(...EXPERT_AND_ADMIN_ROLES)
|
||||
@ApiOperation({
|
||||
summary: "Get offline inquiry master switch",
|
||||
description:
|
||||
"When enabled, matching `offlineInquiries` seeds short-circuit live plate inquiry. When disabled, live/mock paths run as usual.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: OfflineInquirySettingsViewDto })
|
||||
getOfflineInquirySettings(): Promise<OfflineInquirySettingsViewDto> {
|
||||
return this.systemSettingsService.getOfflineInquirySettings();
|
||||
}
|
||||
|
||||
@Patch("offline-inquiry")
|
||||
@UseGuards(SettingsJwtGuard, RolesGuard)
|
||||
@Roles(...EXPERT_AND_ADMIN_ROLES)
|
||||
@ApiOperation({
|
||||
summary: "Enable or disable offline seeded plate inquiries",
|
||||
description:
|
||||
"Global master switch stored in `system_settings.offlineInquiry.enabled`. Per-seed `enabled` flags still apply when this is on.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: OfflineInquirySettingsViewDto })
|
||||
updateOfflineInquirySettings(
|
||||
@Body() body: UpdateOfflineInquirySettingsDto,
|
||||
): Promise<OfflineInquirySettingsViewDto> {
|
||||
return this.systemSettingsService.updateOfflineInquirySettings(body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { MongooseModule } from "@nestjs/mongoose";
|
||||
import { FanavaranLookupModule } from "src/fanavaran/fanavaran-lookup.module";
|
||||
import { SystemSettingsDbService } from "./entities/db-service/system-settings.db.service";
|
||||
import {
|
||||
SystemSettingsModel,
|
||||
@@ -13,6 +14,7 @@ import { SystemSettingsService } from "./system-settings.service";
|
||||
MongooseModule.forFeature([
|
||||
{ name: SystemSettingsModel.name, schema: SystemSettingsSchema },
|
||||
]),
|
||||
FanavaranLookupModule,
|
||||
],
|
||||
controllers: [SystemSettingsController],
|
||||
providers: [SystemSettingsService, SystemSettingsDbService],
|
||||
|
||||
@@ -5,12 +5,18 @@ describe("SystemSettingsService", () => {
|
||||
findGlobal: jest.fn(),
|
||||
upsertGlobal: jest.fn(),
|
||||
};
|
||||
const fanavaranClientConfigService = {
|
||||
reloadCache: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
let service: SystemSettingsService;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
service = new SystemSettingsService(db as any);
|
||||
service = new SystemSettingsService(
|
||||
db as any,
|
||||
fanavaranClientConfigService as any,
|
||||
);
|
||||
});
|
||||
|
||||
it("defaults to mock when DB field is false", async () => {
|
||||
@@ -28,4 +34,41 @@ describe("SystemSettingsService", () => {
|
||||
});
|
||||
await expect(service.isSandHubLiveEnabled()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("treats unset offlineInquiry as enabled", async () => {
|
||||
db.findGlobal.mockResolvedValue({
|
||||
key: "global",
|
||||
externalApis: { sandHubUseLiveApi: false },
|
||||
});
|
||||
await expect(service.isOfflineInquiryEnabled()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("disables offline inquiry when explicitly false", async () => {
|
||||
db.findGlobal.mockResolvedValue({
|
||||
key: "global",
|
||||
offlineInquiry: { enabled: false },
|
||||
});
|
||||
await expect(service.isOfflineInquiryEnabled()).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("updates offline inquiry master switch", async () => {
|
||||
db.upsertGlobal.mockResolvedValue({
|
||||
key: "global",
|
||||
offlineInquiry: { enabled: false },
|
||||
});
|
||||
await expect(
|
||||
service.updateOfflineInquirySettings({ enabled: false }),
|
||||
).resolves.toEqual({ enabled: false });
|
||||
expect(db.upsertGlobal).toHaveBeenCalledWith({
|
||||
$set: { "offlineInquiry.enabled": false },
|
||||
});
|
||||
});
|
||||
|
||||
it("reloads Fanavaran client config cache", async () => {
|
||||
await expect(service.reloadFanavaranConfigCache()).resolves.toEqual({
|
||||
ok: true,
|
||||
message: "Fanavaran client config cache reloaded",
|
||||
});
|
||||
expect(fanavaranClientConfigService.reloadCache).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { FanavaranClientConfigService } from "src/fanavaran/fanavaran-client-config.service";
|
||||
import {
|
||||
OfflineInquirySettingsViewDto,
|
||||
SystemSettingsResponseDto,
|
||||
UpdateOfflineInquirySettingsDto,
|
||||
UpdateSystemSettingsDto,
|
||||
} from "./dto/system-settings.dto";
|
||||
import { SystemSettingsDbService } from "./entities/db-service/system-settings.db.service";
|
||||
@@ -12,7 +15,10 @@ export class SystemSettingsService {
|
||||
private cacheAt = 0;
|
||||
private readonly cacheTtlMs = 15_000;
|
||||
|
||||
constructor(private readonly db: SystemSettingsDbService) {}
|
||||
constructor(
|
||||
private readonly db: SystemSettingsDbService,
|
||||
private readonly fanavaranClientConfigService: FanavaranClientConfigService,
|
||||
) {}
|
||||
|
||||
private async loadGlobal(): Promise<Record<string, unknown>> {
|
||||
const now = Date.now();
|
||||
@@ -22,10 +28,13 @@ export class SystemSettingsService {
|
||||
let doc = await this.db.findGlobal();
|
||||
if (!doc) {
|
||||
doc = await this.db.upsertGlobal({
|
||||
$set: { externalApis: { sandHubUseLiveApi: false } },
|
||||
$set: {
|
||||
externalApis: { sandHubUseLiveApi: false },
|
||||
offlineInquiry: { enabled: true },
|
||||
},
|
||||
});
|
||||
this.logger.log(
|
||||
"Created default system_settings document (sandHubUseLiveApi=false)",
|
||||
"Created default system_settings document (sandHubUseLiveApi=false, offlineInquiry.enabled=true)",
|
||||
);
|
||||
}
|
||||
this.cache = doc;
|
||||
@@ -38,6 +47,14 @@ export class SystemSettingsService {
|
||||
this.cacheAt = 0;
|
||||
}
|
||||
|
||||
private readOfflineInquiryEnabled(doc: Record<string, unknown>): boolean {
|
||||
const offlineInquiry = doc.offlineInquiry as
|
||||
| { enabled?: boolean }
|
||||
| undefined;
|
||||
// Unset → enabled (preserve prior always-on seed behavior).
|
||||
return offlineInquiry?.enabled !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether SandHub/Tejarat live HTTP should run (`system_settings` collection).
|
||||
*/
|
||||
@@ -49,6 +66,18 @@ export class SystemSettingsService {
|
||||
return externalApis?.sandHubUseLiveApi === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether seeded offline plate inquiries may short-circuit live/mock inquiry.
|
||||
*/
|
||||
async isOfflineInquiryEnabled(): Promise<boolean> {
|
||||
const doc = await this.loadGlobal();
|
||||
return this.readOfflineInquiryEnabled(doc);
|
||||
}
|
||||
|
||||
async getOfflineInquirySettings(): Promise<OfflineInquirySettingsViewDto> {
|
||||
return { enabled: await this.isOfflineInquiryEnabled() };
|
||||
}
|
||||
|
||||
async getSettingsView(): Promise<SystemSettingsResponseDto> {
|
||||
const doc = await this.loadGlobal();
|
||||
const externalApis = doc.externalApis as
|
||||
@@ -58,6 +87,7 @@ export class SystemSettingsService {
|
||||
return {
|
||||
key: String(doc.key ?? "global"),
|
||||
externalApis: { sandHubUseLiveApi },
|
||||
offlineInquiry: { enabled: this.readOfflineInquiryEnabled(doc) },
|
||||
sandHubMode: sandHubUseLiveApi ? "live" : "mock",
|
||||
};
|
||||
}
|
||||
@@ -69,6 +99,9 @@ export class SystemSettingsService {
|
||||
if (body.externalApis?.sandHubUseLiveApi !== undefined) {
|
||||
$set["externalApis.sandHubUseLiveApi"] = body.externalApis.sandHubUseLiveApi;
|
||||
}
|
||||
if (body.offlineInquiry?.enabled !== undefined) {
|
||||
$set["offlineInquiry.enabled"] = body.offlineInquiry.enabled;
|
||||
}
|
||||
if (Object.keys($set).length > 0) {
|
||||
await this.db.upsertGlobal({ $set });
|
||||
this.invalidateCache();
|
||||
@@ -76,4 +109,30 @@ export class SystemSettingsService {
|
||||
}
|
||||
return this.getSettingsView();
|
||||
}
|
||||
|
||||
async updateOfflineInquirySettings(
|
||||
body: UpdateOfflineInquirySettingsDto,
|
||||
): Promise<OfflineInquirySettingsViewDto> {
|
||||
await this.db.upsertGlobal({
|
||||
$set: { "offlineInquiry.enabled": body.enabled },
|
||||
});
|
||||
this.invalidateCache();
|
||||
this.logger.log(
|
||||
`Offline inquiry master switch set to enabled=${body.enabled}`,
|
||||
);
|
||||
return { enabled: body.enabled };
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-read `fanavaranClientConfigs` into the in-memory profile cache.
|
||||
* Auth fingerprint changes invalidate cached Fanavaran tokens.
|
||||
*/
|
||||
async reloadFanavaranConfigCache(): Promise<{ ok: true; message: string }> {
|
||||
await this.fanavaranClientConfigService.reloadCache();
|
||||
this.logger.log("Fanavaran client config cache reloaded via system-settings API");
|
||||
return {
|
||||
ok: true,
|
||||
message: "Fanavaran client config cache reloaded",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user