feat(ai): centralize durable usage
CI and Deploy / test (pull_request) Successful in 5m19s
CI and Deploy / deploy (pull_request) Has been skipped

Add a content-free usage ledger with legacy backfill. Reserve Workspace and durable Strategy/CV work before execution so deleted history or duplicate admission cannot reset limits.
This commit is contained in:
cesnimda
2026-08-15 20:03:06 +02:00
parent dbff0f8d49
commit 134aac7bcf
28 changed files with 3539 additions and 83 deletions
@@ -174,6 +174,7 @@ public sealed class AccountDataExportService(
var interviewNotes = await db.InterviewPrepNotes.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
var aiNotes = await db.AiWorkspaceNotes.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
var aiInteractions = await db.AiInteractions.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
var aiUsage = await db.AiUsageRecords.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
var checklist = await db.ApplicationChecklistItems.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
var coverLetters = await db.CoverLetterVersions.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
var interviewItems = await db.InterviewPrepItems.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.Id).ToListAsync(cancellationToken);
@@ -187,12 +188,13 @@ public sealed class AccountDataExportService(
InterviewPrepNotes = interviewNotes,
AiWorkspaceNotes = aiNotes,
AiInteractions = aiInteractions,
AiUsage = aiUsage,
ChecklistItems = checklist,
CoverLetterVersions = coverLetters,
InterviewPrepItems = interviewItems,
EmailDrafts = emailDrafts,
EmailSendAttempts = emailAttempts,
}, tailoredDrafts.Count + interviewNotes.Count + aiNotes.Count + aiInteractions.Count + checklist.Count + coverLetters.Count + interviewItems.Count + emailDrafts.Count + emailAttempts.Count);
}, tailoredDrafts.Count + interviewNotes.Count + aiNotes.Count + aiInteractions.Count + aiUsage.Count + checklist.Count + coverLetters.Count + interviewItems.Count + emailDrafts.Count + emailAttempts.Count);
var operations = await db.UserOperations.IgnoreQueryFilters().AsNoTracking().Where(item => item.OwnerUserId == ownerUserId).OrderBy(item => item.CreatedAtUtc)
.Select(item => new