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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-25 15:24:26 -07:00
parent 4a43ecdee0
commit 858457c056
1 changed files with 29 additions and 7 deletions

View File

@ -1067,12 +1067,28 @@ export async function getSpaceInviteByToken(token: string): Promise<StoredSpaceI
} }
export async function listSpaceInvites(spaceSlug: string): Promise<StoredSpaceInvite[]> { export async function listSpaceInvites(spaceSlug: string): Promise<StoredSpaceInvite[]> {
const rows = await sql` const [spaceRows, identityRows] = await Promise.all([
SELECT * FROM space_invites sql`SELECT * FROM space_invites WHERE space_slug = ${spaceSlug} ORDER BY created_at DESC`,
WHERE space_slug = ${spaceSlug} sql`SELECT * FROM identity_invites WHERE space_slug = ${spaceSlug} ORDER BY created_at DESC`,
ORDER BY created_at DESC ]);
`; const spaceInvites = await Promise.all(spaceRows.map(rowToInvite));
return Promise.all(rows.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<StoredSpaceInvite | null> { export async function acceptSpaceInvite(token: string, acceptedByDid: string): Promise<StoredSpaceInvite | null> {
@ -1091,7 +1107,13 @@ export async function revokeSpaceInvite(id: string, spaceSlug: string): Promise<
UPDATE space_invites SET status = 'revoked' UPDATE space_invites SET status = 'revoked'
WHERE id = ${id} AND space_slug = ${spaceSlug} AND status = 'pending' 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;
} }
// ============================================================================ // ============================================================================