// ============================================================================ // W5 runtime body — footer purchase/expiry check (public, read-only). // Query: GET /dk-purchase-check?u=&k= // 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, }, };