This commit is contained in:
2026-07-18 23:29:57 +02:00
parent 72f42369a0
commit 99c4bb8408
109 changed files with 5134 additions and 2163 deletions
@@ -2,6 +2,7 @@ using MassTransit;
using Microsoft.Extensions.Logging;
using Musify.Application.Contracts;
using Musify.Application.Events;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.MassTransit.Arguments;
using Musify.Infrastructure.MassTransit.Logs;
@@ -37,6 +38,13 @@ namespace Musify.Infrastructure.MassTransit.Activities.Pictures
throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} not found");
}
if (playList.Pictures is null)
{
logger.LogWarning("Playlist {PlayListId} has no pending picture upload",
executeContext.Arguments.PlayListId);
throw new InvalidOperationException($"PlayList with id {executeContext.Arguments.PlayListId} has no pending picture upload");
}
var log = new UpdatePlayListPictureLog(
playList.Id,
playList.Pictures.OriginalName,
@@ -82,10 +90,13 @@ namespace Musify.Infrastructure.MassTransit.Activities.Pictures
return compensateContext.Compensated();
}
playList.Pictures.OriginalName = compensateContext.Log.PreviousOriginalPictureKey;
playList.Pictures.SmallName = compensateContext.Log.PreviousSmallPictureKey;
playList.Pictures.MediumName = compensateContext.Log.PreviousMediumPictureKey;
playList.Pictures.LargeName = compensateContext.Log.PreviousLargePictureKey;
playList.Pictures = new PlayListPictures
{
OriginalName = compensateContext.Log.PreviousOriginalPictureKey,
SmallName = compensateContext.Log.PreviousSmallPictureKey,
MediumName = compensateContext.Log.PreviousMediumPictureKey,
LargeName = compensateContext.Log.PreviousLargePictureKey
};
database.PlayLists.Update(playList);
await database.SaveChangesAsync(compensateContext.CancellationToken);
@@ -2,8 +2,8 @@ namespace Musify.Infrastructure.MassTransit.Logs
{
public record UpdatePlayListPictureLog(
Guid PlayListId,
string PreviousOriginalPictureKey,
string PreviousSmallPictureKey,
string PreviousMediumPictureKey,
string PreviousLargePictureKey);
string? PreviousOriginalPictureKey,
string? PreviousSmallPictureKey,
string? PreviousMediumPictureKey,
string? PreviousLargePictureKey);
}
@@ -37,49 +37,49 @@ namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders
EndpointHelper.BuildExecuteActivityUri(MarkPlayListAsRemovingActivity.ExecuteEndpointName),
new MarkPlayListAsRemovingArguments(playListId));
if (playList is not null)
if (playList?.Pictures is not null)
{
var bucket = storageConfiguration.Bucket;
var routes = playListConfiguration.Routes;
if (playList.Pictures.OriginalName != routes.PresetOriginalPicture)
if (playList.Pictures.OriginalName is { } originalName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListOriginalPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildOriginalPicturePath(userId, playList.Pictures.OriginalName)));
routes.BuildOriginalPicturePath(userId, originalName)));
}
if (playList.Pictures.SmallName != routes.PresetSmallPicture)
if (playList.Pictures.SmallName is { } smallName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListSmallPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildSmallPicturePath(playList.Pictures.SmallName)));
routes.BuildSmallPicturePath(smallName)));
}
if (playList.Pictures.MediumName != routes.PresetMediumPicture)
if (playList.Pictures.MediumName is { } mediumName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListMediumPicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildMediumPicturePath(playList.Pictures.MediumName)));
routes.BuildMediumPicturePath(mediumName)));
}
if (playList.Pictures.LargeName != routes.PresetLargePicture)
if (playList.Pictures.LargeName is { } largeName)
{
routingSlipBuilder.AddActivity(
ActivityNames.RemovePlayListLargePicture,
EndpointHelper.BuildExecuteActivityUri(RemoveFileFromBucketActivity.ExecuteEndpointName),
new RemoveFileFromBucketArguments(
bucket,
routes.BuildLargePicturePath(playList.Pictures.LargeName)));
routes.BuildLargePicturePath(largeName)));
}
}
@@ -30,21 +30,6 @@ namespace Musify.Infrastructure.Persistence
trackProcessing.Property(state => state.CorrelationId).ValueGeneratedNever();
trackProcessing.Property(state => state.CurrentState).HasMaxLength(64);
modelBuilder.Entity<Track>()
.HasIndex(track => new { track.Source, track.ExternalId })
.IsUnique();
modelBuilder.Entity<Track>()
.HasOne(track => track.Owner)
.WithMany()
.HasForeignKey(track => track.OwnerUserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Track>()
.ToTable(t => t.HasCheckConstraint(
"CK_Tracks_OwnerUserId_Source",
"(\"OwnerUserId\" IS NOT NULL) = (\"Source\" = 0)"));
modelBuilder.Entity<Track>()
.OwnsOne(track => track.Pictures, pictures =>
{
@@ -68,6 +53,22 @@ namespace Musify.Infrastructure.Persistence
});
modelBuilder.Entity<Track>().Navigation(track => track.Audio).IsRequired();
modelBuilder.Entity<LocalTrack>()
.ToTable("LocalTracks");
modelBuilder.Entity<LocalTrack>()
.HasOne(track => track.Owner)
.WithMany()
.HasForeignKey(track => track.OwnerUserId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<ExternalTrack>()
.ToTable("ExternalTracks");
modelBuilder.Entity<ExternalTrack>()
.HasIndex(track => new { track.Source, track.ExternalId })
.IsUnique();
modelBuilder.Entity<TrackArtist>()
.HasKey(trackArtist => new { trackArtist.TrackId, trackArtist.ArtistId });
@@ -83,19 +84,10 @@ namespace Musify.Infrastructure.Persistence
.HasForeignKey(trackArtist => trackArtist.ArtistId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Artist>()
.HasIndex(artist => artist.UserId)
.IsUnique();
modelBuilder.Entity<Artist>()
.HasIndex(artist => artist.ExternalId)
.IsUnique();
modelBuilder.Entity<Artist>()
.HasIndex(artist => artist.NormalizedName)
.IsUnique()
.HasFilter("\"ExternalId\" IS NULL AND \"UserId\" IS NULL");
modelBuilder.Entity<PlayList>()
.OwnsOne(playList => playList.Pictures, pictures =>
{
@@ -104,7 +96,7 @@ namespace Musify.Infrastructure.Persistence
pictures.Property(p => p.MediumName).HasColumnName("MediumPictureName").HasMaxLength(64);
pictures.Property(p => p.LargeName).HasColumnName("LargePictureName").HasMaxLength(64);
});
modelBuilder.Entity<PlayList>().Navigation(playList => playList.Pictures).IsRequired();
modelBuilder.Entity<PlayList>().Navigation(playList => playList.Pictures).IsRequired(false);
var playListProcessing = modelBuilder.Entity<PlayListProcessingState>();
playListProcessing.HasKey(state => state.CorrelationId);
@@ -116,6 +108,10 @@ namespace Musify.Infrastructure.Persistence
public DbSet<Track> Tracks => Set<Track>();
public DbSet<LocalTrack> LocalTracks => Set<LocalTrack>();
public DbSet<ExternalTrack> ExternalTracks => Set<ExternalTrack>();
public DbSet<Artist> Artists => Set<Artist>();
public DbSet<TrackArtist> TrackArtists => Set<TrackArtist>();
@@ -0,0 +1,903 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Musify.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(Database))]
[Migration("20260717172507_SplitTrackIntoLocalAndExternal")]
partial class SplitTrackIntoLocalAndExternal
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime?>("Consumed")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("ConsumerId")
.HasColumnType("uuid");
b.Property<DateTime?>("Delivered")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LastSequenceNumber")
.HasColumnType("bigint");
b.Property<Guid>("LockId")
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<int>("ReceiveCount")
.HasColumnType("integer");
b.Property<DateTime>("Received")
.HasColumnType("timestamp with time zone");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("bytea");
b.HasKey("Id");
b.HasIndex("Delivered");
b.ToTable("InboxState");
});
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
{
b.Property<long>("SequenceNumber")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("SequenceNumber"));
b.Property<string>("Body")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ContentType")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid?>("ConversationId")
.HasColumnType("uuid");
b.Property<Guid?>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("DestinationAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime?>("EnqueueTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("FaultAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Headers")
.HasColumnType("text");
b.Property<Guid?>("InboxConsumerId")
.HasColumnType("uuid");
b.Property<Guid?>("InboxMessageId")
.HasColumnType("uuid");
b.Property<Guid?>("InitiatorId")
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<string>("MessageType")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("OutboxId")
.HasColumnType("uuid");
b.Property<string>("Properties")
.HasColumnType("text");
b.Property<Guid?>("RequestId")
.HasColumnType("uuid");
b.Property<string>("ResponseAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("SentTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("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<Guid>("OutboxId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("Delivered")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LastSequenceNumber")
.HasColumnType("bigint");
b.Property<Guid>("LockId")
.HasColumnType("uuid");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("bytea");
b.HasKey("OutboxId");
b.HasIndex("Created");
b.ToTable("OutboxState");
});
modelBuilder.Entity("Musify.Domain.Entities.Artist", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ExternalId")
.IsUnique();
b.ToTable("Artists");
});
modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("ListenedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("TrackId");
b.HasIndex("UserId");
b.ToTable("ListeningHistory");
});
modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("LifeCycleStatus")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("PlayLists");
});
modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("PlayListId")
.HasColumnType("uuid");
b.Property<int>("Position")
.HasColumnType("integer");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("PlayListId");
b.HasIndex("TrackId");
b.ToTable("PlayListHasTrack");
});
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("DurationSeconds")
.HasColumnType("integer");
b.Property<int>("LifeCycleStatus")
.HasColumnType("integer");
b.Property<string>("NormalizedTitle")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Tracks");
b.UseTptMappingStrategy();
});
modelBuilder.Entity("Musify.Domain.Entities.TrackArtist", b =>
{
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<Guid>("ArtistId")
.HasColumnType("uuid");
b.Property<int>("Position")
.HasColumnType("integer");
b.HasKey("TrackId", "ArtistId");
b.HasIndex("ArtistId");
b.ToTable("TrackArtist");
});
modelBuilder.Entity("Musify.Domain.Entities.Upload", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Bucket")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ContentType")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<int>("State")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Upload");
});
modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Bucket")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ContentType")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<long?>("ExpectedSizeBytes")
.HasColumnType("bigint");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("ObjectName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<int>("Purpose")
.HasColumnType("integer");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UploadIntents");
});
modelBuilder.Entity("Musify.Domain.Entities.User", b =>
{
b.Property<long>("Id")
.HasColumnType("bigint");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FirstName")
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<string>("ProfilePictureUrl")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b.Property<string>("SecondName")
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("User");
});
modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("TrackId");
b.HasIndex("UserId");
b.ToTable("UserHasTrack");
});
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.PlayListProcessingState", b =>
{
b.Property<Guid>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("Bucket")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CurrentState")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("PictureKey")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("CorrelationId");
b.ToTable("PlayListProcessingState");
});
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.TrackProcessingState", b =>
{
b.Property<Guid>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("AudioKey")
.HasColumnType("text");
b.Property<bool>("AudioProcessed")
.HasColumnType("boolean");
b.Property<string>("Bucket")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CurrentState")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("PictureKey")
.HasColumnType("text");
b.Property<bool>("PictureProcessed")
.HasColumnType("boolean");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("CorrelationId");
b.ToTable("TrackProcessingState");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Source")
.HasColumnType("integer");
b.HasIndex("Source", "ExternalId")
.IsUnique();
b.ToTable("ExternalTracks", (string)null);
});
modelBuilder.Entity("Musify.Domain.Entities.LocalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
b.Property<long>("OwnerUserId")
.HasColumnType("bigint");
b.HasIndex("OwnerUserId");
b.ToTable("LocalTracks", (string)null);
});
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.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.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<Guid>("PlayListId")
.HasColumnType("uuid");
b1.Property<string>("LargeName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("LargePictureName");
b1.Property<string>("MediumName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("MediumPictureName");
b1.Property<string>("OriginalName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("OriginalPictureName");
b1.Property<string>("SmallName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("SmallPictureName");
b1.HasKey("PlayListId");
b1.ToTable("PlayLists");
b1.WithOwner()
.HasForeignKey("PlayListId");
});
b.Navigation("Pictures")
.IsRequired();
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.OwnsOne("Musify.Domain.ValueObjects.TrackAudio", "Audio", b1 =>
{
b1.Property<Guid>("TrackId")
.HasColumnType("uuid");
b1.Property<bool>("DownloadRequested")
.HasColumnType("boolean")
.HasColumnName("DownloadRequested");
b1.Property<string>("FolderName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("AudioFolderName");
b1.Property<DateTime?>("LastRetryAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("LastRetryAt");
b1.Property<string>("OriginalName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("OriginalAudioName");
b1.Property<int>("RetryCount")
.HasColumnType("integer")
.HasColumnName("RetryCount");
b1.Property<int>("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<Guid>("TrackId")
.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<int>("ProcessingStatus")
.HasColumnType("integer")
.HasColumnName("PicturesProcessingStatus");
b1.Property<string>("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("Pictures")
.IsRequired();
});
modelBuilder.Entity("Musify.Domain.Entities.TrackArtist", b =>
{
b.HasOne("Musify.Domain.Entities.Artist", "Artist")
.WithMany("TrackArtists")
.HasForeignKey("ArtistId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Musify.Domain.Entities.ExternalTrack", "Track")
.WithMany("TrackArtists")
.HasForeignKey("TrackId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Artist");
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.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.ExternalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.ExternalTrack", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Musify.Domain.Entities.LocalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.LocalTrack", "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.Artist", b =>
{
b.Navigation("TrackArtists");
});
modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
{
b.Navigation("PlayListTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.Navigation("ListeningHistories");
b.Navigation("PlayListTracks");
b.Navigation("UserTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.User", b =>
{
b.Navigation("ListeningHistories");
b.Navigation("PlayLists");
b.Navigation("UserTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.Navigation("TrackArtists");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,274 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class SplitTrackIntoLocalAndExternal : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_TrackArtist_Tracks_TrackId",
table: "TrackArtist");
migrationBuilder.DropForeignKey(
name: "FK_Tracks_User_OwnerUserId",
table: "Tracks");
migrationBuilder.DropIndex(
name: "IX_Tracks_OwnerUserId",
table: "Tracks");
migrationBuilder.DropIndex(
name: "IX_Tracks_Source_ExternalId",
table: "Tracks");
migrationBuilder.DropCheckConstraint(
name: "CK_Tracks_OwnerUserId_Source",
table: "Tracks");
migrationBuilder.CreateTable(
name: "ExternalTracks",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Source = table.Column<int>(type: "integer", nullable: false),
ExternalId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ExternalTracks", x => x.Id);
table.ForeignKey(
name: "FK_ExternalTracks_Tracks_Id",
column: x => x.Id,
principalTable: "Tracks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "LocalTracks",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
OwnerUserId = table.Column<long>(type: "bigint", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_LocalTracks", x => x.Id);
table.ForeignKey(
name: "FK_LocalTracks_Tracks_Id",
column: x => x.Id,
principalTable: "Tracks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_LocalTracks_User_OwnerUserId",
column: x => x.OwnerUserId,
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_ExternalTracks_Source_ExternalId",
table: "ExternalTracks",
columns: new[] { "Source", "ExternalId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_LocalTracks_OwnerUserId",
table: "LocalTracks",
column: "OwnerUserId");
migrationBuilder.Sql(
"""
INSERT INTO "LocalTracks" ("Id", "OwnerUserId")
SELECT "Id", "OwnerUserId" FROM "Tracks" WHERE "Source" = 0;
""");
migrationBuilder.Sql(
"""
INSERT INTO "ExternalTracks" ("Id", "Source", "ExternalId")
SELECT "Id", "Source", "ExternalId" FROM "Tracks" WHERE "Source" <> 0;
""");
migrationBuilder.Sql(
"""
DELETE FROM "TrackArtist" tra
USING "Tracks" t
WHERE tra."TrackId" = t."Id" AND t."Source" = 0;
""");
migrationBuilder.Sql(
"""
DELETE FROM "Artists" WHERE "UserId" IS NOT NULL;
""");
migrationBuilder.AddForeignKey(
name: "FK_TrackArtist_ExternalTracks_TrackId",
table: "TrackArtist",
column: "TrackId",
principalTable: "ExternalTracks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.DropColumn(
name: "ExternalId",
table: "Tracks");
migrationBuilder.DropColumn(
name: "OwnerUserId",
table: "Tracks");
migrationBuilder.DropColumn(
name: "Source",
table: "Tracks");
migrationBuilder.DropIndex(
name: "IX_Artists_NormalizedName",
table: "Artists");
migrationBuilder.DropIndex(
name: "IX_Artists_UserId",
table: "Artists");
migrationBuilder.DropForeignKey(
name: "FK_Artists_User_UserId",
table: "Artists");
migrationBuilder.DropColumn(
name: "UserId",
table: "Artists");
migrationBuilder.AlterColumn<string>(
name: "ExternalId",
table: "Artists",
type: "character varying(64)",
maxLength: 64,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_TrackArtist_ExternalTracks_TrackId",
table: "TrackArtist");
migrationBuilder.AddColumn<string>(
name: "ExternalId",
table: "Tracks",
type: "character varying(16)",
maxLength: 16,
nullable: true);
migrationBuilder.AddColumn<long>(
name: "OwnerUserId",
table: "Tracks",
type: "bigint",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "Source",
table: "Tracks",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql(
"""
UPDATE "Tracks" t SET "OwnerUserId" = lt."OwnerUserId", "Source" = 0
FROM "LocalTracks" lt WHERE lt."Id" = t."Id";
""");
migrationBuilder.Sql(
"""
UPDATE "Tracks" t SET "ExternalId" = et."ExternalId", "Source" = et."Source"
FROM "ExternalTracks" et WHERE et."Id" = t."Id";
""");
migrationBuilder.DropTable(
name: "ExternalTracks");
migrationBuilder.DropTable(
name: "LocalTracks");
migrationBuilder.AlterColumn<string>(
name: "ExternalId",
table: "Artists",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AddColumn<long>(
name: "UserId",
table: "Artists",
type: "bigint",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_Tracks_OwnerUserId",
table: "Tracks",
column: "OwnerUserId");
migrationBuilder.CreateIndex(
name: "IX_Tracks_Source_ExternalId",
table: "Tracks",
columns: new[] { "Source", "ExternalId" },
unique: true);
migrationBuilder.AddCheckConstraint(
name: "CK_Tracks_OwnerUserId_Source",
table: "Tracks",
sql: "(\"OwnerUserId\" IS NOT NULL) = (\"Source\" = 0)");
migrationBuilder.CreateIndex(
name: "IX_Artists_NormalizedName",
table: "Artists",
column: "NormalizedName",
unique: true,
filter: "\"ExternalId\" IS NULL AND \"UserId\" IS NULL");
migrationBuilder.CreateIndex(
name: "IX_Artists_UserId",
table: "Artists",
column: "UserId",
unique: true);
migrationBuilder.AddForeignKey(
name: "FK_Artists_User_UserId",
table: "Artists",
column: "UserId",
principalTable: "User",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_TrackArtist_Tracks_TrackId",
table: "TrackArtist",
column: "TrackId",
principalTable: "Tracks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Tracks_User_OwnerUserId",
table: "Tracks",
column: "OwnerUserId",
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}
@@ -0,0 +1,898 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Musify.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(Database))]
[Migration("20260717223358_MakePlayListPicturesNullable")]
partial class MakePlayListPicturesNullable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
b.Property<DateTime?>("Consumed")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("ConsumerId")
.HasColumnType("uuid");
b.Property<DateTime?>("Delivered")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LastSequenceNumber")
.HasColumnType("bigint");
b.Property<Guid>("LockId")
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<int>("ReceiveCount")
.HasColumnType("integer");
b.Property<DateTime>("Received")
.HasColumnType("timestamp with time zone");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("bytea");
b.HasKey("Id");
b.HasIndex("Delivered");
b.ToTable("InboxState");
});
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
{
b.Property<long>("SequenceNumber")
.ValueGeneratedOnAdd()
.HasColumnType("bigint");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("SequenceNumber"));
b.Property<string>("Body")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ContentType")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<Guid?>("ConversationId")
.HasColumnType("uuid");
b.Property<Guid?>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("DestinationAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime?>("EnqueueTime")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("FaultAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("Headers")
.HasColumnType("text");
b.Property<Guid?>("InboxConsumerId")
.HasColumnType("uuid");
b.Property<Guid?>("InboxMessageId")
.HasColumnType("uuid");
b.Property<Guid?>("InitiatorId")
.HasColumnType("uuid");
b.Property<Guid>("MessageId")
.HasColumnType("uuid");
b.Property<string>("MessageType")
.IsRequired()
.HasColumnType("text");
b.Property<Guid?>("OutboxId")
.HasColumnType("uuid");
b.Property<string>("Properties")
.HasColumnType("text");
b.Property<Guid?>("RequestId")
.HasColumnType("uuid");
b.Property<string>("ResponseAddress")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<DateTime>("SentTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("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<Guid>("OutboxId")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("Created")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("Delivered")
.HasColumnType("timestamp with time zone");
b.Property<long?>("LastSequenceNumber")
.HasColumnType("bigint");
b.Property<Guid>("LockId")
.HasColumnType("uuid");
b.Property<byte[]>("RowVersion")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("bytea");
b.HasKey("OutboxId");
b.HasIndex("Created");
b.ToTable("OutboxState");
});
modelBuilder.Entity("Musify.Domain.Entities.Artist", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("character varying(200)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ExternalId")
.IsUnique();
b.ToTable("Artists");
});
modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("ListenedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("TrackId");
b.HasIndex("UserId");
b.ToTable("ListeningHistory");
});
modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<int>("LifeCycleStatus")
.HasColumnType("integer");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("PlayLists");
});
modelBuilder.Entity("Musify.Domain.Entities.PlayListHasTrack", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("AddedAt")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("PlayListId")
.HasColumnType("uuid");
b.Property<int>("Position")
.HasColumnType("integer");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("PlayListId");
b.HasIndex("TrackId");
b.ToTable("PlayListHasTrack");
});
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<int>("DurationSeconds")
.HasColumnType("integer");
b.Property<int>("LifeCycleStatus")
.HasColumnType("integer");
b.Property<string>("NormalizedTitle")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Tracks");
b.UseTptMappingStrategy();
});
modelBuilder.Entity("Musify.Domain.Entities.TrackArtist", b =>
{
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<Guid>("ArtistId")
.HasColumnType("uuid");
b.Property<int>("Position")
.HasColumnType("integer");
b.HasKey("TrackId", "ArtistId");
b.HasIndex("ArtistId");
b.ToTable("TrackArtist");
});
modelBuilder.Entity("Musify.Domain.Entities.Upload", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Bucket")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ContentType")
.IsRequired()
.HasColumnType("text");
b.Property<DateTime>("CreatedTime")
.HasColumnType("timestamp with time zone");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("text");
b.Property<int>("State")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("Upload");
});
modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Bucket")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("ContentType")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<long?>("ExpectedSizeBytes")
.HasColumnType("bigint");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Key")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("ObjectName")
.IsRequired()
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<int>("Purpose")
.HasColumnType("integer");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("UploadIntents");
});
modelBuilder.Entity("Musify.Domain.Entities.User", b =>
{
b.Property<long>("Id")
.HasColumnType("bigint");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("FirstName")
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<string>("ProfilePictureUrl")
.HasMaxLength(2048)
.HasColumnType("character varying(2048)");
b.Property<string>("SecondName")
.HasMaxLength(48)
.HasColumnType("character varying(48)");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("User");
});
modelBuilder.Entity("Musify.Domain.Entities.UserHasTrack", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.Property<long>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("TrackId");
b.HasIndex("UserId");
b.ToTable("UserHasTrack");
});
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.PlayListProcessingState", b =>
{
b.Property<Guid>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("Bucket")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CurrentState")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("PictureKey")
.HasColumnType("text");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("CorrelationId");
b.ToTable("PlayListProcessingState");
});
modelBuilder.Entity("Musify.Infrastructure.MassTransit.Sagas.TrackProcessingState", b =>
{
b.Property<Guid>("CorrelationId")
.HasColumnType("uuid");
b.Property<string>("AudioKey")
.HasColumnType("text");
b.Property<bool>("AudioProcessed")
.HasColumnType("boolean");
b.Property<string>("Bucket")
.HasColumnType("text");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CurrentState")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("PictureKey")
.HasColumnType("text");
b.Property<bool>("PictureProcessed")
.HasColumnType("boolean");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("CorrelationId");
b.ToTable("TrackProcessingState");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Source")
.HasColumnType("integer");
b.HasIndex("Source", "ExternalId")
.IsUnique();
b.ToTable("ExternalTracks", (string)null);
});
modelBuilder.Entity("Musify.Domain.Entities.LocalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
b.Property<long>("OwnerUserId")
.HasColumnType("bigint");
b.HasIndex("OwnerUserId");
b.ToTable("LocalTracks", (string)null);
});
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.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.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<Guid>("PlayListId")
.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("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.OwnsOne("Musify.Domain.ValueObjects.TrackAudio", "Audio", b1 =>
{
b1.Property<Guid>("TrackId")
.HasColumnType("uuid");
b1.Property<bool>("DownloadRequested")
.HasColumnType("boolean")
.HasColumnName("DownloadRequested");
b1.Property<string>("FolderName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("AudioFolderName");
b1.Property<DateTime?>("LastRetryAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("LastRetryAt");
b1.Property<string>("OriginalName")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("OriginalAudioName");
b1.Property<int>("RetryCount")
.HasColumnType("integer")
.HasColumnName("RetryCount");
b1.Property<int>("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<Guid>("TrackId")
.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<int>("ProcessingStatus")
.HasColumnType("integer")
.HasColumnName("PicturesProcessingStatus");
b1.Property<string>("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("Pictures")
.IsRequired();
});
modelBuilder.Entity("Musify.Domain.Entities.TrackArtist", b =>
{
b.HasOne("Musify.Domain.Entities.Artist", "Artist")
.WithMany("TrackArtists")
.HasForeignKey("ArtistId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Musify.Domain.Entities.ExternalTrack", "Track")
.WithMany("TrackArtists")
.HasForeignKey("TrackId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Artist");
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.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.ExternalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.ExternalTrack", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Musify.Domain.Entities.LocalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.LocalTrack", "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.Artist", b =>
{
b.Navigation("TrackArtists");
});
modelBuilder.Entity("Musify.Domain.Entities.PlayList", b =>
{
b.Navigation("PlayListTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.Navigation("ListeningHistories");
b.Navigation("PlayListTracks");
b.Navigation("UserTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.User", b =>
{
b.Navigation("ListeningHistories");
b.Navigation("PlayLists");
b.Navigation("UserTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.Navigation("TrackArtists");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,106 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class MakePlayListPicturesNullable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "SmallPictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<string>(
name: "OriginalPictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<string>(
name: "MediumPictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
migrationBuilder.AlterColumn<string>(
name: "LargePictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "SmallPictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "OriginalPictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "MediumPictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "LargePictureName",
table: "PlayLists",
type: "character varying(64)",
maxLength: 64,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);
}
}
}
@@ -200,6 +200,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasColumnType("timestamp with time zone");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
@@ -216,21 +217,11 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<long?>("UserId")
.HasColumnType("bigint");
b.HasKey("Id");
b.HasIndex("ExternalId")
.IsUnique();
b.HasIndex("NormalizedName")
.IsUnique()
.HasFilter("\"ExternalId\" IS NULL AND \"UserId\" IS NULL");
b.HasIndex("UserId")
.IsUnique();
b.ToTable("Artists");
});
@@ -337,10 +328,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Property<int>("DurationSeconds")
.HasColumnType("integer");
b.Property<string>("ExternalId")
.HasMaxLength(16)
.HasColumnType("character varying(16)");
b.Property<int>("LifeCycleStatus")
.HasColumnType("integer");
@@ -349,12 +336,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<long?>("OwnerUserId")
.HasColumnType("bigint");
b.Property<int>("Source")
.HasColumnType("integer");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(50)
@@ -365,15 +346,9 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.HasKey("Id");
b.HasIndex("OwnerUserId");
b.ToTable("Tracks");
b.HasIndex("Source", "ExternalId")
.IsUnique();
b.ToTable("Tracks", t =>
{
t.HasCheckConstraint("CK_Tracks_OwnerUserId_Source", "(\"OwnerUserId\" IS NOT NULL) = (\"Source\" = 0)");
});
b.UseTptMappingStrategy();
});
modelBuilder.Entity("Musify.Domain.Entities.TrackArtist", b =>
@@ -596,6 +571,36 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.ToTable("TrackProcessingState");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
b.Property<string>("ExternalId")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("Source")
.HasColumnType("integer");
b.HasIndex("Source", "ExternalId")
.IsUnique();
b.ToTable("ExternalTracks", (string)null);
});
modelBuilder.Entity("Musify.Domain.Entities.LocalTrack", b =>
{
b.HasBaseType("Musify.Domain.Entities.Track");
b.Property<long>("OwnerUserId")
.HasColumnType("bigint");
b.HasIndex("OwnerUserId");
b.ToTable("LocalTracks", (string)null);
});
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.OutboxMessage", b =>
{
b.HasOne("MassTransit.EntityFrameworkCoreIntegration.OutboxState", null)
@@ -608,15 +613,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasPrincipalKey("MessageId", "ConsumerId");
});
modelBuilder.Entity("Musify.Domain.Entities.Artist", b =>
{
b.HasOne("Musify.Domain.Entities.User", "User")
.WithMany()
.HasForeignKey("UserId");
b.Navigation("User");
});
modelBuilder.Entity("Musify.Domain.Entities.ListeningHistory", b =>
{
b.HasOne("Musify.Domain.Entities.Track", "Track")
@@ -650,25 +646,21 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasColumnType("uuid");
b1.Property<string>("LargeName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("LargePictureName");
b1.Property<string>("MediumName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("MediumPictureName");
b1.Property<string>("OriginalName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("OriginalPictureName");
b1.Property<string>("SmallName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasColumnName("SmallPictureName");
@@ -681,8 +673,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasForeignKey("PlayListId");
});
b.Navigation("Pictures")
.IsRequired();
b.Navigation("Pictures");
b.Navigation("User");
});
@@ -708,11 +699,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.HasOne("Musify.Domain.Entities.User", "Owner")
.WithMany()
.HasForeignKey("OwnerUserId")
.OnDelete(DeleteBehavior.Restrict);
b.OwnsOne("Musify.Domain.ValueObjects.TrackAudio", "Audio", b1 =>
{
b1.Property<Guid>("TrackId")
@@ -792,8 +778,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("Audio")
.IsRequired();
b.Navigation("Owner");
b.Navigation("Pictures")
.IsRequired();
});
@@ -806,7 +790,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Musify.Domain.Entities.Track", "Track")
b.HasOne("Musify.Domain.Entities.ExternalTrack", "Track")
.WithMany("TrackArtists")
.HasForeignKey("TrackId")
.OnDelete(DeleteBehavior.Cascade)
@@ -847,6 +831,32 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.ExternalTrack", "Id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Musify.Domain.Entities.LocalTrack", b =>
{
b.HasOne("Musify.Domain.Entities.Track", null)
.WithOne()
.HasForeignKey("Musify.Domain.Entities.LocalTrack", "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.Artist", b =>
{
b.Navigation("TrackArtists");
@@ -863,8 +873,6 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("PlayListTracks");
b.Navigation("TrackArtists");
b.Navigation("UserTracks");
});
@@ -876,6 +884,11 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("UserTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.ExternalTrack", b =>
{
b.Navigation("TrackArtists");
});
#pragma warning restore 612, 618
}
}