using Microsoft.EntityFrameworkCore; using NodaTime; namespace DysonNetwork.Drive.Billing; public class UsageDetails { public required Guid PoolId { get; set; } public required string PoolName { get; set; } public required long UsageBytes { get; set; } public required double Cost { get; set; } public required long FileCount { get; set; } } public class TotalUsageDetails { public required List PoolUsages { get; set; } public required long TotalUsageBytes { get; set; } public required long TotalFileCount { get; set; } // Quota, cannot be loaded in the service, cause circular dependency // Let the controller do the calculation public long? TotalQuota { get; set; } public long? UsedQuota { get; set; } } public class UsageService(AppDatabase db) { public async Task GetTotalUsage(Guid accountId) { var now = SystemClock.Instance.GetCurrentInstant(); var fileQuery = db.Files .Where(f => !f.IsMarkedRecycle) .Where(f => !f.ExpiredAt.HasValue || f.ExpiredAt > now) .Where(f => f.AccountId == accountId) .AsQueryable(); var poolUsages = await db.Pools .Select(p => new UsageDetails { PoolId = p.Id, PoolName = p.Name, UsageBytes = fileQuery .Where(f => f.PoolId == p.Id) .Sum(f => f.Size), Cost = fileQuery .Where(f => f.PoolId == p.Id) .Sum(f => f.Size) / 1024.0 / 1024.0 * (p.BillingConfig.CostMultiplier ?? 1.0), FileCount = db.Files .Count(f => f.PoolId == p.Id) }) .ToListAsync(); var totalUsage = poolUsages.Sum(p => p.UsageBytes); var totalCost = poolUsages.Sum(p => p.Cost); var totalFileCount = poolUsages.Sum(p => p.FileCount); return new TotalUsageDetails { PoolUsages = poolUsages, TotalUsageBytes = totalUsage, TotalFileCount = totalFileCount, UsedQuota = await GetTotalBillableUsage() }; } public async Task GetPoolUsage(Guid poolId, Guid accountId) { var pool = await db.Pools.FindAsync(poolId); if (pool == null) { return null; } var now = SystemClock.Instance.GetCurrentInstant(); var fileQuery = db.Files .Where(f => !f.IsMarkedRecycle) .Where(f => f.ExpiredAt.HasValue && f.ExpiredAt > now) .Where(f => f.AccountId == accountId) .AsQueryable(); var usageBytes = await fileQuery .SumAsync(f => f.Size); var fileCount = await fileQuery .CountAsync(); var cost = usageBytes / 1024.0 / 1024.0 * (pool.BillingConfig.CostMultiplier ?? 1.0); return new UsageDetails { PoolId = pool.Id, PoolName = pool.Name, UsageBytes = usageBytes, Cost = cost, FileCount = fileCount }; } public async Task GetTotalBillableUsage() { var now = SystemClock.Instance.GetCurrentInstant(); var files = await db.Files .Where(f => f.PoolId.HasValue) .Where(f => !f.IsMarkedRecycle) .Include(f => f.Pool) .Where(f => !f.ExpiredAt.HasValue || f.ExpiredAt > now) .Select(f => new { f.Size, Multiplier = f.Pool!.BillingConfig.CostMultiplier ?? 1.0 }) .ToListAsync(); var totalCost = files.Sum(f => f.Size * f.Multiplier) / 1024.0 / 1024.0; return (long)Math.Ceiling(totalCost); } }