| 42 | [firstName, lastName].filter(Boolean).join(" ") || null; |
| 43 | |
| 44 | const makeService = (db: DrizzleDb): MemberDirectoryShape => { |
| 45 | const read = <A>(op: string, fn: () => Promise<A>) => |
| 46 | withServiceLogging( |
| 47 | `member_directory.${op}`, |
| 48 | () => |
| 49 | new MemberDirectoryError({ |
| 50 | message: `Failed to read the member directory (${op})`, |
| 51 | }), |
| 52 | tryPromiseService(fn), |
| 53 | ); |
| 54 | |
| 55 | // One projection for every read, so the seam's row shape is built in exactly |
| 56 | // one place. `membershipId` is non-null by the `known` predicate below. |
| 57 | const select = () => |
| 58 | db |
| 59 | .select({ |
| 60 | accountId: memberships.accountId, |
| 61 | membershipId: memberships.membershipId, |
| 62 | organizationId: memberships.organizationId, |
| 63 | role: memberships.role, |
| 64 | status: memberships.status, |
| 65 | email: accounts.email, |
| 66 | firstName: accounts.firstName, |
| 67 | lastName: accounts.lastName, |
| 68 | avatarUrl: accounts.avatarUrl, |
| 69 | lastSignInAt: accounts.lastSignInAt, |
| 70 | }) |
| 71 | .from(memberships) |
| 72 | .innerJoin(accounts, eq(accounts.id, memberships.accountId)) |
| 73 | .$dynamic(); |
| 74 | |
| 75 | type Row = Awaited<ReturnType<typeof select>>[number]; |
| 76 | |
| 77 | const toMember = (row: Row): DirectoryMember | null => |
| 78 | row.membershipId === null |
| 79 | ? null |
| 80 | : { |
| 81 | accountId: row.accountId, |
| 82 | membershipId: row.membershipId, |
| 83 | organizationId: row.organizationId, |
| 84 | email: row.email, |
| 85 | name: displayName(row.firstName, row.lastName), |
| 86 | avatarUrl: row.avatarUrl, |
| 87 | role: row.role, |
| 88 | status: row.status, |
| 89 | lastActiveAt: row.lastSignInAt === null ? null : row.lastSignInAt.getTime(), |
| 90 | }; |
| 91 | |
| 92 | const toMembers = (rows: readonly Row[]): DirectoryMember[] => { |
| 93 | const members: DirectoryMember[] = []; |
| 94 | for (const row of rows) { |
| 95 | const member = toMember(row); |
| 96 | if (member !== null) members.push(member); |
| 97 | } |
| 98 | return members; |
| 99 | }; |
| 100 | |
| 101 | const known = isNotNull(memberships.membershipId); |