Service ownership phase 2: CityPlanning and Shifts#219
Conversation
…ing tables CityPlanningService no longer queries CampSeasons, Camps, CampLeads, CampSettings, Teams, TeamMembers, or Profiles directly — routes through ICampService, ITeamService, and IProfileService instead. DbContext access is now limited to owned tables (CampPolygons, CampPolygonHistories, CityPlanningSettings). ShiftManagementService and ShiftSignupService no longer query Teams, TeamMembers, TeamRoleAssignments, or RoleAssignments directly — routes through ITeamService and IRoleAssignmentService. Circular dependency with TeamService resolved via IServiceProvider lazy resolution (existing pattern). New service methods: CampService gains 6 camp-season read APIs, TeamService gains GetUserCoordinatedTeamIdsAsync/GetCoordinatorUserIdsAsync, RoleAssignmentService gains HasActiveRoleAsync. Closes nobodies-collective#471 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert phase 2 target dependencies to real edges, add new CityPlan→Team and CityPlan→Prof edges, update circular dependency note to reflect IServiceProvider resolution, and fix ShiftSignupService lazy-resolution comment accuracy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f76132031d
ℹ️ 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".
| var polygonSeasonIds = await _dbContext.CampPolygons | ||
| .Select(p => p.CampSeasonId) | ||
| .ToListAsync(cancellationToken); |
There was a problem hiding this comment.
Filter polygon season IDs by requested year
This query now loads every CampPolygons row across all years into memory before filtering, so the cost of GetCampSeasonsWithoutCampPolygonAsync grows with total historical data instead of the selected year. The previous implementation kept this filtering in SQL, but this version can add avoidable latency and memory pressure on City Planning page/API loads as data grows.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 81ff6b5 — polygons are now filtered in SQL using the year's camp season IDs instead of loading all historical rows.
| .Where(p => p.UserId == userId) | ||
| .Select(p => p.BurnerName) | ||
| .FirstOrDefaultAsync(cancellationToken); | ||
| var profile = await _profileService.GetCachedProfileAsync(userId, cancellationToken); |
There was a problem hiding this comment.
Use targeted profile lookup for display name
Calling GetCachedProfileAsync here turns a single-user burner-name read into a full profile-cache warm on cold cache (ProfileService loads all profiles and volunteer history), which can make the first city-planning connection unexpectedly expensive. This path is used during SignalR connect, so users can see avoidable startup latency and memory spikes in production.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 81ff6b5 — switched from GetCachedProfileAsync to GetProfileAsync, which is a targeted per-user lookup that doesn't warm the full profile cache.
…ookup - GetCampSeasonsWithoutCampPolygonAsync now filters CampPolygons in SQL using the year's camp season IDs instead of loading all historical polygon rows into memory. - GetUserDisplayNameAsync now calls IProfileService.GetProfileAsync (targeted per-user lookup) instead of GetCachedProfileAsync, which would warm the full profile cache on cold-cache paths like SignalR connect. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolves the CampAdminController conflict from PR #219 (service ownership phase 2): drop the direct HumansDbContext dependency in favor of ICityPlanningService, keeping the new registration-info editor wired through the service layer. Codex P2: registration info was keyed to CityPlanningSettings.PublicYear but /Barrios/Register targets the highest open season year, so during season transitions admins would edit/view the wrong year's row. Add GetRegistrationInfoAsync and key both read and write to max(OpenSeasons) (falling back to PublicYear) so the info shown matches the year the register page targets. Renumber the migration to 20260413131100 so it sorts after two newer migrations that landed on main during the merge, and refresh its designer snapshot to reflect the full merged model. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Acceptance Criteria (from nobodies-collective#471)
Test plan
dotnet build Humans.slnx— 0 errors, 0 warningsdotnet test Humans.slnx— 942 application tests pass, 108 domain tests pass_dbContext.(CampSeasons|Camps|CampLeads|CampSettings|Teams|TeamMembers|Profiles)in CityPlanningService_dbContext.(Teams|TeamMembers|TeamRoleAssignments|RoleAssignments)in shift servicesCloses nobodies-collective#471
🤖 Generated with Claude Code