fix(rnetwork): trust data renders for spaces without CRM token

- Restructure graph API so trust enrichment runs regardless of whether
  Twenty CRM token is configured (demo space has no CRM token)
- Add missing listActiveDelegations import in encryptid server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-11 20:07:56 -07:00
parent 7ecb6f0417
commit 5d019566f3
2 changed files with 43 additions and 80 deletions

View File

@ -199,95 +199,57 @@ routes.get("/api/graph", async (c) => {
return c.json(cached.data); return c.json(cached.data);
} }
try {
// Start with CRM data if available, otherwise demo placeholders
const nodes: Array<{ id: string; label: string; type: string; data: unknown }> = [];
const edges: Array<{ source: string; target: string; type: string; weight?: number }> = [];
const nodeIds = new Set<string>();
let isDemoData = false;
if (!token) { if (!token) {
return c.json({ isDemoData = true;
nodes: [ nodes.push(
{ id: "demo-1", label: "Alice", type: "person", data: {} }, { id: "demo-1", label: "Alice", type: "person", data: {} },
{ id: "demo-2", label: "Bob", type: "person", data: {} }, { id: "demo-2", label: "Bob", type: "person", data: {} },
{ id: "demo-3", label: "Acme Corp", type: "company", data: {} }, { id: "demo-3", label: "Acme Corp", type: "company", data: {} },
], );
edges: [ edges.push(
{ source: "demo-1", target: "demo-3", type: "works_at" }, { source: "demo-1", target: "demo-3", type: "works_at" },
{ source: "demo-2", target: "demo-3", type: "works_at" }, { source: "demo-2", target: "demo-3", type: "works_at" },
{ source: "demo-1", target: "demo-2", type: "contact_of" }, { source: "demo-1", target: "demo-2", type: "contact_of" },
], );
demo: true, for (const n of nodes) nodeIds.add(n.id);
}); } else {
}
try {
const data = await twentyQuery(`{ const data = await twentyQuery(`{
people(first: 200) { people(first: 200) {
edges { edges { node { id name { firstName lastName } emails { primaryEmail } city company { id name } } }
node {
id
name { firstName lastName }
emails { primaryEmail }
city
company { id name }
}
}
} }
companies(first: 200) { companies(first: 200) {
edges { edges { node { id name domainName { primaryLinkUrl } employees address { addressCity addressCountry } } }
node {
id
name
domainName { primaryLinkUrl }
employees
address { addressCity addressCountry }
}
}
} }
opportunities(first: 200) { opportunities(first: 200) {
edges { edges { node { id name stage amount { amountMicros currencyCode } company { id name } pointOfContact { id name { firstName lastName } } } }
node {
id
name
stage
amount { amountMicros currencyCode }
company { id name }
pointOfContact { id name { firstName lastName } }
}
}
} }
}`, undefined, dataSpace); }`, undefined, dataSpace);
if (!data) return c.json({ nodes: [], edges: [], error: "Twenty API error" }); if (data) {
const d = data as any; const d = data as any;
const nodes: Array<{ id: string; label: string; type: string; data: unknown }> = [];
const edges: Array<{ source: string; target: string; type: string }> = [];
const nodeIds = new Set<string>();
// People → nodes
for (const { node: p } of d.people?.edges || []) { for (const { node: p } of d.people?.edges || []) {
const label = [p.name?.firstName, p.name?.lastName].filter(Boolean).join(" ") || "Unknown"; const label = [p.name?.firstName, p.name?.lastName].filter(Boolean).join(" ") || "Unknown";
nodes.push({ id: p.id, label, type: "person", data: { email: p.emails?.primaryEmail, location: p.city } }); nodes.push({ id: p.id, label, type: "person", data: { email: p.emails?.primaryEmail, location: p.city } });
nodeIds.add(p.id); nodeIds.add(p.id);
if (p.company?.id) edges.push({ source: p.id, target: p.company.id, type: "works_at" });
// Person → Company edge
if (p.company?.id) {
edges.push({ source: p.id, target: p.company.id, type: "works_at" });
} }
}
// Companies → nodes
for (const { node: co } of d.companies?.edges || []) { for (const { node: co } of d.companies?.edges || []) {
nodes.push({ id: co.id, label: co.name || "Unknown", type: "company", data: { domain: co.domainName?.primaryLinkUrl, employees: co.employees, location: co.address?.addressCity } }); nodes.push({ id: co.id, label: co.name || "Unknown", type: "company", data: { domain: co.domainName?.primaryLinkUrl, employees: co.employees, location: co.address?.addressCity } });
nodeIds.add(co.id); nodeIds.add(co.id);
} }
// Opportunities → nodes + edges
for (const { node: opp } of d.opportunities?.edges || []) { for (const { node: opp } of d.opportunities?.edges || []) {
nodes.push({ id: opp.id, label: opp.name || "Opportunity", type: "opportunity", data: { stage: opp.stage, amount: opp.amount } }); nodes.push({ id: opp.id, label: opp.name || "Opportunity", type: "opportunity", data: { stage: opp.stage, amount: opp.amount } });
nodeIds.add(opp.id); nodeIds.add(opp.id);
if (opp.company?.id && nodeIds.has(opp.company.id)) edges.push({ source: opp.id, target: opp.company.id, type: "involves" });
if (opp.company?.id && nodeIds.has(opp.company.id)) { if (opp.pointOfContact?.id && nodeIds.has(opp.pointOfContact.id)) edges.push({ source: opp.pointOfContact.id, target: opp.id, type: "involved_in" });
edges.push({ source: opp.id, target: opp.company.id, type: "involves" });
} }
if (opp.pointOfContact?.id && nodeIds.has(opp.pointOfContact.id)) {
edges.push({ source: opp.pointOfContact.id, target: opp.id, type: "involved_in" });
} }
} }
@ -342,7 +304,7 @@ routes.get("/api/graph", async (c) => {
} catch (e) { console.error("[Network] Trust enrichment error:", e); } } catch (e) { console.error("[Network] Trust enrichment error:", e); }
} }
const result = { nodes, edges, demo: false }; const result = { nodes, edges, demo: isDemoData && !includeTrust };
graphCaches.set(cacheKey, { data: result, ts: Date.now() }); graphCaches.set(cacheKey, { data: result, ts: Date.now() });
c.header("Cache-Control", "public, max-age=60"); c.header("Cache-Control", "public, max-age=60");
return c.json(result); return c.json(result);

View File

@ -105,6 +105,7 @@ import {
getDelegation, getDelegation,
listDelegationsFrom, listDelegationsFrom,
listDelegationsTo, listDelegationsTo,
listActiveDelegations,
updateDelegation, updateDelegation,
revokeDelegation, revokeDelegation,
getTotalDelegatedWeight, getTotalDelegatedWeight,