forked from Yara724/api
locations dynamically in fanavaran
This commit is contained in:
139
src/fanavaran/fanavaran-location.service.ts
Normal file
139
src/fanavaran/fanavaran-location.service.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { ForbiddenException, Injectable, Logger } from "@nestjs/common";
|
||||
import {
|
||||
FanavaranClientKey,
|
||||
getFanavaranClientProfile,
|
||||
resolveFanavaranClientKey,
|
||||
} from "src/core/config/fanavaran-client.config";
|
||||
import { FileMakerDbService } from "src/users/entities/db-service/file-maker.db.service";
|
||||
import { FileReviewerDbService } from "src/users/entities/db-service/file-reviewer.db.service";
|
||||
|
||||
type LocationEntry = { id?: string; name?: string };
|
||||
|
||||
/**
|
||||
* Parsian V4/V5 FileMaker flows: resolve Fanavaran HTTP `Location` from
|
||||
* file-maker / file-reviewer `locations[].id`, and enforce maker/reviewer match.
|
||||
*/
|
||||
@Injectable()
|
||||
export class FanavaranLocationService {
|
||||
private readonly logger = new Logger(FanavaranLocationService.name);
|
||||
|
||||
constructor(
|
||||
private readonly fileMakerDbService: FileMakerDbService,
|
||||
private readonly fileReviewerDbService: FileReviewerDbService,
|
||||
) {}
|
||||
|
||||
isParsianClient(clientKey?: FanavaranClientKey): boolean {
|
||||
return (clientKey ?? resolveFanavaranClientKey()) === "parsian";
|
||||
}
|
||||
|
||||
extractLocationIds(locations?: LocationEntry[] | null): string[] {
|
||||
if (!Array.isArray(locations) || locations.length === 0) return [];
|
||||
const ids: string[] = [];
|
||||
for (const entry of locations) {
|
||||
const id = String(entry?.id ?? "").trim();
|
||||
if (id && !ids.includes(id)) ids.push(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
primaryLocationId(locations?: LocationEntry[] | null): string | undefined {
|
||||
return this.extractLocationIds(locations)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maker primary location → reviewer primary → tenant auth.location.
|
||||
* Non-parsian or non-FileMaker flows always use the tenant default.
|
||||
*/
|
||||
async resolveBusinessLocation(input: {
|
||||
clientKey: FanavaranClientKey;
|
||||
isMadeByFileMaker?: boolean;
|
||||
fileMakerId?: string | null;
|
||||
fileReviewerId?: string | null;
|
||||
}): Promise<string> {
|
||||
const defaultLocation = getFanavaranClientProfile(input.clientKey).auth
|
||||
.location;
|
||||
|
||||
if (input.clientKey !== "parsian" || input.isMadeByFileMaker !== true) {
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
const makerLocation = await this.loadPrimaryLocationId(input.fileMakerId, "maker");
|
||||
if (makerLocation) {
|
||||
this.logger.debug(
|
||||
`Fanavaran Location from file-maker id=${input.fileMakerId} → ${makerLocation}`,
|
||||
);
|
||||
return makerLocation;
|
||||
}
|
||||
|
||||
const reviewerLocation = await this.loadPrimaryLocationId(
|
||||
input.fileReviewerId,
|
||||
"reviewer",
|
||||
);
|
||||
if (reviewerLocation) {
|
||||
this.logger.debug(
|
||||
`Fanavaran Location from file-reviewer id=${input.fileReviewerId} → ${reviewerLocation}`,
|
||||
);
|
||||
return reviewerLocation;
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`Fanavaran Location fallback to tenant default → ${defaultLocation}`,
|
||||
);
|
||||
return defaultLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* When both maker and reviewer have locations, they must share at least one id.
|
||||
* No-op for non-parsian deployments or when either side has no locations.
|
||||
*/
|
||||
async assertMakerReviewerLocationCompatible(input: {
|
||||
fileMakerId?: string | null;
|
||||
fileReviewerId?: string | null;
|
||||
clientKey?: FanavaranClientKey;
|
||||
}): Promise<void> {
|
||||
if (!this.isParsianClient(input.clientKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const makerIds = await this.loadLocationIds(input.fileMakerId, "maker");
|
||||
const reviewerIds = await this.loadLocationIds(
|
||||
input.fileReviewerId,
|
||||
"reviewer",
|
||||
);
|
||||
|
||||
if (makerIds.length === 0 || reviewerIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shared = makerIds.some((id) => reviewerIds.includes(id));
|
||||
if (!shared) {
|
||||
throw new ForbiddenException(
|
||||
"FileReviewer location must match the FileMaker location for this case.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadPrimaryLocationId(
|
||||
userId: string | null | undefined,
|
||||
kind: "maker" | "reviewer",
|
||||
): Promise<string | undefined> {
|
||||
const ids = await this.loadLocationIds(userId, kind);
|
||||
return ids[0];
|
||||
}
|
||||
|
||||
private async loadLocationIds(
|
||||
userId: string | null | undefined,
|
||||
kind: "maker" | "reviewer",
|
||||
): Promise<string[]> {
|
||||
if (!userId) return [];
|
||||
try {
|
||||
const doc =
|
||||
kind === "maker"
|
||||
? await this.fileMakerDbService.findById(String(userId))
|
||||
: await this.fileReviewerDbService.findById(String(userId));
|
||||
return this.extractLocationIds((doc as any)?.locations);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user