# Backend architecture refactor — findings Tracking doc for `refactor/backend-architecture-cleanup`. Each item is verified against the actual code (not just a grep hit) before being listed here. Checked off as fixed, with tests, on this branch. ## 0. Security finding (not an architecture item, fixed anyway — user confirmed) - [x] **`AssetController` and `ContactController` both had `[Authorize]` commented out** (`//[Authorize] // Removed for testing`), so every endpoint on both — asset CRUD, contact CRUD, and (via the next item) Location CRUD — was reachable with no authentication. Git history shows it landed via a commit titled "fix" / "backend changes" — looks like forgotten debug code, not an intentional exception. Confirmed with the user before fixing (changes production auth behavior). **Fixed:** restored `[Authorize]` on both. ## 1. Layering / dependency-injection violations - [x] `AssetController` injected `UserManager` via constructor but never used it anywhere in the file. **Fixed:** removed the dead dependency and its now-unused `using` statements. - [ ] `ContactController` depends on both `IContactService` **and** `ILocationService` and implements full Location CRUD (`GetAllLocationsAsync`, `CreateLocationAsync`, `UpdateLocationAsync`, `DeleteLocationAsync`, `GetLocationsPagedAsync`) even though a dedicated `LocationController` with the same `ILocationService` already exists. Move the Location endpoints out of `ContactController` into `LocationController`; a Contact controller should depend only on `IContactService`. - [ ] `ArchitectureTests` (G2) currently passes with 0 failures — dependency *direction* is otherwise clean (no controller/service injects `DbContext`/`IConfiguration`/a concrete class). Re-run after each fix above to confirm it stays green. ## 2. Validation coverage gap Established pattern: `I{Action}{Feature}Validation : IValidator<{Dto}>` + `{Action}{Feature}Validation : AbstractValidator<{Dto}>` in `SeaHaven.Services/Validation/`, injected into the owning business service (see `LocationValidation.cs` / `LocationService`). Confirmed: **validation stays in the service layer** (not moved to data services) — only extending coverage to features that lack it. Have a validator today: Account, Asset, Contact, Dispatch, Employee, FollowUp, Location, PMSchedule, Vendor, WorkOrder, WorkOrderBoardCreate. Confirmed missing (real external Create/Update DTO, no validator): - [ ] `CalendarService.CreateEventAsync` / `UpdateEventAsync` - [ ] `CompletionDocTemplateService.CreateAsync` / `UpdateAsync` - [ ] `QuotesService.CreateQuoteAsync` / `UpdateQuoteAsync` - [ ] `ServicesRegistryService.CreateAsync` / `UpdateAsync` - [ ] `TaskListTemplateService.CreateAsync` / `UpdateAsync` - [ ] `TeamMemberInviteService.CreateAsync` - [ ] `TeamMemberService.CreateAsync` / `UpdateAsync` - [ ] `VendorCompanyRosterService.CreateRosterAsync` - [ ] `VendorOperationsService.UpdateAvailabilityAsync` / `UpdateCommercialStatusAsync` - [ ] `VendorPortalService.UpdateChecklistItemAsync` - [ ] `WorkOrderCommentService.UpdateCommentAsync` - [ ] `WorkOrderDispatchService.UpdateDispatchAsync` / `UpdateChecklistItemAsync` - [ ] `WorkOrderMediaService.UpdateMediaCategoryAsync` - [ ] `WorkOrderPocService.UpdatePocAsync` - [ ] `WorkOrderUpliftService.CreateAsync` - [ ] `DropdownOptionsService.CreateAsync` / `UpdateAsync` - [ ] `AuthenticationService.UpdateProfileAsync` Ruled out (internal/no external DTO, not a validator candidate): `AuthenticationService.CreateSessionAsync`, `VendorPortalTokenService.GetOrCreateActiveTokenAsync`, `WorkOrderAccountResolver` (no real Create/Update method; grep false positive). ## 3. N+1 / query-shape issues (verified, not grep-only) - [x] **`WorkOrderService.DeleteWorkOrderCascadeAsync`** loaded all comments then called `_commentDataService.DeleteAsync(comment.Id)` once per comment (2N round trips, N separate commits — broke the one-commit convention, §4). **Fixed:** added `ICommentDataService.DeleteAllForWorkOrderAsync` (single `ExecuteDeleteAsync`), caller now calls it once. - [x] **`WorkOrderService.SaveAttachmentsAndPhotos`** looped over uploaded attachments calling `_workOrderDataService.AddWorkOrderAttachmentAsync` per file (N separate commits). **Fixed:** added `AddWorkOrderAttachmentsAsync` (mirrors the existing `AddAuditLogsAsync` batch pattern already in this interface) — `AddRangeAsync` + one `SaveChangesAsync`; file-storage save stays per-file (I/O, not DB). Also fixed the same class of issue in the before/after/signoff-photo block right below it: up to 3 separate `GetByIdAsync` + `UpdateAsync` (each its own commit) when all three photos are submitted together, now 1 `GetByIdAsync` + 1 `UpdateAsync`. - [x] **`VendorCompanyRosterDataService.SaveRosterAsync`** (the roster-update method only — checked the other two call sites and they don't do this: `CreateRosterAsync` is a pure additive create with nothing to match against, and the third occurrence only does a uniform update over all existing technicians, no per-item lookup) loads `existingTechnicians` once via a single query, then did `existingTechnicians.FirstOrDefault(e => e.Id == ...)` **inside** a loop over the incoming roster — O(n·m) in-memory scan. **Fixed:** built `existingTechniciansById = existingTechnicians.ToDictionary(v => v.Id)` once before the loop and used `TryGetValue` inside it — O(n+m). loop and `TryGetValue` inside it — O(n+m). ## Ruled out during the sweep (false positives / legitimate design) - `WorkOrderAuditDataService.PersistAsync`: the `foreach` detaches conflicting entries from a caught `DbUpdateException`; the retry `SaveChangesAsync` is after the loop, not inside it. Not an issue. - `VendorPortalTokenService.RotateAsync` / `RevokeAllAsync`: the `foreach` only mutates already-tracked entities in memory (`t.RevokedAt = now`); the single `SaveChangesAsync` is after the loop. Not an issue. - `WorkOrderWeekRolledService`: calls `TryProcessWeekRolledAsync` once per work order inside a batch-job loop, each with its own try/catch. This is intentional per-item fault isolation for a background job — collapsing it into one transaction would change the failure semantics (one bad work order would abort the whole batch). Not an issue; do not "optimize" this away. - Legacy `SeaHavenIndustries` project (old Blazor admin app) has its own ad-hoc `Service` classes touching `DbContext` directly, violating the layering model wholesale — but it is being actively sunset (see commit `64ca9e0`, "add legacy deprecation middleware and Blazor WO sunset guard"). Out of scope: refactoring code on its way out is wasted effort. ## Still to audit - Cancellation-token forwarding (§6) on data-service methods — not yet swept. - Error disclosure (§5) — spot-check only so far (`VendorCompanyRosterController` catches `DbUpdateConcurrencyException` directly; check whether that duplicates the existing `ConcurrencyExceptionFilter`). - Tenant-scope derivation (§2) — not yet swept. - `AsNoTracking()` coverage on read-only data-service queries — not yet swept.