Conversation
Promote QA batch: service ownership, dashboard cluster, rota partials
Introduces a post-hoc CampMember record so humans can tell Humans which camp they have joined for the current season. Humans do not manage a camp's real membership here — each camp runs its own process. This record enables per-camp roles, Early Entry allocations, and future notification flows. - CampMember entity (Pending/Active/Removed) with partial unique index on (CampSeasonId, UserId) WHERE Status <> 'Removed' to allow re-requesting after removal. - CampService owns camp_members; new methods cover request, approve, reject, withdraw, leave, remove, list, and per-user grouping. - Camp detail page shows a clearly-worded "Request to join" flow for authenticated humans; the button is disabled when no open season exists, and membership state (Pending/Active) is never rendered on anonymous or public views. - Camp edit page shows pending requests (Approve/Reject) and active members (Remove) to leads/CampAdmin/Admin via the existing CampAuthorizationHandler. - Approving or rejecting a request sends an in-app notification to the requester via the existing NotificationService, mapped to MessageCategory.TeamUpdates. - Rejecting or withdrawing a camp season auto-withdraws its pending member requests. - Profile page includes a MyCamps view component grouping memberships by year so humans can see what they belong to without hunting. - Migration reviewer gate: no bool sentinel traps, no hand edits, snapshot updated, namespace Humans.Infrastructure.Migrations. Closes nobodies-collective#488 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The preview deployment for humans-qa is ready. 🟢 Open Preview | Open Build Logs | Open Application Logs Last updated at: 2026-04-15 02:28:43 CET |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 17b8ddfc82
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| try | ||
| { | ||
| await _campService.ApproveCampMemberAsync(campMemberId, user.Id); |
There was a problem hiding this comment.
Scope membership mutations to the authorized camp
This action authorizes the caller against the camp identified by slug, but then mutates membership state using only campMemberId. Because the service methods also load by membership ID alone, a lead of camp A can submit a crafted member ID from camp B and approve/reject/remove another camp’s membership record. Validate that the target membership belongs to camp.Id before mutating (or require campId in the service API and enforce it there).
Useful? React with 👍 / 👎.
| var existing = await _dbContext.CampMembers | ||
| .Where(m => m.CampSeasonId == season.Id && m.UserId == userId && m.Status != CampMemberStatus.Removed) | ||
| .FirstOrDefaultAsync(cancellationToken); |
There was a problem hiding this comment.
Handle duplicate-key races in membership requests
The request flow is a non-atomic read-then-insert (FirstOrDefaultAsync followed by Add/SaveChangesAsync), so concurrent submissions from the same user/season can both pass the existence check and one will hit the unique index (IX_camp_members_active_unique). That exception is not translated to an idempotent result, and the controller catches only InvalidOperationException, so this can surface as a 500 instead of AlreadyPending.
Useful? React with 👍 / 👎.
Summary
Introduces
CampMember— a post-hoc per-season membership record so humans can tell Humans which camp they joined this year. Humans does not manage a camp's real membership; each camp runs its own process (website, spreadsheet, WhatsApp). This record enables per-camp roles, Early Entry allocations, and future notification flows.CampMemberentity (Pending/Active/Removed) owned byCampService, with a partial unique index on(CampSeasonId, UserId) WHERE Status <> 'Removed'so removed rows retain audit history and allow re-requesting.CampAuthorizationHandler.CampMembershipApproved/CampMembershipRejected, mapped toMessageCategory.TeamUpdates).MyCampsview component grouping a human's memberships by year with links to each camp.Closes nobodies-collective#488
Test plan
dotnet build -v q -clp:ErrorsOnly)dotnet format --verify-no-changes)CampMembertests covering request, approve, reject, withdraw, leave, remove, re-request after removal, auto-withdraw-on-season-change, membership state lookup, user-cannot-withdraw-others)HasDefaultValue(false)traps, no hand edits,Humans.Infrastructure.Migrationsnamespace, snapshot updated, partial unique index correct.🤖 Generated with Claude Code