feat(tracks): add track cover endpoint

Serve cover images via GET /tracks/{id}/cover with a size parameter, streamed from storage with long-lived immutable cache headers.
This commit is contained in:
2026-07-14 04:54:40 +02:00
parent 5c1edc0dc7
commit 54484d9fd4
2 changed files with 78 additions and 0 deletions
@@ -2,6 +2,7 @@ using Mediator;
using Musify.Application.Tracks;
using Musify.Application.Tracks.Responses;
using Musify.Application.Shared;
using Musify.Application.Contracts;
using Musify.Api.Authentication;
using Musify.Api.DataTransferObjects.Tracks;
using Musify.Api.Extensions;
@@ -27,6 +28,12 @@ public static class TrackEndpoints
.Produces<TrackApplicationResponse>()
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/{id}/cover", GetTrackCover)
.WithName("GetTrackCover")
.WithSummary("Get A Track Cover Image.")
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
group.MapGet("/{id}/stream", GetTrackStream)
.WithName("GetTrackStream")
.WithSummary("Get A Streaming Manifest URL And Ticket For A Track.")
@@ -94,6 +101,24 @@ public static class TrackEndpoints
return result.ToHttpResult();
}
private static async Task<IResult> GetTrackCover(
IMediator mediator,
IStorageService storageService,
HttpResponse response,
Guid id,
CancellationToken cancellationToken,
string size = "medium")
{
var result = await mediator.Send(new GetTrackCoverQuery(id, size), cancellationToken);
if (result.IsError)
return Results.NotFound();
var location = result.Value;
var stream = await storageService.GetFileAsync(location.Bucket, location.Key, cancellationToken);
response.Headers.CacheControl = "public, max-age=31536000, immutable";
return Results.Stream(stream, location.ContentType);
}
private static async Task<IResult> GetTrackStream(
IMediator mediator,
CurrentUser currentUser,