Files
ersatztv/ErsatzTV/Controllers/Api/LibrariesController.cs
T
timothyandClaude Opus 4.8 589c35d357
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 6s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 8m33s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 9m59s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
fix(235): make QueueShowScanResult switch total (explicit NotFound + throwing fallback)
Codex-review Low: NotFound previously reached the catch-all by coincidence; a
future enum value would silently 404. Explicit arm + UnreachableException fallback
so an unmapped outcome fails loudly rather than mis-mapping to 404.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 18:41:33 +02:00

107 lines
5.2 KiB
C#

using System.Diagnostics;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core.Api.Libraries;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Extensions;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class LibrariesController(ITelevisionRepository televisionRepository, IMediator mediator) : ControllerBase
{
[HttpGet("/api/libraries/scan-status", Name = "GetLibraryScanStatus")]
[Tags("Libraries")]
[EndpointSummary("Get active library scan status")]
[ProducesResponseType(typeof(List<LibraryScanStatusResponseModel>), StatusCodes.Status200OK)]
public async Task<List<LibraryScanStatusResponseModel>> GetScanStatus(CancellationToken cancellationToken) =>
await mediator.Send(new GetLibraryScanStatus(), cancellationToken);
[HttpPost("/api/libraries/{id:int}/scan")]
[Tags("Libraries")]
[EndpointSummary("Scan library")]
[EndpointDescription("Queues a scan of the whole library. Pass ?deep=true for a deep (metadata-refresh) scan.")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> ScanLibrary(
int id,
[FromQuery] bool deep = false,
CancellationToken cancellationToken = default)
{
QueueLibraryScanResult result =
await mediator.Send(new QueueLibraryScanByLibraryId(id, deep), cancellationToken);
return result switch
{
QueueLibraryScanResult.Queued => new AcceptedResult(),
QueueLibraryScanResult.AlreadyScanning => ApiResults.ConflictProblem(
"Library scan in progress",
$"A scan for library {id} is already in progress."),
QueueLibraryScanResult.SyncDisabled => new UnprocessableEntityObjectResult(
new ProblemDetails
{
Status = StatusCodes.Status422UnprocessableEntity,
Title = "Library sync is disabled",
Detail = $"Item sync is disabled for library {id}."
}),
_ => ApiResults.NotFoundProblem($"Library {id} does not exist.")
};
}
[HttpPost("/api/libraries/{id:int}/scan-show")]
[Tags("Libraries")]
[EndpointSummary("Scan show")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status409Conflict)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> ScanShow(int id, [FromBody] ScanShowRequest request)
{
Option<string> maybeTitle = await televisionRepository.GetShowTitle(id, request.ShowId);
foreach (string title in maybeTitle)
{
QueueShowScanResult result =
await mediator.Send(new QueueShowScanByLibraryId(id, request.ShowId, title, request.DeepScan));
return result switch
{
QueueShowScanResult.Queued => new AcceptedResult(),
QueueShowScanResult.AlreadyScanning => ApiResults.ConflictProblem(
"Library scan in progress",
$"A scan for library {id} is already in progress; cannot scan an individual show."),
QueueShowScanResult.SyncDisabled => new UnprocessableEntityObjectResult(
new ProblemDetails
{
Status = StatusCodes.Status422UnprocessableEntity,
Title = "Library sync is disabled",
Detail = $"Item sync is disabled for library {id}."
}),
QueueShowScanResult.Unsupported => new UnprocessableEntityObjectResult(
new ProblemDetails
{
Status = StatusCodes.Status422UnprocessableEntity,
Title = "Single show scanning is not supported",
Detail = $"Library {id} does not support scanning an individual show."
}),
QueueShowScanResult.ScanFailed => new UnprocessableEntityObjectResult(
new ProblemDetails
{
Status = StatusCodes.Status422UnprocessableEntity,
Title = "Unable to scan show",
Detail = $"The scan for show {request.ShowId} in library {id} could not be completed."
}),
QueueShowScanResult.NotFound => ApiResults.NotFoundProblem($"Library {id} does not exist."),
_ => throw new UnreachableException($"Unmapped QueueShowScanResult: {result}")
};
}
return ApiResults.NotFoundProblem($"Show {request.ShowId} does not exist in library {id}.");
}
}
public record ScanShowRequest(int ShowId, bool DeepScan = false);