Skip to content

Commit 38ed656

Browse files
committed
Add usage events tracking for detailed analytics
- Add UsageEvent entity for tracking translation usage - Add migration for UsageEvents table - Add usage breakdown DTOs and endpoints - Add organization member usage tracking
1 parent 3dfb999 commit 38ed656

9 files changed

Lines changed: 2191 additions & 0 deletions

File tree

cloud/src/LrmCloud.Api/Controllers/UsageController.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,43 @@ public async Task<ActionResult<ApiResponse<OrganizationUsageDto>>> GetOrganizati
5252

5353
return Success(stats);
5454
}
55+
56+
/// <summary>
57+
/// Get user's usage breakdown by personal vs organization contributions.
58+
/// </summary>
59+
[HttpGet("breakdown")]
60+
public async Task<ActionResult<ApiResponse<UserUsageBreakdownDto>>> GetUserBreakdown()
61+
{
62+
var userId = GetUserId();
63+
var breakdown = await _usageService.GetUserUsageBreakdownAsync(userId);
64+
return Success(breakdown);
65+
}
66+
67+
/// <summary>
68+
/// Get organization usage breakdown by member (admins/owners only).
69+
/// </summary>
70+
[HttpGet("organizations/{organizationId:int}/members")]
71+
public async Task<ActionResult<ApiResponse<List<OrgMemberUsageDto>>>> GetOrgMemberUsage(int organizationId)
72+
{
73+
var userId = GetUserId();
74+
var usage = await _usageService.GetOrgMemberUsageAsync(organizationId, userId);
75+
return Success(usage);
76+
}
77+
78+
/// <summary>
79+
/// Get project usage breakdown by contributor.
80+
/// </summary>
81+
[HttpGet("projects/{projectId:int}")]
82+
public async Task<ActionResult<ApiResponse<ProjectUsageDto>>> GetProjectUsage(int projectId)
83+
{
84+
var userId = GetUserId();
85+
var usage = await _usageService.GetProjectUsageAsync(projectId, userId);
86+
87+
if (usage == null)
88+
{
89+
return NotFound("Project not found or you don't have access to it.");
90+
}
91+
92+
return Success(usage);
93+
}
5594
}

cloud/src/LrmCloud.Api/Data/AppDbContext.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
4141
// Translation Usage History
4242
public DbSet<TranslationUsageHistory> TranslationUsageHistory => Set<TranslationUsageHistory>();
4343

44+
// Usage Events (detailed per-translation tracking)
45+
public DbSet<UsageEvent> UsageEvents => Set<UsageEvent>();
46+
4447
protected override void OnModelCreating(ModelBuilder modelBuilder)
4548
{
4649
base.OnModelCreating(modelBuilder);
@@ -298,6 +301,40 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
298301
// Match parent's soft-delete filter
299302
entity.HasQueryFilter(e => e.User!.DeletedAt == null);
300303
});
304+
305+
// =====================================================================
306+
// Usage Events (detailed per-translation tracking)
307+
// =====================================================================
308+
modelBuilder.Entity<UsageEvent>(entity =>
309+
{
310+
entity.HasIndex(e => e.ActingUserId);
311+
entity.HasIndex(e => e.BilledUserId);
312+
entity.HasIndex(e => e.ProjectId);
313+
entity.HasIndex(e => e.OrganizationId);
314+
entity.HasIndex(e => e.CreatedAt);
315+
entity.HasIndex(e => new { e.OrganizationId, e.CreatedAt });
316+
entity.HasIndex(e => new { e.ProjectId, e.CreatedAt });
317+
318+
entity.HasOne(e => e.ActingUser)
319+
.WithMany()
320+
.HasForeignKey(e => e.ActingUserId)
321+
.OnDelete(DeleteBehavior.Cascade);
322+
323+
entity.HasOne(e => e.BilledUser)
324+
.WithMany()
325+
.HasForeignKey(e => e.BilledUserId)
326+
.OnDelete(DeleteBehavior.Cascade);
327+
328+
entity.HasOne(e => e.Project)
329+
.WithMany()
330+
.HasForeignKey(e => e.ProjectId)
331+
.OnDelete(DeleteBehavior.SetNull);
332+
333+
entity.HasOne(e => e.Organization)
334+
.WithMany()
335+
.HasForeignKey(e => e.OrganizationId)
336+
.OnDelete(DeleteBehavior.SetNull);
337+
});
301338
}
302339

303340
/// <summary>

0 commit comments

Comments
 (0)