Files
yara724-api/src/fanavaran/fanavaran-client-config.service.ts

136 lines
4.3 KiB
TypeScript

import { Injectable, Logger, OnModuleInit } from "@nestjs/common";
import { InjectModel } from "@nestjs/mongoose";
import { Model } from "mongoose";
import {
FANAVARAN_CLIENT_KEYS,
SEED_FANAVARAN_CLIENT_PROFILES,
getFanavaranClientProfile,
setFanavaranClientProfilesCache,
type FanavaranClientKey,
type FanavaranClientProfile,
} from "src/core/config/fanavaran-client.config";
import { FanavaranAuthService } from "./fanavaran-auth.service";
import {
FanavaranClientConfig,
FanavaranClientConfigDocument,
} from "./schema/fanavaran-client-config.schema";
/**
* Loads / seeds per-tenant Fanavaran auth+defaults from Mongo and exposes them
* through the sync helpers in fanavaran-client.config.ts.
*
* Boot behaviour: for each known client key, if no document exists, insert the
* seed profile. Existing documents are never overwritten (ops can edit Mongo).
*/
@Injectable()
export class FanavaranClientConfigService implements OnModuleInit {
private readonly logger = new Logger(FanavaranClientConfigService.name);
constructor(
@InjectModel(FanavaranClientConfig.name)
private readonly configModel: Model<FanavaranClientConfigDocument>,
private readonly fanavaranAuthService: FanavaranAuthService,
) {}
async onModuleInit(): Promise<void> {
await this.seedMissingProfiles();
await this.reloadCache();
}
async seedMissingProfiles(): Promise<void> {
for (const key of FANAVARAN_CLIENT_KEYS) {
const seed = SEED_FANAVARAN_CLIENT_PROFILES[key];
const existing = await this.configModel
.findOne({ key })
.select({ _id: 1 })
.lean()
.exec();
if (existing) {
continue;
}
await this.configModel.create({
key: seed.key,
auth: seed.auth,
defaults: seed.defaults,
});
this.logger.log(
`Seeded fanavaranClientConfigs for client "${key}" (ClaimExpertId=${seed.defaults.ClaimExpertId}, ExpertiseClaimExpertId=${seed.defaults.ExpertiseClaimExpertId})`,
);
}
}
/**
* Re-read Mongo into the runtime profile cache. If any tenant's auth block
* changed (location, credentials, …), drop cached Fanavaran tokens so the
* next call re-logins instead of waiting for Tehran midnight.
*/
async reloadCache(): Promise<void> {
const previousFingerprints = new Map<FanavaranClientKey, string>();
for (const key of FANAVARAN_CLIENT_KEYS) {
try {
previousFingerprints.set(
key,
FanavaranAuthService.authFingerprint(
getFanavaranClientProfile(key).auth,
),
);
} catch {
// First boot / empty cache — nothing to compare.
}
}
const docs = await this.configModel.find().lean().exec();
const cache: Partial<Record<FanavaranClientKey, FanavaranClientProfile>> =
{};
for (const key of FANAVARAN_CLIENT_KEYS) {
const doc = docs.find((row) => row.key === key);
if (doc?.auth && doc?.defaults) {
cache[key] = {
key,
auth: {
appName: doc.auth.appName,
secret: doc.auth.secret,
username: doc.auth.username,
password: doc.auth.password,
corpId: doc.auth.corpId,
contractId: doc.auth.contractId,
...(doc.auth.hullContractId
? { hullContractId: doc.auth.hullContractId }
: {}),
location: doc.auth.location,
},
defaults: { ...doc.defaults },
};
} else {
cache[key] = SEED_FANAVARAN_CLIENT_PROFILES[key];
}
}
setFanavaranClientProfilesCache(cache);
for (const key of FANAVARAN_CLIENT_KEYS) {
const next = FanavaranAuthService.authFingerprint(cache[key]!.auth);
const prev = previousFingerprints.get(key);
if (prev && prev !== next) {
this.logger.warn(
`[${key}] Fanavaran auth config changed — invalidating cached authenticationToken`,
);
this.fanavaranAuthService.invalidateToken(key);
}
}
this.logger.log(
`Loaded ${docs.length} Fanavaran client config(s) from Mongo into runtime cache`,
);
}
async findByKey(key: FanavaranClientKey) {
return this.configModel.findOne({ key }).lean().exec();
}
async list() {
return this.configModel.find().lean().exec();
}
}