Fix query and API bugs: 403 for non-owners, page bounds, lifecycle filters, cover fallback, race-safe inserts, async album delete
This commit is contained in:
@@ -6,6 +6,7 @@ using Musify.Api.Filters;
|
||||
using Musify.Application.Albums;
|
||||
using Musify.Application.Albums.Responses;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Api.Models;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
|
||||
@@ -13,12 +14,15 @@ namespace Musify.Api.Endpoints;
|
||||
|
||||
public static class AlbumEndpoints
|
||||
{
|
||||
private const int MaxRecentAlbumsLimit = 50;
|
||||
|
||||
public static IEndpointRouteBuilder MapAlbumEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/albums")
|
||||
.WithTags("Albums");
|
||||
|
||||
group.MapGet("/", GetAlbums)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetAlbums")
|
||||
.WithSummary("Get Paginated Albums, Optionally Filtered By Title.")
|
||||
.Produces<AlbumsSearchResponse>();
|
||||
@@ -30,6 +34,7 @@ public static class AlbumEndpoints
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/users/{userId}", GetAlbumsByUserId)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetAlbumsByUserId")
|
||||
.WithSummary("Get Paginated Albums For A User.")
|
||||
.Produces<PaginatedResponse<AlbumApplicationResponse>>()
|
||||
@@ -43,6 +48,7 @@ public static class AlbumEndpoints
|
||||
.Produces(StatusCodes.Status401Unauthorized);
|
||||
|
||||
group.MapGet("/{albumId}/tracks", GetAlbumTracks)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetAlbumTracks")
|
||||
.WithSummary("Get The Tracks Of An Album Ordered By Track Number.")
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>()
|
||||
@@ -82,6 +88,7 @@ public static class AlbumEndpoints
|
||||
.Produces<AlbumApplicationResponse>()
|
||||
.ProducesValidationProblem()
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapDelete("/{albumId}", DeleteAlbum)
|
||||
@@ -90,6 +97,7 @@ public static class AlbumEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapPost("/{albumId}/tracks/{trackId}", AddTrackToAlbum)
|
||||
@@ -98,6 +106,7 @@ public static class AlbumEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict);
|
||||
|
||||
@@ -107,6 +116,7 @@ public static class AlbumEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
return app;
|
||||
@@ -116,10 +126,9 @@ public static class AlbumEndpoints
|
||||
IMediator mediator,
|
||||
CancellationToken cancellationToken,
|
||||
string? title,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetAlbumsQuery(title, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetAlbumsQuery(title, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -136,10 +145,9 @@ public static class AlbumEndpoints
|
||||
IMediator mediator,
|
||||
long userId,
|
||||
CancellationToken cancellationToken,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -149,6 +157,14 @@ public static class AlbumEndpoints
|
||||
CancellationToken cancellationToken,
|
||||
int limit = 12)
|
||||
{
|
||||
if (limit is < 1 or > MaxRecentAlbumsLimit)
|
||||
{
|
||||
return Results.ValidationProblem(new Dictionary<string, string[]>
|
||||
{
|
||||
[nameof(limit)] = [$"'{nameof(limit)}' must be between 1 and {MaxRecentAlbumsLimit}."]
|
||||
});
|
||||
}
|
||||
|
||||
var result = await mediator.Send(new GetRecentlyListenedAlbumsQuery(currentUser.RequiredId, limit), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
@@ -157,10 +173,9 @@ public static class AlbumEndpoints
|
||||
IMediator mediator,
|
||||
Guid albumId,
|
||||
CancellationToken cancellationToken,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 50)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetAlbumTracksQuery(albumId, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetAlbumTracksQuery(albumId, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -178,6 +193,9 @@ public static class AlbumEndpoints
|
||||
|
||||
var location = result.Value;
|
||||
var stream = await storageService.GetFileAsync(location.Bucket, location.Key, cancellationToken);
|
||||
if (stream == null)
|
||||
return Results.NotFound();
|
||||
|
||||
response.Headers.CacheControl = "public, max-age=31536000, immutable";
|
||||
return Results.Stream(stream, location.ContentType);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ using Musify.Api.Authentication;
|
||||
using Musify.Api.DataTransferObjects.Likes;
|
||||
using Musify.Api.Extensions;
|
||||
using Musify.Application.Likes;
|
||||
using Musify.Api.Models;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
using Musify.Api.Filters;
|
||||
|
||||
namespace Musify.Api.Endpoints;
|
||||
|
||||
@@ -17,6 +19,7 @@ public static class LikeEndpoints
|
||||
.RequireAuthorization();
|
||||
|
||||
group.MapGet("/", GetLikedTracks)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetLikedTracks")
|
||||
.WithSummary("Get The Current User'S Liked Tracks.")
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>()
|
||||
@@ -36,10 +39,9 @@ public static class LikeEndpoints
|
||||
IMediator mediator,
|
||||
CurrentUser currentUser,
|
||||
CancellationToken cancellationToken,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetLikedTracksQuery(currentUser.RequiredId, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetLikedTracksQuery(currentUser.RequiredId, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Musify.Api.Filters;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Application.PlayLists;
|
||||
using Musify.Application.PlayLists.Responses;
|
||||
using Musify.Api.Models;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Tracks;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
@@ -26,11 +27,13 @@ public static class PlayListEndpoints
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/users/{userId}", GetPlayListsByUserId)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetPlayListsByUserId")
|
||||
.WithSummary("Get Paginated PlayLists For A User. Private PlayLists Are Only Returned To Their Owner.")
|
||||
.Produces<PaginatedResponse<PlayListApplicationResponse>>();
|
||||
|
||||
group.MapGet("/{playlistId}/tracks", GetPlayListTracks)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetPlayListTracks")
|
||||
.WithSummary("Get Paginated Tracks Of A PlayList.")
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>()
|
||||
@@ -60,6 +63,7 @@ public static class PlayListEndpoints
|
||||
.Produces<PlayListApplicationResponse>()
|
||||
.ProducesValidationProblem()
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapDelete("/{playlistId}", DeletePlayList)
|
||||
@@ -68,6 +72,7 @@ public static class PlayListEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapPost("/upload-picture", RequestPlayListPictureUpload)
|
||||
@@ -86,6 +91,7 @@ public static class PlayListEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces<TrackApplicationResponse>(StatusCodes.Status201Created)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict);
|
||||
|
||||
@@ -95,6 +101,7 @@ public static class PlayListEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
return app;
|
||||
@@ -116,10 +123,9 @@ public static class PlayListEndpoints
|
||||
long userId,
|
||||
CancellationToken cancellationToken,
|
||||
string? name,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, pageNumber, pageSize, currentUser.Id), cancellationToken);
|
||||
var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -128,10 +134,9 @@ public static class PlayListEndpoints
|
||||
CurrentUser currentUser,
|
||||
Guid playlistId,
|
||||
CancellationToken cancellationToken,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetPlayListTracksQuery(playlistId, pageNumber, pageSize, currentUser.Id), cancellationToken);
|
||||
var result = await mediator.Send(new GetPlayListTracksQuery(playlistId, page.PageNumber, page.PageSize, currentUser.Id), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -149,6 +154,9 @@ public static class PlayListEndpoints
|
||||
|
||||
var location = result.Value;
|
||||
var stream = await storageService.GetFileAsync(location.Bucket, location.Key, cancellationToken);
|
||||
if (stream == null)
|
||||
return Results.NotFound();
|
||||
|
||||
response.Headers.CacheControl = "public, max-age=31536000, immutable";
|
||||
return Results.Stream(stream, location.ContentType);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Musify.Api.DataTransferObjects.Tracks;
|
||||
using Musify.Api.Extensions;
|
||||
using Musify.Api.Filters;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Api.Models;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Tracks;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
@@ -19,6 +20,7 @@ public static class TrackEndpoints
|
||||
.WithTags("Tracks");
|
||||
|
||||
group.MapGet("/", GetTracks)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetTracks")
|
||||
.WithSummary("Get Paginated Tracks, Optionally Filtered By Name And Genre.")
|
||||
.Produces<TracksSearchResponse>()
|
||||
@@ -52,6 +54,7 @@ public static class TrackEndpoints
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/users/{userId}", GetTracksByUserId)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetTracksByUserId")
|
||||
.WithSummary("Get Paginated Tracks For A User.")
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>();
|
||||
@@ -73,6 +76,7 @@ public static class TrackEndpoints
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict);
|
||||
|
||||
@@ -95,8 +99,7 @@ public static class TrackEndpoints
|
||||
CancellationToken cancellationToken,
|
||||
string? name,
|
||||
string? genre,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
Genre? parsedGenre = null;
|
||||
if (!string.IsNullOrWhiteSpace(genre))
|
||||
@@ -112,7 +115,7 @@ public static class TrackEndpoints
|
||||
parsedGenre = value;
|
||||
}
|
||||
|
||||
var result = await mediator.Send(new GetTracksQuery(name, pageNumber, pageSize, parsedGenre), cancellationToken);
|
||||
var result = await mediator.Send(new GetTracksQuery(name, page.PageNumber, page.PageSize, parsedGenre), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -139,6 +142,9 @@ public static class TrackEndpoints
|
||||
|
||||
var location = result.Value;
|
||||
var stream = await storageService.GetFileAsync(location.Bucket, location.Key, cancellationToken);
|
||||
if (stream == null)
|
||||
return Results.NotFound();
|
||||
|
||||
response.Headers.CacheControl = "public, max-age=31536000, immutable";
|
||||
return Results.Stream(stream, location.ContentType);
|
||||
}
|
||||
@@ -171,10 +177,9 @@ public static class TrackEndpoints
|
||||
long userId,
|
||||
CancellationToken cancellationToken,
|
||||
string? name,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetTracksByUserIdQuery(userId, name, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetTracksByUserIdQuery(userId, name, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using Mediator;
|
||||
using Musify.Api.Authentication;
|
||||
using Musify.Api.Extensions;
|
||||
using Musify.Api.Models;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Tracks;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
using Musify.Application.Users;
|
||||
using Musify.Application.Users.Responses;
|
||||
using Musify.Api.Filters;
|
||||
|
||||
namespace Musify.Api.Endpoints;
|
||||
|
||||
@@ -17,6 +19,7 @@ public static class UserEndpoints
|
||||
.WithTags("Users");
|
||||
|
||||
group.MapGet("/", GetUsers)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetUsers")
|
||||
.WithSummary("Get Paginated Users.")
|
||||
.Produces<PaginatedResponse<UserSummaryResponse>>();
|
||||
@@ -45,7 +48,7 @@ public static class UserEndpoints
|
||||
.WithName("GetLastTrackListenedByUserId")
|
||||
.WithSummary("Get The Last Track A User Listened To.")
|
||||
.Produces<TrackApplicationResponse>()
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
.Produces(StatusCodes.Status204NoContent);
|
||||
|
||||
group.MapGet("/{id}/profile", GetUserProfile)
|
||||
.WithName("GetUserProfile")
|
||||
@@ -54,6 +57,7 @@ public static class UserEndpoints
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/{id}/followers", GetFollowers)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetUserFollowers")
|
||||
.WithSummary("Get The Users Following A User. Visible To The User And To Mutual Followers.")
|
||||
.Produces<PaginatedResponse<UserSummaryResponse>>()
|
||||
@@ -61,6 +65,7 @@ public static class UserEndpoints
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/{id}/following", GetFollowing)
|
||||
.AddEndpointFilter<ValidationFilter<PageQuery>>()
|
||||
.WithName("GetUserFollowing")
|
||||
.WithSummary("Get The Users A User Follows.")
|
||||
.Produces<PaginatedResponse<UserSummaryResponse>>()
|
||||
@@ -97,10 +102,9 @@ public static class UserEndpoints
|
||||
CurrentUser currentUser,
|
||||
CancellationToken cancellationToken,
|
||||
string? usernameSearch,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 10)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetUsersQuery(pageNumber, pageSize, usernameSearch, currentUser.Id), cancellationToken);
|
||||
var result = await mediator.Send(new GetUsersQuery(page.PageNumber, page.PageSize, usernameSearch, currentUser.Id), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -138,8 +142,8 @@ public static class UserEndpoints
|
||||
long id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await mediator.Send(new GetLastTrackListenedByUserIdQuery(id), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
var track = await mediator.Send(new GetLastTrackListenedByUserIdQuery(id), cancellationToken);
|
||||
return track == null ? Results.NoContent() : Results.Ok(track);
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetUserProfile(
|
||||
@@ -157,10 +161,9 @@ public static class UserEndpoints
|
||||
CurrentUser currentUser,
|
||||
long id,
|
||||
CancellationToken cancellationToken,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 20)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetUserFollowersQuery(currentUser.Id, id, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetUserFollowersQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
@@ -169,10 +172,9 @@ public static class UserEndpoints
|
||||
CurrentUser currentUser,
|
||||
long id,
|
||||
CancellationToken cancellationToken,
|
||||
int pageNumber = 1,
|
||||
int pageSize = 20)
|
||||
[AsParameters] PageQuery page)
|
||||
{
|
||||
var result = await mediator.Send(new GetUserFollowingQuery(currentUser.Id, id, pageNumber, pageSize), cancellationToken);
|
||||
var result = await mediator.Send(new GetUserFollowingQuery(currentUser.Id, id, page.PageNumber, page.PageSize), cancellationToken);
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ public static class ErrorOrHttpExtensions
|
||||
var error = errors[0];
|
||||
return error.Type switch
|
||||
{
|
||||
ErrorType.NotFound => Results.NotFound(error.Description),
|
||||
ErrorType.NotFound => Results.Problem(error.Description, statusCode: StatusCodes.Status404NotFound),
|
||||
ErrorType.Unauthorized => Results.Unauthorized(),
|
||||
ErrorType.Forbidden => Results.StatusCode(StatusCodes.Status403Forbidden),
|
||||
ErrorType.Conflict => Results.Conflict(error.Description),
|
||||
ErrorType.Forbidden => Results.Problem(error.Description, statusCode: StatusCodes.Status403Forbidden),
|
||||
ErrorType.Conflict => Results.Problem(error.Description, statusCode: StatusCodes.Status409Conflict),
|
||||
_ => Results.Problem(error.Description)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Musify.Application.Shared;
|
||||
|
||||
namespace Musify.Api.Models;
|
||||
|
||||
/// <summary>Query-string binding for <see cref="PageRequest"/>, keeping the camelCase parameter names of the API.</summary>
|
||||
public record PageQuery(
|
||||
[property: FromQuery(Name = "pageNumber")] int PageNumber = 1,
|
||||
[property: FromQuery(Name = "pageSize")] int PageSize = 20);
|
||||
@@ -0,0 +1,17 @@
|
||||
using FluentValidation;
|
||||
using Musify.Api.Models;
|
||||
using Musify.Application.Shared;
|
||||
|
||||
namespace Musify.Api.Validators;
|
||||
|
||||
public sealed class PageQueryValidator : AbstractValidator<PageQuery>
|
||||
{
|
||||
public PageQueryValidator()
|
||||
{
|
||||
RuleFor(x => x.PageNumber)
|
||||
.GreaterThanOrEqualTo(1);
|
||||
|
||||
RuleFor(x => x.PageSize)
|
||||
.InclusiveBetween(1, PageRequest.MaxPageSize);
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,8 @@ public sealed class CreatePlayListRequestValidator : AbstractValidator<CreatePla
|
||||
|
||||
RuleFor(x => x.Description)
|
||||
.MaximumLength(256);
|
||||
|
||||
RuleFor(x => x.Visibility)
|
||||
.IsInEnum();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,5 +15,9 @@ public sealed class UpdatePlayListRequestValidator : AbstractValidator<UpdatePla
|
||||
RuleFor(x => x.NewDescription)
|
||||
.MaximumLength(256)
|
||||
.When(x => x.NewDescription != null);
|
||||
|
||||
RuleFor(x => x.NewVisibility)
|
||||
.IsInEnum()
|
||||
.When(x => x.NewVisibility != null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user