From a4ca1f32d320c331bb7d243eccc776ce2be0a0d8 Mon Sep 17 00:00:00 2001 From: XShine555 Date: Sun, 20 Sep 2026 22:40:40 +0200 Subject: [PATCH] Remove no-op playlist picture migration; cover picture-add transition in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Sept migration MakePlayListPictureColumnsOptional altered the PlayLists picture columns to nullable:true when they were already nullable:true (set back in MakePlayListPicturesNullable, two months earlier) — a dead migration left over from making the Pictures navigation itself optional, which produces no actual schema change. Verified with `dotnet ef migrations has-pending-model-changes` and a full migration run against a fresh Postgres container that removing it introduces no drift. Also add a test for updating a playlist that has no picture yet, which was the one Pictures-related state transition without coverage. --- ...PlayListPictureColumnsOptional.Designer.cs | 1164 ----------------- ...5334_MakePlayListPictureColumnsOptional.cs | 106 -- .../Migrations/DatabaseModelSnapshot.cs | 56 +- .../UpdatePlayListCommandHandlerTests.cs | 25 + 4 files changed, 55 insertions(+), 1296 deletions(-) delete mode 100644 backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.Designer.cs delete mode 100644 backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.cs diff --git a/backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.Designer.cs b/backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.Designer.cs deleted file mode 100644 index 7e64f8f..0000000 --- a/backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.Designer.cs +++ /dev/null @@ -1,1164 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Musify.Domain.ValueObjects; -using Musify.Infrastructure.Persistence; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Musify.Infrastructure.Persistence.Migrations -{ - [DbContext(typeof(Database))] - [Migration("20260917195334_MakePlayListPictureColumnsOptional")] - partial class MakePlayListPictureColumnsOptional - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.8") - .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.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Consumed") - .HasColumnType("timestamp with time zone"); - - b.Property("ConsumerId") - .HasColumnType("uuid"); - - b.Property("Delivered") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("LastSequenceNumber") - .HasColumnType("bigint"); - - b.Property("LockId") - .HasColumnType("uuid"); - - b.Property("MessageId") - .HasColumnType("uuid"); - - b.Property("ReceiveCount") - .HasColumnType("integer"); - - b.Property("Received") - .HasColumnType("timestamp with time zone"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("bytea"); - - b.HasKey("Id"); - - b.HasIndex("Delivered"); - - b.ToTable("InboxState"); - }); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b => - { - b.Property("SequenceNumber") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("SequenceNumber")); - - b.Property("Body") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContentType") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ConversationId") - .HasColumnType("uuid"); - - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("DestinationAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EnqueueTime") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpirationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("FaultAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Headers") - .HasColumnType("text"); - - b.Property("InboxConsumerId") - .HasColumnType("uuid"); - - b.Property("InboxMessageId") - .HasColumnType("uuid"); - - b.Property("InitiatorId") - .HasColumnType("uuid"); - - b.Property("MessageId") - .HasColumnType("uuid"); - - b.Property("MessageType") - .IsRequired() - .HasColumnType("text"); - - b.Property("OutboxId") - .HasColumnType("uuid"); - - b.Property("Properties") - .HasColumnType("text"); - - b.Property("RequestId") - .HasColumnType("uuid"); - - b.Property("ResponseAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("SentTime") - .HasColumnType("timestamp with time zone"); - - b.Property("SourceAddress") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("SequenceNumber"); - - b.HasIndex("EnqueueTime"); - - b.HasIndex("ExpirationTime"); - - b.HasIndex("OutboxId", "SequenceNumber") - .IsUnique(); - - b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber") - .IsUnique(); - - b.ToTable("OutboxMessage"); - }); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b => - { - b.Property("OutboxId") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Delivered") - .HasColumnType("timestamp with time zone"); - - b.Property("LastSequenceNumber") - .HasColumnType("bigint"); - - b.Property("LockId") - .HasColumnType("uuid"); - - b.Property("RowVersion") - .IsConcurrencyToken() - .ValueGeneratedOnAddOrUpdate() - .HasColumnType("bytea"); - - b.HasKey("OutboxId"); - - b.HasIndex("Created"); - - b.ToTable("OutboxState"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Album", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("LifeCycleStatus") - .HasColumnType("integer"); - - b.Property("NormalizedTitle") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("OwnerUserId") - .HasColumnType("bigint"); - - b.Property("ReleaseYear") - .HasColumnType("integer"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OwnerUserId"); - - b.ToTable("Albums"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.AlbumHasTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AddedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("AlbumId") - .HasColumnType("uuid"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.Property("TrackNumber") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TrackId"); - - b.HasIndex("AlbumId", "TrackId") - .IsUnique(); - - b.ToTable("AlbumHasTrack"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("ListenedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("TrackId"); - - b.HasIndex("UserId"); - - b.ToTable("ListeningHistory"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Mix", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Position") - .HasColumnType("integer"); - - b.Property("Subtitle") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId", "Position"); - - b.ToTable("Mixes"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.MixItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("MixId") - .HasColumnType("uuid"); - - b.Property("Position") - .HasColumnType("integer"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("TrackId"); - - b.HasIndex("MixId", "Position"); - - b.ToTable("MixItems"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.PlayList", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("LifeCycleStatus") - .HasColumnType("integer"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("NormalizedName") - .IsRequired() - .HasMaxLength(50) - .HasColumnType("character varying(50)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.Property("Visibility") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(0); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("PlayLists"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("AddedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("PlayListId") - .HasColumnType("uuid"); - - b.Property("Position") - .HasColumnType("integer"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("PlayListId"); - - b.HasIndex("TrackId"); - - b.ToTable("PlayListHasTrack"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Track", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("DurationSeconds") - .HasColumnType("double precision"); - - b.Property("LifeCycleStatus") - .HasColumnType("integer"); - - b.Property("NormalizedTitle") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("OwnerUserId") - .HasColumnType("bigint"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("OwnerUserId"); - - b.ToTable("Tracks"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.TrackLike", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("TrackId"); - - b.HasIndex("UserId", "TrackId") - .IsUnique(); - - b.ToTable("TrackLikes"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.TrackTag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Tag") - .HasColumnType("genre"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("TrackId", "Tag") - .IsUnique(); - - b.ToTable("TrackTags"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Upload", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Bucket") - .IsRequired() - .HasColumnType("text"); - - b.Property("ContentType") - .IsRequired() - .HasColumnType("text"); - - b.Property("CreatedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Upload"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("Bucket") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("ContentType") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpectedSizeBytes") - .HasColumnType("bigint"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("ObjectName") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Purpose") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("UploadIntents"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.User", b => - { - b.Property("Id") - .HasColumnType("bigint"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("FirstName") - .HasMaxLength(48) - .HasColumnType("character varying(48)"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(48) - .HasColumnType("character varying(48)"); - - b.Property("NormalizedName") - .IsRequired() - .HasMaxLength(48) - .HasColumnType("character varying(48)"); - - b.Property("ProfilePictureUrl") - .HasMaxLength(2048) - .HasColumnType("character varying(2048)"); - - b.Property("SecondName") - .HasMaxLength(48) - .HasColumnType("character varying(48)"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.UserFollow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("FollowedId") - .HasColumnType("bigint"); - - b.Property("FollowerId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("FollowedId"); - - b.HasIndex("FollowerId", "FollowedId") - .IsUnique(); - - b.ToTable("UserFollows"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("TrackId") - .HasColumnType("uuid"); - - b.Property("UserId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("TrackId"); - - b.HasIndex("UserId"); - - b.ToTable("UserHasTrack"); - }); - - modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.AlbumProcessingState", b => - { - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("Bucket") - .HasColumnType("text"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CurrentState") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.Property("PictureKey") - .HasColumnType("text"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("CorrelationId"); - - b.ToTable("AlbumProcessingState"); - }); - - modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.PlayListProcessingState", b => - { - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("Bucket") - .HasColumnType("text"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CurrentState") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.Property("PictureKey") - .HasColumnType("text"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("CorrelationId"); - - b.ToTable("PlayListProcessingState"); - }); - - modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.TrackProcessingState", b => - { - b.Property("CorrelationId") - .HasColumnType("uuid"); - - b.Property("AudioKey") - .HasColumnType("text"); - - b.Property("AudioProcessed") - .HasColumnType("boolean"); - - b.Property("Bucket") - .HasColumnType("text"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("CurrentState") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.Property("PictureKey") - .HasColumnType("text"); - - b.Property("PictureProcessed") - .HasColumnType("boolean"); - - b.Property("UpdatedAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("CorrelationId"); - - b.ToTable("TrackProcessingState"); - }); - - modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b => - { - b.HasOne("MassTransit.EntityFrameworkCoreIntegration.OutboxState", null) - .WithMany() - .HasForeignKey("OutboxId"); - - b.HasOne("MassTransit.EntityFrameworkCoreIntegration.InboxState", null) - .WithMany() - .HasForeignKey("InboxMessageId", "InboxConsumerId") - .HasPrincipalKey("MessageId", "ConsumerId"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Album", b => - { - b.HasOne("Musify.Domain.Entities.User", "Owner") - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.OwnsOne("Musify.Domain.ValueObjects.AlbumPictures", "Pictures", b1 => - { - b1.Property("AlbumId") - .HasColumnType("uuid"); - - b1.Property("LargeName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("LargePictureName"); - - b1.Property("MediumName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("MediumPictureName"); - - b1.Property("OriginalName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("OriginalPictureName"); - - b1.Property("SmallName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("SmallPictureName"); - - b1.HasKey("AlbumId"); - - b1.ToTable("Albums"); - - b1.WithOwner() - .HasForeignKey("AlbumId"); - }); - - b.Navigation("Owner"); - - b.Navigation("Pictures"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.AlbumHasTrack", b => - { - b.HasOne("Musify.Domain.Entities.Album", "Album") - .WithMany("AlbumTracks") - .HasForeignKey("AlbumId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany("AlbumTracks") - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Album"); - - b.Navigation("Track"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b => - { - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany("ListeningHistories") - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.User", "User") - .WithMany("ListeningHistories") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Track"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Mix", b => - { - b.HasOne("Musify.Domain.Entities.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.MixItem", b => - { - b.HasOne("Musify.Domain.Entities.Mix", "Mix") - .WithMany("Items") - .HasForeignKey("MixId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany() - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Mix"); - - b.Navigation("Track"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.PlayList", b => - { - b.HasOne("Musify.Domain.Entities.User", "User") - .WithMany("PlayLists") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.OwnsOne("Musify.Domain.ValueObjects.PlayListPictures", "Pictures", b1 => - { - b1.Property("PlayListId") - .HasColumnType("uuid"); - - b1.Property("LargeName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("LargePictureName"); - - b1.Property("MediumName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("MediumPictureName"); - - b1.Property("OriginalName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("OriginalPictureName"); - - b1.Property("SmallName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("SmallPictureName"); - - b1.HasKey("PlayListId"); - - b1.ToTable("PlayLists"); - - b1.WithOwner() - .HasForeignKey("PlayListId"); - }); - - b.Navigation("Pictures"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b => - { - b.HasOne("Musify.Domain.Entities.PlayList", "PlayList") - .WithMany("PlayListTracks") - .HasForeignKey("PlayListId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany("PlayListTracks") - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("PlayList"); - - b.Navigation("Track"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Track", b => - { - b.HasOne("Musify.Domain.Entities.User", "Owner") - .WithMany() - .HasForeignKey("OwnerUserId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.OwnsOne("Musify.Domain.ValueObjects.TrackAudio", "Audio", b1 => - { - b1.Property("TrackId") - .HasColumnType("uuid"); - - b1.Property("DownloadRequested") - .HasColumnType("boolean") - .HasColumnName("DownloadRequested"); - - b1.Property("FolderName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("AudioFolderName"); - - b1.Property("LastRetryAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("LastRetryAt"); - - b1.Property("OriginalName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("OriginalAudioName"); - - b1.Property("RetryCount") - .HasColumnType("integer") - .HasColumnName("RetryCount"); - - b1.Property("TranscodeStatus") - .HasColumnType("integer") - .HasColumnName("AudioTranscodeProcessingStatus"); - - b1.HasKey("TrackId"); - - b1.ToTable("Tracks"); - - b1.WithOwner() - .HasForeignKey("TrackId"); - }); - - b.OwnsOne("Musify.Domain.ValueObjects.TrackPictures", "Pictures", b1 => - { - b1.Property("TrackId") - .HasColumnType("uuid"); - - b1.Property("LargeName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("LargePictureName"); - - b1.Property("MediumName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("MediumPictureName"); - - b1.Property("OriginalName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("OriginalPictureName"); - - b1.Property("ProcessingStatus") - .HasColumnType("integer") - .HasColumnName("PicturesProcessingStatus"); - - b1.Property("SmallName") - .HasMaxLength(64) - .HasColumnType("character varying(64)") - .HasColumnName("SmallPictureName"); - - b1.HasKey("TrackId"); - - b1.ToTable("Tracks"); - - b1.WithOwner() - .HasForeignKey("TrackId"); - }); - - b.Navigation("Audio") - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("Pictures") - .IsRequired(); - }); - - modelBuilder.Entity("Musify.Domain.Entities.TrackLike", b => - { - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany() - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Track"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.TrackTag", b => - { - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany("Tags") - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Track"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b => - { - b.HasOne("Musify.Domain.Entities.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.UserFollow", b => - { - b.HasOne("Musify.Domain.Entities.User", "Followed") - .WithMany() - .HasForeignKey("FollowedId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.User", "Follower") - .WithMany() - .HasForeignKey("FollowerId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Followed"); - - b.Navigation("Follower"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b => - { - b.HasOne("Musify.Domain.Entities.Track", "Track") - .WithMany("UserTracks") - .HasForeignKey("TrackId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Musify.Domain.Entities.User", "User") - .WithMany("UserTracks") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Track"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Album", b => - { - b.Navigation("AlbumTracks"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Mix", b => - { - b.Navigation("Items"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.PlayList", b => - { - b.Navigation("PlayListTracks"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.Track", b => - { - b.Navigation("AlbumTracks"); - - b.Navigation("ListeningHistories"); - - b.Navigation("PlayListTracks"); - - b.Navigation("Tags"); - - b.Navigation("UserTracks"); - }); - - modelBuilder.Entity("Musify.Domain.Entities.User", b => - { - b.Navigation("ListeningHistories"); - - b.Navigation("PlayLists"); - - b.Navigation("UserTracks"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.cs b/backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.cs deleted file mode 100644 index 815ee9a..0000000 --- a/backend/Core/Musify.Infrastructure/Persistence/Migrations/20260917195334_MakePlayListPictureColumnsOptional.cs +++ /dev/null @@ -1,106 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Musify.Infrastructure.Persistence.Migrations -{ - /// - public partial class MakePlayListPictureColumnsOptional : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "SmallPictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "OriginalPictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "MediumPictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LargePictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "SmallPictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "OriginalPictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "MediumPictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LargePictureName", - table: "PlayLists", - type: "character varying(64)", - maxLength: 64, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(64)", - oldMaxLength: 64, - oldNullable: true); - } - } -} diff --git a/backend/Core/Musify.Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs b/backend/Core/Musify.Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs index 6f3e6d9..f5767e5 100644 --- a/backend/Core/Musify.Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs +++ b/backend/Core/Musify.Infrastructure/Persistence/Migrations/DatabaseModelSnapshot.cs @@ -18,7 +18,7 @@ namespace Musify.Infrastructure.Persistence.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.8") + .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" }); @@ -68,7 +68,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("Delivered"); - b.ToTable("InboxState"); + b.ToTable("InboxState", (string)null); }); modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b => @@ -159,7 +159,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("InboxMessageId", "InboxConsumerId", "SequenceNumber") .IsUnique(); - b.ToTable("OutboxMessage"); + b.ToTable("OutboxMessage", (string)null); }); modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxState", b => @@ -189,7 +189,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("Created"); - b.ToTable("OutboxState"); + b.ToTable("OutboxState", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.Album", b => @@ -231,7 +231,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("OwnerUserId"); - b.ToTable("Albums"); + b.ToTable("Albums", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.AlbumHasTrack", b => @@ -259,7 +259,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("AlbumId", "TrackId") .IsUnique(); - b.ToTable("AlbumHasTrack"); + b.ToTable("AlbumHasTrack", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b => @@ -283,7 +283,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("UserId"); - b.ToTable("ListeningHistory"); + b.ToTable("ListeningHistory", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.Mix", b => @@ -317,7 +317,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("UserId", "Position"); - b.ToTable("Mixes"); + b.ToTable("Mixes", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.MixItem", b => @@ -341,7 +341,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("MixId", "Position"); - b.ToTable("MixItems"); + b.ToTable("MixItems", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.PlayList", b => @@ -385,7 +385,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("UserId"); - b.ToTable("PlayLists"); + b.ToTable("PlayLists", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b => @@ -412,7 +412,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("TrackId"); - b.ToTable("PlayListHasTrack"); + b.ToTable("PlayListHasTrack", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.Track", b => @@ -450,7 +450,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("OwnerUserId"); - b.ToTable("Tracks"); + b.ToTable("Tracks", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.TrackLike", b => @@ -475,7 +475,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("UserId", "TrackId") .IsUnique(); - b.ToTable("TrackLikes"); + b.ToTable("TrackLikes", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.TrackTag", b => @@ -495,7 +495,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("TrackId", "Tag") .IsUnique(); - b.ToTable("TrackTags"); + b.ToTable("TrackTags", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.Upload", b => @@ -524,7 +524,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasKey("Id"); - b.ToTable("Upload"); + b.ToTable("Upload", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b => @@ -575,7 +575,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("UserId"); - b.ToTable("UploadIntents"); + b.ToTable("UploadIntents", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.User", b => @@ -613,7 +613,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasKey("Id"); - b.ToTable("User"); + b.ToTable("User", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.UserFollow", b => @@ -638,7 +638,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("FollowerId", "FollowedId") .IsUnique(); - b.ToTable("UserFollows"); + b.ToTable("UserFollows", (string)null); }); modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b => @@ -659,7 +659,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasIndex("UserId"); - b.ToTable("UserHasTrack"); + b.ToTable("UserHasTrack", (string)null); }); modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.AlbumProcessingState", b => @@ -686,7 +686,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasKey("CorrelationId"); - b.ToTable("AlbumProcessingState"); + b.ToTable("AlbumProcessingState", (string)null); }); modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.PlayListProcessingState", b => @@ -713,7 +713,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasKey("CorrelationId"); - b.ToTable("PlayListProcessingState"); + b.ToTable("PlayListProcessingState", (string)null); }); modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.TrackProcessingState", b => @@ -749,7 +749,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b.HasKey("CorrelationId"); - b.ToTable("TrackProcessingState"); + b.ToTable("TrackProcessingState", (string)null); }); modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b => @@ -803,7 +803,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b1.HasKey("AlbumId"); - b1.ToTable("Albums"); + b1.ToTable("Albums", (string)null); b1.WithOwner() .HasForeignKey("AlbumId"); @@ -896,28 +896,32 @@ namespace Musify.Infrastructure.Persistence.Migrations .HasColumnType("uuid"); b1.Property("LargeName") + .IsRequired() .HasMaxLength(64) .HasColumnType("character varying(64)") .HasColumnName("LargePictureName"); b1.Property("MediumName") + .IsRequired() .HasMaxLength(64) .HasColumnType("character varying(64)") .HasColumnName("MediumPictureName"); b1.Property("OriginalName") + .IsRequired() .HasMaxLength(64) .HasColumnType("character varying(64)") .HasColumnName("OriginalPictureName"); b1.Property("SmallName") + .IsRequired() .HasMaxLength(64) .HasColumnType("character varying(64)") .HasColumnName("SmallPictureName"); b1.HasKey("PlayListId"); - b1.ToTable("PlayLists"); + b1.ToTable("PlayLists", (string)null); b1.WithOwner() .HasForeignKey("PlayListId"); @@ -988,7 +992,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b1.HasKey("TrackId"); - b1.ToTable("Tracks"); + b1.ToTable("Tracks", (string)null); b1.WithOwner() .HasForeignKey("TrackId"); @@ -1025,7 +1029,7 @@ namespace Musify.Infrastructure.Persistence.Migrations b1.HasKey("TrackId"); - b1.ToTable("Tracks"); + b1.ToTable("Tracks", (string)null); b1.WithOwner() .HasForeignKey("TrackId"); diff --git a/backend/Tests/Musify.Application.Tests/PlayLists/UpdatePlayListCommandHandlerTests.cs b/backend/Tests/Musify.Application.Tests/PlayLists/UpdatePlayListCommandHandlerTests.cs index 10e1c20..ccee5d0 100644 --- a/backend/Tests/Musify.Application.Tests/PlayLists/UpdatePlayListCommandHandlerTests.cs +++ b/backend/Tests/Musify.Application.Tests/PlayLists/UpdatePlayListCommandHandlerTests.cs @@ -107,5 +107,30 @@ namespace Musify.Application.Tests.PlayLists Assert.Equal("Original Name", result.Value.Name); Assert.Equal("Original description", result.Value.Description); } + + [Fact] + public async Task Handle_PlayListWithoutPictures_ValidPictureIntent_SetsPictureAndPublishesSourceEvent() + { + storageService + .HeadObjectAsync(Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(new ObjectMetaData("image/webp", 1024)); + + var owner = TestEntities.User(); + var playList = TestEntities.PlayList(owner.Id); + playList.Pictures = null; + var intent = TestEntities.UploadIntent(owner.Id, Musify.Domain.ValueObjects.UploadIntentPurpose.PlayListPicture, objectName: "new-cover.webp"); + await SeedAsync(owner, playList, intent); + + var command = new UpdatePlayListCommand(owner.Id, playList.Id, null, null, intent.Id); + + var result = await CreateHandler().Handle(command, TestContext.Current.CancellationToken); + + Assert.False(result.IsError); + Assert.Equal("new-cover.webp", result.Value.SmallImageKeyName); + var stored = await Database.PlayLists.FindAsync([playList.Id], TestContext.Current.CancellationToken); + Assert.NotNull(stored); + Assert.Equal("new-cover.webp", stored.Pictures?.OriginalName); + await eventBus.Received(1).PublishAsync(Arg.Any(), Arg.Any()); + } } }