Skip to content

Service ownership phase 2: CityPlanning and Shifts#219

Merged
peterdrier merged 3 commits intomainfrom
svc-ownership-phase-2
Apr 13, 2026
Merged

Service ownership phase 2: CityPlanning and Shifts#219
peterdrier merged 3 commits intomainfrom
svc-ownership-phase-2

Conversation

@peterdrier
Copy link
Copy Markdown
Owner

Summary

  • CityPlanningService no longer queries CampSeasons, Camps, CampLeads, CampSettings, Teams, TeamMembers, or Profiles directly — routes through ICampService, ITeamService, and IProfileService
  • ShiftManagementService/ShiftSignupService no longer query Teams, TeamMembers, TeamRoleAssignments, or RoleAssignments directly — routes through ITeamService and IRoleAssignmentService
  • New service API methods added to CampService (6), TeamService (2), and RoleAssignmentService (1)
  • Circular ShiftManagement↔TeamService dependency resolved via IServiceProvider lazy resolution (existing codebase pattern)

Acceptance Criteria (from nobodies-collective#471)

  • CityPlanningService does not query CampSeasons, Camps, CampLeads, or CampSettings directly
  • CityPlanningService calls CampService for all camp-related data and mutations
  • ShiftManagementService/ShiftSignupService call TeamService for membership/team data
  • ShiftManagementService calls RoleAssignmentService for role checks
  • CampService cache remains reliable
  • Existing tests pass (942/942 application tests)

Test plan

  • dotnet build Humans.slnx — 0 errors, 0 warnings
  • dotnet test Humans.slnx — 942 application tests pass, 108 domain tests pass
  • Grep confirms zero _dbContext.(CampSeasons|Camps|CampLeads|CampSettings|Teams|TeamMembers|Profiles) in CityPlanningService
  • Grep confirms zero _dbContext.(Teams|TeamMembers|TeamRoleAssignments|RoleAssignments) in shift services
  • QA deploy and smoke test city planning map and shift signup flows

Closes nobodies-collective#471

🤖 Generated with Claude Code

peterdrier and others added 2 commits April 11, 2026 16:55
…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>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +85 to 87
var polygonSeasonIds = await _dbContext.CampPolygons
.Select(p => p.CampSeasonId)
.ToListAsync(cancellationToken);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@peterdrier peterdrier merged commit 573e9d9 into main Apr 13, 2026
3 checks passed
@peterdrier peterdrier deleted the svc-ownership-phase-2 branch April 13, 2026 11:52
peterdrier added a commit that referenced this pull request Apr 13, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Service ownership phase 2: Self-contained sections (Camps←CityPlanning, Shifts)

1 participant