Clean up domain entities: shared picture value object, lifecycle and ownership interfaces, explicit tables

This commit is contained in:
2026-09-24 11:43:50 +02:00
parent 4d943e068e
commit 5a0f82b77b
45 changed files with 122 additions and 190 deletions
@@ -56,7 +56,7 @@ public class CreateAlbumCommandHandler(
NormalizedTitle = title.ToUpperInvariant(), NormalizedTitle = title.ToUpperInvariant(),
Description = request.Description, Description = request.Description,
ReleaseYear = request.ReleaseYear, ReleaseYear = request.ReleaseYear,
Pictures = AlbumPictures.Pending(intent.ObjectName) Pictures = EntityPictures.Pending(intent.ObjectName)
}; };
await database.Albums.AddAsync(album, cancellationToken); await database.Albums.AddAsync(album, cancellationToken);
@@ -64,7 +64,7 @@ public class UpdateAlbumCommandHandler(
return validation.Errors; return validation.Errors;
var pictureIntent = validation.Value; var pictureIntent = validation.Value;
album.Pictures = AlbumPictures.Pending(pictureIntent.ObjectName); album.Pictures = EntityPictures.Pending(pictureIntent.ObjectName);
var finalPictureKey = albumConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName); var finalPictureKey = albumConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName);
var publishResult = await PublishUpdateAlbumPictureSourceEventAsync( var publishResult = await PublishUpdateAlbumPictureSourceEventAsync(
@@ -24,7 +24,7 @@ public class AddTrackToPlayListCommandHandler(
if (playList == null) if (playList == null)
return AppErrors.NotFound("PlayList", request.PlayListId); return AppErrors.NotFound("PlayList", request.PlayListId);
if (playList.UserId != request.UserId) if (playList.OwnerUserId != request.UserId)
return AppErrors.Forbidden("PlayList", request.PlayListId); return AppErrors.Forbidden("PlayList", request.PlayListId);
var trackExists = await database.Tracks var trackExists = await database.Tracks
@@ -17,7 +17,7 @@ public record CreatePlayListCommand(
string Name, string Name,
string? Description, string? Description,
Guid? PictureIntentId, Guid? PictureIntentId,
PlaylistVisibility Visibility = PlaylistVisibility.Private) PlayListVisibility Visibility = PlayListVisibility.Private)
: ICommand<ErrorOr<PlayListApplicationResponse>>; : ICommand<ErrorOr<PlayListApplicationResponse>>;
public class CreatePlayListCommandHandler( public class CreatePlayListCommandHandler(
@@ -30,7 +30,7 @@ public class CreatePlayListCommandHandler(
UploadIntentConfiguration uploadIntentConfiguration) UploadIntentConfiguration uploadIntentConfiguration)
: ICommandHandler<CreatePlayListCommand, ErrorOr<PlayListApplicationResponse>> : ICommandHandler<CreatePlayListCommand, ErrorOr<PlayListApplicationResponse>>
{ {
private sealed record ResolvedPlayListPictures(PlayListPictures? Pictures, UploadIntent? Intent); private sealed record ResolvedPlayListPictures(EntityPictures? Pictures, UploadIntent? Intent);
public async ValueTask<ErrorOr<PlayListApplicationResponse>> Handle(CreatePlayListCommand request, CancellationToken cancellationToken) public async ValueTask<ErrorOr<PlayListApplicationResponse>> Handle(CreatePlayListCommand request, CancellationToken cancellationToken)
{ {
@@ -51,7 +51,7 @@ public class CreatePlayListCommandHandler(
var playList = new PlayList var playList = new PlayList
{ {
UserId = request.UserId, OwnerUserId = request.UserId,
Name = request.Name.Trim(), Name = request.Name.Trim(),
NormalizedName = request.Name.Trim().ToUpperInvariant(), NormalizedName = request.Name.Trim().ToUpperInvariant(),
Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(), Description = string.IsNullOrWhiteSpace(request.Description) ? null : request.Description.Trim(),
@@ -96,7 +96,7 @@ public class CreatePlayListCommandHandler(
if (validation.IsError) if (validation.IsError)
return validation.Errors; return validation.Errors;
return new ResolvedPlayListPictures(PlayListPictures.Pending(validation.Value.ObjectName), validation.Value); return new ResolvedPlayListPictures(EntityPictures.Pending(validation.Value.ObjectName), validation.Value);
} }
private async Task<ErrorOr<Success>> PublishCreatePlayListEventAsync( private async Task<ErrorOr<Success>> PublishCreatePlayListEventAsync(
@@ -27,7 +27,7 @@ public class DeletePlayListCommandHandler(
return Error.NotFound(); return Error.NotFound();
} }
if (playList.UserId != request.UserId) if (playList.OwnerUserId != request.UserId)
{ {
logger.LogWarning("Playlist {PlayListId} does not belong to user {UserId}", request.PlayListId, request.UserId); logger.LogWarning("Playlist {PlayListId} does not belong to user {UserId}", request.PlayListId, request.UserId);
return AppErrors.Forbidden("PlayList", request.PlayListId); return AppErrors.Forbidden("PlayList", request.PlayListId);
@@ -32,8 +32,8 @@ public class GetPlayListByIdQueryHandler(IDatabase database)
if (entry == null) if (entry == null)
return Error.NotFound(); return Error.NotFound();
if (entry.PlayList.Visibility == PlaylistVisibility.Private if (entry.PlayList.Visibility == PlayListVisibility.Private
&& entry.PlayList.UserId != request.RequestingUserId) && entry.PlayList.OwnerUserId != request.RequestingUserId)
{ {
return Error.NotFound(); return Error.NotFound();
} }
@@ -24,7 +24,7 @@ public class GetPlayListCoverQueryHandler(
{ {
var owned = await database.PlayLists.AsNoTracking() var owned = await database.PlayLists.AsNoTracking()
.Where(e => e.Id == request.PlayListId && e.LifeCycleStatus == LifeCycleStatus.Active) .Where(e => e.Id == request.PlayListId && e.LifeCycleStatus == LifeCycleStatus.Active)
.Select(e => new { OwnerUserId = e.UserId, e.Pictures }) .Select(e => new { OwnerUserId = e.OwnerUserId, e.Pictures })
.SingleOrDefaultAsync(cancellationToken); .SingleOrDefaultAsync(cancellationToken);
if (owned?.Pictures == null) if (owned?.Pictures == null)
@@ -27,7 +27,7 @@ public class GetPlayListTracksQueryHandler(IDatabase database)
.AnyAsync( .AnyAsync(
p => p.Id == request.PlayListId p => p.Id == request.PlayListId
&& p.LifeCycleStatus == LifeCycleStatus.Active && p.LifeCycleStatus == LifeCycleStatus.Active
&& (p.Visibility == PlaylistVisibility.Public || p.UserId == request.ViewerId), && (p.Visibility == PlayListVisibility.Public || p.OwnerUserId == request.ViewerId),
cancellationToken); cancellationToken);
if (!playListExists) if (!playListExists)
return Error.NotFound(); return Error.NotFound();
@@ -25,9 +25,9 @@ public class GetPlayListsByUserIdQueryHandler(IDatabase database)
{ {
var playListsQuery = database.PlayLists var playListsQuery = database.PlayLists
.AsNoTracking() .AsNoTracking()
.Where(p => p.UserId == request.UserId .Where(p => p.OwnerUserId == request.UserId
&& p.LifeCycleStatus == LifeCycleStatus.Active && p.LifeCycleStatus == LifeCycleStatus.Active
&& (p.Visibility == PlaylistVisibility.Public || p.UserId == request.ViewerId)); && (p.Visibility == PlayListVisibility.Public || p.OwnerUserId == request.ViewerId));
if (!string.IsNullOrEmpty(request.Name)) if (!string.IsNullOrEmpty(request.Name))
{ {
@@ -23,7 +23,7 @@ public class RemoveTrackFromPlayListCommandHandler(
if (playList == null) if (playList == null)
return AppErrors.NotFound("PlayList", request.PlayListId); return AppErrors.NotFound("PlayList", request.PlayListId);
if (playList.UserId != request.UserId) if (playList.OwnerUserId != request.UserId)
return AppErrors.Forbidden("PlayList", request.PlayListId); return AppErrors.Forbidden("PlayList", request.PlayListId);
var playListTracks = await database.PlayListHasTracks var playListTracks = await database.PlayListHasTracks
@@ -10,7 +10,7 @@ public record PlayListApplicationResponse(
string? SmallImageKeyName, string? SmallImageKeyName,
string? MediumImageKeyName, string? MediumImageKeyName,
string? LargeImageKeyName, string? LargeImageKeyName,
PlaylistVisibility Visibility, PlayListVisibility Visibility,
DateTime CreatedAt, DateTime CreatedAt,
DateTime UpdatedAt, DateTime UpdatedAt,
IReadOnlyList<Guid> CoverTrackIds) IReadOnlyList<Guid> CoverTrackIds)
@@ -19,7 +19,7 @@ public record UpdatePlayListCommand(
string? NewName, string? NewName,
string? NewDescription, string? NewDescription,
Guid? NewPictureIntentId, Guid? NewPictureIntentId,
PlaylistVisibility? NewVisibility = null) PlayListVisibility? NewVisibility = null)
: ICommand<ErrorOr<PlayListApplicationResponse>>; : ICommand<ErrorOr<PlayListApplicationResponse>>;
public class UpdatePlayListCommandHandler( public class UpdatePlayListCommandHandler(
@@ -41,7 +41,7 @@ public class UpdatePlayListCommandHandler(
return Error.NotFound(); return Error.NotFound();
} }
if (playListEntity.UserId != request.UserId) if (playListEntity.OwnerUserId != request.UserId)
{ {
logger.LogWarning("User {UserId} is not the owner of playlist {PlayListId}", request.UserId, request.PlayListId); logger.LogWarning("User {UserId} is not the owner of playlist {PlayListId}", request.UserId, request.PlayListId);
return AppErrors.Forbidden("PlayList", request.PlayListId); return AppErrors.Forbidden("PlayList", request.PlayListId);
@@ -72,7 +72,7 @@ public class UpdatePlayListCommandHandler(
return validation.Errors; return validation.Errors;
var pictureIntent = validation.Value; var pictureIntent = validation.Value;
playListEntity.Pictures = PlayListPictures.Pending(pictureIntent.ObjectName); playListEntity.Pictures = EntityPictures.Pending(pictureIntent.ObjectName);
var finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName); var finalPictureKey = playListConfiguration.Routes.BuildOriginalPicturePath(request.UserId, pictureIntent.ObjectName);
var publishResult = await PublishUpdatePlayListPictureSourceEventAsync( var publishResult = await PublishUpdatePlayListPictureSourceEventAsync(
@@ -52,7 +52,7 @@ public sealed class UploadIntentValidator(
if (intent.IsConsumed) if (intent.IsConsumed)
return Error.Conflict(description: "Upload intent has already been consumed."); return Error.Conflict(description: "Upload intent has already been consumed.");
if (intent.IsExpired) if (intent.IsExpiredAt(DateTime.UtcNow))
return Error.Validation(description: "Upload intent has expired."); return Error.Validation(description: "Upload intent has expired.");
var metadata = await storageService.HeadObjectAsync(intent.Bucket, intent.Key, cancellationToken); var metadata = await storageService.HeadObjectAsync(intent.Bucket, intent.Key, cancellationToken);
@@ -0,0 +1,10 @@
using Musify.Domain.ValueObjects;
namespace Musify.Domain.Abstractions;
public interface IHasLifeCycle
{
public Guid Id { get; }
public LifeCycleStatus LifeCycleStatus { get; set; }
}
@@ -0,0 +1,8 @@
namespace Musify.Domain.Abstractions;
public interface IOwnedEntity
{
public Guid Id { get; }
public long OwnerUserId { get; }
}
+4 -10
View File
@@ -5,17 +5,15 @@ using Musify.Domain.ValueObjects;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618 [Table("Albums")]
public class Album : IAuditable public class Album : IAuditable, IHasLifeCycle, IOwnedEntity
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
[MaxLength(200)] [MaxLength(200)]
public required string Title { get; set; } public required string Title { get; set; }
[Required]
[MaxLength(200)] [MaxLength(200)]
public required string NormalizedTitle { get; set; } public required string NormalizedTitle { get; set; }
@@ -24,21 +22,17 @@ public class Album : IAuditable
public int? ReleaseYear { get; set; } public int? ReleaseYear { get; set; }
public AlbumPictures? Pictures { get; set; } public EntityPictures? Pictures { get; set; }
[Required]
public required long OwnerUserId { get; set; } public required long OwnerUserId { get; set; }
[ForeignKey(nameof(OwnerUserId))] [ForeignKey(nameof(OwnerUserId))]
public User Owner { get; set; } public User Owner { get; set; } = null!;
[Required]
public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active; public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active;
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public ICollection<AlbumHasTrack> AlbumTracks { get; set; } = new List<AlbumHasTrack>(); public ICollection<AlbumHasTrack> AlbumTracks { get; set; } = new List<AlbumHasTrack>();
@@ -1,29 +1,23 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("AlbumHasTrack")] [Table("AlbumHasTrack")]
public class AlbumHasTrack public class AlbumHasTrack
{ {
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required Guid AlbumId { get; set; } public required Guid AlbumId { get; set; }
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[Required]
public int TrackNumber { get; set; } = 1; public int TrackNumber { get; set; } = 1;
[Required]
public DateTime AddedAt { get; set; } = DateTime.UtcNow; public DateTime AddedAt { get; set; } = DateTime.UtcNow;
[ForeignKey(nameof(AlbumId))] [ForeignKey(nameof(AlbumId))]
public Album Album { get; set; } public Album Album { get; set; } = null!;
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
@@ -3,20 +3,16 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("ListeningHistory")] [Table("ListeningHistory")]
public class ListeningHistory public class ListeningHistory
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required long UserId { get; set; } public required long UserId { get; set; }
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[Required]
public DateTime ListenedAt { get; set; } = DateTime.UtcNow; public DateTime ListenedAt { get; set; } = DateTime.UtcNow;
public double? PlayedSeconds { get; set; } public double? PlayedSeconds { get; set; }
@@ -26,8 +22,8 @@ public class ListeningHistory
public bool IsCounted { get; set; } public bool IsCounted { get; set; }
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(UserId))]
public User User { get; set; } public User User { get; set; } = null!;
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
+2 -8
View File
@@ -5,34 +5,28 @@ using Musify.Domain.Abstractions;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("Mixes")] [Table("Mixes")]
public class Mix : IAuditable public class Mix : IAuditable
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required long UserId { get; set; } public required long UserId { get; set; }
[Required]
[MaxLength(100)] [MaxLength(100)]
public required string Title { get; set; } public required string Title { get; set; }
[MaxLength(256)] [MaxLength(256)]
public string? Subtitle { get; set; } public string? Subtitle { get; set; }
[Required] public int Position { get; set; }
public int Position { get; set; } = 0;
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(UserId))]
public User User { get; set; } public User User { get; set; } = null!;
public ICollection<MixItem> Items { get; set; } = new List<MixItem>(); public ICollection<MixItem> Items { get; set; } = new List<MixItem>();
} }
@@ -3,25 +3,21 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("MixItems")] [Table("MixItems")]
public class MixItem public class MixItem
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required Guid MixId { get; set; } public required Guid MixId { get; set; }
[Required] public int Position { get; set; }
public int Position { get; set; } = 0;
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[ForeignKey(nameof(MixId))] [ForeignKey(nameof(MixId))]
public Mix Mix { get; set; } public Mix Mix { get; set; } = null!;
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
@@ -6,43 +6,36 @@ using Musify.Domain.ValueObjects;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("PlayLists")] [Table("PlayLists")]
public class PlayList : IAuditable public class PlayList : IAuditable, IHasLifeCycle, IOwnedEntity
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required] [Column("UserId")]
public required long UserId { get; set; } public required long OwnerUserId { get; set; }
[Required]
[MaxLength(50)] [MaxLength(50)]
public required string Name { get; set; } public required string Name { get; set; }
[Required]
[MaxLength(50)] [MaxLength(50)]
public required string NormalizedName { get; set; } public required string NormalizedName { get; set; }
[MaxLength(256)] [MaxLength(256)]
public string? Description { get; set; } public string? Description { get; set; }
public PlayListPictures? Pictures { get; set; } public EntityPictures? Pictures { get; set; }
[Required]
public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active; public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active;
[Required] public PlayListVisibility Visibility { get; set; } = PlayListVisibility.Private;
public PlaylistVisibility Visibility { get; set; } = PlaylistVisibility.Private;
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(OwnerUserId))]
public User User { get; set; } public User Owner { get; set; } = null!;
public ICollection<PlayListHasTrack> PlayListTracks { get; set; } = new List<PlayListHasTrack>(); public ICollection<PlayListHasTrack> PlayListTracks { get; set; } = new List<PlayListHasTrack>();
} }
@@ -1,29 +1,23 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("PlayListHasTrack")] [Table("PlayListHasTrack")]
public class PlayListHasTrack public class PlayListHasTrack
{ {
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required Guid PlayListId { get; set; } public required Guid PlayListId { get; set; }
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[Required] public int Position { get; set; }
public int Position { get; set; } = 0;
[Required]
public DateTime AddedAt { get; set; } = DateTime.UtcNow; public DateTime AddedAt { get; set; } = DateTime.UtcNow;
[ForeignKey(nameof(PlayListId))] [ForeignKey(nameof(PlayListId))]
public PlayList PlayList { get; set; } public PlayList PlayList { get; set; } = null!;
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
+3 -11
View File
@@ -5,39 +5,31 @@ using Musify.Domain.ValueObjects;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618 [Table("Tracks")]
public class Track : IAuditable public class Track : IAuditable, IHasLifeCycle, IOwnedEntity
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
[MaxLength(200)] [MaxLength(200)]
public required string Title { get; set; } public required string Title { get; set; }
[Required]
[MaxLength(200)] [MaxLength(200)]
public required string NormalizedTitle { get; set; } public required string NormalizedTitle { get; set; }
[Required]
public double DurationSeconds { get; set; } public double DurationSeconds { get; set; }
[Required]
public required long OwnerUserId { get; set; } public required long OwnerUserId { get; set; }
[ForeignKey(nameof(OwnerUserId))] [ForeignKey(nameof(OwnerUserId))]
public required User Owner { get; set; } public User Owner { get; set; } = null!;
[Required]
public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active; public LifeCycleStatus LifeCycleStatus { get; set; } = LifeCycleStatus.Active;
[Required]
public bool IsExplicit { get; set; } public bool IsExplicit { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public TrackPictures Pictures { get; set; } = new(); public TrackPictures Pictures { get; set; } = new();
@@ -3,25 +3,21 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("TrackLikes")] [Table("TrackLikes")]
public class TrackLike public class TrackLike
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required long UserId { get; set; } public required long UserId { get; set; }
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(UserId))]
public User User { get; set; } public User User { get; set; } = null!;
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
@@ -4,18 +4,16 @@ using Musify.Domain.ValueObjects;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618 [Table("TrackTags")]
public class TrackTag public class TrackTag
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[Required]
public required Genre Tag { get; set; } public required Genre Tag { get; set; }
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
@@ -11,40 +11,34 @@ public class UploadIntent
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required long UserId { get; set; } public required long UserId { get; set; }
[Required, MaxLength(128)] [MaxLength(128)]
public required string Bucket { get; set; } public required string Bucket { get; set; }
[Required, MaxLength(512)] [MaxLength(512)]
public required string Key { get; set; } public required string Key { get; set; }
[Required, MaxLength(128)] [MaxLength(128)]
public required string ObjectName { get; set; } public required string ObjectName { get; set; }
[Required, MaxLength(128)] [MaxLength(128)]
public required string ContentType { get; set; } public required string ContentType { get; set; }
public long? ExpectedSizeBytes { get; set; } public long? ExpectedSizeBytes { get; set; }
[Required]
public UploadIntentPurpose Purpose { get; set; } public UploadIntentPurpose Purpose { get; set; }
[Required]
public UploadIntentStatus Status { get; set; } = UploadIntentStatus.Issued; public UploadIntentStatus Status { get; set; } = UploadIntentStatus.Issued;
[Required]
public DateTime ExpiresAt { get; set; } public DateTime ExpiresAt { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[NotMapped] [NotMapped]
public bool IsConsumed => Status == UploadIntentStatus.Consumed; public bool IsConsumed => Status == UploadIntentStatus.Consumed;
[NotMapped] public bool IsExpiredAt(DateTime now) => Status == UploadIntentStatus.Expired || ExpiresAt < now;
public bool IsExpired => Status == UploadIntentStatus.Expired || ExpiresAt < DateTime.UtcNow;
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(UserId))]
public User User { get; set; } = null!; public User User { get; set; } = null!;
@@ -11,11 +11,9 @@ public class User : IAuditable
[DatabaseGenerated(DatabaseGeneratedOption.None)] [DatabaseGenerated(DatabaseGeneratedOption.None)]
public required long Id { get; set; } public required long Id { get; set; }
[Required]
[MaxLength(48)] [MaxLength(48)]
public required string Name { get; set; } public required string Name { get; set; }
[Required]
[MaxLength(48)] [MaxLength(48)]
public required string NormalizedName { get; set; } public required string NormalizedName { get; set; }
@@ -28,10 +26,8 @@ public class User : IAuditable
[MaxLength(2048)] [MaxLength(2048)]
public string? ProfilePictureUrl { get; set; } public string? ProfilePictureUrl { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[Required]
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
public ICollection<UserHasTrack> UserTracks { get; set; } = new List<UserHasTrack>(); public ICollection<UserHasTrack> UserTracks { get; set; } = new List<UserHasTrack>();
@@ -3,25 +3,21 @@ using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("UserFollows")] [Table("UserFollows")]
public class UserFollow public class UserFollow
{ {
[Key] [Key]
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required long FollowerId { get; set; } public required long FollowerId { get; set; }
[Required]
public required long FollowedId { get; set; } public required long FollowedId { get; set; }
[Required]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[ForeignKey(nameof(FollowerId))] [ForeignKey(nameof(FollowerId))]
public User Follower { get; set; } public User Follower { get; set; } = null!;
[ForeignKey(nameof(FollowedId))] [ForeignKey(nameof(FollowedId))]
public User Followed { get; set; } public User Followed { get; set; } = null!;
} }
@@ -1,23 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace Musify.Domain.Entities; namespace Musify.Domain.Entities;
#pragma warning disable CS8618
[Table("UserHasTrack")] [Table("UserHasTrack")]
public class UserHasTrack public class UserHasTrack
{ {
public Guid Id { get; set; } = Guid.NewGuid(); public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required long UserId { get; set; } public required long UserId { get; set; }
[Required]
public required Guid TrackId { get; set; } public required Guid TrackId { get; set; }
[ForeignKey(nameof(UserId))] [ForeignKey(nameof(UserId))]
public User User { get; set; } public User User { get; set; } = null!;
[ForeignKey(nameof(TrackId))] [ForeignKey(nameof(TrackId))]
public Track Track { get; set; } public Track Track { get; set; } = null!;
} }
@@ -2,7 +2,8 @@ using System.ComponentModel.DataAnnotations;
namespace Musify.Domain.ValueObjects; namespace Musify.Domain.ValueObjects;
public class AlbumPictures /// <summary>The picture file names of an album or playlist. Resized names stay null until processing finishes.</summary>
public class EntityPictures
{ {
[Required] [Required]
public required string OriginalName { get; set; } public required string OriginalName { get; set; }
@@ -13,5 +14,5 @@ public class AlbumPictures
public string? LargeName { get; set; } public string? LargeName { get; set; }
public static AlbumPictures Pending(string originalName) => new() { OriginalName = originalName }; public static EntityPictures Pending(string objectName) => new() { OriginalName = objectName };
} }
@@ -1,17 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace Musify.Domain.ValueObjects;
public class PlayListPictures
{
[Required]
public required string OriginalName { get; set; }
public string? SmallName { get; set; }
public string? MediumName { get; set; }
public string? LargeName { get; set; }
public static PlayListPictures Pending(string originalName) => new() { OriginalName = originalName };
}
@@ -1,6 +1,6 @@
namespace Musify.Domain.ValueObjects; namespace Musify.Domain.ValueObjects;
public enum PlaylistVisibility public enum PlayListVisibility
{ {
Private = 0, Private = 0,
Public = 1 Public = 1
@@ -89,7 +89,7 @@ internal class UpdateAlbumPictureActivity(
return compensateContext.Compensated(); return compensateContext.Compensated();
} }
album.Pictures = new AlbumPictures album.Pictures = new EntityPictures
{ {
OriginalName = compensateContext.Log.PreviousOriginalPictureKey, OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey, SmallName = compensateContext.Log.PreviousSmallPictureKey,
@@ -89,7 +89,7 @@ internal class UpdatePlayListPictureActivity(
return compensateContext.Compensated(); return compensateContext.Compensated();
} }
playList.Pictures = new PlayListPictures playList.Pictures = new EntityPictures
{ {
OriginalName = compensateContext.Log.PreviousOriginalPictureKey, OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey, SmallName = compensateContext.Log.PreviousSmallPictureKey,
@@ -64,7 +64,7 @@ public class Database(DatabaseConfiguration configuration)
modelBuilder.Entity<PlayList>() modelBuilder.Entity<PlayList>()
.Property(playList => playList.Visibility) .Property(playList => playList.Visibility)
.HasDefaultValue(PlaylistVisibility.Private); .HasDefaultValue(PlayListVisibility.Private);
modelBuilder.Entity<PlayList>() modelBuilder.Entity<PlayList>()
.OwnsOne(playList => playList.Pictures, pictures => .OwnsOne(playList => playList.Pictures, pictures =>
@@ -379,12 +379,13 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasMaxLength(50) .HasMaxLength(50)
.HasColumnType("character varying(50)"); .HasColumnType("character varying(50)");
b.Property<long>("OwnerUserId")
.HasColumnType("bigint")
.HasColumnName("UserId");
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.Property<int>("Visibility") b.Property<int>("Visibility")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("integer") .HasColumnType("integer")
@@ -392,7 +393,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("UserId"); b.HasIndex("OwnerUserId");
b.ToTable("PlayLists"); b.ToTable("PlayLists");
}); });
@@ -756,7 +757,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.OwnsOne("Musify.Domain.ValueObjects.AlbumPictures", "Pictures", b1 => b.OwnsOne("Musify.Domain.ValueObjects.EntityPictures", "Pictures", b1 =>
{ {
b1.Property<Guid>("AlbumId") b1.Property<Guid>("AlbumId")
.HasColumnType("uuid"); .HasColumnType("uuid");
@@ -865,13 +866,13 @@ namespace Musify.Infrastructure.Persistence.Migrations
modelBuilder.Entity("Musify.Domain.Entities.PlayList", b => modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
{ {
b.HasOne("Musify.Domain.Entities.User", "User") b.HasOne("Musify.Domain.Entities.User", "Owner")
.WithMany("PlayLists") .WithMany("PlayLists")
.HasForeignKey("UserId") .HasForeignKey("OwnerUserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.OwnsOne("Musify.Domain.ValueObjects.PlayListPictures", "Pictures", b1 => b.OwnsOne("Musify.Domain.ValueObjects.EntityPictures", "Pictures", b1 =>
{ {
b1.Property<Guid>("PlayListId") b1.Property<Guid>("PlayListId")
.HasColumnType("uuid"); .HasColumnType("uuid");
@@ -904,9 +905,9 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasForeignKey("PlayListId"); .HasForeignKey("PlayListId");
}); });
b.Navigation("Pictures"); b.Navigation("Owner");
b.Navigation("User"); b.Navigation("Pictures");
}); });
modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b => modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b =>
@@ -6,4 +6,4 @@ public record CreatePlayListRequest(
string Name, string Name,
string? Description, string? Description,
Guid? PictureIntentId, Guid? PictureIntentId,
PlaylistVisibility Visibility = PlaylistVisibility.Private); PlayListVisibility Visibility = PlayListVisibility.Private);
@@ -6,4 +6,4 @@ public record UpdatePlayListRequest(
string? NewName, string? NewName,
string? NewDescription, string? NewDescription,
Guid? NewPictureIntentId, Guid? NewPictureIntentId,
PlaylistVisibility? NewVisibility = null); PlayListVisibility? NewVisibility = null);
@@ -39,7 +39,7 @@ public sealed class GetAlbumCoverQueryHandlerTests : HandlerTestBase
public async Task Handle_PicturesStillProcessing_FallsBackToTheOriginal() public async Task Handle_PicturesStillProcessing_FallsBackToTheOriginal()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var album = TestEntities.Album(owner.Id, pictures: Musify.Domain.ValueObjects.AlbumPictures.Pending("original.png")); var album = TestEntities.Album(owner.Id, pictures: Musify.Domain.ValueObjects.EntityPictures.Pending("original.png"));
await SeedAsync(owner, album); await SeedAsync(owner, album);
var result = await CreateHandler().Handle(new GetAlbumCoverQuery(album.Id, "small"), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetAlbumCoverQuery(album.Id, "small"), TestContext.Current.CancellationToken);
@@ -71,7 +71,7 @@ public sealed class CreatePlayListCommandHandlerTests : HandlerTestBase
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Private, result.Value.Visibility); Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Private, result.Value.Visibility);
} }
[Fact] [Fact]
@@ -80,12 +80,12 @@ public sealed class CreatePlayListCommandHandlerTests : HandlerTestBase
var user = TestEntities.User(); var user = TestEntities.User();
await SeedAsync(user); await SeedAsync(user);
var command = new CreatePlayListCommand(user.Id, "My Playlist", null, PictureIntentId: null, Visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public); var command = new CreatePlayListCommand(user.Id, "My Playlist", null, PictureIntentId: null, Visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Public, result.Value.Visibility); Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Public, result.Value.Visibility);
} }
[Fact] [Fact]
@@ -14,7 +14,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
public async Task Handle_ExistingPlayList_ReturnsItWithCoverTrackIds() public async Task Handle_ExistingPlayList_ReturnsItWithCoverTrackIds()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public); var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
var track = TestEntities.Track(owner); var track = TestEntities.Track(owner);
await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 }); await SeedAsync(owner, playList, track, new PlayListHasTrack { PlayListId = playList.Id, TrackId = track.Id, Position = 0 });
@@ -37,7 +37,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
public async Task Handle_PrivatePlayList_RequestedByOwner_ReturnsIt() public async Task Handle_PrivatePlayList_RequestedByOwner_ReturnsIt()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Private); var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Private);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, owner.Id), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, owner.Id), TestContext.Current.CancellationToken);
@@ -50,7 +50,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
{ {
var owner = TestEntities.User(1, "owner"); var owner = TestEntities.User(1, "owner");
var stranger = TestEntities.User(2, "stranger"); var stranger = TestEntities.User(2, "stranger");
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Private); var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Private);
await SeedAsync(owner, stranger, playList); await SeedAsync(owner, stranger, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, stranger.Id), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, stranger.Id), TestContext.Current.CancellationToken);
@@ -62,7 +62,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
public async Task Handle_PrivatePlayList_RequestedAnonymously_ReturnsNotFound() public async Task Handle_PrivatePlayList_RequestedAnonymously_ReturnsNotFound()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Private); var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Private);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, RequestingUserId: null), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, RequestingUserId: null), TestContext.Current.CancellationToken);
@@ -74,7 +74,7 @@ public sealed class GetPlayListByIdQueryHandlerTests : HandlerTestBase
public async Task Handle_PublicPlayList_RequestedAnonymously_ReturnsIt() public async Task Handle_PublicPlayList_RequestedAnonymously_ReturnsIt()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public); var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, RequestingUserId: null), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListByIdQuery(playList.Id, RequestingUserId: null), TestContext.Current.CancellationToken);
@@ -41,7 +41,7 @@ public sealed class GetPlayListTracksQueryHandlerTests : HandlerTestBase
public async Task Handle_PrivatePlayListOfAnotherUser_ReturnsNotFound() public async Task Handle_PrivatePlayListOfAnotherUser_ReturnsNotFound()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: PlaylistVisibility.Private); var playList = TestEntities.PlayList(owner.Id, visibility: PlayListVisibility.Private);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: 999), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: 999), TestContext.Current.CancellationToken);
@@ -53,7 +53,7 @@ public sealed class GetPlayListTracksQueryHandlerTests : HandlerTestBase
public async Task Handle_PrivatePlayListOfViewer_ReturnsTracks() public async Task Handle_PrivatePlayListOfViewer_ReturnsTracks()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: PlaylistVisibility.Private); var playList = TestEntities.PlayList(owner.Id, visibility: PlayListVisibility.Private);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListTracksQuery(playList.Id, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
@@ -33,8 +33,8 @@ public sealed class GetPlayListsByUserIdQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync( await SeedAsync(
owner, owner,
TestEntities.PlayList(owner.Id, "Public one", visibility: PlaylistVisibility.Public), TestEntities.PlayList(owner.Id, "Public one", visibility: PlayListVisibility.Public),
TestEntities.PlayList(owner.Id, "Private one", visibility: PlaylistVisibility.Private)); TestEntities.PlayList(owner.Id, "Private one", visibility: PlayListVisibility.Private));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: 999), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: 999), TestContext.Current.CancellationToken);
@@ -49,8 +49,8 @@ public sealed class GetPlayListsByUserIdQueryHandlerTests : HandlerTestBase
var owner = TestEntities.User(); var owner = TestEntities.User();
await SeedAsync( await SeedAsync(
owner, owner,
TestEntities.PlayList(owner.Id, "Public one", visibility: PlaylistVisibility.Public), TestEntities.PlayList(owner.Id, "Public one", visibility: PlayListVisibility.Public),
TestEntities.PlayList(owner.Id, "Private one", visibility: PlaylistVisibility.Private)); TestEntities.PlayList(owner.Id, "Private one", visibility: PlayListVisibility.Private));
var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(new GetPlayListsByUserIdQuery(owner.Id, Name: null, PageNumber: 1, PageSize: 10, ViewerId: owner.Id), TestContext.Current.CancellationToken);
@@ -69,19 +69,19 @@ public sealed class UpdatePlayListCommandHandlerTests : HandlerTestBase
var playList = TestEntities.PlayList(owner.Id); var playList = TestEntities.PlayList(owner.Id);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var command = new UpdatePlayListCommand(owner.Id, playList.Id, null, null, null, Musify.Domain.ValueObjects.PlaylistVisibility.Public); var command = new UpdatePlayListCommand(owner.Id, playList.Id, null, null, null, Musify.Domain.ValueObjects.PlayListVisibility.Public);
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Public, result.Value.Visibility); Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Public, result.Value.Visibility);
} }
[Fact] [Fact]
public async Task Handle_NoVisibilitySpecified_LeavesItUnchanged() public async Task Handle_NoVisibilitySpecified_LeavesItUnchanged()
{ {
var owner = TestEntities.User(); var owner = TestEntities.User();
var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlaylistVisibility.Public); var playList = TestEntities.PlayList(owner.Id, visibility: Musify.Domain.ValueObjects.PlayListVisibility.Public);
await SeedAsync(owner, playList); await SeedAsync(owner, playList);
var command = new UpdatePlayListCommand(owner.Id, playList.Id, "New Name", null, null); var command = new UpdatePlayListCommand(owner.Id, playList.Id, "New Name", null, null);
@@ -89,7 +89,7 @@ public sealed class UpdatePlayListCommandHandlerTests : HandlerTestBase
var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken); var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken);
Assert.False(result.IsError); Assert.False(result.IsError);
Assert.Equal(Musify.Domain.ValueObjects.PlaylistVisibility.Public, result.Value.Visibility); Assert.Equal(Musify.Domain.ValueObjects.PlayListVisibility.Public, result.Value.Visibility);
} }
[Fact] [Fact]
@@ -79,7 +79,7 @@ public static class TestEntities
return track; return track;
} }
public static PlayListPictures PlayListPictures() => new() public static EntityPictures PlayListPictures() => new()
{ {
OriginalName = "original.webp", OriginalName = "original.webp",
SmallName = "small.webp", SmallName = "small.webp",
@@ -91,12 +91,12 @@ public static class TestEntities
long userId, long userId,
string name = "Test Playlist", string name = "Test Playlist",
string? description = null, string? description = null,
PlayListPictures? pictures = null, EntityPictures? pictures = null,
LifeCycleStatus lifeCycleStatus = LifeCycleStatus.Active, LifeCycleStatus lifeCycleStatus = LifeCycleStatus.Active,
PlaylistVisibility visibility = PlaylistVisibility.Private) => PlayListVisibility visibility = PlayListVisibility.Private) =>
new() new()
{ {
UserId = userId, OwnerUserId = userId,
Name = name, Name = name,
NormalizedName = name.ToUpperInvariant(), NormalizedName = name.ToUpperInvariant(),
Description = description, Description = description,
@@ -105,7 +105,7 @@ public static class TestEntities
Visibility = visibility Visibility = visibility
}; };
public static AlbumPictures AlbumPictures() => new() public static EntityPictures AlbumPictures() => new()
{ {
OriginalName = "original.webp", OriginalName = "original.webp",
SmallName = "small.webp", SmallName = "small.webp",
@@ -118,7 +118,7 @@ public static class TestEntities
string title = "Test Album", string title = "Test Album",
string? description = null, string? description = null,
int? releaseYear = 2024, int? releaseYear = 2024,
AlbumPictures? pictures = null) => EntityPictures? pictures = null) =>
new() new()
{ {
Title = title, Title = title,