Make release Medium writable via upload + meta-edit; resolve detail-page track by releaseId not album title
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -19,4 +19,5 @@ public record UpdateTrackMetadataRequest(
|
||||
DateOnly? ReleaseDate,
|
||||
string? ImagePath = null,
|
||||
ReleaseType? ReleaseType = null,
|
||||
ReleaseMedium? Medium = null,
|
||||
int? TrackNumber = null);
|
||||
|
||||
@@ -53,6 +53,7 @@ public class UnifiedTrackService
|
||||
long createdByUserId,
|
||||
string? originalFileName,
|
||||
ReleaseType releaseType,
|
||||
ReleaseMedium medium,
|
||||
int trackNumber,
|
||||
CancellationToken ct)
|
||||
{
|
||||
@@ -81,9 +82,15 @@ public class UnifiedTrackService
|
||||
Genre = genre,
|
||||
ReleaseDate = releaseDate,
|
||||
ReleaseType = releaseType,
|
||||
Medium = medium,
|
||||
CreatedByUserId = createdByUserId,
|
||||
};
|
||||
|
||||
// Medium (like every other field in releaseData) applies only when this upload CREATES the
|
||||
// release. FindOrCreateRelease returns an existing (title, artist) row untouched — the first
|
||||
// upload's medium is authoritative. Do NOT "fix" this to overwrite the stored medium on a
|
||||
// subsequent track add: medium is a release-level property, changed only via the edit path
|
||||
// (PUT api/track/meta), never silently flipped by adding a track to an existing release.
|
||||
var releaseResult = await _sqlTrackService.FindOrCreateRelease(album, artist, releaseData, ct);
|
||||
if (!releaseResult.Success || releaseResult.Value is null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user