Design Prot Merge #1
@@ -3,6 +3,7 @@ using Musify.Application.PlayLists;
|
|||||||
using Musify.Application.PlayLists.Responses;
|
using Musify.Application.PlayLists.Responses;
|
||||||
using Musify.Application.Tracks.Responses;
|
using Musify.Application.Tracks.Responses;
|
||||||
using Musify.Application.Shared;
|
using Musify.Application.Shared;
|
||||||
|
using Musify.Application.Contracts;
|
||||||
using Musify.Api.Authentication;
|
using Musify.Api.Authentication;
|
||||||
using Musify.Api.DataTransferObjects.PlayLists;
|
using Musify.Api.DataTransferObjects.PlayLists;
|
||||||
using Musify.Api.Extensions;
|
using Musify.Api.Extensions;
|
||||||
@@ -39,6 +40,12 @@ public static class PlayListEndpoints
|
|||||||
.Produces<PaginatedResponse<TrackApplicationResponse>>()
|
.Produces<PaginatedResponse<TrackApplicationResponse>>()
|
||||||
.Produces(StatusCodes.Status404NotFound);
|
.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)
|
group.MapPost("/", CreatePlayList)
|
||||||
.WithName("CreatePlayList")
|
.WithName("CreatePlayList")
|
||||||
.WithSummary("Create A New PlayList.")
|
.WithSummary("Create A New PlayList.")
|
||||||
@@ -139,14 +146,36 @@ public static class PlayListEndpoints
|
|||||||
return result.ToHttpResult();
|
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(
|
private static async Task<IResult> CreatePlayList(
|
||||||
IMediator mediator,
|
IMediator mediator,
|
||||||
CurrentUser currentUser,
|
CurrentUser currentUser,
|
||||||
CreatePlayListRequest request,
|
CreatePlayListRequest request,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var description = string.IsNullOrWhiteSpace(request.Description)
|
||||||
|
? "No description was provided."
|
||||||
|
: request.Description;
|
||||||
|
|
||||||
var result = await mediator.Send(
|
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);
|
cancellationToken);
|
||||||
|
|
||||||
return result.ToCreatedResult(playList => $"/playlists/{playList.Id}");
|
return result.ToCreatedResult(playList => $"/playlists/{playList.Id}");
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ public sealed class CreatePlayListRequestValidator : AbstractValidator<CreatePla
|
|||||||
.MaximumLength(50);
|
.MaximumLength(50);
|
||||||
|
|
||||||
RuleFor(x => x.Description)
|
RuleFor(x => x.Description)
|
||||||
.NotEmpty()
|
|
||||||
.MaximumLength(256);
|
.MaximumLength(256);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ public sealed class UpdatePlayListRequestValidator : AbstractValidator<UpdatePla
|
|||||||
.When(x => x.NewName is not null);
|
.When(x => x.NewName is not null);
|
||||||
|
|
||||||
RuleFor(x => x.NewDescription)
|
RuleFor(x => x.NewDescription)
|
||||||
.NotEmpty()
|
|
||||||
.MaximumLength(256)
|
.MaximumLength(256)
|
||||||
.When(x => x.NewDescription is not null);
|
.When(x => x.NewDescription is not null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" />
|
<PackageReference Include="Microsoft.Extensions.Options.DataAnnotations" />
|
||||||
|
<PackageReference Include="MimeMapping" />
|
||||||
<PackageReference Include="X.PagedList" />
|
<PackageReference Include="X.PagedList" />
|
||||||
<PackageReference Include="X.PagedList.EF" />
|
<PackageReference Include="X.PagedList.EF" />
|
||||||
</ItemGroup>
|
</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