(user: AppUser | null | undefined)
| 175 | * Checks both direct and inherited membership via the brand registry hierarchy. |
| 176 | */ |
| 177 | export async function enrichUserWithMembership(user: AppUser | null | undefined): Promise<AppUser | null | undefined> { |
| 178 | if (!user?.id || user.isMember !== undefined) return user; |
| 179 | try { |
| 180 | const pool = getPool(); |
| 181 | |
| 182 | // Try primary_organization_id first (fast path) |
| 183 | const result = await pool.query( |
| 184 | `SELECT u.primary_organization_id |
| 185 | FROM users u |
| 186 | WHERE u.workos_user_id = $1 |
| 187 | AND u.primary_organization_id IS NOT NULL`, |
| 188 | [user.id] |
| 189 | ); |
| 190 | |
| 191 | let orgId: string | null = result.rows[0]?.primary_organization_id ?? null; |
| 192 | |
| 193 | // Fall back to organization_memberships if primary_organization_id is not set. |
| 194 | // This handles users who signed up after the initial backfill migration. |
| 195 | if (!orgId) { |
| 196 | orgId = await resolvePreferredOrganization(user.id); |
| 197 | |
| 198 | // Backfill primary_organization_id so future lookups use the fast path |
| 199 | if (orgId) { |
| 200 | backfillPrimaryOrganization(user.id, orgId).catch((err) => { |
| 201 | logger.warn({ error: err, userId: user.id }, 'Best-effort backfill of primary_organization_id failed'); |
| 202 | }); |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | if (orgId) { |
| 207 | const membership = await resolveEffectiveMembership(orgId); |
| 208 | user.isMember = membership.is_member; |
| 209 | } else { |
| 210 | user.isMember = false; |
| 211 | } |
| 212 | } catch { |
| 213 | user.isMember = false; |
| 214 | } |
| 215 | return user; |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Serve an HTML file with app config injected. |
no test coverage detected