1cb7a8ca4a
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>
56 lines
2.1 KiB
C#
56 lines
2.1 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using PowderCoating.Application.Interfaces;
|
|
using PowderCoating.Infrastructure.Data;
|
|
|
|
namespace PowderCoating.Infrastructure.Services;
|
|
|
|
/// <summary>
|
|
/// Implements <see cref="IAiUsageReportService"/> by querying <c>AiUsageLogs</c> and
|
|
/// <c>QuotePhotos</c> directly via <c>ApplicationDbContext</c>. Both tables either are
|
|
/// not BaseEntity (AiUsageLog) or require cross-tenant GROUP BY aggregations that must
|
|
/// execute in SQL; this service encapsulates those queries so the controller stays clean.
|
|
/// </summary>
|
|
public class AiUsageReportService : IAiUsageReportService
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
public AiUsageReportService(ApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<AiUsageReportData> GetReportDataAsync()
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
var todayStart = now.Date;
|
|
var last7Start = todayStart.AddDays(-7);
|
|
var last30Start = todayStart.AddDays(-30);
|
|
|
|
var usageByCompany = await _context.AiUsageLogs
|
|
.GroupBy(l => l.CompanyId)
|
|
.Select(g => new AiCompanyUsage(
|
|
g.Key,
|
|
g.Count(l => l.CalledAt >= todayStart),
|
|
g.Count(l => l.CalledAt >= last7Start),
|
|
g.Count(l => l.CalledAt >= last30Start),
|
|
g.Count()))
|
|
.ToListAsync();
|
|
|
|
var featureStats = await _context.AiUsageLogs
|
|
.Where(l => l.CalledAt >= last30Start)
|
|
.GroupBy(l => new { l.CompanyId, l.Feature })
|
|
.Select(g => new AiFeatureStat(g.Key.CompanyId, g.Key.Feature, g.Count()))
|
|
.ToListAsync();
|
|
|
|
var photoCounts = await _context.QuotePhotos
|
|
.IgnoreQueryFilters()
|
|
.Where(p => p.IsAiAnalysisPhoto && !p.IsDeleted)
|
|
.GroupBy(p => p.CompanyId)
|
|
.Select(g => new { CompanyId = g.Key, Count = g.Count() })
|
|
.ToDictionaryAsync(x => x.CompanyId, x => x.Count);
|
|
|
|
return new AiUsageReportData(usageByCompany, featureStats, photoCounts);
|
|
}
|
|
}
|