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 `
|
||||
<div class="delegations-layout">
|
||||
<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 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>`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
|
||||
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<string, string>();
|
||||
|
|
|
|||
|
|
@ -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<string, number>();
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
Loading…
Reference in New Issue