diff --git a/modules/rnetwork/components/folk-crm-view.ts b/modules/rnetwork/components/folk-crm-view.ts index b900feb..3beded7 100644 --- a/modules/rnetwork/components/folk-crm-view.ts +++ b/modules/rnetwork/components/folk-crm-view.ts @@ -657,10 +657,10 @@ class FolkCrmView extends HTMLElement { return `
- +
- +
`; } diff --git a/modules/rnetwork/components/folk-trust-sankey.ts b/modules/rnetwork/components/folk-trust-sankey.ts index f1f33ef..73f820f 100644 --- a/modules/rnetwork/components/folk-trust-sankey.ts +++ b/modules/rnetwork/components/folk-trust-sankey.ts @@ -73,44 +73,35 @@ class FolkTrustSankey extends HTMLElement { private async loadData() { const authBase = this.getAuthBase(); - const headers = this.getAuthHeaders(); + const apiBase = this.getApiBase(); try { - // Fetch outbound delegations for all users in the space - // We use the trust scores endpoint to get the flow data - const [outRes, inRes] = await Promise.all([ - fetch(`${authBase}/api/delegations/from?space=${encodeURIComponent(this.space)}`, { headers }), - fetch(`${authBase}/api/delegations/to?space=${encodeURIComponent(this.space)}`, { headers }), + // Fetch all space-level delegations and user directory in parallel + const [delegRes, usersRes] = await Promise.all([ + fetch(`${authBase}/api/delegations/space?space=${encodeURIComponent(this.space)}`), + fetch(`${apiBase}/api/users?space=${encodeURIComponent(this.space)}`), ]); const allFlows: DelegationFlow[] = []; - const seen = new Set(); - - for (const res of [outRes, inRes]) { - if (res.ok) { - const data = await res.json(); - for (const d of data.delegations || []) { - if (seen.has(d.id)) continue; - seen.add(d.id); - allFlows.push({ - id: d.id, - fromDid: d.delegatorDid, - fromName: d.delegatorDid.slice(0, 12) + "...", - toDid: d.delegateDid, - toName: d.delegateDid.slice(0, 12) + "...", - authority: d.authority, - weight: d.weight, - state: d.state, - createdAt: d.createdAt, - }); - } + if (delegRes.ok) { + const data = await delegRes.json(); + for (const d of data.delegations || []) { + allFlows.push({ + id: d.id, + fromDid: d.from, + fromName: d.from.slice(0, 12) + "...", + toDid: d.to, + toName: d.to.slice(0, 12) + "...", + authority: d.authority, + weight: d.weight, + state: "active", + createdAt: Date.now(), + }); } } this.flows = allFlows; - // Load user names - const apiBase = this.getApiBase(); - const usersRes = await fetch(`${apiBase}/api/users?space=${encodeURIComponent(this.space)}`); + // Resolve user display names if (usersRes.ok) { const userData = await usersRes.json(); const nameMap = new Map(); diff --git a/modules/rnetwork/mod.ts b/modules/rnetwork/mod.ts index c05ba3f..e8b1d87 100644 --- a/modules/rnetwork/mod.ts +++ b/modules/rnetwork/mod.ts @@ -189,8 +189,11 @@ routes.get("/api/graph", async (c) => { const dataSpace = (c.get("effectiveSpace" as any) as string) || space; const token = getTokenForSpace(dataSpace); - // Check per-space cache - const cached = graphCaches.get(dataSpace); + // Check per-space cache (keyed by space + trust params) + const includeTrust = c.req.query("trust") === "true"; + const authority = c.req.query("authority") || "voting"; + const cacheKey = includeTrust ? `${dataSpace}:trust:${authority}` : dataSpace; + const cached = graphCaches.get(cacheKey); if (cached && Date.now() - cached.ts < CACHE_TTL) { c.header("Cache-Control", "public, max-age=60"); return c.json(cached.data); @@ -289,15 +292,13 @@ routes.get("/api/graph", async (c) => { } // If trust=true, merge EncryptID user nodes + delegation edges - const includeTrust = c.req.query("trust") === "true"; - const authority = c.req.query("authority") || "voting"; - if (includeTrust) { const ENCRYPTID_URL = process.env.ENCRYPTID_INTERNAL_URL || "http://encryptid:3000"; try { - const [usersRes, scoresRes] = await Promise.all([ + const [usersRes, scoresRes, delegRes] = await Promise.all([ fetch(`${ENCRYPTID_URL}/api/users/directory?space=${encodeURIComponent(dataSpace)}`, { signal: AbortSignal.timeout(5000) }), fetch(`${ENCRYPTID_URL}/api/trust/scores?space=${encodeURIComponent(dataSpace)}&authority=${encodeURIComponent(authority)}`, { signal: AbortSignal.timeout(5000) }), + fetch(`${ENCRYPTID_URL}/api/delegations/space?space=${encodeURIComponent(dataSpace)}&authority=${encodeURIComponent(authority)}`, { signal: AbortSignal.timeout(5000) }), ]); if (usersRes.ok) { @@ -318,23 +319,31 @@ routes.get("/api/graph", async (c) => { if (scoresRes.ok) { const scoreData = await scoresRes.json() as { scores: Array<{ did: string; totalScore: number }> }; - // Build trust score lookup for node sizing const trustMap = new Map(); for (const s of scoreData.scores || []) { trustMap.set(s.did, s.totalScore); } - // Annotate existing nodes with trust scores for (const node of nodes) { if (trustMap.has(node.id)) { (node.data as any).trustScore = trustMap.get(node.id); } } } - } catch { /* trust enrichment is best-effort */ } + + // Add delegation edges + if (delegRes.ok) { + const delegData = await delegRes.json() as { delegations: Array<{ from: string; to: string; authority: string; weight: number }> }; + for (const d of delegData.delegations || []) { + if (nodeIds.has(d.from) && nodeIds.has(d.to)) { + edges.push({ source: d.from, target: d.to, type: "delegates_to", weight: d.weight } as any); + } + } + } + } catch (e) { console.error("[Network] Trust enrichment error:", e); } } const result = { nodes, edges, demo: false }; - graphCaches.set(dataSpace, { data: result, ts: Date.now() }); + graphCaches.set(cacheKey, { data: result, ts: Date.now() }); c.header("Cache-Control", "public, max-age=60"); return c.json(result); } catch (e) { diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index fbf68fb..59e3c00 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -7081,6 +7081,25 @@ app.delete('/api/delegations/:id', async (c) => { return c.json({ success: true }); }); +// GET /api/delegations/space — all active delegations for a space (internal use, no auth) +app.get('/api/delegations/space', async (c) => { + const spaceSlug = c.req.query('space'); + if (!spaceSlug) return c.json({ error: 'space query param required' }, 400); + const authority = c.req.query('authority'); + const delegations = await listActiveDelegations(spaceSlug, authority || undefined); + return c.json({ + delegations: delegations.map(d => ({ + id: d.id, + from: d.delegatorDid, + to: d.delegateDid, + authority: d.authority, + weight: d.weight, + })), + space: spaceSlug, + authority: authority || 'all', + }); +}); + // GET /api/trust/scores — aggregated trust scores for visualization app.get('/api/trust/scores', async (c) => { const authority = c.req.query('authority') || 'voting';