121 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			121 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
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<UsageDetails> 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<TotalUsageDetails> 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 = fileQuery
 | 
						|
                    .Count(f => f.PoolId == p.Id)
 | 
						|
            })
 | 
						|
            .ToListAsync();
 | 
						|
 | 
						|
        var totalUsage = poolUsages.Sum(p => p.UsageBytes);
 | 
						|
        var totalFileCount = poolUsages.Sum(p => p.FileCount);
 | 
						|
 | 
						|
        return new TotalUsageDetails
 | 
						|
        {
 | 
						|
            PoolUsages = poolUsages,
 | 
						|
            TotalUsageBytes = totalUsage,
 | 
						|
            TotalFileCount = totalFileCount,
 | 
						|
            UsedQuota = await GetTotalBillableUsage(accountId)
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<UsageDetails?> 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<long> GetTotalBillableUsage(Guid accountId)
 | 
						|
    {
 | 
						|
        var now = SystemClock.Instance.GetCurrentInstant();
 | 
						|
        var files = await db.Files
 | 
						|
            .Where(f => f.AccountId == accountId)
 | 
						|
            .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);
 | 
						|
    }
 | 
						|
} |