feat(W1): status lifecycle (suspend/delete/resume) + shared effective-grants lib
- Extract calculateEffectiveGrants into n8n/shared/effective-grants.js (single source of truth), inlined at build time into W1/W2/W3 via gen_w*.js + inline_shared.js — removes the duplicated algorithm. - W1 now provisions with a status branch: suspended/deleted -> disable AList user + clear role scopes (soft delete) active -> create/update user+role, RECOMPUTE desired scopes (never restore stale), re-enable - Loop guard corrected: check AList user LIVE disabled state via GET /api/admin/user/get (sync_status alone breaks suspend->resume). - W2/W3 rebuilt on shared lib (identical behaviour). - Tests: test_effective_grants.js (15), test_w3_refactored.js (13), test_w1_lifecycle.js (23) — all green. - Drop superseded w2-compute.js / w3-compute.js / test_w3.js.
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
// Builds n8n/W2-customerproduct-sync.json — the CustomerProduct webhook.
|
||||
// Single source of truth: shared/effective-grants.js (inlined) + w2-body.js.
|
||||
// Run: node n8n/gen_w2.js
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { sharedSource } = require('./inline_shared');
|
||||
|
||||
let body = fs.readFileSync(path.join(__dirname, 'w2-body.js'), 'utf8');
|
||||
body = body.replace('// __SHARED_INLINE__ (replaced by shared/effective-grants.js source at build)', sharedSource());
|
||||
|
||||
const workflow = {
|
||||
name: 'W2 - CustomerProduct Sync (Effective Grants)',
|
||||
nodes: [
|
||||
{
|
||||
parameters: {
|
||||
httpMethod: 'POST',
|
||||
path: 'alist-customerproduct-sync',
|
||||
responseMode: 'onReceived',
|
||||
options: {}
|
||||
},
|
||||
id: 'trigger-webhook',
|
||||
name: 'NocoDB: CustomerProduct Webhook',
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
typeVersion: 2,
|
||||
position: [0, 0],
|
||||
webhookId: 'customerproduct-sync'
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
jsCode: "const body = $input.item.json.body ?? $input.item.json;\nconst data = body.data ?? {};\nconst rows = data.rows?.[0] ?? data.rows_new?.[0] ?? data.rows_old?.[0] ?? {};\nconst id = rows.Id ?? data.data?.rows?.[0]?.Id;\nif (!id) throw new Error('no CustomerProduct Id in webhook');\nreturn { json: { customerProductId: Number(id), eventType: body.type ?? '' } };\n"
|
||||
},
|
||||
id: 'extract-id',
|
||||
name: 'Extract CustomerProduct Id',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [220, 0]
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
jsCode: "// pass-through env + webhook fields (Code node reads $env reliably only here)\nconst prev = $input.item.json;\nreturn { json: {\n customerProductId: prev.customerProductId,\n eventType: prev.eventType,\n NOCODB_URL: $env.NOCODB_URL,\n NOCODB_TOKEN: $env.NOCODB_TOKEN,\n NOCODB_TABLE_CUSTOMERPRODUCTS: $env.NOCODB_TABLE_CUSTOMERPRODUCTS,\n NOCODB_TABLE_CUSTOMERS: $env.NOCODB_TABLE_CUSTOMERS,\n NOCODB_TABLE_PRODUCTS: $env.NOCODB_TABLE_PRODUCTS,\n NOCODB_TABLE_RESOURCES: $env.NOCODB_TABLE_RESOURCES,\n NOCODB_TABLE_ACCESSGRANTS: $env.NOCODB_TABLE_ACCESSGRANTS,\n NOCODB_LINK_PROD_RESOURCES: $env.NOCODB_LINK_PROD_RESOURCES,\n ALIST_URL: $env.ALIST_URL,\n ALIST_ADMIN_USERNAME: $env.ALIST_ADMIN_USERNAME,\n ALIST_ADMIN_PASSWORD: $env.ALIST_ADMIN_PASSWORD,\n} };\n"
|
||||
},
|
||||
id: 'load-env',
|
||||
name: 'Load Env',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [220, 0]
|
||||
},
|
||||
{
|
||||
parameters: { jsCode: body },
|
||||
id: 'compute-apply',
|
||||
name: 'Compute Effective Grants & Apply',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [440, 0]
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
conditions: {
|
||||
options: { caseSensitive: true, leftValue: '', typeName: 'boolean', ignoreCase: true },
|
||||
conditions: [
|
||||
{
|
||||
id: 'cond-skip',
|
||||
leftValue: "={{ $('Compute Effective Grants & Apply').item.json.skipWriteBack }}",
|
||||
rightValue: true,
|
||||
operator: { type: 'boolean', operation: 'equals', singleValue: true, rightType: 'boolean' }
|
||||
}
|
||||
],
|
||||
combinator: 'and'
|
||||
},
|
||||
options: {}
|
||||
},
|
||||
id: 'if-skip',
|
||||
name: 'Skip if no change',
|
||||
type: 'n8n-nodes-base.if',
|
||||
typeVersion: 2,
|
||||
position: [660, 0]
|
||||
},
|
||||
{
|
||||
parameters: {
|
||||
jsCode: "return { json: { skipped: true, customerId: $('Compute Effective Grants & Apply').item.json.customerId } };"
|
||||
},
|
||||
id: 'skip-echo',
|
||||
name: 'Skip Echo (No-op)',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [660, -200]
|
||||
}
|
||||
],
|
||||
connections: {
|
||||
'NocoDB: CustomerProduct Webhook': { main: [[{ node: 'Extract CustomerProduct Id', type: 'main', index: 0 }]] },
|
||||
'Extract CustomerProduct Id': { main: [[{ node: 'Load Env', type: 'main', index: 0 }]] },
|
||||
'Load Env': { main: [[{ node: 'Compute Effective Grants & Apply', type: 'main', index: 0 }]] },
|
||||
'Compute Effective Grants & Apply': { main: [[{ node: 'Skip if no change', type: 'main', index: 0 }]] },
|
||||
'Skip if no change': {
|
||||
main: [
|
||||
[{ node: 'Skip Echo (No-op)', type: 'main', index: 0 }],
|
||||
[] // no-change path terminates silently (nothing to do)
|
||||
]
|
||||
},
|
||||
'Skip Echo (No-op)': { main: [] }
|
||||
},
|
||||
settings: { executionOrder: 'v1' },
|
||||
pinData: {}
|
||||
};
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, 'W2-customerproduct-sync.json'), JSON.stringify(workflow, null, 2) + '\n');
|
||||
console.log('wrote W2-customerproduct-sync.json (' + body.length + ' chars body)');
|
||||
Reference in New Issue
Block a user