Phase 1-3: AList stock deployment, NocoDB 7-table schema, W1/W2 n8n workflows + docs
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// 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 })),
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user