using System.ComponentModel.DataAnnotations; using ErsatzTV.Application.FFmpegProfiles; using ErsatzTV.Controllers.Api.Requests; using ErsatzTV.Core; using ErsatzTV.Core.Api.FFmpegProfiles; using ErsatzTV.Core.Domain; using ErsatzTV.Extensions; using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ErsatzTV.Controllers.Api; [ApiController] public class FFmpegProfileController(IMediator mediator) : ControllerBase { [HttpGet("/api/v1/ffmpeg/profiles", Name = "GetFFmpegProfiles")] [Tags("FFmpeg Profiles")] [EndpointSummary("Get all FFmpeg profiles")] [EndpointGroupName("general")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task> GetAll(CancellationToken cancellationToken) => await mediator.Send(new GetAllFFmpegProfilesForApi(), cancellationToken); [HttpGet("/api/v1/ffmpeg/hardware-acceleration-kinds", Name = "GetSupportedHardwareAccelerationKinds")] [Tags("FFmpeg Profiles")] [EndpointSummary("Get supported hardware acceleration kinds")] [EndpointDescription( "Returns the hardware-acceleration kinds available on this host (probed from the configured " + "FFmpeg binary). Always includes None; falls back to just None when FFmpeg is unavailable.")] [EndpointGroupName("general")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] public async Task> GetHardwareAccelerationKinds(CancellationToken cancellationToken) => // returns the enum values directly; the API serializes enums as their string names await mediator.Send(new GetSupportedHardwareAccelerationKinds(), cancellationToken); [HttpGet("/api/v1/ffmpeg/profiles/{id:int}", Name = "GetFFmpegProfileById")] [Tags("FFmpeg Profiles")] [EndpointSummary("Get an FFmpeg profile by id")] [EndpointGroupName("general")] [ProducesResponseType(typeof(FFmpegFullProfileResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] public async Task GetById(int id, CancellationToken cancellationToken) { Option result = await mediator.Send(new GetFFmpegFullProfileByIdForApi(id), cancellationToken); return result.ToGetResult(); } [HttpPost("/api/v1/ffmpeg/profiles", Name = "CreateFFmpegProfile")] [Tags("FFmpeg Profiles")] [EndpointSummary("Create an FFmpeg profile")] [EndpointGroupName("general")] [ProducesResponseType(typeof(FFmpegFullProfileResponseModel), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)] public async Task AddOne( [Required] [FromBody] CreateFFmpegProfileRequest request, CancellationToken cancellationToken) { Either result = await mediator.Send(request.ToCommand(), cancellationToken); return await result.Match( Left: error => Task.FromResult(error.ToErrorResult()), Right: async created => { Option profile = await mediator.Send(new GetFFmpegFullProfileByIdForApi(created.FFmpegProfileId), cancellationToken); return profile.Match( Some: vm => (IActionResult)new CreatedResult($"/api/v1/ffmpeg/profiles/{vm.Id}", vm), None: () => ApiResults.NotFoundProblem()); }); } [HttpPut("/api/v1/ffmpeg/profiles/{id:int}", Name = "UpdateFFmpegProfile")] [Tags("FFmpeg Profiles")] [EndpointSummary("Update an FFmpeg profile")] [EndpointGroupName("general")] [ProducesResponseType(typeof(FFmpegFullProfileResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)] public async Task UpdateOne( int id, [Required] [FromBody] UpdateFFmpegProfileRequest request, CancellationToken cancellationToken) { Either result = await mediator.Send(request.ToCommand(id), cancellationToken); return await result.Match( Left: error => Task.FromResult(error.ToErrorResult()), Right: async updated => { Option profile = await mediator.Send(new GetFFmpegFullProfileByIdForApi(updated.FFmpegProfileId), cancellationToken); return profile.Match( Some: vm => (IActionResult)new OkObjectResult(vm), None: () => ApiResults.NotFoundProblem()); }); } [HttpDelete("/api/v1/ffmpeg/profiles/{id:int}", Name = "DeleteFFmpegProfile")] [Tags("FFmpeg Profiles")] [EndpointSummary("Delete an FFmpeg profile")] [EndpointGroupName("general")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)] public async Task DeleteProfileAsync(int id, CancellationToken cancellationToken) { Either result = await mediator.Send(new DeleteFFmpegProfile(id), cancellationToken); return result.ToDeletedResult(); } }