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';