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; // keycloak → Profile rootIdCache: Map; // profileId → rootId updatedAt: Date; } class LogMemoryStore { private cache: LogCacheData = { currentRevision: null, profileCache: new Map(), rootIdCache: new Map(), updatedAt: new Date(), }; private readonly REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes private isRefreshing = false; private isInitialized = false; private refreshTimer: NodeJS.Timeout | null = null; constructor() { // ไม่ refresh ทันที - รอให้เรียก initialize() หลัง TypeORM ready } // เริ่มต้น cache หลังจาก TypeORM initialize เสร็จ initialize() { if (this.isInitialized) { console.log("[LogMemoryStore] Already initialized"); return; } this.isInitialized = true; this.refreshCache(); this.refreshTimer = setInterval(() => { this.refreshCache(); }, this.REFRESH_INTERVAL); console.log( "[LogMemoryStore] Initialized with", this.REFRESH_INTERVAL / 1000, "second refresh interval", ); } private async refreshCache() { if (this.isRefreshing) { console.log("[LogMemoryStore] Already refreshing, skipping..."); return; } this.isRefreshing = true; 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); } finally { this.isRefreshing = false; } } getCurrentRevision(): OrgRevision | null { return this.cache.currentRevision; } getLastUpdateTime(): Date { return this.cache.updatedAt; } /** * Get Profile by keycloak ID with caching */ async getProfileByKeycloak(keycloak: string): Promise { // 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 { if (!profileId) return null; // 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() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } } } export const logMemoryStore = new LogMemoryStore();