Make release Medium writable via upload + meta-edit; resolve detail-page track by releaseId not album title

This commit is contained in:
daniel-c-harvey
2026-06-13 11:34:45 -04:00
parent ea018beb3e
commit 8b62915083
15 changed files with 314 additions and 28 deletions
+36 -3
View File
@@ -48,9 +48,10 @@ public class TrackController : ControllerBase
// These are declared before the parameterized "{trackId}" / "{id:long}" actions so route
// resolution never treats "page", "upload", or "meta" as a trackId.
// GET api/track/page?page=1&pageSize=20&sortColumn=TrackName&sortDescending=false&q=&album=&genre=
// GET api/track/page?page=1&pageSize=20&sortColumn=TrackName&sortDescending=false&q=&album=&genre=&releaseId=
// Public track listing — paged read straight from SQL. Unauthenticated, like GET api/track/{id}.
// q/album/genre build an optional TrackFilter; all null → null passthrough (no filtering).
// q/album/genre/releaseId build an optional TrackFilter; all null → null passthrough (no filtering).
// releaseId is the authoritative release→tracks join (exact match), preferred over album title.
[HttpGet("page")]
public async Task<ActionResult> GetPage(
[FromQuery] int page = 1,
@@ -60,9 +61,10 @@ public class TrackController : ControllerBase
[FromQuery] string? q = null,
[FromQuery] string? album = null,
[FromQuery] string? genre = null,
[FromQuery] long? releaseId = null,
CancellationToken cancellationToken = default)
{
var filter = new TrackFilter { SearchText = q, Album = album, Genre = genre };
var filter = new TrackFilter { SearchText = q, Album = album, Genre = genre, ReleaseId = releaseId };
var effectiveFilter = filter.IsEmpty ? null : filter;
var result = await _sqlTrackService.GetPaged(page, pageSize, sortColumn, sortDescending, effectiveFilter, cancellationToken);
@@ -191,6 +193,7 @@ public class TrackController : ControllerBase
[FromForm] string? originalFileName,
[FromForm] long createdByUserId,
[FromForm] string? releaseType,
[FromForm] string? medium,
[FromForm] int? trackNumber,
CancellationToken cancellationToken)
{
@@ -242,6 +245,21 @@ public class TrackController : ControllerBase
if (!string.IsNullOrWhiteSpace(releaseType))
_logger.LogWarning("UploadTrack: unrecognised releaseType value '{Value}', defaulting to Single", releaseType);
}
// Default to Cut for null/unparseable medium, mirroring the releaseType defensive parse above.
ReleaseMedium parsedMedium;
if (!string.IsNullOrWhiteSpace(medium)
&& Enum.TryParse<ReleaseMedium>(medium, ignoreCase: true, out var rm)
&& Enum.IsDefined(rm))
{
parsedMedium = rm;
}
else
{
parsedMedium = ReleaseMedium.Cut;
if (!string.IsNullOrWhiteSpace(medium))
_logger.LogWarning("UploadTrack: unrecognised medium value '{Value}', defaulting to Cut", medium);
}
var resolvedTrackNumber = trackNumber is > 0 ? trackNumber.Value : 1;
// The processor router selects by extension and reads from disk, so the temp file must carry
@@ -269,6 +287,7 @@ public class TrackController : ControllerBase
createdByUserId,
string.IsNullOrWhiteSpace(originalFileName) ? null : originalFileName,
parsedReleaseType,
parsedMedium,
resolvedTrackNumber,
cancellationToken);
@@ -393,6 +412,20 @@ public class TrackController : ControllerBase
// ReleaseType is non-null on the release; null in the request means "no change".
if (request.ReleaseType is not null)
release.ReleaseType = request.ReleaseType.Value;
// Medium is non-null on the release; null in the request means "no change".
if (request.Medium is not null)
{
release.Medium = request.Medium.Value;
// ReleaseType is meaningful only for Cut. When the medium is anything else, reset
// ReleaseType to the DB-level default rather than leaving a stale studio-format value —
// mirroring TrackConverter's read-path nulling of ReleaseType for non-Cut releases. This
// runs after the ReleaseType apply above, so it correctly overrides a contradictory
// ReleaseType sent in the same request alongside a non-Cut medium.
if (request.Medium.Value != ReleaseMedium.Cut)
release.ReleaseType = ReleaseType.Single;
}
}
var update = await _sqlTrackService.Update(track);