// W2 core computation: effective desired grants + diff vs AList. // Uses this.helpers.httpRequest (n8n Code node) for all HTTP. // Config read via process.env (n8n $env proxy is unreliable for custom vars). const NC = process.env.NOCODB_URL; const NC_TOKEN = process.env.NOCODB_TOKEN; const ALIST = process.env.ALIST_URL; const T_CUSTPROD = process.env.NOCODB_TABLE_CUSTOMERPRODUCTS; const T_PROD = process.env.NOCODB_TABLE_PRODUCTS; const T_CUST = process.env.NOCODB_TABLE_CUSTOMERS; const LNK_PROD_RES = process.env.NOCODB_LINK_PROD_RESOURCES; // c4ko4x8kwi16swg async function ncGet(path) { return await this.helpers.httpRequest({ method: 'GET', url: NC + path, headers: { 'xc-token': NC_TOKEN }, json: true, }); } async function alistReq(method, path, body, token) { return await this.helpers.httpRequest({ method, url: ALIST + path, headers: token ? { Authorization: token } : {}, body, json: true, }); } function linkId(v) { if (v == null) return null; if (typeof v === 'object' && !Array.isArray(v)) return v.Id ?? v.id ?? null; if (Array.isArray(v)) return v[0]?.Id ?? v[0]?.id ?? null; return v; } const custProdId = $json.customerProductId; // 1. re-fetch the CP record const cp = await ncGet(`/api/v2/tables/${T_CUSTPROD}/records/${custProdId}`); const customerId = linkId(cp.customer); if (!customerId) throw new Error('CP has no customer link'); // 2. load all active CPs for this customer const cps = await ncGet(`/api/v2/tables/${T_CUSTPROD}/records?where=(customer,eq,${customerId})&limit=1000`); const active = (cps.list || []).filter(r => { if (r.status && r.status !== 'active') return false; return true; }); // 3. build resource path -> max expires_at const effective = {}; for (const c of active) { const pid = linkId(c.product); if (!pid) continue; const resList = await ncGet(`/api/v2/tables/${T_PROD}/links/${LNK_PROD_RES}/records/${pid}`); const exp = c.expires_at; // 'YYYY-MM-DD' for (const res of (resList.list || [])) { const path = res.alist_path; if (!path) continue; const cur = effective[path]; if (!cur || !cur.expires || (exp && exp > cur.expires)) { effective[path] = { expires: exp || null, permission: 0 }; } } } // 4. admin login const login = await alistReq('POST', '/api/auth/login', { username: process.env.ALIST_ADMIN_USERNAME, password: process.env.ALIST_ADMIN_PASSWORD, }, null); const token = login?.data?.token; if (!token) throw new Error('AList login failed'); // customer's role id const cust = await ncGet(`/api/v2/tables/${T_CUST}/records/${customerId}`); const roleId = cust.alist_role_id; let currentScopes = []; if (roleId) { const role = await alistReq('GET', `/api/admin/role/get?id=${roleId}`, null, token); currentScopes = role?.data?.permission_scopes ?? []; } const desiredPaths = Object.keys(effective); const currentPaths = currentScopes.map(s => s.path); const toAdd = desiredPaths.filter(p => !currentPaths.includes(p)); const toRemove = currentPaths.filter(p => !desiredPaths.includes(p)); let roleName = null, roleDesc = null; if (roleId) { const role = await alistReq('GET', `/api/admin/role/get?id=${roleId}`, null, token); roleName = role?.data?.name; roleDesc = role?.data?.description; } return { json: { customerId, roleId, roleName, roleDesc, token, effective, desiredPaths, currentScopes, diff: { toAdd, toRemove }, newScopes: desiredPaths.map(p => ({ path: p, permission: effective[p].permission })), }, };