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);
}
if (!token) {
return c.json({
nodes: [
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) {
isDemoData = true;
nodes.push(
{ id: "demo-1", label: "Alice", type: "person", data: {} },
{ id: "demo-2", label: "Bob", type: "person", data: {} },
{ id: "demo-3", label: "Acme Corp", type: "company", data: {} },
],
edges: [
);
edges.push(
{ source: "demo-1", target: "demo-3", type: "works_at" },
{ source: "demo-2", target: "demo-3", type: "works_at" },
{ source: "demo-1", target: "demo-2", type: "contact_of" },
],
demo: true,
});
}
try {
const data = await twentyQuery(`{
people(first: 200) {
edges {
node {
id
name { firstName lastName }
emails { primaryEmail }
city
company { id name }
}
);
for (const n of nodes) nodeIds.add(n.id);
} else {
const data = await twentyQuery(`{
people(first: 200) {
edges { node { id name { firstName lastName } emails { primaryEmail } city company { id name } } }
}
}
companies(first: 200) {
edges {
node {
id
name
domainName { primaryLinkUrl }
employees
address { addressCity addressCountry }
}
companies(first: 200) {
edges { node { id name domainName { primaryLinkUrl } employees address { addressCity addressCountry } } }
}
}
opportunities(first: 200) {
edges {
node {
id
name
stage
amount { amountMicros currencyCode }
company { id name }
pointOfContact { id name { firstName lastName } }
}
opportunities(first: 200) {
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" });
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 || []) {
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 } });
nodeIds.add(p.id);
// 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 || []) {
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" });
if (data) {
const d = data as any;
for (const { node: p } of d.people?.edges || []) {
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 } });
nodeIds.add(p.id);
if (p.company?.id) edges.push({ source: p.id, target: p.company.id, type: "works_at" });
}
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);
}
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); }
}
const result = { nodes, edges, demo: false };
const result = { nodes, edges, demo: isDemoData && !includeTrust };
graphCaches.set(cacheKey, { data: result, ts: Date.now() });
c.header("Cache-Control", "public, max-age=60");
return c.json(result);

View File

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