@@ -0,0 +1,22 @@
|
||||
using ErsatzTV.Application.ProgramSchedules;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record CreateScheduleRequest(
|
||||
string Name,
|
||||
bool KeepMultiPartEpisodesTogether,
|
||||
bool TreatCollectionsAsShows,
|
||||
bool ShuffleScheduleItems,
|
||||
bool RandomStartPoint,
|
||||
FixedStartTimeBehavior FixedStartTimeBehavior)
|
||||
{
|
||||
public CreateProgramSchedule ToCreateCommand() =>
|
||||
new(
|
||||
Name,
|
||||
KeepMultiPartEpisodesTogether,
|
||||
TreatCollectionsAsShows,
|
||||
ShuffleScheduleItems,
|
||||
RandomStartPoint,
|
||||
FixedStartTimeBehavior);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using ErsatzTV.Application.ProgramSchedules;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record ReplaceScheduleItemsRequest(List<ScheduleItemRequest> Items)
|
||||
{
|
||||
public ReplaceProgramScheduleItems ToCommand(int scheduleId) =>
|
||||
new(
|
||||
scheduleId,
|
||||
(Items ?? []).Select((item, index) => item.ToReplaceCommand(index)).ToList());
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using ErsatzTV.Application.ProgramSchedules;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record ScheduleItemRequest(
|
||||
StartType StartType,
|
||||
TimeSpan? StartTime,
|
||||
FixedStartTimeBehavior? FixedStartTimeBehavior,
|
||||
PlayoutMode PlayoutMode,
|
||||
CollectionType CollectionType,
|
||||
int? CollectionId,
|
||||
int? MultiCollectionId,
|
||||
int? SmartCollectionId,
|
||||
int? RerunCollectionId,
|
||||
int? MediaItemId,
|
||||
int? PlaylistId,
|
||||
string SearchTitle,
|
||||
string SearchQuery,
|
||||
PlaybackOrder PlaybackOrder,
|
||||
MarathonGroupBy MarathonGroupBy,
|
||||
bool MarathonShuffleGroups,
|
||||
bool MarathonShuffleItems,
|
||||
int? MarathonBatchSize,
|
||||
FillWithGroupMode FillWithGroupMode,
|
||||
MultipleMode MultipleMode,
|
||||
string MultipleCount,
|
||||
TimeSpan? PlayoutDuration,
|
||||
TailMode TailMode,
|
||||
int? DiscardToFillAttempts,
|
||||
string CustomTitle,
|
||||
GuideMode GuideMode,
|
||||
int? PreRollFillerId,
|
||||
int? MidRollFillerId,
|
||||
int? PostRollFillerId,
|
||||
int? TailFillerId,
|
||||
int? FallbackFillerId,
|
||||
List<int> WatermarkIds,
|
||||
List<int> GraphicsElementIds,
|
||||
string PreferredAudioLanguageCode,
|
||||
string PreferredAudioTitle,
|
||||
string PreferredSubtitleLanguageCode,
|
||||
ChannelSubtitleMode? SubtitleMode)
|
||||
{
|
||||
public AddProgramScheduleItem ToAddCommand(int scheduleId) =>
|
||||
new(
|
||||
scheduleId,
|
||||
StartType,
|
||||
StartTime,
|
||||
FixedStartTimeBehavior,
|
||||
PlayoutMode,
|
||||
CollectionType,
|
||||
CollectionId,
|
||||
MultiCollectionId,
|
||||
SmartCollectionId,
|
||||
RerunCollectionId,
|
||||
MediaItemId,
|
||||
PlaylistId,
|
||||
SearchTitle,
|
||||
SearchQuery,
|
||||
PlaybackOrder,
|
||||
MarathonGroupBy,
|
||||
MarathonShuffleGroups,
|
||||
MarathonShuffleItems,
|
||||
MarathonBatchSize,
|
||||
FillWithGroupMode,
|
||||
MultipleMode,
|
||||
MultipleCount,
|
||||
PlayoutDuration,
|
||||
TailMode,
|
||||
DiscardToFillAttempts,
|
||||
CustomTitle,
|
||||
GuideMode,
|
||||
PreRollFillerId,
|
||||
MidRollFillerId,
|
||||
PostRollFillerId,
|
||||
TailFillerId,
|
||||
FallbackFillerId,
|
||||
WatermarkIds ?? [],
|
||||
GraphicsElementIds ?? [],
|
||||
PreferredAudioLanguageCode,
|
||||
PreferredAudioTitle,
|
||||
PreferredSubtitleLanguageCode,
|
||||
SubtitleMode);
|
||||
|
||||
public ReplaceProgramScheduleItem ToReplaceCommand(int index) =>
|
||||
new(
|
||||
index,
|
||||
StartType,
|
||||
StartTime,
|
||||
FixedStartTimeBehavior,
|
||||
PlayoutMode,
|
||||
CollectionType,
|
||||
CollectionId,
|
||||
MultiCollectionId,
|
||||
SmartCollectionId,
|
||||
RerunCollectionId,
|
||||
MediaItemId,
|
||||
PlaylistId,
|
||||
SearchTitle,
|
||||
SearchQuery,
|
||||
PlaybackOrder,
|
||||
MarathonGroupBy,
|
||||
MarathonShuffleGroups,
|
||||
MarathonShuffleItems,
|
||||
MarathonBatchSize,
|
||||
FillWithGroupMode,
|
||||
MultipleMode,
|
||||
MultipleCount,
|
||||
PlayoutDuration,
|
||||
TailMode,
|
||||
DiscardToFillAttempts,
|
||||
CustomTitle,
|
||||
GuideMode,
|
||||
PreRollFillerId,
|
||||
MidRollFillerId,
|
||||
PostRollFillerId,
|
||||
TailFillerId,
|
||||
FallbackFillerId,
|
||||
WatermarkIds ?? [],
|
||||
GraphicsElementIds ?? [],
|
||||
PreferredAudioLanguageCode,
|
||||
PreferredAudioTitle,
|
||||
PreferredSubtitleLanguageCode,
|
||||
SubtitleMode);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using ErsatzTV.Application.ProgramSchedules;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record UpdateScheduleRequest(
|
||||
string Name,
|
||||
bool KeepMultiPartEpisodesTogether,
|
||||
bool TreatCollectionsAsShows,
|
||||
bool ShuffleScheduleItems,
|
||||
bool RandomStartPoint,
|
||||
FixedStartTimeBehavior FixedStartTimeBehavior)
|
||||
{
|
||||
public UpdateProgramSchedule ToCommand(int id) =>
|
||||
new(
|
||||
id,
|
||||
Name,
|
||||
KeepMultiPartEpisodesTogether,
|
||||
TreatCollectionsAsShows,
|
||||
ShuffleScheduleItems,
|
||||
RandomStartPoint,
|
||||
FixedStartTimeBehavior);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using ErsatzTV.Application.ProgramSchedules;
|
||||
using ErsatzTV.Controllers.Api.Requests;
|
||||
using ErsatzTV.Core;
|
||||
using ErsatzTV.Extensions;
|
||||
using ErsatzTV.Filters;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[ServiceFilter(typeof(ApiKeyAuthorizationFilter))]
|
||||
public class ScheduleController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
[HttpGet("/api/schedules")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Get all schedules")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(List<ProgramScheduleViewModel>), StatusCodes.Status200OK)]
|
||||
public async Task<List<ProgramScheduleViewModel>> GetAll(CancellationToken cancellationToken) =>
|
||||
await mediator.Send(new GetAllProgramSchedules(), cancellationToken);
|
||||
|
||||
[HttpGet("/api/schedules/{id:int}", Name = "GetScheduleById")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Get a schedule by id")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(ProgramScheduleViewModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
Option<ProgramScheduleViewModel> result = await mediator.Send(new GetProgramScheduleById(id), cancellationToken);
|
||||
return result.ToGetResult();
|
||||
}
|
||||
|
||||
[HttpPost("/api/schedules")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Create a schedule")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(ProgramScheduleViewModel), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> Create(
|
||||
[Required] [FromBody] CreateScheduleRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Either<BaseError, CreateProgramScheduleResult> result =
|
||||
await mediator.Send(request.ToCreateCommand(), cancellationToken);
|
||||
return await result.Match(
|
||||
Left: error => Task.FromResult(error.ToErrorResult()),
|
||||
Right: async created =>
|
||||
{
|
||||
Option<ProgramScheduleViewModel> schedule =
|
||||
await mediator.Send(new GetProgramScheduleById(created.ProgramScheduleId), cancellationToken);
|
||||
return schedule.Match(
|
||||
Some: vm => (IActionResult)new CreatedResult($"/api/schedules/{vm.Id}", vm),
|
||||
None: () => ApiResults.NotFoundProblem());
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut("/api/schedules/{id:int}")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Update a schedule")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(ProgramScheduleViewModel), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> Update(
|
||||
int id,
|
||||
[Required] [FromBody] UpdateScheduleRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Either<BaseError, UpdateProgramScheduleResult> result =
|
||||
await mediator.Send(request.ToCommand(id), cancellationToken);
|
||||
return await result.Match(
|
||||
Left: error => Task.FromResult(error.ToErrorResult()),
|
||||
Right: async _ =>
|
||||
{
|
||||
Option<ProgramScheduleViewModel> schedule =
|
||||
await mediator.Send(new GetProgramScheduleById(id), cancellationToken);
|
||||
return schedule.Match(
|
||||
Some: vm => (IActionResult)new OkObjectResult(vm),
|
||||
None: () => ApiResults.NotFoundProblem());
|
||||
});
|
||||
}
|
||||
|
||||
[HttpDelete("/api/schedules/{id:int}")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Delete a schedule")]
|
||||
[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 DeleteProgramSchedule(id), cancellationToken);
|
||||
return result.ToDeletedResult();
|
||||
}
|
||||
|
||||
[HttpGet("/api/schedules/{id:int}/items")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Get schedule items")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(List<ProgramScheduleItemViewModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetItems(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
Option<ProgramScheduleViewModel> schedule = await mediator.Send(new GetProgramScheduleById(id), cancellationToken);
|
||||
if (schedule.IsNone)
|
||||
{
|
||||
return ApiResults.NotFoundProblem();
|
||||
}
|
||||
|
||||
List<ProgramScheduleItemViewModel> items =
|
||||
await mediator.Send(new GetProgramScheduleItems(id), cancellationToken);
|
||||
return new OkObjectResult(items);
|
||||
}
|
||||
|
||||
[HttpPost("/api/schedules/{id:int}/items")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Add a schedule item")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(ProgramScheduleItemViewModel), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> AddItem(
|
||||
int id,
|
||||
[Required] [FromBody] ScheduleItemRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Either<BaseError, ProgramScheduleItemViewModel> result =
|
||||
await mediator.Send(request.ToAddCommand(id), cancellationToken);
|
||||
return result.ToCreatedResult(item => $"/api/schedules/{id}/items/{item.Id}", item => item);
|
||||
}
|
||||
|
||||
[HttpPut("/api/schedules/{id:int}/items")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Replace schedule items")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(typeof(IEnumerable<ProgramScheduleItemViewModel>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> ReplaceItems(
|
||||
int id,
|
||||
[Required] [FromBody] ReplaceScheduleItemsRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Either<BaseError, IEnumerable<ProgramScheduleItemViewModel>> result =
|
||||
await mediator.Send(request.ToCommand(id), cancellationToken);
|
||||
return result.ToUpdatedResult();
|
||||
}
|
||||
|
||||
[HttpDelete("/api/schedules/{id:int}/items/{itemId:int}")]
|
||||
[Tags("Schedules")]
|
||||
[EndpointSummary("Delete a schedule item")]
|
||||
[EndpointGroupName("general")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> DeleteItem(int id, int itemId, CancellationToken cancellationToken)
|
||||
{
|
||||
Either<BaseError, Unit> result =
|
||||
await mediator.Send(new DeleteProgramScheduleItem(id, itemId), cancellationToken);
|
||||
return result.ToDeletedResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user