Design Prot Merge #1
@@ -3,6 +3,7 @@ using Musify.Application.PlayLists;
|
||||
using Musify.Application.PlayLists.Responses;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Api.Authentication;
|
||||
using Musify.Api.DataTransferObjects.PlayLists;
|
||||
using Musify.Api.Extensions;
|
||||
@@ -39,6 +40,12 @@ public static class PlayListEndpoints
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>()
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/{id}/cover", GetPlayListCover)
|
||||
.WithName("GetPlayListCover")
|
||||
.WithSummary("Get A PlayList Cover Image.")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapPost("/", CreatePlayList)
|
||||
.WithName("CreatePlayList")
|
||||
.WithSummary("Create A New PlayList.")
|
||||
@@ -139,14 +146,36 @@ public static class PlayListEndpoints
|
||||
return result.ToHttpResult();
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetPlayListCover(
|
||||
IMediator mediator,
|
||||
IStorageService storageService,
|
||||
HttpResponse response,
|
||||
Guid id,
|
||||
CancellationToken cancellationToken,
|
||||
string size = "medium")
|
||||
{
|
||||
var result = await mediator.Send(new GetPlayListCoverQuery(id, size), cancellationToken);
|
||||
if (result.IsError)
|
||||
return Results.NotFound();
|
||||
|
||||
var location = result.Value;
|
||||
var stream = await storageService.GetFileAsync(location.Bucket, location.Key, cancellationToken);
|
||||
response.Headers.CacheControl = "public, max-age=31536000, immutable";
|
||||
return Results.Stream(stream, location.ContentType);
|
||||
}
|
||||
|
||||
private static async Task<IResult> CreatePlayList(
|
||||
IMediator mediator,
|
||||
CurrentUser currentUser,
|
||||
CreatePlayListRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var description = string.IsNullOrWhiteSpace(request.Description)
|
||||
? "No description was provided."
|
||||
: request.Description;
|
||||
|
||||
var result = await mediator.Send(
|
||||
new CreatePlayListCommand(currentUser.RequiredId, request.Name, request.Description, request.PictureIntentId),
|
||||
new CreatePlayListCommand(currentUser.RequiredId, request.Name, description, request.PictureIntentId),
|
||||
cancellationToken);
|
||||
|
||||
return result.ToCreatedResult(playList => $"/playlists/{playList.Id}");
|
||||
|
||||
@@ -12,7 +12,6 @@ public sealed class CreatePlayListRequestValidator : AbstractValidator<CreatePla
|
||||
.MaximumLength(50);
|
||||
|
||||
RuleFor(x => x.Description)
|
||||
.NotEmpty()
|
||||
.MaximumLength(256);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ public sealed class UpdatePlayListRequestValidator : AbstractValidator<UpdatePla
|
||||
.When(x => x.NewName is not null);
|
||||
|
||||
RuleFor(x => x.NewDescription)
|
||||
.NotEmpty()
|
||||
.MaximumLength(256)
|
||||
.When(x => x.NewDescription is not null);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" />
|
||||
<PackageReference Include="MimeMapping" />
|
||||
<PackageReference Include="X.PagedList" />
|
||||
<PackageReference Include="X.PagedList.EF" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using ErrorOr;
|
||||
using Mediator;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MimeMapping;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
|
||||
namespace Musify.Application.PlayLists
|
||||
{
|
||||
public record GetPlayListCoverQuery(Guid PlayListId, string Size)
|
||||
: IQuery<ErrorOr<PlayListCoverLocation>>;
|
||||
|
||||
public record PlayListCoverLocation(string Bucket, string Key, string ContentType);
|
||||
|
||||
public class GetPlayListCoverQueryHandler(
|
||||
IDatabase database,
|
||||
ApplicationStorageConfiguration storageConfiguration,
|
||||
PlayListConfiguration playListConfiguration)
|
||||
: IQueryHandler<GetPlayListCoverQuery, ErrorOr<PlayListCoverLocation>>
|
||||
{
|
||||
public async ValueTask<ErrorOr<PlayListCoverLocation>> Handle(GetPlayListCoverQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var playList = await database.PlayLists.AsNoTracking()
|
||||
.Where(p => p.Id == request.PlayListId)
|
||||
.Select(p => new { p.SmallPictureName, p.MediumPictureName, p.LargePictureName })
|
||||
.SingleOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (playList == null)
|
||||
return Error.NotFound();
|
||||
|
||||
var routes = playListConfiguration.Routes;
|
||||
var (name, key, presetName) = request.Size.ToLowerInvariant() switch
|
||||
{
|
||||
"small" => (playList.SmallPictureName, BuildKey(routes.BuildSmallPicturePath, playList.SmallPictureName), routes.PresetSmallPicture),
|
||||
"large" => (playList.LargePictureName, BuildKey(routes.BuildLargePicturePath, playList.LargePictureName), routes.PresetLargePicture),
|
||||
_ => (playList.MediumPictureName, BuildKey(routes.BuildMediumPicturePath, playList.MediumPictureName), routes.PresetMediumPicture)
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(key) || name == presetName)
|
||||
return Error.NotFound();
|
||||
|
||||
var contentType = MimeUtility.GetMimeMapping(name);
|
||||
return new PlayListCoverLocation(storageConfiguration.Bucket, key, contentType);
|
||||
}
|
||||
|
||||
private static string? BuildKey(Func<string, string> builder, string? name)
|
||||
=> string.IsNullOrEmpty(name) ? null : builder(name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user