Root cause: Shift signup/cancel endpoints passed the Keycloak sub JWT claim (external UUID) directly as MemberId, but ShiftSignup.MemberId references the internal Member.Id — a different value.
Fix: ShiftService.SignUpForShiftAsync and CancelSignupAsync now accept string externalUserId, look up the Member by ExternalUserId + TenantId, and use the resolved internal Member.Id.
Tests: All 6 signup/cancel integration tests updated to seed proper Member + Club entities via SeedMemberAsync. All 13 tests pass.
Files Changed
File
Change
ShiftService.cs
Accept string externalUserId, resolve to internal Member.Id
ShiftEndpoints.cs
Pass JWT sub as string, handle "Member not found" → 404
ShiftCrudTests.cs
Seed Member records in all signup/cancel tests
Testing
dotnet build — 0 errors
dotnet test --filter ShiftCrudTests — 13/13 passed
## Summary
- **Root cause**: Shift signup/cancel endpoints passed the Keycloak `sub` JWT claim (external UUID) directly as `MemberId`, but `ShiftSignup.MemberId` references the internal `Member.Id` — a different value.
- **Fix**: `ShiftService.SignUpForShiftAsync` and `CancelSignupAsync` now accept `string externalUserId`, look up the `Member` by `ExternalUserId + TenantId`, and use the resolved internal `Member.Id`.
- **Tests**: All 6 signup/cancel integration tests updated to seed proper `Member` + `Club` entities via `SeedMemberAsync`. All 13 tests pass.
## Files Changed
| File | Change |
|---|---|
| `ShiftService.cs` | Accept `string externalUserId`, resolve to internal `Member.Id` |
| `ShiftEndpoints.cs` | Pass JWT `sub` as string, handle "Member not found" → 404 |
| `ShiftCrudTests.cs` | Seed `Member` records in all signup/cancel tests |
## Testing
- `dotnet build` — 0 errors
- `dotnet test --filter ShiftCrudTests` — 13/13 passed
The signup/cancel endpoints were passing the Keycloak sub claim (external UUID)
directly as MemberId, but ShiftSignup.MemberId references the internal Member.Id.
Now ShiftService resolves ExternalUserId to the internal Member.Id before creating
the signup record. Integration tests updated to seed proper Member entities.
- Bypass NextAuth OIDC discovery with explicit token/userinfo endpoints using internal Docker DNS, avoiding 'issuer string did not match' errors.
- Fix next.config.ts API route interception that incorrectly forwarded NextAuth routes to backend by using 'fallback' rewrites.
- Add 'Use different credentials' button to login page and AuthGuard for clearing stale sessions.
- Backend `/signup` endpoint returns 200 OK with an empty body (`TypedResults.Ok()`), causing `res.json()` to throw 'Unexpected end of JSON input'. Removed the `res.json()` return.
- Added Suspense boundary in login page to fix `useSearchParams` build error.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
subJWT claim (external UUID) directly asMemberId, butShiftSignup.MemberIdreferences the internalMember.Id— a different value.ShiftService.SignUpForShiftAsyncandCancelSignupAsyncnow acceptstring externalUserId, look up theMemberbyExternalUserId + TenantId, and use the resolved internalMember.Id.Member+Clubentities viaSeedMemberAsync. All 13 tests pass.Files Changed
ShiftService.csstring externalUserId, resolve to internalMember.IdShiftEndpoints.cssubas string, handle "Member not found" → 404ShiftCrudTests.csMemberrecords in all signup/cancel testsTesting
dotnet build— 0 errorsdotnet test --filter ShiftCrudTests— 13/13 passed