81 lines
2.9 KiB
JavaScript
81 lines
2.9 KiB
JavaScript
// ============================================================================
|
|
// W5 runtime body — footer purchase/expiry check (public, read-only).
|
|
// Query: GET /dk-purchase-check?u=<username>&k=<shared-key>
|
|
// Returns: [{ product, expires_at, status }] for the customer's products,
|
|
// or { error } when username unknown / key wrong.
|
|
// ============================================================================
|
|
|
|
// n8n Code node context: ctx = { $env, helpers }
|
|
const ctx = { $env, helpers: this.helpers };
|
|
|
|
// --- input from the "Extract Query" node (already validated the shared key) ---
|
|
const username = String($json.username || '').trim();
|
|
|
|
// --- helpers (self-contained; no shared lib needed for pure reads) ---
|
|
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;
|
|
}
|
|
|
|
async function ncGet(path) {
|
|
return await ctx.helpers.httpRequest({
|
|
method: 'GET',
|
|
url: ctx.$env.NOCODB_URL + path,
|
|
headers: { 'xc-token': ctx.$env.NOCODB_TOKEN },
|
|
json: true,
|
|
});
|
|
}
|
|
|
|
const T_CUST = ctx.$env.NOCODB_TABLE_CUSTOMERS;
|
|
const T_CP = ctx.$env.NOCODB_TABLE_CUSTOMERPRODUCTS;
|
|
|
|
// 1. resolve username -> customer id (username is unique in Customers)
|
|
const custRows = (await ncGet(
|
|
`/api/v2/tables/${T_CUST}/records?where=(username,eq,${encodeURIComponent(username)})&limit=1`
|
|
)).list || [];
|
|
const customer = custRows[0];
|
|
|
|
if (!customer) {
|
|
return { json: { username, purchases: [], known: false } };
|
|
}
|
|
|
|
// 2. fetch this customer's products (active + not-yet-expired) OR all for display
|
|
const cps = (await ncGet(
|
|
`/api/v2/tables/${T_CP}/records?where=(customer.Id,eq,${customer.Id})&limit=1000`
|
|
)).list || [];
|
|
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
const purchases = cps
|
|
.map(c => {
|
|
const productObj = (typeof c.product === 'object' && c.product) ? c.product : null;
|
|
const name = productObj?.name ?? 'Product';
|
|
const expires = c.expires_at ? String(c.expires_at).slice(0, 10) : null;
|
|
let state = 'active';
|
|
if (c.status === 'active' && expires && expires <= today) state = 'expired';
|
|
else if (c.status && c.status !== 'active') state = c.status;
|
|
|
|
return {
|
|
product: name,
|
|
product_id: linkId(c.product),
|
|
expires_at: expires,
|
|
status: c.status || null,
|
|
display_state: state, // active / expired / revoked / suspended
|
|
};
|
|
})
|
|
// sort: active (expiring soonest first), then expired (most recent expiry first)
|
|
.sort((a, b) => {
|
|
const rank = { active: 0, suspended: 1, revoked: 2, expired: 3 };
|
|
if (rank[a.display_state] !== rank[b.display_state]) return rank[a.display_state] - rank[b.display_state];
|
|
return String(a.expires_at || '').localeCompare(String(b.expires_at || ''));
|
|
});
|
|
|
|
return {
|
|
json: {
|
|
username,
|
|
known: true,
|
|
purchases,
|
|
},
|
|
};
|