import type { H3Event } from 'h3' // Cache for org-role access mapping let orgRoleAccessCache: Map> | null = null let cacheTimestamp: number = 0 const CACHE_DURATION = 5 * 60 * 1000 // 5 minutes interface OrgRecord { id: number AD_Role_OrgAccess?: Array<{ AD_Role_ID?: { id: number } IsActive?: boolean }> } /** * Fetches and caches organization-role access mapping from iDempiere * Returns a Map where: * - Key: Organization ID * - Value: Set of Role IDs that have access to this organization * * Special case: Organization ID 0 (Name: "*") means "All Organizations" */ export async function getOrgRoleAccessMap(event: H3Event, authToken?: string | null): Promise>> { const now = Date.now() // Return cached data if still valid if (orgRoleAccessCache && (now - cacheTimestamp) < CACHE_DURATION) { return orgRoleAccessCache } const fetchOrgAccess = async (token: string) => { const res: any = await event.context.fetch( 'models/ad_org?$expand=AD_Role_OrgAccess', 'GET', token, null ) if (!res?.records) { console.error('[OrgRoleAccessCache] Failed to fetch org-role access data') return null } // Build the mapping const accessMap = new Map>() for (const org of res.records as OrgRecord[]) { const orgId = org.id const roleSet = new Set() if (org.AD_Role_OrgAccess && Array.isArray(org.AD_Role_OrgAccess)) { for (const access of org.AD_Role_OrgAccess) { if (access.IsActive !== false && access.AD_Role_ID?.id) { roleSet.add(access.AD_Role_ID.id) } } } accessMap.set(orgId, roleSet) } return accessMap } try { let token = authToken || await getTokenHelper(event) let accessMap: Map> | null = null try { accessMap = await fetchOrgAccess(token) } catch (fetchError: any) { // On 401, try refreshing the token and retry once if (fetchError?.statusCode === 401 || fetchError?.status === 401) { try { const refreshedToken = await refreshTokenHelper(event) accessMap = await fetchOrgAccess(refreshedToken) } catch (refreshError: any) { console.error('[OrgRoleAccessCache] Token refresh also failed:', refreshError.message) } } else { throw fetchError } } if (!accessMap) { return orgRoleAccessCache || new Map() } // Cache the result orgRoleAccessCache = accessMap cacheTimestamp = now console.log(`[OrgRoleAccessCache] Loaded ${accessMap.size} organizations with role mappings`) return accessMap } catch (error: any) { console.error('[OrgRoleAccessCache] Error fetching org-role access:', error.message) // Return existing cache or empty map on error return orgRoleAccessCache || new Map() } } /** * Checks if a given role has access to a specific organization * Handles the special case where org ID 0 (Name: "*") means "All Organizations" */ export function hasRoleAccessToOrg( orgRoleAccessMap: Map>, roleId: number, orgId: number ): boolean { // Check if role has access to org ID 0 ("*" - All Organizations) const allOrgsRoles = orgRoleAccessMap.get(0) if (allOrgsRoles && allOrgsRoles.has(roleId)) { return true // Role has access to all organizations } // Check if role has access to the specific organization const orgRoles = orgRoleAccessMap.get(orgId) return orgRoles ? orgRoles.has(roleId) : false } /** * Invalidates the cache, forcing a refresh on next access */ export function invalidateOrgRoleAccessCache() { orgRoleAccessCache = null cacheTimestamp = 0 console.log('[OrgRoleAccessCache] Cache invalidated') }