Files
Swarm/DysonNetwork.Drive/Billing/QuotaService.cs

69 lines
2.3 KiB
C#

using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Proto;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace DysonNetwork.Drive.Billing;
public class QuotaService(
AppDatabase db,
UsageService usage,
AccountService.AccountServiceClient accounts,
ICacheService cache
)
{
public async Task<(bool ok, long billable, long quota)> IsFileAcceptable(Guid accountId, double costMultiplier, long newFileSize)
{
// The billable unit is MiB
var billableUnit = (long)Math.Ceiling(newFileSize / 1024.0 / 1024.0 * costMultiplier);
var totalBillableUsage = await usage.GetTotalBillableUsage();
var quota = await GetQuota(accountId);
return (totalBillableUsage + billableUnit <= quota, billableUnit, quota);
}
public async Task<long> GetQuota(Guid accountId)
{
var cacheKey = $"file:quota:{accountId}";
var cachedResult = await cache.GetAsync<long?>(cacheKey);
if (cachedResult.HasValue) return cachedResult.Value;
var (based, extra) = await GetQuotaVerbose(accountId);
var quota = based + extra;
await cache.SetAsync(cacheKey, quota);
return quota;
}
public async Task<(long based, long extra)> GetQuotaVerbose(Guid accountId)
{
var response = await accounts.GetAccountAsync(new GetAccountRequest { Id = accountId.ToString() });
var perkSubscription = response.PerkSubscription;
// The base quota is 1GiB, T1 is 5GiB, T2 is 10GiB, T3 is 15GiB
var basedQuota = 1L;
if (perkSubscription != null)
{
var privilege = PerkSubscriptionPrivilege.GetPrivilegeFromIdentifier(perkSubscription.Identifier);
basedQuota = privilege switch
{
1 => 5L,
2 => 10L,
3 => 15L,
_ => basedQuota
};
}
// The based quota is in GiB, we need to convert it to MiB
basedQuota *= 1024L;
var now = SystemClock.Instance.GetCurrentInstant();
var extraQuota = await db.QuotaRecords
.Where(e => e.AccountId == accountId)
.Where(e => !e.ExpiredAt.HasValue || e.ExpiredAt > now)
.SumAsync(e => e.Quota);
return (basedQuota, extraQuota);
}
}