165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
import { AppDataSource } from "../database/data-source";
|
|
import { OrgRevision } from "../entities/OrgRevision";
|
|
import { Profile } from "../entities/Profile";
|
|
import { PosMaster } from "../entities/PosMaster";
|
|
|
|
interface LogCacheData {
|
|
currentRevision: OrgRevision | null;
|
|
profileCache: Map<string, Profile>; // keycloak → Profile
|
|
rootIdCache: Map<string, string>; // profileId → rootId
|
|
updatedAt: Date;
|
|
}
|
|
|
|
class LogMemoryStore {
|
|
private cache: LogCacheData = {
|
|
currentRevision: null,
|
|
profileCache: new Map(),
|
|
rootIdCache: new Map(),
|
|
updatedAt: new Date(),
|
|
};
|
|
private readonly CACHE_TTL = 60 * 60 * 1000; // 60 minutes
|
|
private isInitialized = false;
|
|
|
|
constructor() {
|
|
// ไม่ refresh ทันที - รอให้เรียก initialize() หลัง TypeORM ready
|
|
}
|
|
|
|
// เริ่มต้น cache หลังจาก TypeORM initialize เสร็จ
|
|
initialize() {
|
|
if (this.isInitialized) {
|
|
console.log("[LogMemoryStore] Already initialized");
|
|
return;
|
|
}
|
|
|
|
this.isInitialized = true;
|
|
this.refreshCache();
|
|
|
|
console.log(
|
|
"[LogMemoryStore] Initialized with",
|
|
this.CACHE_TTL / 1000 / 60,
|
|
"minute TTL (passive cleanup on access)",
|
|
);
|
|
}
|
|
|
|
private async refreshCache() {
|
|
try {
|
|
// Refresh revision cache
|
|
const repoRevision = AppDataSource.getRepository(OrgRevision);
|
|
const revision = await repoRevision.findOne({
|
|
where: {
|
|
orgRevisionIsCurrent: true,
|
|
orgRevisionIsDraft: false,
|
|
},
|
|
});
|
|
this.cache.currentRevision = revision;
|
|
|
|
// Clear on-demand caches (they will be rebuilt as needed)
|
|
this.cache.profileCache.clear();
|
|
this.cache.rootIdCache.clear();
|
|
|
|
this.cache.updatedAt = new Date();
|
|
console.log("[LogMemoryStore] Cache refreshed at", this.cache.updatedAt.toISOString());
|
|
} catch (error) {
|
|
console.error("[LogMemoryStore] Error refreshing cache:", error);
|
|
}
|
|
}
|
|
|
|
// Check if cache is stale and refresh if needed
|
|
private async checkAndRefreshIfNeeded() {
|
|
const now = new Date();
|
|
const age = now.getTime() - this.cache.updatedAt.getTime();
|
|
if (age > this.CACHE_TTL) {
|
|
console.log(
|
|
"[LogMemoryStore] Cache is stale (age:",
|
|
Math.round(age / 1000 / 60),
|
|
"minutes), refreshing...",
|
|
);
|
|
await this.refreshCache();
|
|
}
|
|
}
|
|
|
|
getCurrentRevision(): OrgRevision | null {
|
|
// Check for stale data (fire and forget)
|
|
this.checkAndRefreshIfNeeded();
|
|
return this.cache.currentRevision;
|
|
}
|
|
|
|
getLastUpdateTime(): Date {
|
|
return this.cache.updatedAt;
|
|
}
|
|
|
|
/**
|
|
* Get Profile by keycloak ID with caching
|
|
*/
|
|
async getProfileByKeycloak(keycloak: string): Promise<Profile | null> {
|
|
// Check for stale data
|
|
await this.checkAndRefreshIfNeeded();
|
|
|
|
// Check cache first
|
|
if (this.cache.profileCache.has(keycloak)) {
|
|
return this.cache.profileCache.get(keycloak)!;
|
|
}
|
|
|
|
// Fetch from database
|
|
const repoProfile = AppDataSource.getRepository(Profile);
|
|
const profile = await repoProfile.findOne({
|
|
where: { keycloak },
|
|
});
|
|
|
|
// Cache the result
|
|
if (profile) {
|
|
this.cache.profileCache.set(keycloak, profile);
|
|
}
|
|
|
|
return profile;
|
|
}
|
|
|
|
/**
|
|
* Get RootId by profileId with caching
|
|
*/
|
|
async getRootIdByProfileId(profileId: string | undefined): Promise<string | null> {
|
|
if (!profileId) return null;
|
|
|
|
// Check for stale data
|
|
await this.checkAndRefreshIfNeeded();
|
|
|
|
// Check cache first
|
|
if (this.cache.rootIdCache.has(profileId)) {
|
|
return this.cache.rootIdCache.get(profileId)!;
|
|
}
|
|
|
|
// Fetch from database
|
|
const repoPosmaster = AppDataSource.getRepository(PosMaster);
|
|
const revision = this.getCurrentRevision();
|
|
//
|
|
const posMaster = await repoPosmaster.findOne({
|
|
where: {
|
|
current_holderId: profileId,
|
|
orgRevisionId: revision?.id,
|
|
},
|
|
relations: ["orgRoot"],
|
|
select: {
|
|
orgRoot: {
|
|
ancestorDNA: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
const rootId = posMaster?.orgRoot?.ancestorDNA ?? null;
|
|
|
|
// Cache the result
|
|
if (rootId) {
|
|
this.cache.rootIdCache.set(profileId, rootId);
|
|
}
|
|
|
|
return rootId;
|
|
}
|
|
|
|
// สำหรับ shutdown
|
|
destroy() {
|
|
// No active timer to clear
|
|
console.log("[LogMemoryStore] Destroyed");
|
|
}
|
|
}
|
|
|
|
export const logMemoryStore = new LogMemoryStore();
|