Make TeamResourceService sole owner of google_resources (#491)#225
Make TeamResourceService sole owner of google_resources (#491)#225peterdrier merged 2 commits intomainfrom
Conversation
…lective#491) Phase 1 of the google_resources ownership cleanup: consumers stop reaching into DbSet<GoogleResource> directly and go through ITeamResourceService instead. Adds seven read methods (GetResourcesByTeamIdsAsync, GetTeamResourceSummariesAsync, GetActiveResourceCountsByTeamAsync, GetUserTeamResourcesAsync, GetActiveDriveFoldersAsync, GetResourceCountAsync, plus existing GetTeamResourcesAsync / GetResourceByIdAsync), migrates HumansMetricsService, DriveActivityMonitorService, GoogleAdminService, and TeamService (4 call sites + GetUserTeamGoogleResourcesAsync moved out), deletes TeamResourcePersistence, and removes the Team.GoogleResources EF navigation property. A CI guardrail (scripts/check-google-resource-ownership.sh) enforces that future changes keep DbSet access confined to TeamResourceService, its stub, the DbContext, and TeamConfiguration — GoogleWorkspaceSyncService and three known callers remain as Phase 2 exceptions pending a separate decomposition issue. TeamResourceService <-> TeamService is now mutually recursive for the GetUserTeamResourcesAsync read path, so both sides resolve each other lazily through IServiceProvider to avoid the DI cycle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR Review — 2026-04-14PR: #225 — Make TeamResourceService sole owner of google_resources (nobodies-collective#491) Spec Compliance — Issue nobodies-collective#491Verdict: SUBSTANTIALLY ADDRESSED (7 of 8 criteria met)
Notes:
Code QualityNo CRITICAL issues. IMPORTANT:
MINOR:
What looks good:
Bottom LineWell-executed Phase 1. Architecture intent is correct, EF patterns are sound, and the CI guardrail is a meaningful regression gate. The actionable items before merge are: (a) file the Phase 2 tracking issue (criterion #8); (b) decide whether to fix the pre-existing Review by Claude Code |
- Change google_resources FK from SET NULL to RESTRICT. TeamId is non-nullable, so the previous SET NULL would have thrown on team delete. Adds migration RestrictGoogleResourceTeamDelete (db:yes, was previously db:no). - Rewrite GetUserTeamResourcesAsync in both TeamResourceService and StubTeamResourceService as a single DB-sorted JOIN. Restores pre-PR ordering (DB collation, not OrdinalIgnoreCase), removes the silent null-Team filter, and eliminates the service-locator hop through ITeamService for this path. - Add AsNoTracking to GetResourceCountAsync for consistency with sibling reads. Phase 2 decomposition of GoogleWorkspaceSyncService writes is tracked as nobodies-collective#492. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes Applied — 2026-04-14Commit: Addressed review findings 1–4:
Also filed: Phase 2 tracking issue at nobodies-collective#492, covering the remaining Verification:
Not addressed (minor, deferred):
Fix & re-review by Claude Code |
Summary
Phase 1 of consolidating
google_resourcesownership underTeamResourceService, per.claude/DESIGN_RULES.md. Consumers stop touchingDbSet<GoogleResource>directly and go throughITeamResourceServiceinstead.ITeamResourceService:GetResourcesByTeamIdsAsync,GetTeamResourceSummariesAsync,GetActiveResourceCountsByTeamAsync,GetUserTeamResourcesAsync,GetActiveDriveFoldersAsync,GetResourceCountAsync, plus existingGetTeamResourcesAsync/GetResourceByIdAsync.HumansMetricsService,DriveActivityMonitorService,GoogleAdminService, andTeamService(email-send read, admin-list.Include,CreateAdminTeamSummarycounts, and the relocatedGetUserTeamGoogleResourcesAsync— now onITeamResourceServiceand consumed directly byMyGoogleResourcesViewComponent).TeamResourcePersistence(static helper is gone; each impl does its own DbContext access).Team.GoogleResourcesEF navigation property and configure the relationship from theGoogleResourceside only. Model snapshot updated;dotnet ef migrations has-pending-model-changesconfirms no schema diff (this PR isdb:no).TeamService↔TeamResourceServicenow have a mutual read-path dependency (GetUserTeamResourcesAsyncneedsGetUserTeamsAsync); resolved lazily throughIServiceProvideron both sides to avoid the DI cycle.scripts/check-google-resource-ownership.sh(wired into thecode-qualityCI job) fails if anything outsideTeamResourceService/StubTeamResourceService/TeamConfiguration/HumansDbContexttouchesDbSet<GoogleResource>.GoogleWorkspaceSyncService,SystemTeamSyncJob,ProcessGoogleSyncOutboxJob, andGoogleControllerstay on the Phase 2 exception list.Phase 2 — decomposing
GoogleWorkspaceSyncService's own writes/reads to the table, the return-tuple pattern for Google state, and removing the remaining exception-list entries — is out of scope for this PR and should get its own issue.Test plan
dotnet build Humans.slnx— cleandotnet test Humans.slnx(non-integration) — 1055 passing, 0 failingdotnet format Humans.slnx --verify-no-changes— cleandotnet ef migrations has-pending-model-changes— no changesbash scripts/check-google-resource-ownership.sh— passes locally/Admin/Teamsadmin list still shows correctHasMailGroup/DriveResourceCountper team; added-to-team email still lists the team's drive resources.Closes nobodies-collective#491 (tracked upstream on
nobodies-collective/Humans; auto-close will fire when this batch promotes to upstreammain).🤖 Generated with Claude Code