Checkpoint web-player design-system refactor and backend track genre tagging

Advances the ongoing token/componentization cleanup (anchored player dock,
border-based panels instead of floating shadows, corrected light-mode
accent/gradient tokens) alongside in-progress backend support for track
genre tags.
This commit is contained in:
2026-09-17 17:43:37 +02:00
parent 5ae9955bd0
commit c14db5a711
83 changed files with 1872 additions and 248 deletions
@@ -15,7 +15,7 @@ namespace Musify.Infrastructure.Persistence
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(configuration.ConnectionString);
optionsBuilder.UseNpgsql(configuration.ConnectionString, o => o.MapEnum<Genre>("genre"));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -128,6 +128,16 @@ namespace Musify.Infrastructure.Persistence
.HasIndex(like => new { like.UserId, like.TrackId })
.IsUnique();
modelBuilder.Entity<TrackTag>()
.HasOne(trackTag => trackTag.Track)
.WithMany(track => track.Tags)
.HasForeignKey(trackTag => trackTag.TrackId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<TrackTag>()
.HasIndex(trackTag => new { trackTag.TrackId, trackTag.Tag })
.IsUnique();
modelBuilder.Entity<UserFollow>()
.HasOne(follow => follow.Follower)
.WithMany()
@@ -181,6 +191,8 @@ namespace Musify.Infrastructure.Persistence
public DbSet<TrackLike> TrackLikes => Set<TrackLike>();
public DbSet<TrackTag> TrackTags => Set<TrackTag>();
public DbSet<UserFollow> UserFollows => Set<UserFollow>();
public async Task<IDatabaseTransaction> BeginTransactionAsync(System.Data.IsolationLevel isolationLevel, CancellationToken cancellationToken)
@@ -0,0 +1,54 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Musify.Domain.ValueObjects;
#nullable disable
namespace Musify.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddTrackTags : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:genre", "ambient,blues,classical,country,drum_and_bass,dubstep,electronic,folk,funk,hip_hop,house,indie,jazz,k_pop,latin,lofi,metal,pop,punk,reggae,reggaeton,rn_b,rock,soul,techno,trance");
migrationBuilder.CreateTable(
name: "TrackTags",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
TrackId = table.Column<Guid>(type: "uuid", nullable: false),
Tag = table.Column<Genre>(type: "genre", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_TrackTags", x => x.Id);
table.ForeignKey(
name: "FK_TrackTags_Tracks_TrackId",
column: x => x.TrackId,
principalTable: "Tracks",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_TrackTags_TrackId_Tag",
table: "TrackTags",
columns: new[] { "TrackId", "Tag" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "TrackTags");
migrationBuilder.AlterDatabase()
.OldAnnotation("Npgsql:Enum:genre", "ambient,blues,classical,country,drum_and_bass,dubstep,electronic,folk,funk,hip_hop,house,indie,jazz,k_pop,latin,lofi,metal,pop,punk,reggae,reggaeton,rn_b,rock,soul,techno,trance");
}
}
}
@@ -3,6 +3,7 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Musify.Domain.ValueObjects;
using Musify.Infrastructure.Persistence;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
@@ -20,6 +21,7 @@ namespace Musify.Infrastructure.Persistence.Migrations
.HasAnnotation("ProductVersion", "10.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "ambient", "blues", "classical", "country", "drum_and_bass", "dubstep", "electronic", "folk", "funk", "hip_hop", "house", "indie", "jazz", "k_pop", "latin", "lofi", "metal", "pop", "punk", "reggae", "reggaeton", "rn_b", "rock", "soul", "techno", "trance" });
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("MassTransit.EntityFrameworkCoreIntegration.InboxState", b =>
@@ -476,6 +478,26 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.ToTable("TrackLikes");
});
modelBuilder.Entity("Musify.Domain.Entities.TrackTag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Genre>("Tag")
.HasColumnType("genre");
b.Property<Guid>("TrackId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("TrackId", "Tag")
.IsUnique();
b.ToTable("TrackTags");
});
modelBuilder.Entity("Musify.Domain.Entities.Upload", b =>
{
b.Property<Guid>("Id")
@@ -1041,6 +1063,17 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("User");
});
modelBuilder.Entity("Musify.Domain.Entities.TrackTag", b =>
{
b.HasOne("Musify.Domain.Entities.Track", "Track")
.WithMany("Tags")
.HasForeignKey("TrackId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Track");
});
modelBuilder.Entity("Musify.Domain.Entities.UploadIntent", b =>
{
b.HasOne("Musify.Domain.Entities.User", "User")
@@ -1113,6 +1146,8 @@ namespace Musify.Infrastructure.Persistence.Migrations
b.Navigation("PlayListTracks");
b.Navigation("Tags");
b.Navigation("UserTracks");
});