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
@@ -13,263 +13,264 @@ using Musify.Application.Pictures.Responses;
using Musify.Application.Shared;
using Musify.Application.Tracks.Responses;
namespace Musify.Api.Endpoints;
public static class AlbumEndpoints
namespace Musify.Api.Endpoints
{
private const int MaxRecentAlbumsLimit = 50;
public static IEndpointRouteBuilder MapAlbumEndpoints(this IEndpointRouteBuilder app)
public static class AlbumEndpoints
{
var group = app.MapGroup("/albums")
.WithTags("Albums");
private const int MaxRecentAlbumsLimit = 50;
group.MapGet("/", GetAlbums)
.AddEndpointFilter<ValidationFilter<PageQuery>>()
.WithName("GetAlbums")
.WithSummary("Get paginated albums, optionally filtered by title")
.Produces<PaginatedResponse<AlbumApplicationResponse>>();
group.MapGet("/{id:guid}", GetAlbumById)
.WithName("GetAlbumById")
.WithSummary("Get an album by id")
.Produces<AlbumApplicationResponse>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/users/{userId:long}", GetAlbumsByUserId)
.AddEndpointFilter<ValidationFilter<PageQuery>>()
.WithName("GetAlbumsByUserId")
.WithSummary("Get paginated albums for a user")
.Produces<PaginatedResponse<AlbumApplicationResponse>>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/recent", GetRecentlyListenedAlbums)
.WithName("GetRecentlyListenedAlbums")
.WithSummary("Get the albums the current user listened to most recently")
.RequireAuthorization()
.Produces<IReadOnlyList<AlbumApplicationResponse>>()
.Produces(StatusCodes.Status401Unauthorized);
group.MapGet("/{id:guid}/tracks", GetAlbumTracks)
.AddEndpointFilter<ValidationFilter<PageQuery>>()
.WithName("GetAlbumTracks")
.WithSummary("Get the tracks of an album ordered by track number")
.Produces<PaginatedResponse<TrackApplicationResponse>>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/{id:guid}/cover", GetAlbumCover)
.WithName("GetAlbumCover")
.WithSummary("Get an album cover image")
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
group.MapPost("/upload-picture", RequestAlbumPictureUpload)
.WithName("RequestAlbumPictureUpload")
.WithSummary("Request a pre-signed URL to upload an album picture")
.AddEndpointFilter<ValidationFilter<PictureUploadRequest>>()
.RequireAuthorization()
.Produces<PictureUploadResponse>()
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound);
group.MapPost("/", CreateAlbum)
.WithName("CreateAlbum")
.WithSummary("Create a new album")
.AddEndpointFilter<ValidationFilter<CreateAlbumRequest>>()
.RequireAuthorization()
.Produces<AlbumApplicationResponse>(StatusCodes.Status201Created)
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound);
group.MapPut("/{id:guid}", UpdateAlbum)
.WithName("UpdateAlbum")
.WithSummary("Update an album")
.AddEndpointFilter<ValidationFilter<UpdateAlbumRequest>>()
.RequireAuthorization()
.Produces<AlbumApplicationResponse>()
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
group.MapDelete("/{id:guid}", DeleteAlbum)
.WithName("DeleteAlbum")
.WithSummary("Delete an album")
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
group.MapPost("/{id:guid}/tracks", AddAlbumTracks)
.WithName("AddAlbumTracks")
.WithSummary("Add your uploaded tracks to an album, skipping the ones it already has")
.AddEndpointFilter<ValidationFilter<AddTracksRequest>>()
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status409Conflict);
group.MapDelete("/{id:guid}/tracks/{trackId:guid}", RemoveTrackFromAlbum)
.WithName("RemoveTrackFromAlbum")
.WithSummary("Remove a track from an album")
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
return app;
}
private static async Task<IResult> GetAlbums(
IMediator mediator,
string? title,
[AsParameters] PageQuery page,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumsQuery(title, page.PageNumber, page.PageSize), cancellationToken);
return Results.Ok(result);
}
private static async Task<IResult> GetAlbumById(
IMediator mediator,
Guid id,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumByIdQuery(id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetAlbumsByUserId(
IMediator mediator,
long userId,
[AsParameters] PageQuery page,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetRecentlyListenedAlbums(
IMediator mediator,
CurrentUser currentUser,
CancellationToken cancellationToken,
int limit = 12)
{
if (limit is < 1 or > MaxRecentAlbumsLimit)
public static IEndpointRouteBuilder MapAlbumEndpoints(this IEndpointRouteBuilder app)
{
return Results.ValidationProblem(new Dictionary<string, string[]>
{
[nameof(limit)] = [$"'{nameof(limit)}' must be between 1 and {MaxRecentAlbumsLimit}."]
});
var group = app.MapGroup("/albums")
.WithTags("Albums");
group.MapGet("/", GetAlbums)
.AddEndpointFilter<ValidationFilter<PageQuery>>()
.WithName("GetAlbums")
.WithSummary("Get paginated albums, optionally filtered by title")
.Produces<PaginatedResponse<AlbumApplicationResponse>>();
group.MapGet("/{id:guid}", GetAlbumById)
.WithName("GetAlbumById")
.WithSummary("Get an album by id")
.Produces<AlbumApplicationResponse>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/users/{userId:long}", GetAlbumsByUserId)
.AddEndpointFilter<ValidationFilter<PageQuery>>()
.WithName("GetAlbumsByUserId")
.WithSummary("Get paginated albums for a user")
.Produces<PaginatedResponse<AlbumApplicationResponse>>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/recent", GetRecentlyListenedAlbums)
.WithName("GetRecentlyListenedAlbums")
.WithSummary("Get the albums the current user listened to most recently")
.RequireAuthorization()
.Produces<IReadOnlyList<AlbumApplicationResponse>>()
.Produces(StatusCodes.Status401Unauthorized);
group.MapGet("/{id:guid}/tracks", GetAlbumTracks)
.AddEndpointFilter<ValidationFilter<PageQuery>>()
.WithName("GetAlbumTracks")
.WithSummary("Get the tracks of an album ordered by track number")
.Produces<PaginatedResponse<TrackApplicationResponse>>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/{id:guid}/cover", GetAlbumCover)
.WithName("GetAlbumCover")
.WithSummary("Get an album cover image")
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
group.MapPost("/upload-picture", RequestAlbumPictureUpload)
.WithName("RequestAlbumPictureUpload")
.WithSummary("Request a pre-signed URL to upload an album picture")
.AddEndpointFilter<ValidationFilter<PictureUploadRequest>>()
.RequireAuthorization()
.Produces<PictureUploadResponse>()
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound);
group.MapPost("/", CreateAlbum)
.WithName("CreateAlbum")
.WithSummary("Create a new album")
.AddEndpointFilter<ValidationFilter<CreateAlbumRequest>>()
.RequireAuthorization()
.Produces<AlbumApplicationResponse>(StatusCodes.Status201Created)
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound);
group.MapPut("/{id:guid}", UpdateAlbum)
.WithName("UpdateAlbum")
.WithSummary("Update an album")
.AddEndpointFilter<ValidationFilter<UpdateAlbumRequest>>()
.RequireAuthorization()
.Produces<AlbumApplicationResponse>()
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
group.MapDelete("/{id:guid}", DeleteAlbum)
.WithName("DeleteAlbum")
.WithSummary("Delete an album")
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
group.MapPost("/{id:guid}/tracks", AddAlbumTracks)
.WithName("AddAlbumTracks")
.WithSummary("Add your uploaded tracks to an album, skipping the ones it already has")
.AddEndpointFilter<ValidationFilter<AddTracksRequest>>()
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.ProducesValidationProblem()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status409Conflict);
group.MapDelete("/{id:guid}/tracks/{trackId:guid}", RemoveTrackFromAlbum)
.WithName("RemoveTrackFromAlbum")
.WithSummary("Remove a track from an album")
.RequireAuthorization()
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);
return app;
}
var result = await mediator.Send(new GetRecentlyListenedAlbumsQuery(currentUser.RequiredId, limit), cancellationToken);
return Results.Ok(result);
}
private static async Task<IResult> GetAlbums(
IMediator mediator,
string? title,
[AsParameters] PageQuery page,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumsQuery(title, page.PageNumber, page.PageSize), cancellationToken);
return Results.Ok(result);
}
private static async Task<IResult> GetAlbumTracks(
IMediator mediator,
Guid id,
[AsParameters] PageQuery page,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumTracksQuery(id, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetAlbumById(
IMediator mediator,
Guid id,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumByIdQuery(id), cancellationToken);
return result.ToHttpResult();
}
private static Task<IResult> GetAlbumCover(
IMediator mediator,
IStorageService storageService,
HttpResponse response,
Guid id,
CancellationToken cancellationToken,
string size = "medium")
{
return CoverEndpoint.Stream(mediator, storageService, response, CoverOwner.Album, id, size, cancellationToken);
}
private static async Task<IResult> GetAlbumsByUserId(
IMediator mediator,
long userId,
[AsParameters] PageQuery page,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumsByUserIdQuery(userId, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> RequestAlbumPictureUpload(
IMediator mediator,
CurrentUser currentUser,
PictureUploadRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new RequestAlbumPictureUploadCommand(currentUser.RequiredId, request.FileType, request.ContentType, request.ExpectedSizeBytes),
cancellationToken);
private static async Task<IResult> GetRecentlyListenedAlbums(
IMediator mediator,
CurrentUser currentUser,
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}."]
});
}
return result.ToHttpResult();
}
var result = await mediator.Send(new GetRecentlyListenedAlbumsQuery(currentUser.RequiredId, limit), cancellationToken);
return Results.Ok(result);
}
private static async Task<IResult> CreateAlbum(
IMediator mediator,
CurrentUser currentUser,
CreateAlbumRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new CreateAlbumCommand(currentUser.RequiredId, request.Title, request.Description, request.ReleaseYear, request.PictureIntentId),
cancellationToken);
private static async Task<IResult> GetAlbumTracks(
IMediator mediator,
Guid id,
[AsParameters] PageQuery page,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new GetAlbumTracksQuery(id, page.PageNumber, page.PageSize), cancellationToken);
return result.ToHttpResult();
}
return result.ToCreatedResult(album => $"/albums/{album.Id}");
}
private static Task<IResult> GetAlbumCover(
IMediator mediator,
IStorageService storageService,
HttpResponse response,
Guid id,
CancellationToken cancellationToken,
string size = "medium")
{
return CoverEndpoint.Stream(mediator, storageService, response, CoverOwner.Album, id, size, cancellationToken);
}
private static async Task<IResult> UpdateAlbum(
IMediator mediator,
CurrentUser currentUser,
Guid id,
UpdateAlbumRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new UpdateAlbumCommand(currentUser.RequiredId, id, request.Title, request.Description, request.ReleaseYear, request.PictureIntentId),
cancellationToken);
private static async Task<IResult> RequestAlbumPictureUpload(
IMediator mediator,
CurrentUser currentUser,
PictureUploadRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new RequestAlbumPictureUploadCommand(currentUser.RequiredId, request.FileType, request.ContentType, request.ExpectedSizeBytes),
cancellationToken);
return result.ToHttpResult();
}
return result.ToHttpResult();
}
private static async Task<IResult> DeleteAlbum(
IMediator mediator,
CurrentUser currentUser,
Guid id,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new DeleteAlbumCommand(currentUser.RequiredId, id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> CreateAlbum(
IMediator mediator,
CurrentUser currentUser,
CreateAlbumRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new CreateAlbumCommand(currentUser.RequiredId, request.Title, request.Description, request.ReleaseYear, request.PictureIntentId),
cancellationToken);
private static async Task<IResult> AddAlbumTracks(
IMediator mediator,
CurrentUser currentUser,
Guid id,
AddTracksRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new AddTracksToAlbumCommand(currentUser.RequiredId, id, request.TrackIds),
cancellationToken);
return result.ToCreatedResult(album => $"/albums/{album.Id}");
}
return result.ToHttpResult();
}
private static async Task<IResult> UpdateAlbum(
IMediator mediator,
CurrentUser currentUser,
Guid id,
UpdateAlbumRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new UpdateAlbumCommand(currentUser.RequiredId, id, request.Title, request.Description, request.ReleaseYear, request.PictureIntentId),
cancellationToken);
private static async Task<IResult> RemoveTrackFromAlbum(
IMediator mediator,
CurrentUser currentUser,
Guid id,
Guid trackId,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new RemoveTrackFromAlbumCommand(currentUser.RequiredId, id, trackId),
cancellationToken);
return result.ToHttpResult();
}
return result.ToHttpResult();
private static async Task<IResult> DeleteAlbum(
IMediator mediator,
CurrentUser currentUser,
Guid id,
CancellationToken cancellationToken)
{
var result = await mediator.Send(new DeleteAlbumCommand(currentUser.RequiredId, id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> AddAlbumTracks(
IMediator mediator,
CurrentUser currentUser,
Guid id,
AddTracksRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new AddTracksToAlbumCommand(currentUser.RequiredId, id, request.TrackIds),
cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> RemoveTrackFromAlbum(
IMediator mediator,
CurrentUser currentUser,
Guid id,
Guid trackId,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new RemoveTrackFromAlbumCommand(currentUser.RequiredId, id, trackId),
cancellationToken);
return result.ToHttpResult();
}
}
}