This commit is contained in:
2026-05-14 21:42:31 +02:00
parent 91ec85760e
commit 80f44422a0
11 changed files with 158 additions and 100 deletions
@@ -1,13 +0,0 @@
using Ardalis.Result;
using Musify.Domain.Entities;
namespace Musify.Application.UploadIntents
{
internal sealed record IntentValidationResult(Result? Error, UploadIntent? Intent)
{
public bool IsSuccess => Error is null;
public static IntentValidationResult Ok(UploadIntent intent) => new(null, intent);
public static IntentValidationResult Fail(Result error) => new(error, null);
}
}
@@ -1,74 +1,9 @@
using Ardalis.Result;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Musify.Application.Abstractions.Infrastructure;
using Musify.Application.Configuration;
using Musify.Domain.Entities;
namespace Musify.Application.UploadIntents
{
internal static class UploadIntentHelpers
{
internal static async Task<IntentValidationResult> ValidateAndLoadAsync(
IDatabase database,
IStorageService storageService,
UploadIntentConfiguration config,
Guid intentId,
Guid userId,
CancellationToken cancellationToken)
{
var intent = await database.UploadIntents
.FirstOrDefaultAsync(i => i.Id == intentId, cancellationToken);
if (intent is null || intent.UserId != userId)
return IntentValidationResult.Fail(Result.NotFound("Upload intent not found or not accessible."));
if (intent.Status == UploadIntentStatus.Consumed)
return IntentValidationResult.Fail(Result.Conflict("Upload intent has already been consumed."));
if (intent.Status == UploadIntentStatus.Expired || intent.ExpiresAt < DateTime.UtcNow)
return IntentValidationResult.Fail(Result.Invalid(new ValidationError("Upload intent has expired.")));
var metadata = await storageService.HeadObjectAsync(intent.Bucket, intent.Key, cancellationToken);
if (metadata is null)
return IntentValidationResult.Fail(Result.NotFound("Uploaded object not found in storage. Upload the file first."));
if (metadata.ContentLength > config.MaxUploadBytes)
return IntentValidationResult.Fail(Result.Invalid(new ValidationError($"Uploaded file exceeds the maximum allowed size of {config.MaxUploadBytes} bytes.")));
return IntentValidationResult.Ok(intent);
}
internal static async Task<Result> CheckQuotaAsync(
IDatabase database,
UploadIntentConfiguration config,
ILogger logger,
Guid userId,
long requiredBytes,
int requiredIntentCount,
CancellationToken cancellationToken)
{
var activeIntents = await database.UploadIntents
.AsNoTracking()
.Where(i => i.UserId == userId && i.Status == UploadIntentStatus.Issued)
.ToListAsync(cancellationToken);
if (activeIntents.Count + requiredIntentCount > config.MaxActiveUploadIntentsPerUser)
{
logger.LogWarning("User {UserId} exceeded max active upload intents ({Max})", userId, config.MaxActiveUploadIntentsPerUser);
return Result.Invalid(new ValidationError("Upload intent limit exceeded. Wait for existing uploads to complete or expire."));
}
var activeBytes = activeIntents.Sum(i => i.ExpectedSizeBytes ?? config.DefaultExpectedPictureSizeBytes);
if (activeBytes + requiredBytes > config.MaxActiveUploadBytesPerUser)
{
logger.LogWarning("User {UserId} exceeded max active upload bytes ({Max})", userId, config.MaxActiveUploadBytesPerUser);
return Result.Invalid(new ValidationError("Upload byte quota exceeded. Wait for existing uploads to complete or expire."));
}
return Result.Success();
}
internal static Result<string> ValidateExtension(string fileType, string[] allowedExtensions)
{
var extension = fileType.Trim();