(workosUserId: string)
| 1139 | * In dev mode, resolves org from dev user config. In production, uses WorkOS API. |
| 1140 | */ |
| 1141 | export async function getWebMemberContext(workosUserId: string): Promise<MemberContext> { |
| 1142 | const context: MemberContext = { |
| 1143 | is_mapped: true, // They're authenticated via WorkOS, so they're "mapped" |
| 1144 | is_member: false, |
| 1145 | slack_linked: false, |
| 1146 | }; |
| 1147 | |
| 1148 | // Dev mode: build context from dev user config, then fall through to local DB lookups |
| 1149 | if (isDevModeEnabled() && workosUserId.startsWith('user_dev_')) { |
| 1150 | const devUser = Object.values(DEV_USERS).find(u => u.id === workosUserId); |
| 1151 | if (devUser) { |
| 1152 | context.workos_user = { |
| 1153 | workos_user_id: devUser.id, |
| 1154 | email: devUser.email, |
| 1155 | first_name: devUser.firstName, |
| 1156 | last_name: devUser.lastName, |
| 1157 | }; |
| 1158 | context.is_member = devUser.isMember; |
| 1159 | context.slack_linked = true; |
| 1160 | |
| 1161 | // Resolve org from dev config, then run local DB lookups |
| 1162 | const devOrgId = devUser.organizationId || 'org_dev_company_001'; |
| 1163 | try { |
| 1164 | return await resolveContextFromLocalDb(context, devOrgId, workosUserId); |
| 1165 | } catch (error) { |
| 1166 | logger.warn({ error, workosUserId }, 'Dev mode: failed to resolve local DB context'); |
| 1167 | return context; |
| 1168 | } |
| 1169 | } |
| 1170 | } |
| 1171 | |
| 1172 | try { |
| 1173 | // Step 1: Get WorkOS user info |
| 1174 | let workosUser; |
| 1175 | try { |
| 1176 | workosUser = await getWorkos().userManagement.getUser(workosUserId); |
| 1177 | context.workos_user = { |
| 1178 | workos_user_id: workosUser.id, |
| 1179 | email: workosUser.email, |
| 1180 | first_name: workosUser.firstName ?? undefined, |
| 1181 | last_name: workosUser.lastName ?? undefined, |
| 1182 | }; |
| 1183 | } catch (error: any) { |
| 1184 | logger.warn({ error, workosUserId }, 'Addie Web: Failed to get WorkOS user'); |
| 1185 | // Only mark unmapped when the user genuinely doesn't exist (WorkOS 404). |
| 1186 | // Transient errors leave is_mapped: true so authenticated sessions degrade |
| 1187 | // gracefully rather than being treated as anonymous. |
| 1188 | const isNotFound = error?.status === 404 || error?.code === 'entity_not_found'; |
| 1189 | if (isNotFound) context.is_mapped = false; |
| 1190 | return context; |
| 1191 | } |
| 1192 | |
| 1193 | // Step 2: Check if user has a Slack mapping (for slack_linked status) |
| 1194 | try { |
| 1195 | const slackMapping = await slackDb.getByWorkosUserId(workosUserId); |
| 1196 | if (slackMapping) { |
| 1197 | context.slack_linked = true; |
| 1198 | context.slack_user = { |
no test coverage detected