fix(rnetwork): trust data rendering in graph and delegation views
- Fix graph cache keying: include trust/authority params so cached non-trust responses don't shadow trust-enriched requests - Add /api/delegations/space endpoint to EncryptID for space-level delegation listing (no auth required, for graph/sankey) - Fetch and include delegates_to edges in graph API response - Pass auth-url attribute to delegation manager and sankey components - Rewrite sankey loadData to use space-level delegation endpoint instead of per-user endpoints (shows all flows, not just current user) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
42c6dea091
commit
913f9aa4d0
|
|
@ -657,10 +657,10 @@ class FolkCrmView extends HTMLElement {
|
||||||
return `
|
return `
|
||||||
<div class="delegations-layout">
|
<div class="delegations-layout">
|
||||||
<div class="delegations-col">
|
<div class="delegations-col">
|
||||||
<folk-delegation-manager space="${this.space}"></folk-delegation-manager>
|
<folk-delegation-manager space="${this.space}" auth-url="https://auth.rspace.online"></folk-delegation-manager>
|
||||||
</div>
|
</div>
|
||||||
<div class="delegations-col">
|
<div class="delegations-col">
|
||||||
<folk-trust-sankey space="${this.space}"></folk-trust-sankey>
|
<folk-trust-sankey space="${this.space}" auth-url="https://auth.rspace.online"></folk-trust-sankey>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,44 +73,35 @@ class FolkTrustSankey extends HTMLElement {
|
||||||
|
|
||||||
private async loadData() {
|
private async loadData() {
|
||||||
const authBase = this.getAuthBase();
|
const authBase = this.getAuthBase();
|
||||||
const headers = this.getAuthHeaders();
|
const apiBase = this.getApiBase();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch outbound delegations for all users in the space
|
// Fetch all space-level delegations and user directory in parallel
|
||||||
// We use the trust scores endpoint to get the flow data
|
const [delegRes, usersRes] = await Promise.all([
|
||||||
const [outRes, inRes] = await Promise.all([
|
fetch(`${authBase}/api/delegations/space?space=${encodeURIComponent(this.space)}`),
|
||||||
fetch(`${authBase}/api/delegations/from?space=${encodeURIComponent(this.space)}`, { headers }),
|
fetch(`${apiBase}/api/users?space=${encodeURIComponent(this.space)}`),
|
||||||
fetch(`${authBase}/api/delegations/to?space=${encodeURIComponent(this.space)}`, { headers }),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const allFlows: DelegationFlow[] = [];
|
const allFlows: DelegationFlow[] = [];
|
||||||
const seen = new Set<string>();
|
if (delegRes.ok) {
|
||||||
|
const data = await delegRes.json();
|
||||||
for (const res of [outRes, inRes]) {
|
for (const d of data.delegations || []) {
|
||||||
if (res.ok) {
|
allFlows.push({
|
||||||
const data = await res.json();
|
id: d.id,
|
||||||
for (const d of data.delegations || []) {
|
fromDid: d.from,
|
||||||
if (seen.has(d.id)) continue;
|
fromName: d.from.slice(0, 12) + "...",
|
||||||
seen.add(d.id);
|
toDid: d.to,
|
||||||
allFlows.push({
|
toName: d.to.slice(0, 12) + "...",
|
||||||
id: d.id,
|
authority: d.authority,
|
||||||
fromDid: d.delegatorDid,
|
weight: d.weight,
|
||||||
fromName: d.delegatorDid.slice(0, 12) + "...",
|
state: "active",
|
||||||
toDid: d.delegateDid,
|
createdAt: Date.now(),
|
||||||
toName: d.delegateDid.slice(0, 12) + "...",
|
});
|
||||||
authority: d.authority,
|
|
||||||
weight: d.weight,
|
|
||||||
state: d.state,
|
|
||||||
createdAt: d.createdAt,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.flows = allFlows;
|
this.flows = allFlows;
|
||||||
|
|
||||||
// Load user names
|
// Resolve user display names
|
||||||
const apiBase = this.getApiBase();
|
|
||||||
const usersRes = await fetch(`${apiBase}/api/users?space=${encodeURIComponent(this.space)}`);
|
|
||||||
if (usersRes.ok) {
|
if (usersRes.ok) {
|
||||||
const userData = await usersRes.json();
|
const userData = await usersRes.json();
|
||||||
const nameMap = new Map<string, string>();
|
const nameMap = new Map<string, string>();
|
||||||
|
|
|
||||||
|
|
@ -189,8 +189,11 @@ routes.get("/api/graph", async (c) => {
|
||||||
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
|
||||||
const token = getTokenForSpace(dataSpace);
|
const token = getTokenForSpace(dataSpace);
|
||||||
|
|
||||||
// Check per-space cache
|
// Check per-space cache (keyed by space + trust params)
|
||||||
const cached = graphCaches.get(dataSpace);
|
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) {
|
if (cached && Date.now() - cached.ts < CACHE_TTL) {
|
||||||
c.header("Cache-Control", "public, max-age=60");
|
c.header("Cache-Control", "public, max-age=60");
|
||||||
return c.json(cached.data);
|
return c.json(cached.data);
|
||||||
|
|
@ -289,15 +292,13 @@ routes.get("/api/graph", async (c) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If trust=true, merge EncryptID user nodes + delegation edges
|
// 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) {
|
if (includeTrust) {
|
||||||
const ENCRYPTID_URL = process.env.ENCRYPTID_INTERNAL_URL || "http://encryptid:3000";
|
const ENCRYPTID_URL = process.env.ENCRYPTID_INTERNAL_URL || "http://encryptid:3000";
|
||||||
try {
|
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/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/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) {
|
if (usersRes.ok) {
|
||||||
|
|
@ -318,23 +319,31 @@ routes.get("/api/graph", async (c) => {
|
||||||
|
|
||||||
if (scoresRes.ok) {
|
if (scoresRes.ok) {
|
||||||
const scoreData = await scoresRes.json() as { scores: Array<{ did: string; totalScore: number }> };
|
const scoreData = await scoresRes.json() as { scores: Array<{ did: string; totalScore: number }> };
|
||||||
// Build trust score lookup for node sizing
|
|
||||||
const trustMap = new Map<string, number>();
|
const trustMap = new Map<string, number>();
|
||||||
for (const s of scoreData.scores || []) {
|
for (const s of scoreData.scores || []) {
|
||||||
trustMap.set(s.did, s.totalScore);
|
trustMap.set(s.did, s.totalScore);
|
||||||
}
|
}
|
||||||
// Annotate existing nodes with trust scores
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
if (trustMap.has(node.id)) {
|
if (trustMap.has(node.id)) {
|
||||||
(node.data as any).trustScore = trustMap.get(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 };
|
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");
|
c.header("Cache-Control", "public, max-age=60");
|
||||||
return c.json(result);
|
return c.json(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -7081,6 +7081,25 @@ app.delete('/api/delegations/:id', async (c) => {
|
||||||
return c.json({ success: true });
|
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
|
// GET /api/trust/scores — aggregated trust scores for visualization
|
||||||
app.get('/api/trust/scores', async (c) => {
|
app.get('/api/trust/scores', async (c) => {
|
||||||
const authority = c.req.query('authority') || 'voting';
|
const authority = c.req.query('authority') || 'voting';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue