diff --git a/server/index.ts b/server/index.ts index 8e36977..8846965 100644 --- a/server/index.ts +++ b/server/index.ts @@ -452,7 +452,7 @@ app.all("/encryptid/*", async (c) => { } }); -// ── User API proxy (forward /api/user/* to EncryptID for tab state, prefs) ── +// ── User API proxy (forward /api/user/* and /api/users/* to EncryptID) ── app.all("/api/user/*", async (c) => { const targetUrl = `${ENCRYPTID_INTERNAL}${c.req.path}${new URL(c.req.url).search}`; const headers = new Headers(c.req.raw.headers); @@ -470,6 +470,23 @@ app.all("/api/user/*", async (c) => { return c.json({ error: "EncryptID service unavailable" }, 502); } }); +app.all("/api/users/*", async (c) => { + const targetUrl = `${ENCRYPTID_INTERNAL}${c.req.path}${new URL(c.req.url).search}`; + const headers = new Headers(c.req.raw.headers); + headers.delete("host"); + try { + const res = await fetch(targetUrl, { + method: c.req.method, + headers, + body: c.req.method !== "GET" && c.req.method !== "HEAD" ? c.req.raw.body : undefined, + // @ts-ignore duplex needed for streaming request bodies + duplex: "half", + }); + return new Response(res.body, { status: res.status, headers: res.headers }); + } catch (e: any) { + return c.json({ error: "EncryptID service unavailable" }, 502); + } +}); // ── Existing /api/communities/* routes (backward compatible) ── diff --git a/shared/components/rstack-space-settings.ts b/shared/components/rstack-space-settings.ts index f4f3f5f..a951627 100644 --- a/shared/components/rstack-space-settings.ts +++ b/shared/components/rstack-space-settings.ts @@ -142,6 +142,26 @@ export class RStackSpaceSettings extends HTMLElement { } catch {} } + // Resolve missing displayNames from EncryptID + const unresolvedDids = this._members.filter(m => !m.displayName).map(m => m.did); + if (unresolvedDids.length && token) { + try { + const res = await fetch("/api/users/resolve-dids", { + method: "POST", + headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }, + body: JSON.stringify({ dids: unresolvedDids }), + }); + if (res.ok) { + const resolved = await res.json() as Record; + for (const m of this._members) { + if (!m.displayName && resolved[m.did]) { + m.displayName = resolved[m.did].displayName || resolved[m.did].username; + } + } + } + } catch {} + } + // Determine my role if (session?.claims?.sub) { this._isOwner = session.claims.sub === this._ownerDID; diff --git a/src/encryptid/server.ts b/src/encryptid/server.ts index 8682169..6ae4c2a 100644 --- a/src/encryptid/server.ts +++ b/src/encryptid/server.ts @@ -3817,6 +3817,26 @@ app.get('/api/users/lookup', async (c) => { }); }); +// POST /api/users/resolve-dids — batch-resolve DIDs to usernames +app.post('/api/users/resolve-dids', async (c) => { + const claims = await verifyTokenFromRequest(c.req.header('Authorization')); + if (!claims) return c.json({ error: 'Authentication required' }, 401); + + const body = await c.req.json(); + const dids: string[] = Array.isArray(body.dids) ? body.dids.slice(0, 100) : []; + if (!dids.length) return c.json({}); + + // Query all matching users in one go + const rows = await sql`SELECT id, did, username, display_name FROM users WHERE did = ANY(${dids}) OR id = ANY(${dids})`; + const result: Record = {}; + for (const row of rows) { + const entry = { username: row.username, displayName: row.display_name || row.username }; + if (row.did) result[row.did] = entry; + result[row.id] = entry; + } + return c.json(result); +}); + // ============================================================================ // SPACE INVITE ROUTES // ============================================================================