feat(backend): model albums in the domain

Album is abstract with UserAlbum as its first table-per-type child, the same
split Track already uses, so the YouTube albums can join later without moving
anything. Tracks are linked through AlbumHasTrack rather than a column on the
track itself: an external track is shared between users and the same song
shows up in more than one album.
This commit is contained in:
2026-07-20 20:08:21 +02:00
parent a59fa3a548
commit 8e561f5b2a
10 changed files with 1481 additions and 0 deletions
@@ -190,6 +190,73 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.ToTable("OutboxState");
});
modelBuilder.Entity("Musify.Domain.Entities.Album", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("LifeCycleStatus")
.HasColumnType("integer");
b.Property<string>("NormalizedTitle")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<int?>("ReleaseYear")
.HasColumnType("integer");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Albums");
b.UseTptMappingStrategy();
});
modelBuilder.Entity("Musify.Domain.Entities.AlbumHasTrack", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("AlbumId")
.HasColumnType("uuid");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<int>("TrackNumber")
.HasColumnType("integer");
b.HasKey("Id");
b.HasIndex("TrackId");
b.HasIndex("AlbumId", "TrackId")
.IsUnique();
b.ToTable("AlbumHasTrack");
});
modelBuilder.Entity("Musify.Domain.Entities.Artist", b =>
{
b.Property<Guid>("Id")
@@ -571,6 +638,18 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.ToTable("TrackProcessingState");
});
modelBuilder.Entity("Musify.Domain.Entities.UserAlbum", b =>
{
b.HasBaseType("Musify.Domain.Entities.Album");
b.Property<long>("OwnerUserId")
.HasColumnType("bigint");
b.HasIndex("OwnerUserId");
b.ToTable("UserAlbums", (string)null);
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
@@ -613,6 +692,63 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasPrincipalKey("MessageId", "ConsumerId");
});
modelBuilder.Entity("Musify.Domain.Entities.Album", b =>
{
b.OwnsOne("Musify.Domain.ValueObjects.AlbumPictures", "Pictures", b1 =>
{
b1.Property<Guid>("AlbumId")
.HasColumnType("uuid");
b1.Property<string>("LargeName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("LargePictureName");
b1.Property<string>("MediumName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("MediumPictureName");
b1.Property<string>("OriginalName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("OriginalPictureName");
b1.Property<string>("SmallName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("SmallPictureName");
b1.HasKey("AlbumId");
b1.ToTable("Albums");
b1.WithOwner()
.HasForeignKey("AlbumId");
});
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")
@@ -831,6 +967,23 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Musify.Domain.Entities.UserAlbum", b =>
{
b.HasOne("Musify.Domain.Entities.Album", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.UserAlbum", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Musify.Domain.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerUserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Owner");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
@@ -857,6 +1010,11 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("Owner");
});
modelBuilder.Entity("Musify.Domain.Entities.Album", b =>
{
b.Navigation("AlbumTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.Artist", b =>
{
b.Navigation("TrackArtists");
@@ -869,6 +1027,8 @@ namespace Musify.Infrastructure.Persistence.Migrations
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.Navigation("AlbumTracks");
b.Navigation("ListeningHistories");
b.Navigation("PlayListTracks");