feat: capture and display original upload filename for tracks
This commit is contained in:
@@ -124,11 +124,12 @@ public class TrackController : ControllerBase
|
|||||||
[FromForm] string? album,
|
[FromForm] string? album,
|
||||||
[FromForm] string? genre,
|
[FromForm] string? genre,
|
||||||
[FromForm] string? releaseDate,
|
[FromForm] string? releaseDate,
|
||||||
|
[FromForm] string? originalFileName,
|
||||||
[FromForm] long createdByUserId,
|
[FromForm] long createdByUserId,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("UploadTrack called: trackName={TrackName}, artist={Artist}, size={Size}",
|
_logger.LogInformation("UploadTrack called: trackName={TrackName}, artist={Artist}, fileName={FileName}, size={Size}",
|
||||||
trackName, artist, wav?.Length);
|
trackName, artist, originalFileName, wav?.Length);
|
||||||
|
|
||||||
if (wav is null || wav.Length == 0)
|
if (wav is null || wav.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -182,6 +183,7 @@ public class TrackController : ControllerBase
|
|||||||
string.IsNullOrWhiteSpace(genre) ? null : genre,
|
string.IsNullOrWhiteSpace(genre) ? null : genre,
|
||||||
parsedReleaseDate,
|
parsedReleaseDate,
|
||||||
createdByUserId,
|
createdByUserId,
|
||||||
|
string.IsNullOrWhiteSpace(originalFileName) ? null : originalFileName,
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
||||||
if (!result.Success || result.Value is null)
|
if (!result.Success || result.Value is null)
|
||||||
|
|||||||
@@ -49,10 +49,11 @@ public class UnifiedTrackService
|
|||||||
string? genre,
|
string? genre,
|
||||||
DateOnly? releaseDate,
|
DateOnly? releaseDate,
|
||||||
long createdByUserId,
|
long createdByUserId,
|
||||||
|
string? originalFileName,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
var unpersisted = await _contentTrackContentService.AddTrackFromWavAsync(
|
var unpersisted = await _contentTrackContentService.AddTrackFromWavAsync(
|
||||||
tempFilePath, trackName, artist, album, genre, releaseDate);
|
tempFilePath, trackName, artist, album, genre, releaseDate, originalFileName: originalFileName);
|
||||||
|
|
||||||
if (unpersisted is null)
|
if (unpersisted is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class TrackContentService
|
|||||||
/// <param name="album">Optional album name</param>
|
/// <param name="album">Optional album name</param>
|
||||||
/// <param name="genre">Optional genre</param>
|
/// <param name="genre">Optional genre</param>
|
||||||
/// <param name="releaseDate">Optional release date</param>
|
/// <param name="releaseDate">Optional release date</param>
|
||||||
|
/// <param name="originalFileName">Optional original browser filename captured at upload time</param>
|
||||||
/// <returns>The track entity with generated ID and media path</returns>
|
/// <returns>The track entity with generated ID and media path</returns>
|
||||||
public async Task<TrackEntity?> AddTrackFromWavAsync(
|
public async Task<TrackEntity?> AddTrackFromWavAsync(
|
||||||
string wavFilePath,
|
string wavFilePath,
|
||||||
@@ -36,7 +37,8 @@ public class TrackContentService
|
|||||||
string artist,
|
string artist,
|
||||||
string? album = null,
|
string? album = null,
|
||||||
string? genre = null,
|
string? genre = null,
|
||||||
DateOnly? releaseDate = null)
|
DateOnly? releaseDate = null,
|
||||||
|
string? originalFileName = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -71,7 +73,8 @@ public class TrackContentService
|
|||||||
Artist = artist,
|
Artist = artist,
|
||||||
Album = album,
|
Album = album,
|
||||||
Genre = genre,
|
Genre = genre,
|
||||||
ReleaseDate = releaseDate
|
ReleaseDate = releaseDate,
|
||||||
|
OriginalFileName = originalFileName
|
||||||
};
|
};
|
||||||
|
|
||||||
return trackEntity;
|
return trackEntity;
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ public class TrackConfiguration : BaseEntityConfiguration<TrackEntity>
|
|||||||
builder.Property(e => e.CreatedByUserId)
|
builder.Property(e => e.CreatedByUserId)
|
||||||
.HasColumnName("created_by_user_id");
|
.HasColumnName("created_by_user_id");
|
||||||
|
|
||||||
|
builder.Property(e => e.OriginalFileName)
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnName("original_file_name");
|
||||||
|
|
||||||
// Names the is_deleted index explicitly. BaseEntityConfiguration.Configure already
|
// Names the is_deleted index explicitly. BaseEntityConfiguration.Configure already
|
||||||
// calls HasIndex(e => e.IsDeleted); this adds HasDatabaseName so EF always uses
|
// calls HasIndex(e => e.IsDeleted); this adds HasDatabaseName so EF always uses
|
||||||
// "IX_track_is_deleted" regardless of auto-naming conventions.
|
// "IX_track_is_deleted" regardless of auto-naming conventions.
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// <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("20260607124422_AddOriginalFileName")]
|
||||||
|
partial class AddOriginalFileName
|
||||||
|
{
|
||||||
|
/// <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.TrackEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Album")
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("album");
|
||||||
|
|
||||||
|
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>("EntryKey")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)")
|
||||||
|
.HasColumnName("entry_key");
|
||||||
|
|
||||||
|
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>("OriginalFileName")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)")
|
||||||
|
.HasColumnName("original_file_name");
|
||||||
|
|
||||||
|
b.Property<DateOnly?>("ReleaseDate")
|
||||||
|
.HasColumnType("date")
|
||||||
|
.HasColumnName("release_date");
|
||||||
|
|
||||||
|
b.Property<string>("TrackName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("character varying(200)")
|
||||||
|
.HasColumnName("track_name");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("IsDeleted")
|
||||||
|
.HasDatabaseName("IX_track_is_deleted");
|
||||||
|
|
||||||
|
b.ToTable("track", (string)null);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DeepDrftData.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddOriginalFileName : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "original_file_name",
|
||||||
|
table: "track",
|
||||||
|
type: "character varying(500)",
|
||||||
|
maxLength: 500,
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "original_file_name",
|
||||||
|
table: "track");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,6 +72,11 @@ namespace DeepDrftData.Migrations
|
|||||||
.HasDefaultValue(false)
|
.HasDefaultValue(false)
|
||||||
.HasColumnName("is_deleted");
|
.HasColumnName("is_deleted");
|
||||||
|
|
||||||
|
b.Property<string>("OriginalFileName")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("character varying(500)")
|
||||||
|
.HasColumnName("original_file_name");
|
||||||
|
|
||||||
b.Property<DateOnly?>("ReleaseDate")
|
b.Property<DateOnly?>("ReleaseDate")
|
||||||
.HasColumnType("date")
|
.HasColumnType("date")
|
||||||
.HasColumnName("release_date");
|
.HasColumnName("release_date");
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ public class TrackConverter : IEntityToModelConverter<TrackEntity, TrackDto>
|
|||||||
Genre = entity.Genre,
|
Genre = entity.Genre,
|
||||||
ReleaseDate = entity.ReleaseDate,
|
ReleaseDate = entity.ReleaseDate,
|
||||||
ImagePath = entity.ImagePath,
|
ImagePath = entity.ImagePath,
|
||||||
CreatedByUserId = entity.CreatedByUserId
|
CreatedByUserId = entity.CreatedByUserId,
|
||||||
|
OriginalFileName = entity.OriginalFileName
|
||||||
};
|
};
|
||||||
|
|
||||||
public static TrackEntity Convert(TrackDto model) => new()
|
public static TrackEntity Convert(TrackDto model) => new()
|
||||||
@@ -39,6 +40,7 @@ public class TrackConverter : IEntityToModelConverter<TrackEntity, TrackDto>
|
|||||||
Genre = model.Genre,
|
Genre = model.Genre,
|
||||||
ReleaseDate = model.ReleaseDate,
|
ReleaseDate = model.ReleaseDate,
|
||||||
ImagePath = model.ImagePath,
|
ImagePath = model.ImagePath,
|
||||||
CreatedByUserId = model.CreatedByUserId
|
CreatedByUserId = model.CreatedByUserId,
|
||||||
|
OriginalFileName = model.OriginalFileName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
<MudTh><MudTableSortLabel SortLabel="Genre" T="TrackDto">Genre</MudTableSortLabel></MudTh>
|
<MudTh><MudTableSortLabel SortLabel="Genre" T="TrackDto">Genre</MudTableSortLabel></MudTh>
|
||||||
<MudTh><MudTableSortLabel SortLabel="ReleaseDate" T="TrackDto">Release Date</MudTableSortLabel></MudTh>
|
<MudTh><MudTableSortLabel SortLabel="ReleaseDate" T="TrackDto">Release Date</MudTableSortLabel></MudTh>
|
||||||
<MudTh>Entry Key</MudTh>
|
<MudTh>Entry Key</MudTh>
|
||||||
|
<MudTh>File Name</MudTh>
|
||||||
<MudTh Style="width: 1%; white-space: nowrap;">Actions</MudTh>
|
<MudTh Style="width: 1%; white-space: nowrap;">Actions</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
<MudTd DataLabel="Genre">@(context.Genre ?? "—")</MudTd>
|
<MudTd DataLabel="Genre">@(context.Genre ?? "—")</MudTd>
|
||||||
<MudTd DataLabel="Release Date">@(context.ReleaseDate?.ToString("yyyy-MM-dd") ?? "—")</MudTd>
|
<MudTd DataLabel="Release Date">@(context.ReleaseDate?.ToString("yyyy-MM-dd") ?? "—")</MudTd>
|
||||||
<MudTd DataLabel="Entry Key"><MudText Typo="Typo.caption" Style="font-family: monospace;">@context.EntryKey</MudText></MudTd>
|
<MudTd DataLabel="Entry Key"><MudText Typo="Typo.caption" Style="font-family: monospace;">@context.EntryKey</MudText></MudTd>
|
||||||
|
<MudTd DataLabel="File Name"><MudText Typo="Typo.caption" Style="font-family: monospace;">@(context.OriginalFileName ?? "—")</MudText></MudTd>
|
||||||
<MudTd DataLabel="Actions">
|
<MudTd DataLabel="Actions">
|
||||||
<MudTooltip Text="Edit">
|
<MudTooltip Text="Edit">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
|
|||||||
@@ -144,6 +144,7 @@
|
|||||||
string.IsNullOrWhiteSpace(_album) ? null : _album,
|
string.IsNullOrWhiteSpace(_album) ? null : _album,
|
||||||
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
|
string.IsNullOrWhiteSpace(_genre) ? null : _genre,
|
||||||
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
|
string.IsNullOrWhiteSpace(_releaseDate) ? null : _releaseDate,
|
||||||
|
_selectedFile.Name,
|
||||||
createdByUserId);
|
createdByUserId);
|
||||||
|
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
string? album,
|
string? album,
|
||||||
string? genre,
|
string? genre,
|
||||||
string? releaseDate,
|
string? releaseDate,
|
||||||
|
string? originalFileName,
|
||||||
long createdByUserId,
|
long createdByUserId,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
@@ -54,6 +55,8 @@ public class CmsTrackService : ICmsTrackService
|
|||||||
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(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.
|
||||||
|
if (!string.IsNullOrWhiteSpace(originalFileName)) multipart.Add(new StringContent(originalFileName), "originalFileName");
|
||||||
multipart.Add(new StringContent(createdByUserId.ToString()), "createdByUserId");
|
multipart.Add(new StringContent(createdByUserId.ToString()), "createdByUserId");
|
||||||
|
|
||||||
var client = _httpClientFactory.CreateClient(ContentCmsClientName);
|
var client = _httpClientFactory.CreateClient(ContentCmsClientName);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public interface ICmsTrackService
|
|||||||
/// Proxy a WAV upload to DeepDrftAPI. The Content API owns the dual-database write and
|
/// Proxy a WAV upload to DeepDrftAPI. The Content API owns the dual-database write and
|
||||||
/// returns the persisted track carrying the SQL-assigned <c>Id</c>. A vault-without-SQL
|
/// returns the persisted track carrying the SQL-assigned <c>Id</c>. A vault-without-SQL
|
||||||
/// orphan is handled and logged server-side; here it surfaces as a failed result.
|
/// orphan is handled and logged server-side; here it surfaces as a failed result.
|
||||||
|
/// <paramref name="originalFileName"/> is the browser's filename, captured at upload time and
|
||||||
|
/// stored as metadata; it is not user-editable afterwards.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Task<ResultContainer<TrackDto>> UploadTrackAsync(
|
Task<ResultContainer<TrackDto>> UploadTrackAsync(
|
||||||
Stream wavStream,
|
Stream wavStream,
|
||||||
@@ -25,6 +27,7 @@ public interface ICmsTrackService
|
|||||||
string? album,
|
string? album,
|
||||||
string? genre,
|
string? genre,
|
||||||
string? releaseDate,
|
string? releaseDate,
|
||||||
|
string? originalFileName,
|
||||||
long createdByUserId,
|
long createdByUserId,
|
||||||
CancellationToken ct = default);
|
CancellationToken ct = default);
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ public class TrackDto : BaseModel
|
|||||||
public DateOnly? ReleaseDate { get; set; }
|
public DateOnly? ReleaseDate { get; set; }
|
||||||
public string? ImagePath { get; set; }
|
public string? ImagePath { get; set; }
|
||||||
public long? CreatedByUserId { get; set; }
|
public long? CreatedByUserId { get; set; }
|
||||||
|
public string? OriginalFileName { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ public class TrackEntity : BaseEntity, IEntity
|
|||||||
public DateOnly? ReleaseDate { get; set; }
|
public DateOnly? ReleaseDate { get; set; }
|
||||||
public string? ImagePath { get; set; }
|
public string? ImagePath { get; set; }
|
||||||
public long? CreatedByUserId { get; set; }
|
public long? CreatedByUserId { get; set; }
|
||||||
|
public string? OriginalFileName { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user