Phase 1-3: AList stock deployment, NocoDB 7-table schema, W1/W2 n8n workflows + docs

This commit is contained in:
2026-08-22 19:26:06 +08:00
commit 71acb88078
21 changed files with 2127 additions and 0 deletions
+591
View File
@@ -0,0 +1,591 @@
{
"name": "W1 - Customer Provision (AList User + Role)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "alist-customer-provision",
"responseMode": "onReceived",
"options": {}
},
"id": "trigger-webhook",
"name": "NocoDB: Customer Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
0
],
"webhookId": "customer-provision"
},
{
"parameters": {
"jsCode": "const body = $input.item.json.body ?? $input.item.json;\nconst data = body.data ?? {};\nconst rows = data.rows?.[0] ?? data.previous_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 customer Id in webhook');\nreturn { json: { customerId: Number(id), eventType: body.type ?? '' } };\n"
},
"id": "extract-id",
"name": "Extract Customer Record Id",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
0
]
},
{
"parameters": {
"jsCode": "// Step 2: read ALL env via process.env (works in Code node), and pass through\n// the webhook-derived fields so downstream keeps customerId.\nconst e = process.env;\nconst prev = $input.item.json;\nreturn { json: {\n customerId: prev.customerId,\n eventType: prev.eventType,\n NOCODB_URL: e.NOCODB_URL,\n NOCODB_TOKEN: e.NOCODB_TOKEN,\n T_CUSTOMERS: e.NOCODB_TABLE_CUSTOMERS,\n ALIST_URL: e.ALIST_URL,\n ALIST_ADMIN_USERNAME: e.ALIST_ADMIN_USERNAME,\n ALIST_ADMIN_PASSWORD: e.ALIST_ADMIN_PASSWORD,\n ALIST_ADMIN_OTP: e.ALIST_ADMIN_OTP || '',\n} };\n"
},
"id": "load-env",
"name": "Load Env",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
220,
0
]
},
{
"parameters": {
"method": "GET",
"url": "={{ $('Load Env').item.json.NOCODB_URL }}/api/v2/tables/{{ $('Load Env').item.json.T_CUSTOMERS }}/records/{{ $json.customerId }}",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "xc-token",
"value": "{{ $('Load Env').item.json.NOCODB_TOKEN }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "fetch-customer",
"name": "Fetch Latest Customer Record",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
0
]
},
{
"parameters": {
"jsCode": "const cust = $input.item.json;\nconst username = cust.username; const password = cust.password;\nif (!username) throw new Error('Customer missing username');\nreturn { json: { customerId: cust.Id ?? cust.id, username, password, displayName: cust.display_name ?? username, basePath: cust.base_path ?? '/Class', alistUserId: cust.alist_user_id ?? null, alistRoleId: cust.alist_role_id ?? null, isCreate: !(cust.alist_user_id) } };\n"
},
"id": "prepare",
"name": "Prepare Provision Payload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
0
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Load Env').item.json.ALIST_URL }}/api/auth/login",
"authentication": "none",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ username: $('Load Env').item.json.ALIST_ADMIN_USERNAME, password: $('Load Env').item.json.ALIST_ADMIN_PASSWORD }) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "alist-login",
"name": "AList Admin Login",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
880,
0
]
},
{
"parameters": {
"jsCode": "const resp = $input.item.json;\nconst token = resp?.data?.token;\nif (!token) throw new Error('AList admin login failed: ' + JSON.stringify(resp));\nreturn { json: { token } };\n"
},
"id": "extract-token",
"name": "Extract Admin Token",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1100,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeName": "boolean",
"ignoreCase": true
},
"conditions": [
{
"id": "cond-create",
"leftValue": "={{ $('Prepare Provision Payload').item.json.isCreate }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals",
"singleValue": true,
"rightType": "boolean"
}
}
],
"combinator": "or"
},
"options": {}
},
"id": "branch-create-update",
"name": "Create vs Update",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1320,
0
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Load Env').item.json.ALIST_URL }}/api/admin/role/create",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Extract Admin Token').item.json.token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ name: 'customer-' + $('Prepare Provision Payload').item.json.username, description: 'Auto role for ' + $('Prepare Provision Payload').item.json.username, default: false, permission_scopes: [] }) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "alist-create-role",
"name": "AList Create Role",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1540,
-240
]
},
{
"parameters": {
"method": "GET",
"url": "={{ $('Load Env').item.json.ALIST_URL }}/api/admin/role/list?page=1&per_page=100",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Extract Admin Token').item.json.token }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "alist-list-roles",
"name": "AList List Roles (find id)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1760,
-240
]
},
{
"parameters": {
"jsCode": "const roles = $input.item.json?.data?.content ?? [];\nconst target = 'customer-' + $('Prepare Provision Payload').item.json.username;\nconst r = roles.find(x => x.name === target);\nif (!r) throw new Error('Role not found: ' + target);\nreturn { json: { alistRoleId: r.id, roleName: target } };\n"
},
"id": "find-role-id",
"name": "Find New Role Id",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1980,
-240
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Load Env').item.json.ALIST_URL }}/api/admin/user/create",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Extract Admin Token').item.json.token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ username: $('Prepare Provision Payload').item.json.username, password: $('Prepare Provision Payload').item.json.password, base_path: $('Prepare Provision Payload').item.json.basePath, role: [$('Find New Role Id').item.json.alistRoleId], permission: 16384 }) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "alist-create-user",
"name": "AList Create User",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2200,
-240
]
},
{
"parameters": {
"method": "GET",
"url": "={{ $('Load Env').item.json.ALIST_URL }}/api/admin/user/list?page=1&per_page=100",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Extract Admin Token').item.json.token }}"
}
]
},
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "alist-list-users",
"name": "AList List Users (find id)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2420,
-240
]
},
{
"parameters": {
"jsCode": "const users = $input.item.json?.data?.content ?? [];\nconst target = $('Prepare Provision Payload').item.json.username;\nconst u = users.find(x => x.username === target);\nif (!u) throw new Error('Created user not found: ' + target);\nreturn { json: { alistUserId: u.id, username: target } };\n"
},
"id": "find-user-id",
"name": "Find New User Id",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2640,
-240
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $('Load Env').item.json.NOCODB_URL }}/api/v2/tables/{{ $('Load Env').item.json.T_CUSTOMERS }}/records",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "xc-token",
"value": "{{ $('Load Env').item.json.NOCODB_TOKEN }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify([{ Id: $('Prepare Provision Payload').item.json.customerId, alist_user_id: $('Find New User Id').item.json.alistUserId, alist_role_id: $('Find New Role Id').item.json.alistRoleId, sync_status: 'synced', last_sync: new Date().toISOString(), last_sync_error: null }]) }}",
"options": {}
},
"id": "nocodb-writeback-create",
"name": "Write Back (Created)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2860,
-240
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $('Load Env').item.json.ALIST_URL }}/api/admin/user/update",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "={{ $('Extract Admin Token').item.json.token }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ id: $('Prepare Provision Payload').item.json.alistUserId, username: $('Prepare Provision Payload').item.json.username, password: $('Prepare Provision Payload').item.json.password, base_path: $('Prepare Provision Payload').item.json.basePath, role: $('Prepare Provision Payload').item.json.alistRoleId ? [$('Prepare Provision Payload').item.json.alistRoleId] : [], permission: 16384 }) }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"id": "alist-update-user",
"name": "AList Update User",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1540,
0
]
},
{
"parameters": {
"method": "PATCH",
"url": "={{ $('Load Env').item.json.NOCODB_URL }}/api/v2/tables/{{ $('Load Env').item.json.T_CUSTOMERS }}/records",
"authentication": "none",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "xc-token",
"value": "{{ $('Load Env').item.json.NOCODB_TOKEN }}"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify([{ Id: $('Prepare Provision Payload').item.json.customerId, sync_status: 'synced', last_sync: new Date().toISOString(), last_sync_error: null }]) }}",
"options": {}
},
"id": "nocodb-writeback-update",
"name": "Write Back (Updated)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1760,
0
]
}
],
"connections": {
"NocoDB: Customer Webhook": {
"main": [
[
{
"node": "Extract Customer Record Id",
"type": "main",
"index": 0
}
]
]
},
"Extract Customer Record Id": {
"main": [
[
{
"node": "Load Env",
"type": "main",
"index": 0
}
]
]
},
"Fetch Latest Customer Record": {
"main": [
[
{
"node": "Prepare Provision Payload",
"type": "main",
"index": 0
}
]
]
},
"Prepare Provision Payload": {
"main": [
[
{
"node": "AList Admin Login",
"type": "main",
"index": 0
}
]
]
},
"AList Admin Login": {
"main": [
[
{
"node": "Extract Admin Token",
"type": "main",
"index": 0
}
]
]
},
"Extract Admin Token": {
"main": [
[
{
"node": "Create vs Update",
"type": "main",
"index": 0
}
]
]
},
"Create vs Update": {
"main": [
[
{
"node": "AList Create Role",
"type": "main",
"index": 0
}
],
[
{
"node": "AList Update User",
"type": "main",
"index": 0
}
]
]
},
"AList Create Role": {
"main": [
[
{
"node": "AList List Roles (find id)",
"type": "main",
"index": 0
}
]
]
},
"AList List Roles (find id)": {
"main": [
[
{
"node": "Find New Role Id",
"type": "main",
"index": 0
}
]
]
},
"Find New Role Id": {
"main": [
[
{
"node": "AList Create User",
"type": "main",
"index": 0
}
]
]
},
"AList Create User": {
"main": [
[
{
"node": "AList List Users (find id)",
"type": "main",
"index": 0
}
]
]
},
"AList List Users (find id)": {
"main": [
[
{
"node": "Find New User Id",
"type": "main",
"index": 0
}
]
]
},
"Find New User Id": {
"main": [
[
{
"node": "Write Back (Created)",
"type": "main",
"index": 0
}
]
]
},
"Write Back (Created)": {
"main": []
},
"AList Update User": {
"main": [
[
{
"node": "Write Back (Updated)",
"type": "main",
"index": 0
}
]
]
},
"Write Back (Updated)": {
"main": []
},
"Load Env": {
"main": [
[
{
"node": "Fetch Latest Customer Record",
"type": "main",
"index": 0
}
]
]
}
}
}