refactor(api): OpenAPI response metadata, cached user sync, ProblemDetails
- Document every endpoint's responses (Produces<T>/status codes/validation) so the generated OpenAPI/Scalar docs carry response schemas and status codes instead of empty responses - Cache recently-synced user ids (IMemoryCache, 15 min) in the JWT TokenValidated handler so user provisioning no longer hits the DB on every authenticated request - Add ProblemDetails + UseExceptionHandler for consistent RFC7807 errors - Add ToCreatedResult ErrorOr helper and use it in the create endpoints - Normalize the playlists route to lowercase (/playlists) Verified: booted the Api against RabbitMQ + Postgres in Docker and inspected /openapi/v1.json — operations now expose response schemas/status codes and the route is /playlists. Build: 0 warnings, 0 errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using Mediator;
|
||||
using Musify.Application.Tracks;
|
||||
using Musify.Application.Tracks.Responses;
|
||||
using Musify.Application.Shared;
|
||||
using Musify.Api.Authentication;
|
||||
using Musify.Api.DataTransferObjects.Tracks;
|
||||
using Musify.Api.Extensions;
|
||||
@@ -16,37 +18,58 @@ public static class TrackEndpoints
|
||||
|
||||
group.MapGet("/", GetTracks)
|
||||
.WithName("GetTracks")
|
||||
.WithSummary("Get Paginated Tracks.");
|
||||
.WithSummary("Get Paginated Tracks.")
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>();
|
||||
|
||||
group.MapGet("/{id}", GetTrackById)
|
||||
.WithName("GetTrackById")
|
||||
.WithSummary("Get A Track By Id.");
|
||||
.WithSummary("Get A Track By Id.")
|
||||
.Produces<TrackApplicationResponse>()
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/{id}/stream", GetTrackStream)
|
||||
.WithName("GetTrackStream")
|
||||
.WithSummary("Get A Streaming Manifest URL And Ticket For A Track.")
|
||||
.RequireAuthorization();
|
||||
.RequireAuthorization()
|
||||
.Produces<TrackStreamResponse>()
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status404NotFound);
|
||||
|
||||
group.MapGet("/users/{userId}", GetTracksByUserId)
|
||||
.WithName("GetTracksByUserId")
|
||||
.WithSummary("Get Paginated Tracks For A User.");
|
||||
.WithSummary("Get Paginated Tracks For A User.")
|
||||
.Produces<PaginatedResponse<TrackApplicationResponse>>();
|
||||
|
||||
group.MapPost("/", CreateTrack)
|
||||
.WithName("CreateTrack")
|
||||
.WithSummary("Create A New Track.")
|
||||
.AddEndpointFilter<ValidationFilter<CreateTrackRequest>>()
|
||||
.RequireAuthorization();
|
||||
.RequireAuthorization()
|
||||
.Produces<TrackApplicationResponse>(StatusCodes.Status201Created)
|
||||
.ProducesValidationProblem()
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict);
|
||||
|
||||
group.MapDelete("/{trackId}", DeleteTrack)
|
||||
.WithName("DeleteTrack")
|
||||
.WithSummary("Delete A Track.")
|
||||
.RequireAuthorization();
|
||||
.RequireAuthorization()
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict);
|
||||
|
||||
group.MapPost("/upload-urls", RequestTrackUploadUrls)
|
||||
.WithName("RequestTrackUploadUrls")
|
||||
.WithSummary("Request Pre-Signed URLs To Upload Track Picture And Audio.")
|
||||
.AddEndpointFilter<ValidationFilter<RequestTrackUploadUrlsRequest>>()
|
||||
.RequireAuthorization();
|
||||
.RequireAuthorization()
|
||||
.Produces<TrackUploadUrlsResponse>()
|
||||
.ProducesValidationProblem()
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status409Conflict);
|
||||
|
||||
return app;
|
||||
}
|
||||
@@ -102,10 +125,7 @@ public static class TrackEndpoints
|
||||
new CreateTrackCommand(currentUser.RequiredId, request.Title, request.PictureIntentId, request.AudioIntentId),
|
||||
cancellationToken);
|
||||
|
||||
if (result.IsError)
|
||||
return result.ToHttpResult();
|
||||
|
||||
return Results.Created($"/tracks/{result.Value.Id}", result.Value);
|
||||
return result.ToCreatedResult(track => $"/tracks/{track.Id}");
|
||||
}
|
||||
|
||||
private static async Task<IResult> DeleteTrack(
|
||||
|
||||
Reference in New Issue
Block a user