fix(api): standardize error response contract
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 3m34s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 4m36s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped

refs #46
This commit is contained in:
2026-06-28 14:58:48 +02:00
parent 7c646a6829
commit c113142a3d
12 changed files with 647 additions and 37 deletions
+16 -2
View File
@@ -10,6 +10,7 @@ using ErsatzTV.Core.Scheduling;
using ErsatzTV.Extensions;
using ErsatzTV.Filters;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
@@ -30,6 +31,8 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[Tags("Channels")]
[EndpointSummary("Get a channel by id")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(ChannelViewModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
{
Option<ChannelViewModel> result = await mediator.Send(new GetChannelById(id), cancellationToken);
@@ -40,6 +43,9 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[Tags("Channels")]
[EndpointSummary("Create a channel")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(ChannelViewModel), StatusCodes.Status201Created)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Create(
[Required] [FromBody] CreateChannelRequest request,
CancellationToken cancellationToken)
@@ -53,7 +59,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
await mediator.Send(new GetChannelById(created.ChannelId), cancellationToken);
return channel.Match(
Some: vm => (IActionResult)new CreatedResult($"/api/channels/{vm.Id}", vm),
None: () => new NotFoundResult());
None: () => ApiResults.NotFoundProblem());
});
}
@@ -61,6 +67,9 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[Tags("Channels")]
[EndpointSummary("Update a channel")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(ChannelViewModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Update(
int id,
[Required] [FromBody] UpdateChannelRequest request,
@@ -75,6 +84,9 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[Tags("Channels")]
[EndpointSummary("Delete a channel")]
[EndpointGroupName("general")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteChannel(id), cancellationToken);
@@ -85,6 +97,8 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[Tags("Channels")]
[EndpointSummary("Reset channel playout")]
[EndpointGroupName("general")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> ResetPlayout(string channelNumber)
{
Option<int> maybePlayoutId = await mediator.Send(new GetPlayoutIdByChannelNumber(channelNumber));
@@ -94,6 +108,6 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
return new OkResult();
}
return new NotFoundResult();
return ApiResults.NotFoundProblem();
}
}
@@ -27,7 +27,7 @@ public class CollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Get a collection by id")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(MediaCollectionViewModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
{
Option<MediaCollectionViewModel> result = await mediator.Send(new GetCollectionById(id), cancellationToken);
@@ -39,8 +39,8 @@ public class CollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Create a collection")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(MediaCollectionViewModel), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Create(
[Required] [FromBody] CreateCollectionRequest request,
CancellationToken cancellationToken)
@@ -55,8 +55,8 @@ public class CollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Update a collection")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(MediaCollectionViewModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Update(
int id,
[Required] [FromBody] UpdateCollectionRequest request,
@@ -71,7 +71,7 @@ public class CollectionController(IMediator mediator) : ControllerBase
await mediator.Send(new GetCollectionById(id), cancellationToken);
return collection.Match(
Some: vm => (IActionResult)new OkObjectResult(vm),
None: () => new NotFoundResult());
None: () => ApiResults.NotFoundProblem());
});
}
@@ -80,8 +80,8 @@ public class CollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Delete a collection")]
[EndpointGroupName("general")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteCollection(id), cancellationToken);
@@ -93,8 +93,8 @@ public class CollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Add items to a collection")]
[EndpointGroupName("general")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> AddItems(
int id,
[Required] [FromBody] AddItemsToCollectionRequest request,
@@ -109,8 +109,8 @@ public class CollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Remove an item from a collection")]
[EndpointGroupName("general")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> RemoveItem(int id, int mediaItemId, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(
@@ -28,7 +28,7 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Get a smart collection by id")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(SmartCollectionViewModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
{
Option<SmartCollectionViewModel> result =
@@ -41,8 +41,8 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Create a smart collection")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(SmartCollectionViewModel), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Create(
[Required] [FromBody] CreateSmartCollectionRequest request,
CancellationToken cancellationToken)
@@ -57,8 +57,8 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Update a smart collection")]
[EndpointGroupName("general")]
[ProducesResponseType(typeof(SmartCollectionViewModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Update(
int id,
[Required] [FromBody] UpdateSmartCollectionRequest request,
@@ -74,7 +74,7 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
await mediator.Send(new GetSmartCollectionById(id), cancellationToken);
return smartCollection.Match(
Some: vm => (IActionResult)new OkObjectResult(vm),
None: () => new NotFoundResult());
None: () => ApiResults.NotFoundProblem());
});
}
@@ -83,8 +83,8 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
[EndpointSummary("Delete a smart collection")]
[EndpointGroupName("general")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status422UnprocessableEntity)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteSmartCollection(id), cancellationToken);