A lot of changes

This commit is contained in:
2026-05-14 14:32:19 +02:00
parent 7160266763
commit f2febb898e
43 changed files with 2941 additions and 444 deletions
+1
View File
@@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="AWSSDK.S3" Version="4.0.19" />
<PackageReference Include="MassTransit" Version="9.0.1" />
<PackageReference Include="MassTransit.EntityFrameworkCore" Version="9.0.1" />
<PackageReference Include="MassTransit.RabbitMQ" Version="9.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
@@ -17,17 +17,20 @@ namespace Musify.Infrastructure.MassTransit.Activities.Files
var sourceFilePath = executeContext.GetVariable<string>(executeContext.Arguments.FilePathVariable);
ArgumentNullException.ThrowIfNull(sourceFilePath, nameof(sourceFilePath));
var fileName = Path.GetFileName(sourceFilePath);
var destinationKey = string.Join('/', new[] { executeContext.Arguments.DestinationRoute, fileName }
.Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s.Trim().Trim('/', '\\')));
try
{
await storageHandler.UploadFileAsync(
sourceFilePath,
executeContext.Arguments.DestinationBucket,
Path.Combine(executeContext.Arguments.DestinationRoute, fileName),
destinationKey,
executeContext.CancellationToken);
return executeContext.CompletedWithVariables(new UploadFileToBucketLog(
executeContext.Arguments.DestinationBucket,
executeContext.Arguments.DestinationRoute));
destinationKey));
}
catch (Exception exception)
{
@@ -33,11 +33,11 @@ namespace Musify.Infrastructure.MassTransit.Activities.Tracks
try
{
await RemovePicturesAsync(track, executeContext.CancellationToken);
await RemovePicturesAsync(track, executeContext.Arguments.UserId, executeContext.CancellationToken);
track.PicturesProcessingStatus = ProcessingStatus.Completed;
await database.SaveChangesAsync(executeContext.CancellationToken);
await RemoveAudiosAsync(track, executeContext.CancellationToken);
await RemoveAudiosAsync(track, executeContext.Arguments.UserId, executeContext.CancellationToken);
track.AudioTranscodeProcessingStatus = ProcessingStatus.Completed;
await database.SaveChangesAsync(executeContext.CancellationToken);
@@ -66,9 +66,9 @@ namespace Musify.Infrastructure.MassTransit.Activities.Tracks
}
}
async Task RemovePicturesAsync(Track track, CancellationToken cancellationToken)
async Task RemovePicturesAsync(Track track, Guid userId, CancellationToken cancellationToken)
{
await RemoveFileAsync(trackConfiguration.Routes.BuildOriginalPicturePath(track.OriginalPictureName), cancellationToken);
await RemoveFileAsync(trackConfiguration.Routes.BuildOriginalPicturePath(userId, track.OriginalPictureName), cancellationToken);
if (!track.IsPicturesProcessed)
{
@@ -83,9 +83,9 @@ namespace Musify.Infrastructure.MassTransit.Activities.Tracks
await RemoveFileAsync(trackConfiguration.Routes.BuildLargePicturePath(track.LargePictureName), cancellationToken);
}
async Task RemoveAudiosAsync(Track track, CancellationToken cancellationToken)
async Task RemoveAudiosAsync(Track track, Guid userId, CancellationToken cancellationToken)
{
await RemoveFileAsync(trackConfiguration.Routes.BuildOriginalAudioPath(track.OriginalAudioName), cancellationToken);
await RemoveFileAsync(trackConfiguration.Routes.BuildOriginalAudioPath(userId, track.OriginalAudioName), cancellationToken);
if (!track.IsAudioProcessed)
{
@@ -1,5 +1,6 @@
using MassTransit;
using Musify.Infrastructure.MassTransit.Consumers;
using Musify.Infrastructure.Persistence;
namespace Musify.Infrastructure.MassTransit
{
@@ -12,7 +13,18 @@ namespace Musify.Infrastructure.MassTransit
where TConsumer : class, IConsumer
{
busFactoryConfigurator.ReceiveEndpoint(queueName, endpointConfigurator =>
endpointConfigurator.ConfigureConsumer<TConsumer>(busRegistrationContext));
{
endpointConfigurator.UseMessageRetry(retryConfigurator =>
retryConfigurator.Exponential(
retryLimit: 5,
minInterval: TimeSpan.FromSeconds(1),
maxInterval: TimeSpan.FromSeconds(30),
intervalDelta: TimeSpan.FromSeconds(2)));
endpointConfigurator.UseEntityFrameworkOutbox<Database>(busRegistrationContext);
endpointConfigurator.ConfigureConsumer<TConsumer>(busRegistrationContext);
});
}
static void ConfigureExecuteActivityEndpoint<TActivity, TArguments>(
@@ -24,6 +36,15 @@ namespace Musify.Infrastructure.MassTransit
{
busFactoryConfigurator.ReceiveEndpoint(MassTransitEndpointNames.ExecuteQueue(endpointName), endpointConfigurator =>
{
endpointConfigurator.UseMessageRetry(retryConfigurator =>
retryConfigurator.Exponential(
retryLimit: 5,
minInterval: TimeSpan.FromSeconds(1),
maxInterval: TimeSpan.FromSeconds(30),
intervalDelta: TimeSpan.FromSeconds(2)));
endpointConfigurator.UseEntityFrameworkOutbox<Database>(busRegistrationContext);
endpointConfigurator.ExecuteActivityHost<TActivity, TArguments>(busRegistrationContext);
});
}
@@ -37,14 +58,34 @@ namespace Musify.Infrastructure.MassTransit
where TLog : class
{
busFactoryConfigurator.ReceiveEndpoint(MassTransitEndpointNames.ExecuteQueue(endpointName), endpointConfigurator =>
{
endpointConfigurator.UseMessageRetry(retryConfigurator =>
retryConfigurator.Exponential(
retryLimit: 5,
minInterval: TimeSpan.FromSeconds(1),
maxInterval: TimeSpan.FromSeconds(30),
intervalDelta: TimeSpan.FromSeconds(2)));
endpointConfigurator.UseEntityFrameworkOutbox<Database>(busRegistrationContext);
endpointConfigurator.ExecuteActivityHost<TActivity, TArguments>(
EndpointHelper.BuildCompensateActivityUri(endpointName),
busRegistrationContext)
);
busRegistrationContext);
});
busFactoryConfigurator.ReceiveEndpoint(MassTransitEndpointNames.CompensateQueue(endpointName), endpointConfigurator =>
endpointConfigurator.CompensateActivityHost<TActivity, TLog>(busRegistrationContext)
);
{
endpointConfigurator.UseMessageRetry(retryConfigurator =>
retryConfigurator.Exponential(
retryLimit: 5,
minInterval: TimeSpan.FromSeconds(1),
maxInterval: TimeSpan.FromSeconds(30),
intervalDelta: TimeSpan.FromSeconds(2)));
endpointConfigurator.UseEntityFrameworkOutbox<Database>(busRegistrationContext);
endpointConfigurator.CompensateActivityHost<TActivity, TLog>(busRegistrationContext);
});
}
}
}
@@ -3,6 +3,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Musify.Application.Abstractions.Infrastructure;
using Musify.Infrastructure.Configuration;
using Musify.Infrastructure.Persistence;
namespace Musify.Infrastructure.MassTransit
{
@@ -18,6 +19,15 @@ namespace Musify.Infrastructure.MassTransit
serviceDescriptors.AddScoped<IEventBus, MassTransitEventBus>();
serviceDescriptors.AddMassTransit(options =>
{
options.AddEntityFrameworkOutbox<Database>(outbox =>
{
outbox.UsePostgres();
outbox.UseBusOutbox();
outbox.QueryDelay = TimeSpan.FromSeconds(1);
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
});
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
ConfigureRabbitMqHost(
busFactoryConfigurator,
@@ -43,6 +53,15 @@ namespace Musify.Infrastructure.MassTransit
serviceDescriptors.AddMassTransit(options =>
{
options.AddEntityFrameworkOutbox<Database>(outbox =>
{
outbox.UsePostgres();
outbox.UseBusOutbox();
outbox.QueryDelay = TimeSpan.FromSeconds(1);
outbox.DuplicateDetectionWindow = TimeSpan.FromMinutes(30);
});
RegisterConsumersAndActivities(options);
options.UsingRabbitMq((busRegistrationContext, busFactoryConfigurator) =>
@@ -6,7 +6,7 @@ namespace Musify.Infrastructure.MassTransit
public class MassTransitEventBus(IPublishEndpoint publishEndpoint)
: IEventBus
{
public async Task PublishAsync<T>(T message, CancellationToken cancellationToken)
public async Task PublishAsync<T>(T message, CancellationToken cancellationToken) where T : class
{
ArgumentNullException.ThrowIfNull(message, nameof(message));
@@ -99,7 +99,7 @@ namespace Musify.Infrastructure.MassTransit.RoutingSlip.Builders
AddResizeActivity(
routingSlipBuilder,
ActivityNames.ResizeMedium,
ActivityNames.ResizeMedium,
ActivityNames.UploadMedium,
bucket,
RoutingSlipVariableNames.Picture.MediumResizedFilePath,
medium);
+10
View File
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using MassTransit;
using Musify.Application.Abstractions.Infrastructure;
using Musify.Domain.Entities;
using Musify.Infrastructure.Configuration;
@@ -13,6 +14,15 @@ namespace Musify.Infrastructure.Persistence
optionsBuilder.UseNpgsql(configuration.ConnectionString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.AddInboxStateEntity();
modelBuilder.AddOutboxMessageEntity();
modelBuilder.AddOutboxStateEntity();
}
public DbSet<User> Users => Set<User>();
public DbSet<Track> Tracks => Set<Track>();
@@ -0,0 +1,512 @@
// <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("20260514095113_AddMassTransitOutbox")]
partial class AddMassTransitOutbox
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.5")
.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.PlayList", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("Description")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("LargePictureName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("MediumPictureName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("OriginalPictureName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("SmallPictureName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime>("UpdatedDate")
.HasColumnType("timestamp with time zone");
b.Property<Guid>("UserId")
.HasColumnType("uuid");
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<string>("AudioFolderName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("AudioTranscodeProcessingStatus")
.HasColumnType("integer");
b.Property<DateTime>("CreatedDate")
.HasColumnType("timestamp with time zone");
b.Property<string>("LargePictureName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<DateTime?>("LastRetryAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("MediumPictureName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("NormalizedTitle")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<string>("OriginalAudioName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("OriginalPictureName")
.IsRequired()
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<int>("PicturesProcessingStatus")
.HasColumnType("integer");
b.Property<int>("RetryCount")
.HasColumnType("integer");
b.Property<string>("SmallPictureName")
.HasMaxLength(64)
.HasColumnType("character varying(64)");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("character varying(50)");
b.Property<DateTime>("UpdatedDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.ToTable("Tracks");
});
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.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
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>("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<Guid>("UserId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TrackId");
b.HasIndex("UserId");
b.ToTable("UserHasTrack");
});
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.PlayList", b =>
{
b.HasOne("Musify.Domain.Entities.User", "User")
.WithMany("PlayLists")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.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.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.PlayList", b =>
{
b.Navigation("PlayListTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.Track", b =>
{
b.Navigation("PlayListTracks");
b.Navigation("UserTracks");
});
modelBuilder.Entity("Musify.Domain.Entities.User", b =>
{
b.Navigation("PlayLists");
b.Navigation("UserTracks");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,142 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddMassTransitOutbox : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "InboxState",
columns: table => new
{
Id = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
MessageId = table.Column<Guid>(type: "uuid", nullable: false),
ConsumerId = table.Column<Guid>(type: "uuid", nullable: false),
LockId = table.Column<Guid>(type: "uuid", nullable: false),
RowVersion = table.Column<byte[]>(type: "bytea", rowVersion: true, nullable: true),
Received = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
ReceiveCount = table.Column<int>(type: "integer", nullable: false),
ExpirationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Consumed = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
Delivered = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
LastSequenceNumber = table.Column<long>(type: "bigint", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_InboxState", x => x.Id);
table.UniqueConstraint("AK_InboxState_MessageId_ConsumerId", x => new { x.MessageId, x.ConsumerId });
});
migrationBuilder.CreateTable(
name: "OutboxState",
columns: table => new
{
OutboxId = table.Column<Guid>(type: "uuid", nullable: false),
LockId = table.Column<Guid>(type: "uuid", nullable: false),
RowVersion = table.Column<byte[]>(type: "bytea", rowVersion: true, nullable: true),
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Delivered = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
LastSequenceNumber = table.Column<long>(type: "bigint", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OutboxState", x => x.OutboxId);
});
migrationBuilder.CreateTable(
name: "OutboxMessage",
columns: table => new
{
SequenceNumber = table.Column<long>(type: "bigint", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
EnqueueTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
SentTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
Headers = table.Column<string>(type: "text", nullable: true),
Properties = table.Column<string>(type: "text", nullable: true),
InboxMessageId = table.Column<Guid>(type: "uuid", nullable: true),
InboxConsumerId = table.Column<Guid>(type: "uuid", nullable: true),
OutboxId = table.Column<Guid>(type: "uuid", nullable: true),
MessageId = table.Column<Guid>(type: "uuid", nullable: false),
ContentType = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
MessageType = table.Column<string>(type: "text", nullable: false),
Body = table.Column<string>(type: "text", nullable: false),
ConversationId = table.Column<Guid>(type: "uuid", nullable: true),
CorrelationId = table.Column<Guid>(type: "uuid", nullable: true),
InitiatorId = table.Column<Guid>(type: "uuid", nullable: true),
RequestId = table.Column<Guid>(type: "uuid", nullable: true),
SourceAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
DestinationAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ResponseAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
FaultAddress = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
ExpirationTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OutboxMessage", x => x.SequenceNumber);
table.ForeignKey(
name: "FK_OutboxMessage_InboxState_InboxMessageId_InboxConsumerId",
columns: x => new { x.InboxMessageId, x.InboxConsumerId },
principalTable: "InboxState",
principalColumns: new[] { "MessageId", "ConsumerId" });
table.ForeignKey(
name: "FK_OutboxMessage_OutboxState_OutboxId",
column: x => x.OutboxId,
principalTable: "OutboxState",
principalColumn: "OutboxId");
});
migrationBuilder.CreateIndex(
name: "IX_InboxState_Delivered",
table: "InboxState",
column: "Delivered");
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_EnqueueTime",
table: "OutboxMessage",
column: "EnqueueTime");
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_ExpirationTime",
table: "OutboxMessage",
column: "ExpirationTime");
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_InboxMessageId_InboxConsumerId_SequenceNumber",
table: "OutboxMessage",
columns: new[] { "InboxMessageId", "InboxConsumerId", "SequenceNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OutboxMessage_OutboxId_SequenceNumber",
table: "OutboxMessage",
columns: new[] { "OutboxId", "SequenceNumber" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OutboxState_Created",
table: "OutboxState",
column: "Created");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "OutboxMessage");
migrationBuilder.DropTable(
name: "InboxState");
migrationBuilder.DropTable(
name: "OutboxState");
}
}
}
@@ -22,6 +22,174 @@ namespace Musify.Infrastructure.Persistence.Migrations
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.PlayList", b =>
{
b.Property<Guid>("Id")
@@ -256,6 +424,18 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.ToTable("UserHasTrack");
});
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.PlayList", b =>
{
b.HasOne("Musify.Domain.Entities.User", "User")
+24 -1
View File
@@ -48,6 +48,27 @@ namespace Musify.Infrastructure.Services
return await amazonS3.GetPreSignedURLAsync(request);
}
public async Task<string> GetUploadUrlAsync(
string bucket,
string key,
string contentType,
TimeSpan expirationTime,
CancellationToken cancellationToken)
{
var request = new GetPreSignedUrlRequest
{
BucketName = bucket,
Key = key,
Expires = DateTime.UtcNow + expirationTime,
Protocol = storageClientConfiguration.UseHttp ? Protocol.HTTP : Protocol.HTTPS,
Verb = HttpVerb.PUT,
ContentType = contentType
};
logger.LogDebug("Generating pre-signed upload URL for {Bucket}/{Key}", bucket, key);
return await amazonS3.GetPreSignedURLAsync(request);
}
public async Task RemoveFileAsync(string bucket, string key, CancellationToken cancellationToken)
{
var request = new DeleteObjectRequest
@@ -74,7 +95,9 @@ namespace Musify.Infrastructure.Services
string fileName = Path.GetFileName(file);
string filePath = Path.Combine(sourceDirectory, file);
var key = Path.Combine(route, fileName);
var key = string.Join('/', new[] { route, fileName }
.Where(static s => !string.IsNullOrWhiteSpace(s))
.Select(static s => s.Trim().Trim('/', '\\')));
var upload = new Upload
{
Id = Guid.NewGuid(),