Phases 3 & 4: Complete data access architecture migration

Phase 3 — eliminated ApplicationDbContext from all non-exempt controllers,
routing all data access through IUnitOfWork. Added IPlainRepository<T> for
the four platform entities (Announcement, BannedIp, DashboardTip, ReleaseNote)
that intentionally don't extend BaseEntity and therefore can't use the
constrained IRepository<T>. Added permanent-exception comments to the 18
controllers that legitimately retain direct DbContext access (Identity infra,
cross-tenant platform ops, bulk streaming exports).

Phase 4 — added EnforceDataAccessArchitecture() to Program.cs, a startup
gate that reflects over every Controller subclass and throws at boot if any
non-exempt controller injects ApplicationDbContext. The app cannot start with
a violation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-28 09:17:29 -04:00
parent 90bc0d965f
commit 1cb7a8ca4a
72 changed files with 9060 additions and 2323 deletions
@@ -7,7 +7,6 @@ using PowderCoating.Application.DTOs.Company;
using PowderCoating.Core.Entities;
using PowderCoating.Core.Enums;
using PowderCoating.Core.Interfaces;
using PowderCoating.Infrastructure.Data;
using PowderCoating.Shared.Constants;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
@@ -21,18 +20,15 @@ public class DepositsController : Controller
private readonly IUnitOfWork _unitOfWork;
private readonly UserManager<ApplicationUser> _userManager;
private readonly ILogger<DepositsController> _logger;
private readonly ApplicationDbContext _context;
public DepositsController(
IUnitOfWork unitOfWork,
UserManager<ApplicationUser> userManager,
ILogger<DepositsController> logger,
ApplicationDbContext context)
ILogger<DepositsController> logger)
{
_unitOfWork = unitOfWork;
_userManager = userManager;
_logger = logger;
_context = context;
}
// -----------------------------------------------------------------------
@@ -220,11 +216,11 @@ public class DepositsController : Controller
{
var prefix = $"DEP-{DateTime.UtcNow:yy}{DateTime.UtcNow.Month:D2}-";
var existing = await _context.Set<Deposit>()
.IgnoreQueryFilters()
.Where(d => d.CompanyId == companyId && d.ReceiptNumber.StartsWith(prefix))
var existing = (await _unitOfWork.Deposits.FindAsync(
d => d.CompanyId == companyId && d.ReceiptNumber.StartsWith(prefix),
ignoreQueryFilters: true))
.Select(d => d.ReceiptNumber)
.ToListAsync();
.ToList();
var maxNum = 0;
foreach (var num in existing)