// ============================================================================ // W2 runtime body — enforced CustomerProduct -> AList role scopes sync. // Rebuilt by `node n8n/gen_w2.js` (single source of truth = shared/effective-grants.js). // ============================================================================ // __SHARED_INLINE__ (replaced by shared/effective-grants.js source at build) // ============================================================================ // n8n Code node context: ctx = { $env, helpers } — but helpers is `this.helpers` // inside the node. Build ctx to hand to the shared library. const ctx = { $env, helpers: this.helpers, }; const custProdId = $json.customerProductId; // 1. re-fetch this CP record to find its customer link (may be null mid-draft) const CP = await ncGet(ctx, `/api/v2/tables/${$env.NOCODB_TABLE_CUSTOMERPRODUCTS}/records/${custProdId}`); const customerId = linkId(CP.customer); if (!customerId) { // Customer link not yet filled (record being drafted). Skip gracefully; the // webhook re-fires when customer is set later. return { json: { skipWriteBack: true, customerProductId: custProdId, reason: 'no customer link yet' } }; } // 2. compute what the customer SHOULD have right now (shared algorithm) const grants = await calculateEffectiveGrants(ctx, customerId); // 3. fetch customer admin facts (role id, alist user id) const cust = await ncGet(ctx, `/api/v2/tables/${$env.NOCODB_TABLE_CUSTOMERS}/records/${customerId}`); const roleId = cust.alist_role_id; const userId = cust.alist_user_id; if (!roleId) { return { json: { skipWriteBack: true, customerId, reason: 'customer not provisioned (no role id)' } }; } // 4. admin login const login = await alistReq(ctx, 'POST', '/api/auth/login', { username: $env.ALIST_ADMIN_USERNAME, password: $env.ALIST_ADMIN_PASSWORD, }, null); const token = login?.data?.token; if (!token) throw new Error('AList login failed'); // 5. current scopes vs desired scopes -> diff const role = await alistReq(ctx, 'GET', `/api/admin/role/get?id=${roleId}`, null, token); const currentScopes = role?.data?.permission_scopes ?? []; const currentPaths = currentScopes.map(s => s.path); const toAdd = grants.desiredPaths.filter(p => !currentPaths.includes(p)); const toRemove = currentPaths.filter(p => !grants.desiredPaths.includes(p)); const changed = toAdd.length > 0 || toRemove.length > 0; // 6. apply if changed if (changed) { await alistReq(ctx, 'POST', '/api/admin/role/update', { id: roleId, name: role?.data?.name, description: role?.data?.description, permission_scopes: grants.newScopes, }, token); } return { json: { customerId, customerProductId: custProdId, roleId, roleName: role?.data?.name, effective: grants.desired, desiredPaths: grants.desiredPaths, currentScopes, diff: { toAdd, toRemove }, newScopes: grants.newScopes, skipWriteBack: changed ? false : true, }, };