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 GetQuota(Guid accountId) { var cacheKey = $"file:quota:{accountId}"; var cachedResult = await cache.GetAsync(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); } }