Merge p11-w1-description-schema into dev (P11 11.G: release Description field, migration authored)
This commit is contained in:
@@ -189,6 +189,7 @@ public class TrackController : ControllerBase
|
|||||||
[FromForm] string? artist,
|
[FromForm] string? artist,
|
||||||
[FromForm] string? album,
|
[FromForm] string? album,
|
||||||
[FromForm] string? genre,
|
[FromForm] string? genre,
|
||||||
|
[FromForm] string? description,
|
||||||
[FromForm] string? releaseDate,
|
[FromForm] string? releaseDate,
|
||||||
[FromForm] string? originalFileName,
|
[FromForm] string? originalFileName,
|
||||||
[FromForm] long createdByUserId,
|
[FromForm] long createdByUserId,
|
||||||
@@ -283,6 +284,7 @@ public class TrackController : ControllerBase
|
|||||||
artist,
|
artist,
|
||||||
string.IsNullOrWhiteSpace(album) ? null : album,
|
string.IsNullOrWhiteSpace(album) ? null : album,
|
||||||
string.IsNullOrWhiteSpace(genre) ? null : genre,
|
string.IsNullOrWhiteSpace(genre) ? null : genre,
|
||||||
|
string.IsNullOrWhiteSpace(description) ? null : description,
|
||||||
parsedReleaseDate,
|
parsedReleaseDate,
|
||||||
createdByUserId,
|
createdByUserId,
|
||||||
string.IsNullOrWhiteSpace(originalFileName) ? null : originalFileName,
|
string.IsNullOrWhiteSpace(originalFileName) ? null : originalFileName,
|
||||||
@@ -412,6 +414,7 @@ public class TrackController : ControllerBase
|
|||||||
release.Artist = request.Artist;
|
release.Artist = request.Artist;
|
||||||
release.Title = request.Album ?? string.Empty;
|
release.Title = request.Album ?? string.Empty;
|
||||||
release.Genre = request.Genre;
|
release.Genre = request.Genre;
|
||||||
|
release.Description = request.Description;
|
||||||
release.ReleaseDate = request.ReleaseDate;
|
release.ReleaseDate = request.ReleaseDate;
|
||||||
|
|
||||||
// ImagePath is tri-state: null = no change, "" = clear, value = set.
|
// ImagePath is tri-state: null = no change, "" = clear, value = set.
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public record UpdateTrackMetadataRequest(
|
|||||||
string Artist,
|
string Artist,
|
||||||
string? Album,
|
string? Album,
|
||||||
string? Genre,
|
string? Genre,
|
||||||
|
string? Description,
|
||||||
DateOnly? ReleaseDate,
|
DateOnly? ReleaseDate,
|
||||||
string? ImagePath = null,
|
string? ImagePath = null,
|
||||||
ReleaseType? ReleaseType = null,
|
ReleaseType? ReleaseType = null,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ public class UnifiedTrackService
|
|||||||
string artist,
|
string artist,
|
||||||
string? album,
|
string? album,
|
||||||
string? genre,
|
string? genre,
|
||||||
|
string? description,
|
||||||
DateOnly? releaseDate,
|
DateOnly? releaseDate,
|
||||||
long createdByUserId,
|
long createdByUserId,
|
||||||
string? originalFileName,
|
string? originalFileName,
|
||||||
@@ -106,8 +107,8 @@ public class UnifiedTrackService
|
|||||||
|
|
||||||
// Resolve the release FK before persisting the track. An upload with an album lands on the
|
// Resolve the release FK before persisting the track. An upload with an album lands on the
|
||||||
// shared release (created on first sighting); an upload without one stays a loose track with
|
// shared release (created on first sighting); an upload without one stays a loose track with
|
||||||
// a null ReleaseId. Release-cardinal metadata (artist/genre/releaseDate/type/uploader) rides
|
// a null ReleaseId. Release-cardinal metadata (artist/genre/description/releaseDate/type/uploader)
|
||||||
// on the release, not the track.
|
// rides on the release, not the track.
|
||||||
long? releaseId = null;
|
long? releaseId = null;
|
||||||
if (!string.IsNullOrWhiteSpace(album))
|
if (!string.IsNullOrWhiteSpace(album))
|
||||||
{
|
{
|
||||||
@@ -116,6 +117,7 @@ public class UnifiedTrackService
|
|||||||
Title = album,
|
Title = album,
|
||||||
Artist = artist,
|
Artist = artist,
|
||||||
Genre = genre,
|
Genre = genre,
|
||||||
|
Description = description,
|
||||||
ReleaseDate = releaseDate,
|
ReleaseDate = releaseDate,
|
||||||
ReleaseType = releaseType,
|
ReleaseType = releaseType,
|
||||||
Medium = medium,
|
Medium = medium,
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ public class ReleaseConfiguration : BaseEntityConfiguration<ReleaseEntity>
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnName("genre");
|
.HasColumnName("genre");
|
||||||
|
|
||||||
|
// Plain-text prose blurb. Generous ceiling for a paragraph; nullable (no data migration).
|
||||||
|
builder.Property(e => e.Description)
|
||||||
|
.HasMaxLength(4000)
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
builder.Property(e => e.ReleaseDate)
|
builder.Property(e => e.ReleaseDate)
|
||||||
.HasColumnName("release_date");
|
.HasColumnName("release_date");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DeepDrftData.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DeepDrftData.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DeepDrftContext))]
|
||||||
|
[Migration("20260616035252_AddReleaseDescription")]
|
||||||
|
partial class AddReleaseDescription
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.MixMetadata", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_deleted");
|
||||||
|
|
||||||
|
b.Property<long>("ReleaseId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("release_id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.Property<string>("WaveformEntryKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)")
|
||||||
|
.HasColumnName("waveform_entry_key");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IsDeleted")
|
||||||
|
.HasDatabaseName("IX_mix_metadata_is_deleted");
|
||||||
|
|
||||||
|
b.HasIndex("ReleaseId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("IX_mix_metadata_release_id");
|
||||||
|
|
||||||
|
b.ToTable("mix_metadata", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.ReleaseEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Artist")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("artist");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<long?>("CreatedByUserId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("created_by_user_id");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(4000)
|
||||||
|
.HasColumnType("character varying(4000)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
|
b.Property<string>("Genre")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("genre");
|
||||||
|
|
||||||
|
b.Property<string>("ImagePath")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)")
|
||||||
|
.HasColumnName("image_path");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_deleted");
|
||||||
|
|
||||||
|
b.Property<string>("Medium")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)")
|
||||||
|
.HasDefaultValue("Cut")
|
||||||
|
.HasColumnName("medium");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("ReleaseDate")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("release_date");
|
||||||
|
|
||||||
|
b.Property<string>("ReleaseType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("character varying(20)")
|
||||||
|
.HasDefaultValue("Single")
|
||||||
|
.HasColumnName("release_type");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("title");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IsDeleted")
|
||||||
|
.HasDatabaseName("IX_release_is_deleted");
|
||||||
|
|
||||||
|
b.HasIndex("Title", "Artist")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("IX_release_title_artist")
|
||||||
|
.HasFilter("\"is_deleted\" = false");
|
||||||
|
|
||||||
|
b.ToTable("release", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.SessionMetadata", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("HeroImageEntryKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)")
|
||||||
|
.HasColumnName("hero_image_entry_key");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_deleted");
|
||||||
|
|
||||||
|
b.Property<long>("ReleaseId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("release_id");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IsDeleted")
|
||||||
|
.HasDatabaseName("IX_session_metadata_is_deleted");
|
||||||
|
|
||||||
|
b.HasIndex("ReleaseId")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("IX_session_metadata_release_id");
|
||||||
|
|
||||||
|
b.ToTable("session_metadata", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<string>("EntryKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("entry_key");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(false)
|
||||||
|
.HasColumnName("is_deleted");
|
||||||
|
|
||||||
|
b.Property<string>("OriginalFileName")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)")
|
||||||
|
.HasColumnName("original_file_name");
|
||||||
|
|
||||||
|
b.Property<long?>("ReleaseId")
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("release_id");
|
||||||
|
|
||||||
|
b.Property<string>("TrackName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("track_name");
|
||||||
|
|
||||||
|
b.Property<int>("TrackNumber")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasDefaultValue(1)
|
||||||
|
.HasColumnName("track_number");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IsDeleted")
|
||||||
|
.HasDatabaseName("IX_track_is_deleted");
|
||||||
|
|
||||||
|
b.HasIndex("ReleaseId");
|
||||||
|
|
||||||
|
b.ToTable("track", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.MixMetadata", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DeepDrftModels.Entities.ReleaseEntity", "Release")
|
||||||
|
.WithOne("MixMetadata")
|
||||||
|
.HasForeignKey("DeepDrftModels.Entities.MixMetadata", "ReleaseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Release");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.SessionMetadata", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DeepDrftModels.Entities.ReleaseEntity", "Release")
|
||||||
|
.WithOne("SessionMetadata")
|
||||||
|
.HasForeignKey("DeepDrftModels.Entities.SessionMetadata", "ReleaseId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Release");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.TrackEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DeepDrftModels.Entities.ReleaseEntity", "Release")
|
||||||
|
.WithMany("Tracks")
|
||||||
|
.HasForeignKey("ReleaseId")
|
||||||
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
b.Navigation("Release");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DeepDrftModels.Entities.ReleaseEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("MixMetadata");
|
||||||
|
|
||||||
|
b.Navigation("SessionMetadata");
|
||||||
|
|
||||||
|
b.Navigation("Tracks");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DeepDrftData.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddReleaseDescription : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "description",
|
||||||
|
table: "release",
|
||||||
|
type: "character varying(4000)",
|
||||||
|
maxLength: 4000,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "description",
|
||||||
|
table: "release");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,6 +90,11 @@ namespace DeepDrftData.Migrations
|
|||||||
.HasColumnType("bigint")
|
.HasColumnType("bigint")
|
||||||
.HasColumnName("created_by_user_id");
|
.HasColumnName("created_by_user_id");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasMaxLength(4000)
|
||||||
|
.HasColumnType("character varying(4000)")
|
||||||
|
.HasColumnName("description");
|
||||||
|
|
||||||
b.Property<string>("Genre")
|
b.Property<string>("Genre")
|
||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("character varying(100)")
|
.HasColumnType("character varying(100)")
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ public class TrackConverter : IEntityToModelConverter<TrackEntity, TrackDto>
|
|||||||
Title = entity.Title,
|
Title = entity.Title,
|
||||||
Artist = entity.Artist,
|
Artist = entity.Artist,
|
||||||
Genre = entity.Genre,
|
Genre = entity.Genre,
|
||||||
|
Description = entity.Description,
|
||||||
ReleaseDate = entity.ReleaseDate,
|
ReleaseDate = entity.ReleaseDate,
|
||||||
ImagePath = entity.ImagePath,
|
ImagePath = entity.ImagePath,
|
||||||
Medium = entity.Medium,
|
Medium = entity.Medium,
|
||||||
@@ -55,6 +56,7 @@ public class TrackConverter : IEntityToModelConverter<TrackEntity, TrackDto>
|
|||||||
Title = dto.Title,
|
Title = dto.Title,
|
||||||
Artist = dto.Artist,
|
Artist = dto.Artist,
|
||||||
Genre = dto.Genre,
|
Genre = dto.Genre,
|
||||||
|
Description = dto.Description,
|
||||||
ReleaseDate = dto.ReleaseDate,
|
ReleaseDate = dto.ReleaseDate,
|
||||||
ImagePath = dto.ImagePath,
|
ImagePath = dto.ImagePath,
|
||||||
Medium = dto.Medium,
|
Medium = dto.Medium,
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ public class TrackManager
|
|||||||
releaseEntity.Title = release.Title;
|
releaseEntity.Title = release.Title;
|
||||||
releaseEntity.Artist = release.Artist;
|
releaseEntity.Artist = release.Artist;
|
||||||
releaseEntity.Genre = release.Genre;
|
releaseEntity.Genre = release.Genre;
|
||||||
|
releaseEntity.Description = release.Description;
|
||||||
releaseEntity.ReleaseDate = release.ReleaseDate;
|
releaseEntity.ReleaseDate = release.ReleaseDate;
|
||||||
releaseEntity.ImagePath = release.ImagePath;
|
releaseEntity.ImagePath = release.ImagePath;
|
||||||
releaseEntity.Medium = release.Medium;
|
releaseEntity.Medium = release.Medium;
|
||||||
|
|||||||
@@ -22,6 +22,11 @@
|
|||||||
T="string" Label="Release Date (YYYY-MM-DD)" Placeholder="2024-01-15"
|
T="string" Label="Release Date (YYYY-MM-DD)" Placeholder="2024-01-15"
|
||||||
Variant="Variant.Outlined" Disabled="Disabled" />
|
Variant="Variant.Outlined" Disabled="Disabled" />
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
<MudItem xs="12">
|
||||||
|
<MudTextField Value="Description" ValueChanged="@((string v) => DescriptionChanged.InvokeAsync(v))"
|
||||||
|
T="string" Label="Description" Lines="4" MaxLength="4000"
|
||||||
|
Variant="Variant.Outlined" Disabled="Disabled" />
|
||||||
|
</MudItem>
|
||||||
<MudItem xs="12" sm="6">
|
<MudItem xs="12" sm="6">
|
||||||
<MudField Label="Cover Art" Variant="Variant.Outlined" InnerPadding="false">
|
<MudField Label="Cover Art" Variant="Variant.Outlined" InnerPadding="false">
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
@@ -79,6 +84,8 @@
|
|||||||
[Parameter] public EventCallback<string> ArtistChanged { get; set; }
|
[Parameter] public EventCallback<string> ArtistChanged { get; set; }
|
||||||
[Parameter] public string Genre { get; set; } = string.Empty;
|
[Parameter] public string Genre { get; set; } = string.Empty;
|
||||||
[Parameter] public EventCallback<string> GenreChanged { get; set; }
|
[Parameter] public EventCallback<string> GenreChanged { get; set; }
|
||||||
|
[Parameter] public string Description { get; set; } = string.Empty;
|
||||||
|
[Parameter] public EventCallback<string> DescriptionChanged { get; set; }
|
||||||
[Parameter] public string ReleaseDate { get; set; } = string.Empty;
|
[Parameter] public string ReleaseDate { get; set; } = string.Empty;
|
||||||
[Parameter] public EventCallback<string> ReleaseDateChanged { get; set; }
|
[Parameter] public EventCallback<string> ReleaseDateChanged { get; set; }
|
||||||
[Parameter] public ReleaseType ReleaseType { get; set; } = ReleaseType.Single;
|
[Parameter] public ReleaseType ReleaseType { get; set; } = ReleaseType.Single;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<AlbumHeaderFields @bind-AlbumName="_albumName"
|
<AlbumHeaderFields @bind-AlbumName="_albumName"
|
||||||
@bind-Artist="_artist"
|
@bind-Artist="_artist"
|
||||||
@bind-Genre="_genre"
|
@bind-Genre="_genre"
|
||||||
|
@bind-Description="_description"
|
||||||
@bind-ReleaseDate="_releaseDate"
|
@bind-ReleaseDate="_releaseDate"
|
||||||
@bind-ReleaseType="_releaseType"
|
@bind-ReleaseType="_releaseType"
|
||||||
Medium="_medium"
|
Medium="_medium"
|
||||||
@@ -137,6 +138,7 @@
|
|||||||
private string _albumName = string.Empty;
|
private string _albumName = string.Empty;
|
||||||
private string _artist = string.Empty;
|
private string _artist = string.Empty;
|
||||||
private string _genre = string.Empty;
|
private string _genre = string.Empty;
|
||||||
|
private string _description = string.Empty;
|
||||||
private string _releaseDate = string.Empty;
|
private string _releaseDate = string.Empty;
|
||||||
private ReleaseType _releaseType = ReleaseType.Single;
|
private ReleaseType _releaseType = ReleaseType.Single;
|
||||||
private ReleaseMedium _medium = ReleaseMedium.Cut;
|
private ReleaseMedium _medium = ReleaseMedium.Cut;
|
||||||
@@ -211,6 +213,7 @@
|
|||||||
_albumName = albumName;
|
_albumName = albumName;
|
||||||
_artist = release?.Artist ?? string.Empty;
|
_artist = release?.Artist ?? string.Empty;
|
||||||
_genre = release?.Genre ?? string.Empty;
|
_genre = release?.Genre ?? string.Empty;
|
||||||
|
_description = release?.Description ?? string.Empty;
|
||||||
_releaseDate = release?.ReleaseDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
_releaseDate = release?.ReleaseDate?.ToString("yyyy-MM-dd") ?? string.Empty;
|
||||||
_releaseType = release?.ReleaseType ?? ReleaseType.Single;
|
_releaseType = release?.ReleaseType ?? ReleaseType.Single;
|
||||||
_medium = release?.Medium ?? ReleaseMedium.Cut;
|
_medium = release?.Medium ?? ReleaseMedium.Cut;
|
||||||
@@ -382,6 +385,7 @@
|
|||||||
: DateOnly.ParseExact(_releaseDate, "yyyy-MM-dd");
|
: DateOnly.ParseExact(_releaseDate, "yyyy-MM-dd");
|
||||||
var album = string.IsNullOrWhiteSpace(_albumName) ? null : _albumName;
|
var album = string.IsNullOrWhiteSpace(_albumName) ? null : _albumName;
|
||||||
var genre = string.IsNullOrWhiteSpace(_genre) ? null : _genre;
|
var genre = string.IsNullOrWhiteSpace(_genre) ? null : _genre;
|
||||||
|
var description = string.IsNullOrWhiteSpace(_description) ? null : _description;
|
||||||
|
|
||||||
// For single-track media (Session/Mix) the track name is derived from the Release Name —
|
// For single-track media (Session/Mix) the track name is derived from the Release Name —
|
||||||
// no separate Track Name editor is shown. Sync here so changes to the Release Name always
|
// no separate Track Name editor is shown. Sync here so changes to the Release Name always
|
||||||
@@ -446,6 +450,7 @@
|
|||||||
_artist,
|
_artist,
|
||||||
album,
|
album,
|
||||||
genre,
|
genre,
|
||||||
|
description,
|
||||||
releaseDate,
|
releaseDate,
|
||||||
imagePathForUpdate,
|
imagePathForUpdate,
|
||||||
_releaseType,
|
_releaseType,
|
||||||
@@ -480,6 +485,7 @@
|
|||||||
_artist,
|
_artist,
|
||||||
album,
|
album,
|
||||||
genre,
|
genre,
|
||||||
|
description,
|
||||||
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
|
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
|
||||||
row.WavFile.Name,
|
row.WavFile.Name,
|
||||||
createdByUserId,
|
createdByUserId,
|
||||||
@@ -508,6 +514,7 @@
|
|||||||
_artist,
|
_artist,
|
||||||
album,
|
album,
|
||||||
genre,
|
genre,
|
||||||
|
description,
|
||||||
releaseDate,
|
releaseDate,
|
||||||
linkPath,
|
linkPath,
|
||||||
_releaseType,
|
_releaseType,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
<AlbumHeaderFields @bind-AlbumName="_albumName"
|
<AlbumHeaderFields @bind-AlbumName="_albumName"
|
||||||
@bind-Artist="_artist"
|
@bind-Artist="_artist"
|
||||||
@bind-Genre="_genre"
|
@bind-Genre="_genre"
|
||||||
|
@bind-Description="_description"
|
||||||
@bind-ReleaseDate="_releaseDate"
|
@bind-ReleaseDate="_releaseDate"
|
||||||
@bind-ReleaseType="_releaseType"
|
@bind-ReleaseType="_releaseType"
|
||||||
Medium="_medium"
|
Medium="_medium"
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
private string _albumName = string.Empty;
|
private string _albumName = string.Empty;
|
||||||
private string _artist = string.Empty;
|
private string _artist = string.Empty;
|
||||||
private string _genre = string.Empty;
|
private string _genre = string.Empty;
|
||||||
|
private string _description = string.Empty;
|
||||||
private string _releaseDate = string.Empty;
|
private string _releaseDate = string.Empty;
|
||||||
private ReleaseType _releaseType = ReleaseType.Single;
|
private ReleaseType _releaseType = ReleaseType.Single;
|
||||||
private ReleaseMedium _medium = ReleaseMedium.Cut;
|
private ReleaseMedium _medium = ReleaseMedium.Cut;
|
||||||
@@ -342,6 +344,7 @@
|
|||||||
_artist,
|
_artist,
|
||||||
string.IsNullOrWhiteSpace(_albumName) ? null : _albumName,
|
string.IsNullOrWhiteSpace(_albumName) ? null : _albumName,
|
||||||
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
|
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
|
||||||
|
string.IsNullOrWhiteSpace(_description) ? null : _description,
|
||||||
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
|
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
|
||||||
row.WavFile.Name,
|
row.WavFile.Name,
|
||||||
createdByUserId,
|
createdByUserId,
|
||||||
@@ -369,6 +372,7 @@
|
|||||||
_artist,
|
_artist,
|
||||||
string.IsNullOrWhiteSpace(_albumName) ? null : _albumName,
|
string.IsNullOrWhiteSpace(_albumName) ? null : _albumName,
|
||||||
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
|
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
|
||||||
|
string.IsNullOrWhiteSpace(_description) ? null : _description,
|
||||||
string.IsNullOrWhiteSpace(_releaseDate) ? null : (DateOnly?)DateOnly.ParseExact(_releaseDate, "yyyy-MM-dd"),
|
string.IsNullOrWhiteSpace(_releaseDate) ? null : (DateOnly?)DateOnly.ParseExact(_releaseDate, "yyyy-MM-dd"),
|
||||||
imgPath,
|
imgPath,
|
||||||
_releaseType,
|
_releaseType,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
string artist,
|
string artist,
|
||||||
string? album,
|
string? album,
|
||||||
string? genre,
|
string? genre,
|
||||||
|
string? description,
|
||||||
string? releaseDate,
|
string? releaseDate,
|
||||||
string? originalFileName,
|
string? originalFileName,
|
||||||
long createdByUserId,
|
long createdByUserId,
|
||||||
@@ -58,6 +59,7 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
multipart.Add(new StringContent(artist), "artist");
|
multipart.Add(new StringContent(artist), "artist");
|
||||||
if (!string.IsNullOrWhiteSpace(album)) multipart.Add(new StringContent(album), "album");
|
if (!string.IsNullOrWhiteSpace(album)) multipart.Add(new StringContent(album), "album");
|
||||||
if (!string.IsNullOrWhiteSpace(genre)) multipart.Add(new StringContent(genre), "genre");
|
if (!string.IsNullOrWhiteSpace(genre)) multipart.Add(new StringContent(genre), "genre");
|
||||||
|
if (!string.IsNullOrWhiteSpace(description)) multipart.Add(new StringContent(description), "description");
|
||||||
if (!string.IsNullOrWhiteSpace(releaseDate)) multipart.Add(new StringContent(releaseDate), "releaseDate");
|
if (!string.IsNullOrWhiteSpace(releaseDate)) multipart.Add(new StringContent(releaseDate), "releaseDate");
|
||||||
// Explicit field — decouples the admin-visible display name from the WAV part's content-disposition filename.
|
// Explicit field — decouples the admin-visible display name from the WAV part's content-disposition filename.
|
||||||
if (!string.IsNullOrWhiteSpace(originalFileName)) multipart.Add(new StringContent(originalFileName), "originalFileName");
|
if (!string.IsNullOrWhiteSpace(originalFileName)) multipart.Add(new StringContent(originalFileName), "originalFileName");
|
||||||
@@ -371,7 +373,7 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
|
|
||||||
public async Task<Result> UpdateAsync(
|
public async Task<Result> UpdateAsync(
|
||||||
long id, string trackName, string artist,
|
long id, string trackName, string artist,
|
||||||
string? album, string? genre, DateOnly? releaseDate,
|
string? album, string? genre, string? description, DateOnly? releaseDate,
|
||||||
string? imagePath = null,
|
string? imagePath = null,
|
||||||
ReleaseType? releaseType = null,
|
ReleaseType? releaseType = null,
|
||||||
ReleaseMedium? medium = null,
|
ReleaseMedium? medium = null,
|
||||||
@@ -385,6 +387,7 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
artist,
|
artist,
|
||||||
album,
|
album,
|
||||||
genre,
|
genre,
|
||||||
|
description,
|
||||||
releaseDate,
|
releaseDate,
|
||||||
imagePath,
|
imagePath,
|
||||||
releaseType = releaseType.HasValue ? (int?)releaseType.Value : null,
|
releaseType = releaseType.HasValue ? (int?)releaseType.Value : null,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public interface ICmsTrackService
|
|||||||
string artist,
|
string artist,
|
||||||
string? album,
|
string? album,
|
||||||
string? genre,
|
string? genre,
|
||||||
|
string? description,
|
||||||
string? releaseDate,
|
string? releaseDate,
|
||||||
string? originalFileName,
|
string? originalFileName,
|
||||||
long createdByUserId,
|
long createdByUserId,
|
||||||
@@ -85,7 +86,7 @@ public interface ICmsTrackService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Task<Result> UpdateAsync(
|
Task<Result> UpdateAsync(
|
||||||
long id, string trackName, string artist,
|
long id, string trackName, string artist,
|
||||||
string? album, string? genre, DateOnly? releaseDate,
|
string? album, string? genre, string? description, DateOnly? releaseDate,
|
||||||
string? imagePath = null,
|
string? imagePath = null,
|
||||||
ReleaseType? releaseType = null,
|
ReleaseType? releaseType = null,
|
||||||
ReleaseMedium? medium = null,
|
ReleaseMedium? medium = null,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class ReleaseDto : BaseModel
|
|||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
public string Artist { get; set; } = string.Empty;
|
public string Artist { get; set; } = string.Empty;
|
||||||
public string? Genre { get; set; }
|
public string? Genre { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
public DateOnly? ReleaseDate { get; set; }
|
public DateOnly? ReleaseDate { get; set; }
|
||||||
public string? ImagePath { get; set; }
|
public string? ImagePath { get; set; }
|
||||||
public ReleaseMedium Medium { get; set; } = ReleaseMedium.Cut;
|
public ReleaseMedium Medium { get; set; } = ReleaseMedium.Cut;
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public class ReleaseEntity : BaseEntity, IEntity
|
|||||||
public required string Title { get; set; }
|
public required string Title { get; set; }
|
||||||
public required string Artist { get; set; }
|
public required string Artist { get; set; }
|
||||||
public string? Genre { get; set; }
|
public string? Genre { get; set; }
|
||||||
|
// Free-text prose blurb describing the release. Uniform across media (Cut/Session/Mix), so it
|
||||||
|
// lives on the base table alongside Genre rather than in a per-medium satellite. Plain text,
|
||||||
|
// max 4000 (configured in ReleaseConfiguration); nullable so existing rows migrate as NULL.
|
||||||
|
public string? Description { get; set; }
|
||||||
public DateOnly? ReleaseDate { get; set; }
|
public DateOnly? ReleaseDate { get; set; }
|
||||||
public string? ImagePath { get; set; }
|
public string? ImagePath { get; set; }
|
||||||
public ReleaseType ReleaseType { get; set; } = ReleaseType.Single;
|
public ReleaseType ReleaseType { get; set; } = ReleaseType.Single;
|
||||||
|
|||||||
@@ -161,6 +161,82 @@ public class MediumWritePathTests
|
|||||||
Assert.That(dto.ReleaseType, Is.Null);
|
Assert.That(dto.ReleaseType, Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 11.G — Description round-trips through both converter directions verbatim (no medium dance,
|
||||||
|
// unlike ReleaseType): entity → DTO preserves the prose, and DTO → entity carries it back.
|
||||||
|
[Test]
|
||||||
|
public void Convert_Description_RoundTripsBothDirections()
|
||||||
|
{
|
||||||
|
const string prose = "A late-night set\nrecorded at the Vault.";
|
||||||
|
var entity = new ReleaseEntity
|
||||||
|
{
|
||||||
|
Title = "Live at the Vault", Artist = "Artist A",
|
||||||
|
Medium = ReleaseMedium.Session, Description = prose,
|
||||||
|
};
|
||||||
|
|
||||||
|
var dto = TrackConverter.Convert(entity);
|
||||||
|
Assert.That(dto.Description, Is.EqualTo(prose), "entity → DTO preserves Description");
|
||||||
|
|
||||||
|
var back = TrackConverter.Convert(dto);
|
||||||
|
Assert.That(back.Description, Is.EqualTo(prose), "DTO → entity preserves Description");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11.G — a null Description round-trips as null in both directions (existing rows migrate as NULL).
|
||||||
|
[Test]
|
||||||
|
public void Convert_NullDescription_RoundTripsAsNull()
|
||||||
|
{
|
||||||
|
var entity = new ReleaseEntity { Title = "Studio Album", Artist = "Artist C", Description = null };
|
||||||
|
|
||||||
|
var dto = TrackConverter.Convert(entity);
|
||||||
|
Assert.That(dto.Description, Is.Null);
|
||||||
|
|
||||||
|
var back = TrackConverter.Convert(dto);
|
||||||
|
Assert.That(back.Description, Is.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11.G — Description rides the release-cardinal write channel onto the persisted release row,
|
||||||
|
// exactly as Genre does. FindOrCreateRelease is the upload-path projection point.
|
||||||
|
[Test]
|
||||||
|
public async Task FindOrCreateRelease_NewRelease_PersistsDescription()
|
||||||
|
{
|
||||||
|
const string prose = "Three cuts pressed for the summer.";
|
||||||
|
var manager = CreateManager(CreateRepository());
|
||||||
|
|
||||||
|
var data = ReleaseData("Studio Album", "Artist C", ReleaseMedium.Cut);
|
||||||
|
data.Description = prose;
|
||||||
|
|
||||||
|
var result = await manager.FindOrCreateRelease("Studio Album", "Artist C", data);
|
||||||
|
|
||||||
|
Assert.That(result.Success, Is.True);
|
||||||
|
Assert.That(result.Value!.Description, Is.EqualTo(prose));
|
||||||
|
|
||||||
|
var stored = await CreateRepository().GetReleaseByIdAsync(result.Value.Id);
|
||||||
|
Assert.That(stored!.Description, Is.EqualTo(prose));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11.C — editing a track's linked release sets the Description on the persisted release row,
|
||||||
|
// mirroring the PUT api/track/meta apply (release.Description = request.Description).
|
||||||
|
[Test]
|
||||||
|
public async Task Update_SetsReleaseDescription_PersistsDescription()
|
||||||
|
{
|
||||||
|
const string prose = "Now with a proper blurb.";
|
||||||
|
var repo = CreateRepository();
|
||||||
|
ITrackService manager = CreateManager(repo);
|
||||||
|
|
||||||
|
var release = new ReleaseEntity { Title = "Studio Album", Artist = "Artist C", Medium = ReleaseMedium.Cut };
|
||||||
|
var track = new TrackEntity { EntryKey = "ek-1", TrackName = "Track", Release = release };
|
||||||
|
_context.Tracks.Add(track);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var loaded = (await manager.GetById(track.Id)).Value!;
|
||||||
|
loaded.Release!.Description = prose;
|
||||||
|
|
||||||
|
var result = await manager.Update(loaded);
|
||||||
|
Assert.That(result.Success, Is.True);
|
||||||
|
|
||||||
|
var stored = await CreateRepository().GetReleaseByIdAsync(release.Id);
|
||||||
|
Assert.That(stored!.Description, Is.EqualTo(prose));
|
||||||
|
}
|
||||||
|
|
||||||
// 9.5.C — releaseId filter returns only the tracks of the given release. Built on the repository
|
// 9.5.C — releaseId filter returns only the tracks of the given release. Built on the repository
|
||||||
// directly to assert the WHERE release_id predicate in isolation.
|
// directly to assert the WHERE release_id predicate in isolation.
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
Reference in New Issue
Block a user