A lot of changes
This commit is contained in:
@@ -3,13 +3,8 @@ using Mediator;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Albums.Responses;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Application.Events;
|
||||
using Musify.Application.Pictures;
|
||||
using Musify.Application.Services;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
namespace Musify.Application.Albums
|
||||
{
|
||||
@@ -18,16 +13,12 @@ namespace Musify.Application.Albums
|
||||
Guid AlbumId,
|
||||
string Title,
|
||||
string? Description,
|
||||
int? ReleaseYear,
|
||||
Guid? NewPictureIntentId)
|
||||
int? ReleaseYear)
|
||||
: ICommand<ErrorOr<AlbumApplicationResponse>>;
|
||||
|
||||
public class UpdateAlbumCommandHandler(
|
||||
IEventBus eventBus,
|
||||
IDatabase database,
|
||||
UploadIntentValidator uploadIntentValidator,
|
||||
ILogger<UpdateAlbumCommandHandler> logger,
|
||||
AlbumConfiguration albumConfiguration)
|
||||
ILogger<UpdateAlbumCommandHandler> logger)
|
||||
: ICommandHandler<UpdateAlbumCommand, ErrorOr<AlbumApplicationResponse>>
|
||||
{
|
||||
public async ValueTask<ErrorOr<AlbumApplicationResponse>> Handle(UpdateAlbumCommand request, CancellationToken cancellationToken)
|
||||
@@ -39,27 +30,12 @@ namespace Musify.Application.Albums
|
||||
var album = found.Value;
|
||||
var title = request.Title.Trim();
|
||||
|
||||
// This is a full replace of the plain fields; the picture has its own command.
|
||||
album.Title = title;
|
||||
album.NormalizedTitle = TextNormalizer.Normalize(title);
|
||||
album.Description = request.Description;
|
||||
album.Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim();
|
||||
album.ReleaseYear = request.ReleaseYear;
|
||||
|
||||
if (request.NewPictureIntentId is { } intentId)
|
||||
{
|
||||
var prepared = await PictureSourceChange.PrepareAsync(
|
||||
uploadIntentValidator, intentId, request.UserId, UploadIntentPurpose.AlbumPicture, albumConfiguration, cancellationToken);
|
||||
if (prepared.IsError)
|
||||
return prepared.Errors;
|
||||
|
||||
var picture = prepared.Value;
|
||||
album.Pictures = picture.Pictures;
|
||||
|
||||
await eventBus.PublishAsync(
|
||||
new UpdateAlbumPictureSourceEvent(
|
||||
album.Id, picture.Intent.Id, picture.Intent.Bucket, picture.Intent.Key, picture.FinalKey, picture.Sizes),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
|
||||
logger.LogInformation("Updated album {AlbumId}", album.Id);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using ErrorOr;
|
||||
using Mediator;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Albums.Responses;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Application.Events;
|
||||
using Musify.Application.Pictures;
|
||||
using Musify.Application.Services;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
namespace Musify.Application.Albums
|
||||
{
|
||||
/// <summary>Replaces an album's picture with a previously uploaded image; the plain fields are untouched.</summary>
|
||||
public record UpdateAlbumPictureCommand(long UserId, Guid AlbumId, Guid PictureIntentId)
|
||||
: ICommand<ErrorOr<AlbumApplicationResponse>>;
|
||||
|
||||
public class UpdateAlbumPictureCommandHandler(
|
||||
IEventBus eventBus,
|
||||
IDatabase database,
|
||||
UploadIntentValidator uploadIntentValidator,
|
||||
ILogger<UpdateAlbumPictureCommandHandler> logger,
|
||||
AlbumConfiguration albumConfiguration)
|
||||
: ICommandHandler<UpdateAlbumPictureCommand, ErrorOr<AlbumApplicationResponse>>
|
||||
{
|
||||
public async ValueTask<ErrorOr<AlbumApplicationResponse>> Handle(UpdateAlbumPictureCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var found = await database.Albums.FindOwnedAsync(request.AlbumId, request.UserId, cancellationToken);
|
||||
if (found.IsError)
|
||||
return found.Errors;
|
||||
|
||||
var album = found.Value;
|
||||
|
||||
var prepared = await PictureSourceChange.PrepareAsync(
|
||||
uploadIntentValidator, request.PictureIntentId, request.UserId, UploadIntentPurpose.AlbumPicture, albumConfiguration, cancellationToken);
|
||||
if (prepared.IsError)
|
||||
return prepared.Errors;
|
||||
|
||||
var picture = prepared.Value;
|
||||
album.Pictures = picture.Pictures;
|
||||
|
||||
await eventBus.PublishAsync(
|
||||
new UpdateAlbumPictureSourceEvent(
|
||||
album.Id, picture.Intent.Id, picture.Intent.Bucket, picture.Intent.Key, picture.FinalKey, picture.Sizes),
|
||||
cancellationToken);
|
||||
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
|
||||
logger.LogInformation("Updated picture of album {AlbumId}", album.Id);
|
||||
|
||||
return await database.Albums
|
||||
.AsNoTracking()
|
||||
.Where(a => a.Id == album.Id)
|
||||
.SelectResponse()
|
||||
.SingleAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace Musify.Application.Genres
|
||||
{
|
||||
public ValueTask<IReadOnlyList<AvailableGenreResponse>> Handle(GetAvailableGenresQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var all = Enum.GetValues<Genre>();
|
||||
var all = Enum.GetValues<Genre>().Where(genre => genre != Genre.None).ToArray();
|
||||
|
||||
IReadOnlyList<AvailableGenreResponse> result = all
|
||||
.Select(genre => new AvailableGenreResponse(
|
||||
|
||||
@@ -7,6 +7,7 @@ using Musify.Application.Contracts;
|
||||
using Musify.Application.PlayLists.Responses;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Domain.Entities;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
namespace Musify.Application.PlayLists
|
||||
{
|
||||
@@ -34,7 +35,7 @@ namespace Musify.Application.PlayLists
|
||||
|
||||
var albumTrackIds = await database.AlbumHasTracks
|
||||
.AsNoTracking()
|
||||
.Where(albumTrack => albumTrack.AlbumId == request.AlbumId && albumTrack.Track.LifeCycleStatus == Domain.ValueObjects.LifeCycleStatus.Active)
|
||||
.Where(albumTrack => albumTrack.AlbumId == request.AlbumId && albumTrack.Track.LifeCycleStatus == LifeCycleStatus.Active)
|
||||
.OrderBy(albumTrack => albumTrack.TrackNumber)
|
||||
.Select(albumTrack => albumTrack.TrackId)
|
||||
.ToListAsync(cancellationToken);
|
||||
@@ -54,8 +55,9 @@ namespace Musify.Application.PlayLists
|
||||
return new AddAlbumToPlayListResponse(0);
|
||||
|
||||
// Playlist positions are internal indexes, so they start at 0.
|
||||
var nextPosition = (await playListTracksQuery
|
||||
.MaxAsync(link => (int?)link.Position, cancellationToken) ?? -1) + 1;
|
||||
var nextPosition = existingTrackIds.Count > 0
|
||||
? await playListTracksQuery.MaxAsync(link => link.Position, cancellationToken) + 1
|
||||
: 0;
|
||||
|
||||
foreach (var trackId in newTrackIds)
|
||||
{
|
||||
|
||||
@@ -47,9 +47,12 @@ namespace Musify.Application.PlayLists
|
||||
return Result.Success;
|
||||
|
||||
// Playlist positions are internal indexes, so they start at 0.
|
||||
var nextPosition = (await database.PlayListHasTracks
|
||||
.Where(link => link.PlayListId == request.PlayListId)
|
||||
.MaxAsync(link => (int?)link.Position, cancellationToken) ?? -1) + 1;
|
||||
var playListTracksQuery = database.PlayListHasTracks
|
||||
.Where(link => link.PlayListId == request.PlayListId);
|
||||
|
||||
var nextPosition = await playListTracksQuery.AnyAsync(cancellationToken)
|
||||
? await playListTracksQuery.MaxAsync(link => link.Position, cancellationToken) + 1
|
||||
: 0;
|
||||
|
||||
foreach (var trackId in toAdd)
|
||||
{
|
||||
|
||||
@@ -39,10 +39,10 @@ namespace Musify.Application.PlayLists
|
||||
return AppErrors.NotFound("User", request.UserId);
|
||||
|
||||
PreparedPicture? picture = null;
|
||||
if (request.PictureIntentId is { } intentId)
|
||||
if (request.PictureIntentId.HasValue)
|
||||
{
|
||||
var prepared = await PictureSourceChange.PrepareAsync(
|
||||
uploadIntentValidator, intentId, request.UserId, UploadIntentPurpose.PlayListPicture, playListConfiguration, cancellationToken);
|
||||
uploadIntentValidator, request.PictureIntentId.Value, request.UserId, UploadIntentPurpose.PlayListPicture, playListConfiguration, cancellationToken);
|
||||
if (prepared.IsError)
|
||||
return prepared.Errors;
|
||||
|
||||
|
||||
@@ -2,12 +2,8 @@ using ErrorOr;
|
||||
using Mediator;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Application.Events;
|
||||
using Musify.Application.Pictures;
|
||||
using Musify.Application.PlayLists.Responses;
|
||||
using Musify.Application.Services;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
@@ -16,18 +12,14 @@ namespace Musify.Application.PlayLists
|
||||
public record UpdatePlayListCommand(
|
||||
long UserId,
|
||||
Guid PlayListId,
|
||||
string? NewName,
|
||||
string NewName,
|
||||
string? NewDescription,
|
||||
Guid? NewPictureIntentId,
|
||||
PlayListVisibility? NewVisibility = null)
|
||||
PlayListVisibility NewVisibility)
|
||||
: ICommand<ErrorOr<PlayListApplicationResponse>>;
|
||||
|
||||
public class UpdatePlayListCommandHandler(
|
||||
IEventBus eventBus,
|
||||
IDatabase database,
|
||||
UploadIntentValidator uploadIntentValidator,
|
||||
ILogger<UpdatePlayListCommandHandler> logger,
|
||||
PlayListConfiguration playListConfiguration)
|
||||
ILogger<UpdatePlayListCommandHandler> logger)
|
||||
: ICommandHandler<UpdatePlayListCommand, ErrorOr<PlayListApplicationResponse>>
|
||||
{
|
||||
public async ValueTask<ErrorOr<PlayListApplicationResponse>> Handle(UpdatePlayListCommand request, CancellationToken cancellationToken)
|
||||
@@ -38,34 +30,11 @@ namespace Musify.Application.PlayLists
|
||||
|
||||
var playList = found.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.NewName))
|
||||
{
|
||||
playList.Name = request.NewName.Trim();
|
||||
playList.NormalizedName = TextNormalizer.Normalize(request.NewName);
|
||||
}
|
||||
|
||||
// null means "unchanged"; a blank description clears it.
|
||||
if (request.NewDescription != null)
|
||||
playList.Description = string.IsNullOrWhiteSpace(request.NewDescription) ? null : request.NewDescription.Trim();
|
||||
|
||||
if (request.NewVisibility is { } visibility)
|
||||
playList.Visibility = visibility;
|
||||
|
||||
if (request.NewPictureIntentId is { } intentId)
|
||||
{
|
||||
var prepared = await PictureSourceChange.PrepareAsync(
|
||||
uploadIntentValidator, intentId, request.UserId, UploadIntentPurpose.PlayListPicture, playListConfiguration, cancellationToken);
|
||||
if (prepared.IsError)
|
||||
return prepared.Errors;
|
||||
|
||||
var picture = prepared.Value;
|
||||
playList.Pictures = picture.Pictures;
|
||||
|
||||
await eventBus.PublishAsync(
|
||||
new UpdatePlayListPictureSourceEvent(
|
||||
playList.Id, picture.Intent.Id, picture.Intent.Bucket, picture.Intent.Key, picture.FinalKey, picture.Sizes),
|
||||
cancellationToken);
|
||||
}
|
||||
// This is a full replace of the plain fields; the picture has its own command.
|
||||
playList.Name = request.NewName.Trim();
|
||||
playList.NormalizedName = TextNormalizer.Normalize(request.NewName);
|
||||
playList.Description = string.IsNullOrWhiteSpace(request.NewDescription) ? null : request.NewDescription.Trim();
|
||||
playList.Visibility = request.NewVisibility;
|
||||
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using ErrorOr;
|
||||
using Mediator;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Musify.Application.Configuration;
|
||||
using Musify.Application.Contracts;
|
||||
using Musify.Application.Events;
|
||||
using Musify.Application.Pictures;
|
||||
using Musify.Application.PlayLists.Responses;
|
||||
using Musify.Application.Services;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Domain.ValueObjects;
|
||||
|
||||
namespace Musify.Application.PlayLists
|
||||
{
|
||||
/// <summary>Replaces a playlist's picture with a previously uploaded image; the plain fields are untouched.</summary>
|
||||
public record UpdatePlayListPictureCommand(long UserId, Guid PlayListId, Guid PictureIntentId)
|
||||
: ICommand<ErrorOr<PlayListApplicationResponse>>;
|
||||
|
||||
public class UpdatePlayListPictureCommandHandler(
|
||||
IEventBus eventBus,
|
||||
IDatabase database,
|
||||
UploadIntentValidator uploadIntentValidator,
|
||||
ILogger<UpdatePlayListPictureCommandHandler> logger,
|
||||
PlayListConfiguration playListConfiguration)
|
||||
: ICommandHandler<UpdatePlayListPictureCommand, ErrorOr<PlayListApplicationResponse>>
|
||||
{
|
||||
public async ValueTask<ErrorOr<PlayListApplicationResponse>> Handle(UpdatePlayListPictureCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var found = await database.PlayLists.FindOwnedAsync(request.PlayListId, request.UserId, cancellationToken);
|
||||
if (found.IsError)
|
||||
return found.Errors;
|
||||
|
||||
var playList = found.Value;
|
||||
|
||||
var prepared = await PictureSourceChange.PrepareAsync(
|
||||
uploadIntentValidator, request.PictureIntentId, request.UserId, UploadIntentPurpose.PlayListPicture, playListConfiguration, cancellationToken);
|
||||
if (prepared.IsError)
|
||||
return prepared.Errors;
|
||||
|
||||
var picture = prepared.Value;
|
||||
playList.Pictures = picture.Pictures;
|
||||
|
||||
await eventBus.PublishAsync(
|
||||
new UpdatePlayListPictureSourceEvent(
|
||||
playList.Id, picture.Intent.Id, picture.Intent.Bucket, picture.Intent.Key, picture.FinalKey, picture.Sizes),
|
||||
cancellationToken);
|
||||
|
||||
await database.SaveChangesAsync(cancellationToken);
|
||||
|
||||
logger.LogInformation("Updated picture of playlist {PlayListId}", playList.Id);
|
||||
|
||||
return await database.PlayLists
|
||||
.AsNoTracking()
|
||||
.Where(p => p.Id == playList.Id)
|
||||
.SelectResponse()
|
||||
.SingleAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,6 @@ namespace Musify.Application.Tracks
|
||||
|
||||
await database.Tracks.AddAsync(trackEntity, cancellationToken);
|
||||
|
||||
|
||||
var sizes = trackConfiguration.PicturesSizes.ToImageSizes(trackConfiguration.Routes);
|
||||
await eventBus.PublishAsync(
|
||||
new CreateTrackResourcesEvent(
|
||||
|
||||
@@ -2,31 +2,33 @@ namespace Musify.Domain.ValueObjects
|
||||
{
|
||||
public enum Genre
|
||||
{
|
||||
Pop = 0,
|
||||
Rock = 1,
|
||||
HipHop = 2,
|
||||
RnB = 3,
|
||||
Jazz = 4,
|
||||
Blues = 5,
|
||||
Classical = 6,
|
||||
Electronic = 7,
|
||||
House = 8,
|
||||
Techno = 9,
|
||||
Trance = 10,
|
||||
Dubstep = 11,
|
||||
DrumAndBass = 12,
|
||||
Metal = 13,
|
||||
Punk = 14,
|
||||
Reggae = 15,
|
||||
Reggaeton = 16,
|
||||
Country = 17,
|
||||
Folk = 18,
|
||||
Indie = 19,
|
||||
KPop = 20,
|
||||
Latin = 21,
|
||||
Soul = 22,
|
||||
Funk = 23,
|
||||
Ambient = 24,
|
||||
Lofi = 25
|
||||
/// <summary>Unset value (the enum default); it is not a real genre and is never stored or offered to clients.</summary>
|
||||
None = 0,
|
||||
Pop = 1,
|
||||
Rock = 2,
|
||||
HipHop = 3,
|
||||
RnB = 4,
|
||||
Jazz = 5,
|
||||
Blues = 6,
|
||||
Classical = 7,
|
||||
Electronic = 8,
|
||||
House = 9,
|
||||
Techno = 10,
|
||||
Trance = 11,
|
||||
Dubstep = 12,
|
||||
DrumAndBass = 13,
|
||||
Metal = 14,
|
||||
Punk = 15,
|
||||
Reggae = 16,
|
||||
Reggaeton = 17,
|
||||
Country = 18,
|
||||
Folk = 19,
|
||||
Indie = 20,
|
||||
KPop = 21,
|
||||
Latin = 22,
|
||||
Soul = 23,
|
||||
Funk = 24,
|
||||
Ambient = 25,
|
||||
Lofi = 26
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1084
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Musify.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class GenreNone : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:genre", "ambient,blues,classical,country,drum_and_bass,dubstep,electronic,folk,funk,hip_hop,house,indie,jazz,k_pop,latin,lofi,metal,none,pop,punk,reggae,reggaeton,rn_b,rock,soul,techno,trance")
|
||||
.OldAnnotation("Npgsql:Enum:genre", "ambient,blues,classical,country,drum_and_bass,dubstep,electronic,folk,funk,hip_hop,house,indie,jazz,k_pop,latin,lofi,metal,pop,punk,reggae,reggaeton,rn_b,rock,soul,techno,trance");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterDatabase()
|
||||
.Annotation("Npgsql:Enum:genre", "ambient,blues,classical,country,drum_and_bass,dubstep,electronic,folk,funk,hip_hop,house,indie,jazz,k_pop,latin,lofi,metal,pop,punk,reggae,reggaeton,rn_b,rock,soul,techno,trance")
|
||||
.OldAnnotation("Npgsql:Enum:genre", "ambient,blues,classical,country,drum_and_bass,dubstep,electronic,folk,funk,hip_hop,house,indie,jazz,k_pop,latin,lofi,metal,none,pop,punk,reggae,reggaeton,rn_b,rock,soul,techno,trance");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
|
||||
.HasAnnotation("ProductVersion", "10.0.12")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "ambient", "blues", "classical", "country", "drum_and_bass", "dubstep", "electronic", "folk", "funk", "hip_hop", "house", "indie", "jazz", "k_pop", "latin", "lofi", "metal", "pop", "punk", "reggae", "reggaeton", "rn_b", "rock", "soul", "techno", "trance" });
|
||||
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "ambient", "blues", "classical", "country", "drum_and_bass", "dubstep", "electronic", "folk", "funk", "hip_hop", "house", "indie", "jazz", "k_pop", "latin", "lofi", "metal", "none", "pop", "punk", "reggae", "reggaeton", "rn_b", "rock", "soul", "techno", "trance" });
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
|
||||
|
||||
Reference in New Issue
Block a user