Change editor rule

This commit is contained in:
2026-09-24 13:24:43 +02:00
parent 16478678b5
commit 73e37dbecd
401 changed files with 14735 additions and 12995 deletions
@@ -1,20 +1,21 @@
using FluentValidation;
using Musify.Api.DataTransferObjects;
namespace Musify.Api.Validators;
public sealed class AddTracksRequestValidator : AbstractValidator<AddTracksRequest>
namespace Musify.Api.Validators
{
public const int MaxTracksPerRequest = 500;
public AddTracksRequestValidator()
public sealed class AddTracksRequestValidator : AbstractValidator<AddTracksRequest>
{
RuleFor(x => x.TrackIds)
.NotEmpty()
.Must(ids => ids.Count <= MaxTracksPerRequest)
.WithMessage($"At most {MaxTracksPerRequest} tracks can be added at once.");
public const int MaxTracksPerRequest = 500;
RuleForEach(x => x.TrackIds)
.NotEmpty();
public AddTracksRequestValidator()
{
RuleFor(x => x.TrackIds)
.NotEmpty()
.Must(ids => ids.Count <= MaxTracksPerRequest)
.WithMessage($"At most {MaxTracksPerRequest} tracks can be added at once.");
RuleForEach(x => x.TrackIds)
.NotEmpty();
}
}
}
@@ -1,24 +1,25 @@
using FluentValidation;
using Musify.Api.DataTransferObjects.Albums;
namespace Musify.Api.Validators.Albums;
public sealed class CreateAlbumRequestValidator : AbstractValidator<CreateAlbumRequest>
namespace Musify.Api.Validators.Albums
{
public CreateAlbumRequestValidator()
public sealed class CreateAlbumRequestValidator : AbstractValidator<CreateAlbumRequest>
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(Limits.AlbumTitle);
public CreateAlbumRequestValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(Limits.AlbumTitle);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description);
RuleFor(x => x.ReleaseYear)
.InclusiveBetween(Limits.EarliestReleaseYear, Limits.LatestReleaseYear)
.When(x => x.ReleaseYear.HasValue);
RuleFor(x => x.ReleaseYear)
.InclusiveBetween(Limits.EarliestReleaseYear, Limits.LatestReleaseYear)
.When(x => x.ReleaseYear.HasValue);
RuleFor(x => x.PictureIntentId)
.NotEmpty();
RuleFor(x => x.PictureIntentId)
.NotEmpty();
}
}
}
@@ -1,21 +1,22 @@
using FluentValidation;
using Musify.Api.DataTransferObjects.Albums;
namespace Musify.Api.Validators.Albums;
public sealed class UpdateAlbumRequestValidator : AbstractValidator<UpdateAlbumRequest>
namespace Musify.Api.Validators.Albums
{
public UpdateAlbumRequestValidator()
public sealed class UpdateAlbumRequestValidator : AbstractValidator<UpdateAlbumRequest>
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(Limits.AlbumTitle);
public UpdateAlbumRequestValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(Limits.AlbumTitle);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description);
RuleFor(x => x.ReleaseYear)
.InclusiveBetween(Limits.EarliestReleaseYear, Limits.LatestReleaseYear)
.When(x => x.ReleaseYear.HasValue);
RuleFor(x => x.ReleaseYear)
.InclusiveBetween(Limits.EarliestReleaseYear, Limits.LatestReleaseYear)
.When(x => x.ReleaseYear.HasValue);
}
}
}
+10 -9
View File
@@ -1,12 +1,13 @@
namespace Musify.Api.Validators;
public static class Limits
namespace Musify.Api.Validators
{
public const int TrackTitle = 200;
public const int AlbumTitle = 200;
public const int PlayListName = 50;
public const int Description = 256;
public const int EarliestReleaseYear = 1877;
public static class Limits
{
public const int TrackTitle = 200;
public const int AlbumTitle = 200;
public const int PlayListName = 50;
public const int Description = 256;
public const int EarliestReleaseYear = 1877;
public static int LatestReleaseYear => DateTime.UtcNow.Year + 1;
public static int LatestReleaseYear => DateTime.UtcNow.Year + 1;
}
}
@@ -2,16 +2,17 @@ using FluentValidation;
using Musify.Api.Models;
using Musify.Application.Shared;
namespace Musify.Api.Validators;
public sealed class PageQueryValidator : AbstractValidator<PageQuery>
namespace Musify.Api.Validators
{
public PageQueryValidator()
public sealed class PageQueryValidator : AbstractValidator<PageQuery>
{
RuleFor(x => x.PageNumber)
.GreaterThanOrEqualTo(1);
public PageQueryValidator()
{
RuleFor(x => x.PageNumber)
.GreaterThanOrEqualTo(1);
RuleFor(x => x.PageSize)
.InclusiveBetween(1, PageRequest.MaxPageSize);
RuleFor(x => x.PageSize)
.InclusiveBetween(1, PageRequest.MaxPageSize);
}
}
}
@@ -2,19 +2,20 @@ using FluentValidation;
using Musify.Api.DataTransferObjects;
using Musify.Application.Configuration;
namespace Musify.Api.Validators;
public sealed class PictureUploadRequestValidator : AbstractValidator<PictureUploadRequest>
namespace Musify.Api.Validators
{
public PictureUploadRequestValidator(UploadIntentConfiguration uploadConfiguration)
public sealed class PictureUploadRequestValidator : AbstractValidator<PictureUploadRequest>
{
RuleFor(x => x.FileType)
.MustBeFileType(Uploads.PictureFileTypes);
public PictureUploadRequestValidator(UploadIntentConfiguration uploadConfiguration)
{
RuleFor(x => x.FileType)
.MustBeFileType(Uploads.PictureFileTypes);
RuleFor(x => x.ContentType)
.MustBeContentType(Uploads.PictureContentTypes);
RuleFor(x => x.ContentType)
.MustBeContentType(Uploads.PictureContentTypes);
RuleFor(x => x.ExpectedSizeBytes)
.MustBeValidUploadSize(uploadConfiguration.MaxUploadBytes);
RuleFor(x => x.ExpectedSizeBytes)
.MustBeValidUploadSize(uploadConfiguration.MaxUploadBytes);
}
}
}
@@ -1,20 +1,21 @@
using FluentValidation;
using Musify.Api.DataTransferObjects.PlayLists;
namespace Musify.Api.Validators.PlayLists;
public sealed class CreatePlayListRequestValidator : AbstractValidator<CreatePlayListRequest>
namespace Musify.Api.Validators.PlayLists
{
public CreatePlayListRequestValidator()
public sealed class CreatePlayListRequestValidator : AbstractValidator<CreatePlayListRequest>
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(Limits.PlayListName);
public CreatePlayListRequestValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(Limits.PlayListName);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description);
RuleFor(x => x.Visibility)
.IsInEnum();
RuleFor(x => x.Visibility)
.IsInEnum();
}
}
}
@@ -1,23 +1,24 @@
using FluentValidation;
using Musify.Api.DataTransferObjects.PlayLists;
namespace Musify.Api.Validators.PlayLists;
public sealed class UpdatePlayListRequestValidator : AbstractValidator<UpdatePlayListRequest>
namespace Musify.Api.Validators.PlayLists
{
public UpdatePlayListRequestValidator()
public sealed class UpdatePlayListRequestValidator : AbstractValidator<UpdatePlayListRequest>
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(Limits.PlayListName)
.When(x => x.Name != null);
public UpdatePlayListRequestValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(Limits.PlayListName)
.When(x => x.Name != null);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description)
.When(x => x.Description != null);
RuleFor(x => x.Description)
.MaximumLength(Limits.Description)
.When(x => x.Description != null);
RuleFor(x => x.Visibility)
.IsInEnum()
.When(x => x.Visibility != null);
RuleFor(x => x.Visibility)
.IsInEnum()
.When(x => x.Visibility != null);
}
}
}
@@ -2,33 +2,34 @@ using FluentValidation;
using Musify.Api.DataTransferObjects.Tracks;
using Musify.Domain.ValueObjects;
namespace Musify.Api.Validators.Tracks;
public sealed class CreateTrackRequestValidator : AbstractValidator<CreateTrackRequest>
namespace Musify.Api.Validators.Tracks
{
public CreateTrackRequestValidator()
public sealed class CreateTrackRequestValidator : AbstractValidator<CreateTrackRequest>
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(Limits.TrackTitle);
public CreateTrackRequestValidator()
{
RuleFor(x => x.Title)
.NotEmpty()
.MaximumLength(Limits.TrackTitle);
RuleFor(x => x.PictureIntentId)
.NotEmpty();
RuleFor(x => x.PictureIntentId)
.NotEmpty();
RuleFor(x => x.AudioIntentId)
.NotEmpty();
RuleFor(x => x.AudioIntentId)
.NotEmpty();
RuleFor(x => x.Tags)
.NotEmpty()
.WithMessage("At least one tag is required.");
RuleFor(x => x.Tags)
.NotEmpty()
.WithMessage("At least one tag is required.");
RuleFor(x => x.Tags)
.Must(tags => GenreCompatibility.FindConflicts(tags).Count == 0)
.When(x => x.Tags is { Count: > 0 })
.WithMessage(x =>
{
var conflict = GenreCompatibility.FindConflicts(x.Tags).First();
return $"Tags '{conflict.First}' and '{conflict.Second}' are not compatible.";
});
RuleFor(x => x.Tags)
.Must(tags => GenreCompatibility.FindConflicts(tags).Count == 0)
.When(x => x.Tags is { Count: > 0 })
.WithMessage(x =>
{
var conflict = GenreCompatibility.FindConflicts(x.Tags).First();
return $"Tags '{conflict.First}' and '{conflict.Second}' are not compatible.";
});
}
}
}
@@ -1,14 +1,15 @@
using FluentValidation;
using Musify.Api.DataTransferObjects.Tracks;
namespace Musify.Api.Validators.Tracks;
public sealed class RecordListeningProgressRequestValidator : AbstractValidator<RecordListeningProgressRequest>
namespace Musify.Api.Validators.Tracks
{
public RecordListeningProgressRequestValidator()
public sealed class RecordListeningProgressRequestValidator : AbstractValidator<RecordListeningProgressRequest>
{
RuleFor(x => x.PlayedSeconds)
.Must(seconds => double.IsFinite(seconds) && seconds >= 0)
.WithMessage("'Played Seconds' must be a non-negative number.");
public RecordListeningProgressRequestValidator()
{
RuleFor(x => x.PlayedSeconds)
.Must(seconds => double.IsFinite(seconds) && seconds >= 0)
.WithMessage("'Played Seconds' must be a non-negative number.");
}
}
}
@@ -2,28 +2,29 @@ using FluentValidation;
using Musify.Api.DataTransferObjects.Tracks;
using Musify.Application.Configuration;
namespace Musify.Api.Validators.Tracks;
public sealed class RequestTrackUploadUrlsRequestValidator : AbstractValidator<RequestTrackUploadUrlsRequest>
namespace Musify.Api.Validators.Tracks
{
public RequestTrackUploadUrlsRequestValidator(UploadIntentConfiguration uploadConfiguration)
public sealed class RequestTrackUploadUrlsRequestValidator : AbstractValidator<RequestTrackUploadUrlsRequest>
{
RuleFor(x => x.PictureFileType)
.MustBeFileType(Uploads.PictureFileTypes);
public RequestTrackUploadUrlsRequestValidator(UploadIntentConfiguration uploadConfiguration)
{
RuleFor(x => x.PictureFileType)
.MustBeFileType(Uploads.PictureFileTypes);
RuleFor(x => x.PictureContentType)
.MustBeContentType(Uploads.PictureContentTypes);
RuleFor(x => x.PictureContentType)
.MustBeContentType(Uploads.PictureContentTypes);
RuleFor(x => x.AudioFileType)
.MustBeFileType(Uploads.AudioFileTypes);
RuleFor(x => x.AudioFileType)
.MustBeFileType(Uploads.AudioFileTypes);
RuleFor(x => x.AudioContentType)
.MustBeContentType(Uploads.AudioContentTypes);
RuleFor(x => x.AudioContentType)
.MustBeContentType(Uploads.AudioContentTypes);
RuleFor(x => x.ExpectedPictureSizeBytes)
.MustBeValidUploadSize(uploadConfiguration.MaxUploadBytes);
RuleFor(x => x.ExpectedPictureSizeBytes)
.MustBeValidUploadSize(uploadConfiguration.MaxUploadBytes);
RuleFor(x => x.ExpectedAudioSizeBytes)
.MustBeValidUploadSize(uploadConfiguration.MaxUploadBytes);
RuleFor(x => x.ExpectedAudioSizeBytes)
.MustBeValidUploadSize(uploadConfiguration.MaxUploadBytes);
}
}
}
+33 -32
View File
@@ -1,42 +1,43 @@
using FluentValidation;
namespace Musify.Api.Validators;
public static class Uploads
namespace Musify.Api.Validators
{
public static readonly IReadOnlySet<string> PictureFileTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "jpg", "jpeg", "png", "webp" };
public static class Uploads
{
public static readonly IReadOnlySet<string> PictureFileTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "jpg", "jpeg", "png", "webp" };
public static readonly IReadOnlySet<string> PictureContentTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "image/jpeg", "image/png", "image/webp" };
public static readonly IReadOnlySet<string> PictureContentTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "image/jpeg", "image/png", "image/webp" };
public static readonly IReadOnlySet<string> AudioFileTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "mp3", "wav", "flac", "ogg", "m4a", "aac" };
public static readonly IReadOnlySet<string> AudioFileTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "mp3", "wav", "flac", "ogg", "m4a", "aac" };
public static readonly IReadOnlySet<string> AudioContentTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"audio/mpeg", "audio/mp3", "audio/wav", "audio/x-wav", "audio/wave", "audio/flac", "audio/x-flac",
"audio/ogg", "audio/mp4", "audio/x-m4a", "audio/m4a", "audio/aac"
};
public static readonly IReadOnlySet<string> AudioContentTypes =
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"audio/mpeg", "audio/mp3", "audio/wav", "audio/x-wav", "audio/wave", "audio/flac", "audio/x-flac",
"audio/ogg", "audio/mp4", "audio/x-m4a", "audio/m4a", "audio/aac"
};
public static IRuleBuilderOptions<T, string> MustBeFileType<T>(
this IRuleBuilder<T, string> rule, IReadOnlySet<string> allowed) =>
rule
.NotEmpty()
.Length(1, 10)
.Matches("^[A-Za-z0-9]+$").WithMessage("'{PropertyName}' must contain only letters and digits.")
.Must(allowed.Contains).WithMessage("'{PropertyName}' must be one of: " + string.Join(", ", allowed) + ".");
public static IRuleBuilderOptions<T, string> MustBeFileType<T>(
this IRuleBuilder<T, string> rule, IReadOnlySet<string> allowed) =>
rule
.NotEmpty()
.Length(1, 10)
.Matches("^[A-Za-z0-9]+$").WithMessage("'{PropertyName}' must contain only letters and digits.")
.Must(allowed.Contains).WithMessage("'{PropertyName}' must be one of: " + string.Join(", ", allowed) + ".");
public static IRuleBuilderOptions<T, string> MustBeContentType<T>(
this IRuleBuilder<T, string> rule, IReadOnlySet<string> allowed) =>
rule
.NotEmpty()
.Must(allowed.Contains).WithMessage("'{PropertyName}' is not an allowed content type.");
public static IRuleBuilderOptions<T, string> MustBeContentType<T>(
this IRuleBuilder<T, string> rule, IReadOnlySet<string> allowed) =>
rule
.NotEmpty()
.Must(allowed.Contains).WithMessage("'{PropertyName}' is not an allowed content type.");
public static IRuleBuilderOptions<T, long?> MustBeValidUploadSize<T>(
this IRuleBuilder<T, long?> rule, long maxUploadBytes) =>
rule
.Must(size => size is null || (size > 0 && size <= maxUploadBytes))
.WithMessage($"'{{PropertyName}}' must be greater than 0 and at most {maxUploadBytes} bytes.");
public static IRuleBuilderOptions<T, long?> MustBeValidUploadSize<T>(
this IRuleBuilder<T, long?> rule, long maxUploadBytes) =>
rule
.Must(size => size is null || (size > 0 && size <= maxUploadBytes))
.WithMessage($"'{{PropertyName}}' must be greater than 0 and at most {maxUploadBytes} bytes.");
}
}