Files
Musify/backend/Musify.Application/Services/UploadIntentValidator.cs
T
AdminandClaude Opus 4.8 6e469dbb4a refactor: replace Ardalis.Result with ErrorOr across the app
Ardalis Result<T> carried incoherent success sub-statuses and a clunky
generic converter; ErrorOr models success-or-errors cleanly.

- Application: all commands/queries/handlers now return ErrorOr<T>
  (non-generic Result -> ErrorOr<Success>); errors via Error.NotFound/
  Conflict/Validation/Unauthorized/Failure; success via value or new Success()
- Drop ResultExtensions.As<>(): error propagation is now just return .Errors
- Rewrite HTTP mapping (ErrorOrHttpExtensions) off Error.Type; success maps
  to 200 (value) or 204 (Success); Created still built in the endpoints
- Endpoints/gateway middleware: !IsSuccess -> IsError
- Swap Ardalis.Result package for ErrorOr 2.1.1 (remove unused ref from Worker)

Build: 0 warnings, 0 errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 04:05:43 +02:00

64 lines
2.6 KiB
C#

using ErrorOr;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Configuration;
using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
using Musify.Application.Contracts;
namespace Musify.Application.Services;
public sealed class UploadIntentValidator(
IDatabase database,
IStorageService storageService)
{
public async Task<ErrorOr<Success>> CheckQuotaAsync(
UploadIntentConfiguration config,
long 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)
return Error.Validation(description: "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)
return Error.Validation(description: "Upload byte quota exceeded. Wait for existing uploads to complete or expire.");
return new Success();
}
public async Task<ErrorOr<UploadIntent>> ValidateAndLoadAsync(
UploadIntentConfiguration config,
Guid intentId,
long userId,
CancellationToken cancellationToken)
{
var intent = await database.UploadIntents
.FirstOrDefaultAsync(i => i.Id == intentId, cancellationToken);
if (intent is null || intent.UserId != userId)
return Error.NotFound(description: "Upload intent not found or not accessible.");
if (intent.Status == UploadIntentStatus.Consumed)
return Error.Conflict(description: "Upload intent has already been consumed.");
if (intent.Status == UploadIntentStatus.Expired || intent.ExpiresAt < DateTime.UtcNow)
return Error.Validation(description: "Upload intent has expired.");
var metadata = await storageService.HeadObjectAsync(intent.Bucket, intent.Key, cancellationToken);
if (metadata is null)
return Error.NotFound(description: "Uploaded object not found in storage. Upload the file first.");
if (metadata.ContentLength > config.MaxUploadBytes)
return Error.Validation(description: $"Uploaded file exceeds the maximum allowed size of {config.MaxUploadBytes} bytes.");
return intent;
}
}