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);
} }
if (!token) { try {
return c.json({ // Start with CRM data if available, otherwise demo placeholders
nodes: [ 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) {
isDemoData = true;
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 {
} const data = await twentyQuery(`{
people(first: 200) {
try { edges { node { id name { firstName lastName } emails { primaryEmail } city company { id name } } }
const data = await twentyQuery(`{
people(first: 200) {
edges {
node {
id
name { firstName lastName }
emails { primaryEmail }
city
company { id name }
}
} }
} companies(first: 200) {
companies(first: 200) { edges { node { id name domainName { primaryLinkUrl } employees address { addressCity addressCountry } } }
edges {
node {
id
name
domainName { primaryLinkUrl }
employees
address { addressCity addressCountry }
}
} }
} opportunities(first: 200) {
opportunities(first: 200) { edges { node { id name stage amount { amountMicros currencyCode } company { id name } pointOfContact { id name { firstName lastName } } } }
edges {
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; for (const { node: p } of d.people?.edges || []) {
const nodes: Array<{ id: string; label: string; type: string; data: unknown }> = []; const label = [p.name?.firstName, p.name?.lastName].filter(Boolean).join(" ") || "Unknown";
const edges: Array<{ source: string; target: string; type: string }> = []; nodes.push({ id: p.id, label, type: "person", data: { email: p.emails?.primaryEmail, location: p.city } });
const nodeIds = new Set<string>(); nodeIds.add(p.id);
if (p.company?.id) edges.push({ source: p.id, target: p.company.id, type: "works_at" });
// People → nodes }
for (const { node: p } of d.people?.edges || []) { for (const { node: co } of d.companies?.edges || []) {
const label = [p.name?.firstName, p.name?.lastName].filter(Boolean).join(" ") || "Unknown"; 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: p.id, label, type: "person", data: { email: p.emails?.primaryEmail, location: p.city } }); nodeIds.add(co.id);
nodeIds.add(p.id); }
for (const { node: opp } of d.opportunities?.edges || []) {
// Person → Company edge nodes.push({ id: opp.id, label: opp.name || "Opportunity", type: "opportunity", data: { stage: opp.stage, amount: opp.amount } });
if (p.company?.id) { nodeIds.add(opp.id);
edges.push({ source: p.id, target: p.company.id, type: "works_at" }); if (opp.company?.id && nodeIds.has(opp.company.id)) 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" });
} }
// Companies → nodes
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 } });
nodeIds.add(co.id);
}
// Opportunities → nodes + 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 } });
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.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,