From 858457c0564a98f50a8a4ad77693529ff34587b4 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 25 Mar 2026 15:24:26 -0700 Subject: [PATCH] fix(invites): show email invites in space settings pending list listSpaceInvites now queries both space_invites and identity_invites tables, merging results so email-based invites (via /invite endpoint) appear in the Pending Invites section. revokeSpaceInvite also falls through to identity_invites if not found in space_invites. Co-Authored-By: Claude Opus 4.6 --- src/encryptid/db.ts | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/encryptid/db.ts b/src/encryptid/db.ts index 273ad3c..135d37b 100644 --- a/src/encryptid/db.ts +++ b/src/encryptid/db.ts @@ -1067,12 +1067,28 @@ export async function getSpaceInviteByToken(token: string): Promise { - const rows = await sql` - SELECT * FROM space_invites - WHERE space_slug = ${spaceSlug} - ORDER BY created_at DESC - `; - return Promise.all(rows.map(rowToInvite)); + const [spaceRows, identityRows] = await Promise.all([ + sql`SELECT * FROM space_invites WHERE space_slug = ${spaceSlug} ORDER BY created_at DESC`, + sql`SELECT * FROM identity_invites WHERE space_slug = ${spaceSlug} ORDER BY created_at DESC`, + ]); + const spaceInvites = await Promise.all(spaceRows.map(rowToInvite)); + const identityInvites = await Promise.all(identityRows.map(async (row: any) => { + const emailDecrypted = await decryptField(row.email_enc); + return { + id: row.id, + spaceSlug: row.space_slug, + email: emailDecrypted ?? row.email ?? null, + role: row.space_role || 'member', + token: row.token, + invitedBy: row.invited_by_user_id, + status: row.status === 'claimed' ? 'accepted' : row.status, + createdAt: new Date(row.created_at).getTime(), + expiresAt: new Date(row.expires_at).getTime(), + acceptedAt: row.claimed_at ? new Date(row.claimed_at).getTime() : null, + acceptedByDid: row.claimed_by_user_id || null, + } as StoredSpaceInvite; + })); + return [...spaceInvites, ...identityInvites].sort((a, b) => b.createdAt - a.createdAt); } export async function acceptSpaceInvite(token: string, acceptedByDid: string): Promise { @@ -1091,7 +1107,13 @@ export async function revokeSpaceInvite(id: string, spaceSlug: string): Promise< UPDATE space_invites SET status = 'revoked' WHERE id = ${id} AND space_slug = ${spaceSlug} AND status = 'pending' `; - return result.count > 0; + if (result.count > 0) return true; + // Also check identity_invites (email invites with space_slug) + const result2 = await sql` + UPDATE identity_invites SET status = 'revoked' + WHERE id = ${id} AND space_slug = ${spaceSlug} AND status = 'pending' + `; + return result2.count > 0; } // ============================================================================