Browse Source

Update API controllers with flat DTOs and OpenAPI attributes

Refactor all API controllers to:
- Use flat API response DTOs instead of nested ViewModels
- Add [EndpointGroupName("general")] attributes for OpenAPI grouping
- Add [Tags] and [EndpointSummary] attributes for documentation
- Add mapping methods to convert ViewModels to flat DTOs

Controllers updated:
- BlockController: Use BlockItemApiResponse with inline mappings
- CollectionController: Use CollectionApiResponse, MultiCollectionApiResponse
- DecoController: Use DecoApiResponse with break content mappings
- PlaylistController: Use PlaylistItemApiResponse
- ScheduleController: Use ScheduleItemApiResponse with filler mappings
- TemplateController: Add OpenAPI attributes and request models
- ChannelController, ConfigController, EmbyController, FillerController,
  JellyfinController, LibrariesController, MaintenanceController,
  MediaController, PlayoutController, PlexController, ResolutionController,
  ScannerController, ScriptedScheduleController, SearchController,
  SessionController, SmartCollectionController, TraktController,
  VersionController, WatermarkController: Add OpenAPI attributes

This enables complete OpenAPI schema generation without depth limit errors.
pull/2735/head
Adam Jacob Muller 7 months ago
parent
commit
102959ed49
  1. 77
      ErsatzTV/Controllers/Api/BlockController.cs
  2. 11
      ErsatzTV/Controllers/Api/ChannelController.cs
  3. 69
      ErsatzTV/Controllers/Api/CollectionController.cs
  4. 10
      ErsatzTV/Controllers/Api/ConfigController.cs
  5. 114
      ErsatzTV/Controllers/Api/DecoController.cs
  6. 9
      ErsatzTV/Controllers/Api/EmbyController.cs
  7. 10
      ErsatzTV/Controllers/Api/FFmpegProfileController.cs
  8. 8
      ErsatzTV/Controllers/Api/FillerController.cs
  9. 9
      ErsatzTV/Controllers/Api/JellyfinController.cs
  10. 12
      ErsatzTV/Controllers/Api/LibrariesController.cs
  11. 6
      ErsatzTV/Controllers/Api/MaintenanceController.cs
  12. 8
      ErsatzTV/Controllers/Api/MediaController.cs
  13. 62
      ErsatzTV/Controllers/Api/PlaylistController.cs
  14. 16
      ErsatzTV/Controllers/Api/PlayoutController.cs
  15. 8
      ErsatzTV/Controllers/Api/PlexController.cs
  16. 6
      ErsatzTV/Controllers/Api/ResolutionController.cs
  17. 3
      ErsatzTV/Controllers/Api/ScannerController.cs
  18. 107
      ErsatzTV/Controllers/Api/ScheduleController.cs
  19. 28
      ErsatzTV/Controllers/Api/ScriptedScheduleController.cs
  20. 11
      ErsatzTV/Controllers/Api/SearchController.cs
  21. 4
      ErsatzTV/Controllers/Api/SessionController.cs
  22. 5
      ErsatzTV/Controllers/Api/SmartCollectionController.cs
  23. 13
      ErsatzTV/Controllers/Api/TemplateController.cs
  24. 7
      ErsatzTV/Controllers/Api/TraktController.cs
  25. 3
      ErsatzTV/Controllers/Api/VersionController.cs
  26. 8
      ErsatzTV/Controllers/Api/WatermarkController.cs

77
ErsatzTV/Controllers/Api/BlockController.cs

@ -1,6 +1,10 @@ @@ -1,6 +1,10 @@
#nullable enable
using System.ComponentModel.DataAnnotations;
using ErsatzTV.Application.Graphics;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Scheduling;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
@ -11,19 +15,74 @@ using Microsoft.AspNetCore.Mvc; @@ -11,19 +15,74 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class BlockController(IMediator mediator) : ControllerBase
{
private static CollectionApiResponse? ToApiResponse(MediaCollectionViewModel? c) =>
c == null ? null : new(c.Id, c.Name, c.CollectionType, c.UseCustomPlaybackOrder, c.State);
private static SmartCollectionApiResponse? ToApiResponse(SmartCollectionViewModel? s) =>
s == null ? null : new(s.Id, s.Name, s.Query);
private static MultiCollectionApiResponse? ToApiResponse(MultiCollectionViewModel? mc) =>
mc == null ? null : new(
mc.Id,
mc.Name,
mc.Items?.Select(i => new MultiCollectionItemApiResponse(
i.MultiCollectionId,
i.Collection?.Id ?? 0,
i.Collection?.Name ?? "",
i.Collection?.CollectionType ?? CollectionType.Collection,
i.Collection?.UseCustomPlaybackOrder ?? false,
i.Collection?.State ?? MediaItemState.Normal,
i.ScheduleAsGroup,
i.PlaybackOrder)).ToList() ?? [],
mc.SmartItems?.Select(s => new MultiCollectionSmartItemApiResponse(
s.MultiCollectionId,
s.SmartCollection?.Id ?? 0,
s.SmartCollection?.Name ?? "",
s.SmartCollection?.Query ?? "",
s.ScheduleAsGroup,
s.PlaybackOrder)).ToList() ?? []);
private static MediaItemApiResponse? ToMediaItemApiResponse(NamedMediaItemViewModel? m) =>
m == null ? null : new(m.MediaItemId, m.Name);
private static WatermarkApiResponse ToApiResponse(WatermarkViewModel w) =>
new(w.Id, w.Name, w.Image?.Path, w.Mode, w.ImageSource, w.Location, w.Size, w.Width,
w.HorizontalMargin, w.VerticalMargin, w.FrequencyMinutes, w.DurationSeconds,
w.Opacity, w.PlaceWithinSourceContent, w.OpacityExpression, w.ZIndex);
private static GraphicsElementApiResponse ToApiResponse(GraphicsElementViewModel g) =>
new(g.Id, g.Name, g.FileName);
private static BlockItemApiResponse ToApiResponse(BlockItemViewModel item) =>
new(
item.Id,
item.Index,
item.CollectionType,
ToApiResponse(item.Collection),
ToApiResponse(item.MultiCollection),
ToApiResponse(item.SmartCollection),
ToMediaItemApiResponse(item.MediaItem),
item.SearchTitle,
item.SearchQuery,
item.PlaybackOrder,
item.IncludeInProgramGuide,
item.DisableWatermarks,
item.Watermarks?.Select(ToApiResponse).ToList() ?? [],
item.GraphicsElements?.Select(ToApiResponse).ToList() ?? []);
// Block Groups
[HttpGet("/api/blocks/groups", Name = "GetBlockGroups")]
[Tags("Blocks")]
[EndpointSummary("Get all block groups")]
[EndpointGroupName("general")]
public async Task<List<BlockGroupViewModel>> GetBlockGroups(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllBlockGroups(), cancellationToken);
[HttpPost("/api/blocks/groups", Name = "CreateBlockGroup")]
[Tags("Blocks")]
[EndpointSummary("Create a block group")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateBlockGroup(
[Required] [FromBody] CreateBlockGroupRequest request,
CancellationToken cancellationToken)
@ -36,6 +95,7 @@ public class BlockController(IMediator mediator) : ControllerBase @@ -36,6 +95,7 @@ public class BlockController(IMediator mediator) : ControllerBase
[HttpDelete("/api/blocks/groups/{id:int}", Name = "DeleteBlockGroup")]
[Tags("Blocks")]
[EndpointSummary("Delete a block group")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteBlockGroup(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteBlockGroup(id), cancellationToken);
@ -46,18 +106,21 @@ public class BlockController(IMediator mediator) : ControllerBase @@ -46,18 +106,21 @@ public class BlockController(IMediator mediator) : ControllerBase
[HttpGet("/api/blocks/groups/{groupId:int}/blocks", Name = "GetBlocksByGroup")]
[Tags("Blocks")]
[EndpointSummary("Get blocks by group")]
[EndpointGroupName("general")]
public async Task<List<BlockViewModel>> GetBlocksByGroup(int groupId, CancellationToken cancellationToken) =>
await mediator.Send(new GetBlocksByBlockGroupId(groupId), cancellationToken);
[HttpGet("/api/blocks", Name = "GetAllBlocks")]
[Tags("Blocks")]
[EndpointSummary("Get all blocks")]
[EndpointGroupName("general")]
public async Task<List<BlockViewModel>> GetAllBlocks(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllBlocks(), cancellationToken);
[HttpGet("/api/blocks/{id:int}", Name = "GetBlockById")]
[Tags("Blocks")]
[EndpointSummary("Get block by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetBlockById(int id, CancellationToken cancellationToken)
{
Option<BlockViewModel> result = await mediator.Send(new GetBlockById(id), cancellationToken);
@ -67,12 +130,17 @@ public class BlockController(IMediator mediator) : ControllerBase @@ -67,12 +130,17 @@ public class BlockController(IMediator mediator) : ControllerBase
[HttpGet("/api/blocks/{id:int}/items", Name = "GetBlockItems")]
[Tags("Blocks")]
[EndpointSummary("Get block items")]
public async Task<List<BlockItemViewModel>> GetBlockItems(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetBlockItems(id), cancellationToken);
[EndpointGroupName("general")]
public async Task<List<BlockItemApiResponse>> GetBlockItems(int id, CancellationToken cancellationToken)
{
List<BlockItemViewModel> items = await mediator.Send(new GetBlockItems(id), cancellationToken);
return items.Select(ToApiResponse).ToList();
}
[HttpPost("/api/blocks", Name = "CreateBlock")]
[Tags("Blocks")]
[EndpointSummary("Create a block")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateBlock(
[Required] [FromBody] CreateBlockRequest request,
CancellationToken cancellationToken)
@ -85,6 +153,7 @@ public class BlockController(IMediator mediator) : ControllerBase @@ -85,6 +153,7 @@ public class BlockController(IMediator mediator) : ControllerBase
[HttpDelete("/api/blocks/{id:int}", Name = "DeleteBlock")]
[Tags("Blocks")]
[EndpointSummary("Delete a block")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteBlock(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteBlock(id), cancellationToken);
@ -94,6 +163,7 @@ public class BlockController(IMediator mediator) : ControllerBase @@ -94,6 +163,7 @@ public class BlockController(IMediator mediator) : ControllerBase
[HttpPost("/api/blocks/{id:int}/copy", Name = "CopyBlock")]
[Tags("Blocks")]
[EndpointSummary("Copy a block")]
[EndpointGroupName("general")]
public async Task<IActionResult> CopyBlock(
int id,
[Required] [FromBody] CopyBlockRequest request,
@ -107,6 +177,7 @@ public class BlockController(IMediator mediator) : ControllerBase @@ -107,6 +177,7 @@ public class BlockController(IMediator mediator) : ControllerBase
[HttpPut("/api/blocks/{id:int}/items", Name = "ReplaceBlockItems")]
[Tags("Blocks")]
[EndpointSummary("Replace block items")]
[EndpointGroupName("general")]
public async Task<IActionResult> ReplaceBlockItems(
int id,
[Required] [FromBody] ReplaceBlockItemsRequest request,

11
ErsatzTV/Controllers/Api/ChannelController.cs

@ -16,18 +16,21 @@ using Microsoft.AspNetCore.Mvc; @@ -16,18 +16,21 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerChannel, IMediator mediator) : ControllerBase
{
[HttpGet("/api/channels", Name = "GetAllChannels")]
[Tags("Channels")]
[EndpointSummary("Get all channels")]
[EndpointGroupName("general")]
public async Task<List<ChannelResponseModel>> GetAll(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllChannelsForApi(), cancellationToken);
[HttpGet("/api/channels/{id:int}", Name = "GetChannelById")]
[Tags("Channels")]
[EndpointSummary("Get channel by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetChannelById(int id, CancellationToken cancellationToken)
{
Option<ChannelViewModel> result = await mediator.Send(new GetChannelById(id), cancellationToken);
@ -37,6 +40,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -37,6 +40,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpGet("/api/channels/by-number/{channelNumber}", Name = "GetChannelByNumber")]
[Tags("Channels")]
[EndpointSummary("Get channel by number")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetChannelByNumber(string channelNumber, CancellationToken cancellationToken)
{
Option<ChannelViewModel> result = await mediator.Send(new GetChannelByNumber(channelNumber), cancellationToken);
@ -46,6 +50,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -46,6 +50,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/channels", Name = "CreateChannel")]
[Tags("Channels")]
[EndpointSummary("Create a channel")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateChannel(
[Required] [FromBody] CreateChannelRequest request,
CancellationToken cancellationToken)
@ -89,6 +94,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -89,6 +94,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPut("/api/channels/{id:int}", Name = "UpdateChannel")]
[Tags("Channels")]
[EndpointSummary("Update a channel")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateChannel(
int id,
[Required] [FromBody] UpdateChannelRequest request,
@ -134,6 +140,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -134,6 +140,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpDelete("/api/channels/{id:int}", Name = "DeleteChannel")]
[Tags("Channels")]
[EndpointSummary("Delete a channel")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteChannel(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteChannel(id), cancellationToken);
@ -143,6 +150,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -143,6 +150,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/channels/{channelNumber}/playout/reset", Name = "ResetChannelPlayout")]
[Tags("Channels")]
[EndpointSummary("Reset channel playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> ResetPlayout(string channelNumber)
{
Option<int> maybePlayoutId = await mediator.Send(new GetPlayoutIdByChannelNumber(channelNumber));
@ -158,6 +166,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -158,6 +166,7 @@ public class ChannelController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/channels/{channelNumber}/playout/build", Name = "BuildChannelPlayout")]
[Tags("Channels")]
[EndpointSummary("Build channel playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> BuildPlayout(string channelNumber)
{
Option<int> maybePlayoutId = await mediator.Send(new GetPlayoutIdByChannelNumber(channelNumber));

69
ErsatzTV/Controllers/Api/CollectionController.cs

@ -11,38 +11,70 @@ using Microsoft.AspNetCore.Mvc; @@ -11,38 +11,70 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class CollectionController(IMediator mediator) : ControllerBase
{
private static CollectionApiResponse ToApiResponse(MediaCollectionViewModel collection) =>
new(collection.Id, collection.Name, collection.CollectionType, collection.UseCustomPlaybackOrder, collection.State);
private static MultiCollectionApiResponse ToApiResponse(MultiCollectionViewModel multiCollection) =>
new(
multiCollection.Id,
multiCollection.Name,
multiCollection.Items?.Select(i => new MultiCollectionItemApiResponse(
i.MultiCollectionId,
i.Collection?.Id ?? 0,
i.Collection?.Name ?? "",
i.Collection?.CollectionType ?? CollectionType.Collection,
i.Collection?.UseCustomPlaybackOrder ?? false,
i.Collection?.State ?? MediaItemState.Normal,
i.ScheduleAsGroup,
i.PlaybackOrder)).ToList() ?? [],
multiCollection.SmartItems?.Select(s => new MultiCollectionSmartItemApiResponse(
s.MultiCollectionId,
s.SmartCollection?.Id ?? 0,
s.SmartCollection?.Name ?? "",
s.SmartCollection?.Query ?? "",
s.ScheduleAsGroup,
s.PlaybackOrder)).ToList() ?? []);
// Collections
[HttpGet("/api/collections", Name = "GetCollections")]
[Tags("Collections")]
[EndpointSummary("Get all collections (paginated)")]
public async Task<PagedMediaCollectionsViewModel> GetCollections(
[EndpointGroupName("general")]
public async Task<PagedCollectionsApiResponse> GetCollections(
[FromQuery] string query = "",
[FromQuery] int pageNumber = 0,
[FromQuery] int pageSize = 50,
CancellationToken cancellationToken = default) =>
await mediator.Send(new GetPagedCollections(query, pageNumber, pageSize), cancellationToken);
CancellationToken cancellationToken = default)
{
PagedMediaCollectionsViewModel result = await mediator.Send(new GetPagedCollections(query, pageNumber, pageSize), cancellationToken);
return new PagedCollectionsApiResponse(result.TotalCount, result.Page.Select(ToApiResponse).ToList());
}
[HttpGet("/api/collections/all", Name = "GetAllCollections")]
[Tags("Collections")]
[EndpointSummary("Get all collections")]
public async Task<List<MediaCollectionViewModel>> GetAllCollections(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllCollections(), cancellationToken);
[EndpointGroupName("general")]
public async Task<List<CollectionApiResponse>> GetAllCollections(CancellationToken cancellationToken)
{
List<MediaCollectionViewModel> collections = await mediator.Send(new GetAllCollections(), cancellationToken);
return collections.Select(ToApiResponse).ToList();
}
[HttpGet("/api/collections/{id:int}", Name = "GetCollectionById")]
[Tags("Collections")]
[EndpointSummary("Get collection by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetCollectionById(int id, CancellationToken cancellationToken)
{
Option<MediaCollectionViewModel> result = await mediator.Send(new GetCollectionById(id), cancellationToken);
return result.Match<IActionResult>(Ok, () => NotFound());
return result.Match<IActionResult>(c => Ok(ToApiResponse(c)), () => NotFound());
}
[HttpGet("/api/collections/{id:int}/items", Name = "GetCollectionItems")]
[Tags("Collections")]
[EndpointSummary("Get collection items")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetCollectionItems(int id, CancellationToken cancellationToken)
{
Either<BaseError, CollectionCardResultsViewModel> result = await mediator.Send(new GetCollectionCards(id), cancellationToken);
@ -52,17 +84,19 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -52,17 +84,19 @@ public class CollectionController(IMediator mediator) : ControllerBase
[HttpPost("/api/collections", Name = "CreateCollection")]
[Tags("Collections")]
[EndpointSummary("Create a new collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateCollection(
[Required] [FromBody] CreateCollectionRequest request,
CancellationToken cancellationToken)
{
Either<BaseError, MediaCollectionViewModel> result = await mediator.Send(new CreateCollection(request.Name), cancellationToken);
return result.Match<IActionResult>(Ok, error => Problem(error.ToString()));
return result.Match<IActionResult>(c => Ok(ToApiResponse(c)), error => Problem(error.ToString()));
}
[HttpPut("/api/collections/{id:int}", Name = "UpdateCollection")]
[Tags("Collections")]
[EndpointSummary("Update a collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateCollection(
int id,
[Required] [FromBody] UpdateCollectionRequest request,
@ -75,6 +109,7 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -75,6 +109,7 @@ public class CollectionController(IMediator mediator) : ControllerBase
[HttpDelete("/api/collections/{id:int}", Name = "DeleteCollection")]
[Tags("Collections")]
[EndpointSummary("Delete a collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteCollection(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteCollection(id), cancellationToken);
@ -84,6 +119,7 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -84,6 +119,7 @@ public class CollectionController(IMediator mediator) : ControllerBase
[HttpPost("/api/collections/{id:int}/items", Name = "AddItemsToCollection")]
[Tags("Collections")]
[EndpointSummary("Add items to a collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddItemsToCollection(
int id,
[Required] [FromBody] AddItemsToCollectionRequest request,
@ -109,6 +145,7 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -109,6 +145,7 @@ public class CollectionController(IMediator mediator) : ControllerBase
[HttpDelete("/api/collections/{id:int}/items", Name = "RemoveItemsFromCollection")]
[Tags("Collections")]
[EndpointSummary("Remove items from a collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> RemoveItemsFromCollection(
int id,
[Required] [FromBody] RemoveItemsFromCollectionRequest request,
@ -123,21 +160,27 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -123,21 +160,27 @@ public class CollectionController(IMediator mediator) : ControllerBase
[HttpGet("/api/collections/multi", Name = "GetMultiCollections")]
[Tags("Collections")]
[EndpointSummary("Get all multi-collections")]
public async Task<List<MultiCollectionViewModel>> GetMultiCollections(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllMultiCollections(), cancellationToken);
[EndpointGroupName("general")]
public async Task<List<MultiCollectionApiResponse>> GetMultiCollections(CancellationToken cancellationToken)
{
List<MultiCollectionViewModel> collections = await mediator.Send(new GetAllMultiCollections(), cancellationToken);
return collections.Select(ToApiResponse).ToList();
}
[HttpGet("/api/collections/multi/{id:int}", Name = "GetMultiCollectionById")]
[Tags("Collections")]
[EndpointSummary("Get multi-collection by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetMultiCollectionById(int id, CancellationToken cancellationToken)
{
Option<MultiCollectionViewModel> result = await mediator.Send(new GetMultiCollectionById(id), cancellationToken);
return result.Match<IActionResult>(Ok, () => NotFound());
return result.Match<IActionResult>(c => Ok(ToApiResponse(c)), () => NotFound());
}
[HttpPost("/api/collections/multi", Name = "CreateMultiCollection")]
[Tags("Collections")]
[EndpointSummary("Create a multi-collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateMultiCollection(
[Required] [FromBody] CreateMultiCollectionRequest request,
CancellationToken cancellationToken)
@ -146,12 +189,13 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -146,12 +189,13 @@ public class CollectionController(IMediator mediator) : ControllerBase
i.CollectionId, i.SmartCollectionId, i.ScheduleAsGroup, i.PlaybackOrder)).ToList() ?? [];
Either<BaseError, MultiCollectionViewModel> result = await mediator.Send(
new CreateMultiCollection(request.Name, items), cancellationToken);
return result.Match<IActionResult>(Ok, error => Problem(error.ToString()));
return result.Match<IActionResult>(c => Ok(ToApiResponse(c)), error => Problem(error.ToString()));
}
[HttpPut("/api/collections/multi/{id:int}", Name = "UpdateMultiCollection")]
[Tags("Collections")]
[EndpointSummary("Update a multi-collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateMultiCollection(
int id,
[Required] [FromBody] UpdateMultiCollectionRequest request,
@ -167,6 +211,7 @@ public class CollectionController(IMediator mediator) : ControllerBase @@ -167,6 +211,7 @@ public class CollectionController(IMediator mediator) : ControllerBase
[HttpDelete("/api/collections/multi/{id:int}", Name = "DeleteMultiCollection")]
[Tags("Collections")]
[EndpointSummary("Delete a multi-collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteMultiCollection(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteMultiCollection(id), cancellationToken);

10
ErsatzTV/Controllers/Api/ConfigController.cs

@ -7,18 +7,20 @@ using Microsoft.AspNetCore.Mvc; @@ -7,18 +7,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class ConfigController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/config/playout", Name = "GetPlayoutSettings")]
[Tags("Configuration")]
[EndpointSummary("Get playout settings")]
[EndpointGroupName("general")]
public async Task<PlayoutSettingsViewModel> GetPlayoutSettings(CancellationToken cancellationToken) =>
await mediator.Send(new GetPlayoutSettings(), cancellationToken);
[HttpPut("/api/config/playout", Name = "UpdatePlayoutSettings")]
[Tags("Configuration")]
[EndpointSummary("Update playout settings")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdatePlayoutSettings(
[Required] [FromBody] PlayoutSettingsViewModel settings,
CancellationToken cancellationToken)
@ -30,12 +32,14 @@ public class ConfigController(IMediator mediator) : ControllerBase @@ -30,12 +32,14 @@ public class ConfigController(IMediator mediator) : ControllerBase
[HttpGet("/api/config/xmltv", Name = "GetXmltvSettings")]
[Tags("Configuration")]
[EndpointSummary("Get XMLTV settings")]
[EndpointGroupName("general")]
public async Task<XmltvSettingsViewModel> GetXmltvSettings(CancellationToken cancellationToken) =>
await mediator.Send(new GetXmltvSettings(), cancellationToken);
[HttpPut("/api/config/xmltv", Name = "UpdateXmltvSettings")]
[Tags("Configuration")]
[EndpointSummary("Update XMLTV settings")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateXmltvSettings(
[Required] [FromBody] XmltvSettingsViewModel settings,
CancellationToken cancellationToken)
@ -47,12 +51,14 @@ public class ConfigController(IMediator mediator) : ControllerBase @@ -47,12 +51,14 @@ public class ConfigController(IMediator mediator) : ControllerBase
[HttpGet("/api/config/logging", Name = "GetLoggingSettings")]
[Tags("Configuration")]
[EndpointSummary("Get logging settings")]
[EndpointGroupName("general")]
public async Task<LoggingSettingsViewModel> GetLoggingSettings(CancellationToken cancellationToken) =>
await mediator.Send(new GetLoggingSettings(), cancellationToken);
[HttpPut("/api/config/logging", Name = "UpdateLoggingSettings")]
[Tags("Configuration")]
[EndpointSummary("Update logging settings")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateLoggingSettings(
[Required] [FromBody] LoggingSettingsViewModel settings,
CancellationToken cancellationToken)
@ -64,12 +70,14 @@ public class ConfigController(IMediator mediator) : ControllerBase @@ -64,12 +70,14 @@ public class ConfigController(IMediator mediator) : ControllerBase
[HttpGet("/api/config/library-refresh-interval", Name = "GetLibraryRefreshInterval")]
[Tags("Configuration")]
[EndpointSummary("Get library refresh interval")]
[EndpointGroupName("general")]
public async Task<int> GetLibraryRefreshInterval(CancellationToken cancellationToken) =>
await mediator.Send(new GetLibraryRefreshInterval(), cancellationToken);
[HttpPut("/api/config/library-refresh-interval", Name = "UpdateLibraryRefreshInterval")]
[Tags("Configuration")]
[EndpointSummary("Update library refresh interval")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateLibraryRefreshInterval(
[Required] [FromBody] UpdateLibraryRefreshIntervalRequest request,
CancellationToken cancellationToken)

114
ErsatzTV/Controllers/Api/DecoController.cs

@ -1,7 +1,11 @@ @@ -1,7 +1,11 @@
#nullable enable
using System.ComponentModel.DataAnnotations;
using ErsatzTV.Application.Graphics;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Scheduling;
using ErsatzTV.Application.Tree;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
@ -12,19 +16,99 @@ using Microsoft.AspNetCore.Mvc; @@ -12,19 +16,99 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class DecoController(IMediator mediator) : ControllerBase
{
private static CollectionApiResponse? ToApiResponse(MediaCollectionViewModel? c) =>
c == null ? null : new(c.Id, c.Name, c.CollectionType, c.UseCustomPlaybackOrder, c.State);
private static SmartCollectionApiResponse? ToApiResponse(SmartCollectionViewModel? s) =>
s == null ? null : new(s.Id, s.Name, s.Query);
private static MultiCollectionApiResponse? ToApiResponse(MultiCollectionViewModel? mc) =>
mc == null ? null : new(
mc.Id,
mc.Name,
mc.Items?.Select(i => new MultiCollectionItemApiResponse(
i.MultiCollectionId,
i.Collection?.Id ?? 0,
i.Collection?.Name ?? "",
i.Collection?.CollectionType ?? CollectionType.Collection,
i.Collection?.UseCustomPlaybackOrder ?? false,
i.Collection?.State ?? MediaItemState.Normal,
i.ScheduleAsGroup,
i.PlaybackOrder)).ToList() ?? [],
mc.SmartItems?.Select(s => new MultiCollectionSmartItemApiResponse(
s.MultiCollectionId,
s.SmartCollection?.Id ?? 0,
s.SmartCollection?.Name ?? "",
s.SmartCollection?.Query ?? "",
s.ScheduleAsGroup,
s.PlaybackOrder)).ToList() ?? []);
private static MediaItemApiResponse? ToMediaItemApiResponse(NamedMediaItemViewModel? m) =>
m == null ? null : new(m.MediaItemId, m.Name);
private static PlaylistApiResponse? ToApiResponse(PlaylistViewModel? p) =>
p == null ? null : new(p.Id, p.PlaylistGroupId, p.Name, p.IsSystem);
private static WatermarkApiResponse ToApiResponse(WatermarkViewModel w) =>
new(w.Id, w.Name, w.Image?.Path, w.Mode, w.ImageSource, w.Location, w.Size, w.Width,
w.HorizontalMargin, w.VerticalMargin, w.FrequencyMinutes, w.DurationSeconds,
w.Opacity, w.PlaceWithinSourceContent, w.OpacityExpression, w.ZIndex);
private static GraphicsElementApiResponse ToApiResponse(GraphicsElementViewModel g) =>
new(g.Id, g.Name, g.FileName);
private static DecoApiResponse ToApiResponse(DecoViewModel deco) =>
new(
deco.Id,
deco.DecoGroupId,
deco.DecoGroupName,
deco.Name,
deco.WatermarkMode,
deco.Watermarks?.Select(ToApiResponse).ToList() ?? [],
deco.UseWatermarkDuringFiller,
deco.GraphicsElementsMode,
deco.GraphicsElements?.Select(ToApiResponse).ToList() ?? [],
deco.UseGraphicsElementsDuringFiller,
deco.BreakContentMode,
deco.BreakContent?.Select(ToApiResponse).ToList() ?? [],
deco.DefaultFillerMode,
deco.DefaultFillerCollectionType,
ToApiResponse(deco.DefaultFillerCollection),
ToMediaItemApiResponse(deco.DefaultFillerMediaItem),
ToApiResponse(deco.DefaultFillerMultiCollection),
ToApiResponse(deco.DefaultFillerSmartCollection),
deco.DefaultFillerTrimToFit,
deco.DeadAirFallbackMode,
deco.DeadAirFallbackCollectionType,
ToApiResponse(deco.DeadAirFallbackCollection),
ToMediaItemApiResponse(deco.DeadAirFallbackMediaItem),
ToApiResponse(deco.DeadAirFallbackMultiCollection),
ToApiResponse(deco.DeadAirFallbackSmartCollection));
private static DecoBreakContentApiResponse ToApiResponse(DecoBreakContentViewModel bc) =>
new(
bc.Id,
bc.CollectionType,
ToApiResponse(bc.Collection),
ToMediaItemApiResponse(bc.MediaItem),
ToApiResponse(bc.MultiCollection),
ToApiResponse(bc.SmartCollection),
ToApiResponse(bc.Playlist),
bc.Placement);
// Deco Groups
[HttpGet("/api/decos/groups", Name = "GetDecoGroups")]
[Tags("Decos")]
[EndpointSummary("Get all deco groups")]
[EndpointGroupName("general")]
public async Task<List<DecoGroupViewModel>> GetDecoGroups(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllDecoGroups(), cancellationToken);
[HttpPost("/api/decos/groups", Name = "CreateDecoGroup")]
[Tags("Decos")]
[EndpointSummary("Create a deco group")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateDecoGroup(
[Required] [FromBody] CreateDecoGroupRequest request,
CancellationToken cancellationToken)
@ -37,6 +121,7 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -37,6 +121,7 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpDelete("/api/decos/groups/{id:int}", Name = "DeleteDecoGroup")]
[Tags("Decos")]
[EndpointSummary("Delete a deco group")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteDecoGroup(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteDecoGroup(id), cancellationToken);
@ -47,39 +132,47 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -47,39 +132,47 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpGet("/api/decos/groups/{groupId:int}/decos", Name = "GetDecosByGroup")]
[Tags("Decos")]
[EndpointSummary("Get decos by group")]
public async Task<List<DecoViewModel>> GetDecosByGroup(int groupId, CancellationToken cancellationToken) =>
await mediator.Send(new GetDecosByDecoGroupId(groupId), cancellationToken);
[EndpointGroupName("general")]
public async Task<List<DecoApiResponse>> GetDecosByGroup(int groupId, CancellationToken cancellationToken)
{
List<DecoViewModel> decos = await mediator.Send(new GetDecosByDecoGroupId(groupId), cancellationToken);
return decos.Select(ToApiResponse).ToList();
}
[HttpGet("/api/decos/tree", Name = "GetDecoTree")]
[Tags("Decos")]
[EndpointSummary("Get deco tree")]
[EndpointGroupName("general")]
public async Task<TreeViewModel> GetDecoTree(CancellationToken cancellationToken) =>
await mediator.Send(new GetDecoTree(), cancellationToken);
[HttpGet("/api/decos/{id:int}", Name = "GetDecoById")]
[Tags("Decos")]
[EndpointSummary("Get deco by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetDecoById(int id, CancellationToken cancellationToken)
{
Option<DecoViewModel> result = await mediator.Send(new GetDecoById(id), cancellationToken);
return result.Match<IActionResult>(Ok, () => NotFound());
return result.Match<IActionResult>(d => Ok(ToApiResponse(d)), () => NotFound());
}
[HttpPost("/api/decos", Name = "CreateDeco")]
[Tags("Decos")]
[EndpointSummary("Create a deco")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateDeco(
[Required] [FromBody] CreateDecoRequest request,
CancellationToken cancellationToken)
{
Either<BaseError, DecoViewModel> result = await mediator.Send(
new CreateDeco(request.DecoGroupId, request.Name), cancellationToken);
return result.Match<IActionResult>(Ok, error => Problem(error.ToString()));
return result.Match<IActionResult>(d => Ok(ToApiResponse(d)), error => Problem(error.ToString()));
}
[HttpPut("/api/decos/{id:int}", Name = "UpdateDeco")]
[Tags("Decos")]
[EndpointSummary("Update a deco")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateDeco(
int id,
[Required] [FromBody] UpdateDecoRequest request,
@ -128,6 +221,7 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -128,6 +221,7 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpDelete("/api/decos/{id:int}", Name = "DeleteDeco")]
[Tags("Decos")]
[EndpointSummary("Delete a deco")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteDeco(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteDeco(id), cancellationToken);
@ -137,6 +231,7 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -137,6 +231,7 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpPut("/api/playouts/{playoutId:int}/deco", Name = "UpdateDefaultDeco")]
[Tags("Decos")]
[EndpointSummary("Update default deco for playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateDefaultDeco(
int playoutId,
[Required] [FromBody] UpdateDefaultDecoRequest request,
@ -151,12 +246,14 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -151,12 +246,14 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpGet("/api/decos/template-groups", Name = "GetDecoTemplateGroups")]
[Tags("Decos")]
[EndpointSummary("Get all deco template groups")]
[EndpointGroupName("general")]
public async Task<List<DecoTemplateGroupViewModel>> GetDecoTemplateGroups(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllDecoTemplateGroups(), cancellationToken);
[HttpPost("/api/decos/template-groups", Name = "CreateDecoTemplateGroup")]
[Tags("Decos")]
[EndpointSummary("Create a deco template group")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateDecoTemplateGroup(
[Required] [FromBody] CreateDecoTemplateGroupRequest request,
CancellationToken cancellationToken)
@ -169,6 +266,7 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -169,6 +266,7 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpDelete("/api/decos/template-groups/{id:int}", Name = "DeleteDecoTemplateGroup")]
[Tags("Decos")]
[EndpointSummary("Delete a deco template group")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteDecoTemplateGroup(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteDecoTemplateGroup(id), cancellationToken);
@ -179,18 +277,21 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -179,18 +277,21 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpGet("/api/decos/template-groups/{groupId:int}/templates", Name = "GetDecoTemplatesByGroup")]
[Tags("Decos")]
[EndpointSummary("Get deco templates by group")]
[EndpointGroupName("general")]
public async Task<List<DecoTemplateViewModel>> GetDecoTemplatesByGroup(int groupId, CancellationToken cancellationToken) =>
await mediator.Send(new GetDecoTemplatesByDecoTemplateGroupId(groupId), cancellationToken);
[HttpGet("/api/decos/templates/tree", Name = "GetDecoTemplateTree")]
[Tags("Decos")]
[EndpointSummary("Get deco template tree")]
[EndpointGroupName("general")]
public async Task<TreeViewModel> GetDecoTemplateTree(CancellationToken cancellationToken) =>
await mediator.Send(new GetDecoTemplateTree(), cancellationToken);
[HttpGet("/api/decos/templates/{id:int}", Name = "GetDecoTemplateById")]
[Tags("Decos")]
[EndpointSummary("Get deco template by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetDecoTemplateById(int id, CancellationToken cancellationToken)
{
Option<DecoTemplateViewModel> result = await mediator.Send(new GetDecoTemplateById(id), cancellationToken);
@ -200,12 +301,14 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -200,12 +301,14 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpGet("/api/decos/templates/{id:int}/items", Name = "GetDecoTemplateItems")]
[Tags("Decos")]
[EndpointSummary("Get deco template items")]
[EndpointGroupName("general")]
public async Task<List<DecoTemplateItemViewModel>> GetDecoTemplateItems(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetDecoTemplateItems(id), cancellationToken);
[HttpPost("/api/decos/templates", Name = "CreateDecoTemplate")]
[Tags("Decos")]
[EndpointSummary("Create a deco template")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateDecoTemplate(
[Required] [FromBody] CreateDecoTemplateRequest request,
CancellationToken cancellationToken)
@ -218,6 +321,7 @@ public class DecoController(IMediator mediator) : ControllerBase @@ -218,6 +321,7 @@ public class DecoController(IMediator mediator) : ControllerBase
[HttpDelete("/api/decos/templates/{id:int}", Name = "DeleteDecoTemplate")]
[Tags("Decos")]
[EndpointSummary("Delete a deco template")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteDecoTemplate(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteDecoTemplate(id), cancellationToken);

9
ErsatzTV/Controllers/Api/EmbyController.cs

@ -10,18 +10,20 @@ using Microsoft.AspNetCore.Mvc; @@ -10,18 +10,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class EmbyController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/emby/sources", Name = "GetEmbyMediaSources")]
[Tags("Emby")]
[EndpointSummary("Get all Emby media sources")]
[EndpointGroupName("general")]
public async Task<List<EmbyMediaSourceViewModel>> GetEmbyMediaSources(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllEmbyMediaSources(), cancellationToken);
[HttpGet("/api/emby/sources/{id:int}", Name = "GetEmbyMediaSourceById")]
[Tags("Emby")]
[EndpointSummary("Get Emby media source by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetEmbyMediaSourceById(int id, CancellationToken cancellationToken)
{
Option<EmbyMediaSourceViewModel> result = await mediator.Send(new GetEmbyMediaSourceById(id), cancellationToken);
@ -31,18 +33,21 @@ public class EmbyController(IMediator mediator) : ControllerBase @@ -31,18 +33,21 @@ public class EmbyController(IMediator mediator) : ControllerBase
[HttpGet("/api/emby/sources/{id:int}/libraries", Name = "GetEmbyLibraries")]
[Tags("Emby")]
[EndpointSummary("Get Emby libraries by source")]
[EndpointGroupName("general")]
public async Task<List<EmbyLibraryViewModel>> GetEmbyLibraries(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetEmbyLibrariesBySourceId(id), cancellationToken);
[HttpGet("/api/emby/sources/{id:int}/path-replacements", Name = "GetEmbyPathReplacements")]
[Tags("Emby")]
[EndpointSummary("Get Emby path replacements by source")]
[EndpointGroupName("general")]
public async Task<List<EmbyPathReplacementViewModel>> GetEmbyPathReplacements(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetEmbyPathReplacementsBySourceId(id), cancellationToken);
[HttpPut("/api/emby/sources/{id:int}/path-replacements", Name = "UpdateEmbyPathReplacements")]
[Tags("Emby")]
[EndpointSummary("Update Emby path replacements")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateEmbyPathReplacements(
int id,
[Required] [FromBody] UpdateEmbyPathReplacementsRequest request,
@ -58,6 +63,7 @@ public class EmbyController(IMediator mediator) : ControllerBase @@ -58,6 +63,7 @@ public class EmbyController(IMediator mediator) : ControllerBase
[HttpPut("/api/emby/library-preferences", Name = "UpdateEmbyLibraryPreferences")]
[Tags("Emby")]
[EndpointSummary("Update Emby library preferences")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateEmbyLibraryPreferences(
[Required] [FromBody] UpdateEmbyLibraryPreferencesRequest request,
CancellationToken cancellationToken)
@ -72,6 +78,7 @@ public class EmbyController(IMediator mediator) : ControllerBase @@ -72,6 +78,7 @@ public class EmbyController(IMediator mediator) : ControllerBase
[HttpGet("/api/emby/secrets", Name = "GetEmbySecrets")]
[Tags("Emby")]
[EndpointSummary("Get Emby secrets")]
[EndpointGroupName("general")]
public async Task<EmbySecrets> GetEmbySecrets(CancellationToken cancellationToken) =>
await mediator.Send(new GetEmbySecrets(), cancellationToken);
}

10
ErsatzTV/Controllers/Api/FFmpegProfileController.cs

@ -9,18 +9,20 @@ using Microsoft.AspNetCore.Mvc; @@ -9,18 +9,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class FFmpegProfileController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/ffmpeg/profiles", Name = "GetFFmpegProfiles")]
[Tags("FFmpeg")]
[EndpointSummary("Get all FFmpeg profiles")]
[EndpointGroupName("general")]
public async Task<List<FFmpegFullProfileResponseModel>> GetAll(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllFFmpegProfilesForApi(), cancellationToken);
[HttpGet("/api/ffmpeg/profiles/{id:int}", Name = "GetFFmpegProfileById")]
[Tags("FFmpeg")]
[EndpointSummary("Get FFmpeg profile by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetFFmpegProfileById(int id, CancellationToken cancellationToken)
{
Option<FFmpegProfileViewModel> result = await mediator.Send(new GetFFmpegProfileById(id), cancellationToken);
@ -30,6 +32,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase @@ -30,6 +32,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase
[HttpPost("/api/ffmpeg/profiles/new", Name = "CreateFFmpegProfile")]
[Tags("FFmpeg")]
[EndpointSummary("Create FFmpeg profile")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddOne(
[Required] [FromBody]
CreateFFmpegProfile request,
@ -42,6 +45,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase @@ -42,6 +45,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase
[HttpPut("/api/ffmpeg/profiles/update", Name = "UpdateFFmpegProfile")]
[Tags("FFmpeg")]
[EndpointSummary("Update FFmpeg profile")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateOne(
[Required] [FromBody]
UpdateFFmpegProfile request,
@ -54,6 +58,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase @@ -54,6 +58,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase
[HttpDelete("/api/ffmpeg/delete/{id:int}", Name = "DeleteFFmpegProfile")]
[Tags("FFmpeg")]
[EndpointSummary("Delete FFmpeg profile")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteProfileAsync(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteFFmpegProfile(id), cancellationToken);
@ -63,6 +68,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase @@ -63,6 +68,7 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase
[HttpPost("/api/ffmpeg/profiles/{id:int}/copy", Name = "CopyFFmpegProfile")]
[Tags("FFmpeg")]
[EndpointSummary("Copy FFmpeg profile")]
[EndpointGroupName("general")]
public async Task<IActionResult> CopyFFmpegProfile(
int id,
[Required] [FromBody] CopyFFmpegProfileRequest request,
@ -76,12 +82,14 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase @@ -76,12 +82,14 @@ public class FFmpegProfileController(IMediator mediator) : ControllerBase
[HttpGet("/api/ffmpeg/settings", Name = "GetFFmpegSettings")]
[Tags("FFmpeg")]
[EndpointSummary("Get FFmpeg settings")]
[EndpointGroupName("general")]
public async Task<FFmpegSettingsViewModel> GetFFmpegSettings(CancellationToken cancellationToken) =>
await mediator.Send(new GetFFmpegSettings(), cancellationToken);
[HttpPut("/api/ffmpeg/settings", Name = "UpdateFFmpegSettings")]
[Tags("FFmpeg")]
[EndpointSummary("Update FFmpeg settings")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateFFmpegSettings(
[Required] [FromBody] FFmpegSettingsViewModel settings,
CancellationToken cancellationToken)

8
ErsatzTV/Controllers/Api/FillerController.cs

@ -11,12 +11,13 @@ using Microsoft.AspNetCore.Mvc; @@ -11,12 +11,13 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class FillerController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/filler/presets", Name = "GetFillerPresets")]
[Tags("Filler")]
[EndpointSummary("Get all filler presets (paginated)")]
[EndpointGroupName("general")]
public async Task<PagedFillerPresetsViewModel> GetFillerPresets(
[FromQuery] int pageNumber = 0,
[FromQuery] int pageSize = 50,
@ -26,12 +27,14 @@ public class FillerController(IMediator mediator) : ControllerBase @@ -26,12 +27,14 @@ public class FillerController(IMediator mediator) : ControllerBase
[HttpGet("/api/filler/presets/all", Name = "GetAllFillerPresets")]
[Tags("Filler")]
[EndpointSummary("Get all filler presets")]
[EndpointGroupName("general")]
public async Task<List<FillerPresetViewModel>> GetAllFillerPresets(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllFillerPresets(), cancellationToken);
[HttpGet("/api/filler/presets/{id:int}", Name = "GetFillerPresetById")]
[Tags("Filler")]
[EndpointSummary("Get filler preset by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetFillerPresetById(int id, CancellationToken cancellationToken)
{
Option<FillerPresetViewModel> result = await mediator.Send(new GetFillerPresetById(id), cancellationToken);
@ -41,6 +44,7 @@ public class FillerController(IMediator mediator) : ControllerBase @@ -41,6 +44,7 @@ public class FillerController(IMediator mediator) : ControllerBase
[HttpPost("/api/filler/presets", Name = "CreateFillerPreset")]
[Tags("Filler")]
[EndpointSummary("Create a filler preset")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateFillerPreset(
[Required] [FromBody] CreateFillerPresetRequest request,
CancellationToken cancellationToken)
@ -69,6 +73,7 @@ public class FillerController(IMediator mediator) : ControllerBase @@ -69,6 +73,7 @@ public class FillerController(IMediator mediator) : ControllerBase
[HttpPut("/api/filler/presets/{id:int}", Name = "UpdateFillerPreset")]
[Tags("Filler")]
[EndpointSummary("Update a filler preset")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateFillerPreset(
int id,
[Required] [FromBody] UpdateFillerPresetRequest request,
@ -99,6 +104,7 @@ public class FillerController(IMediator mediator) : ControllerBase @@ -99,6 +104,7 @@ public class FillerController(IMediator mediator) : ControllerBase
[HttpDelete("/api/filler/presets/{id:int}", Name = "DeleteFillerPreset")]
[Tags("Filler")]
[EndpointSummary("Delete a filler preset")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteFillerPreset(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteFillerPreset(id), cancellationToken);

9
ErsatzTV/Controllers/Api/JellyfinController.cs

@ -10,18 +10,20 @@ using Microsoft.AspNetCore.Mvc; @@ -10,18 +10,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class JellyfinController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/jellyfin/sources", Name = "GetJellyfinMediaSources")]
[Tags("Jellyfin")]
[EndpointSummary("Get all Jellyfin media sources")]
[EndpointGroupName("general")]
public async Task<List<JellyfinMediaSourceViewModel>> GetJellyfinMediaSources(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllJellyfinMediaSources(), cancellationToken);
[HttpGet("/api/jellyfin/sources/{id:int}", Name = "GetJellyfinMediaSourceById")]
[Tags("Jellyfin")]
[EndpointSummary("Get Jellyfin media source by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetJellyfinMediaSourceById(int id, CancellationToken cancellationToken)
{
Option<JellyfinMediaSourceViewModel> result = await mediator.Send(new GetJellyfinMediaSourceById(id), cancellationToken);
@ -31,18 +33,21 @@ public class JellyfinController(IMediator mediator) : ControllerBase @@ -31,18 +33,21 @@ public class JellyfinController(IMediator mediator) : ControllerBase
[HttpGet("/api/jellyfin/sources/{id:int}/libraries", Name = "GetJellyfinLibraries")]
[Tags("Jellyfin")]
[EndpointSummary("Get Jellyfin libraries by source")]
[EndpointGroupName("general")]
public async Task<List<JellyfinLibraryViewModel>> GetJellyfinLibraries(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetJellyfinLibrariesBySourceId(id), cancellationToken);
[HttpGet("/api/jellyfin/sources/{id:int}/path-replacements", Name = "GetJellyfinPathReplacements")]
[Tags("Jellyfin")]
[EndpointSummary("Get Jellyfin path replacements by source")]
[EndpointGroupName("general")]
public async Task<List<JellyfinPathReplacementViewModel>> GetJellyfinPathReplacements(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetJellyfinPathReplacementsBySourceId(id), cancellationToken);
[HttpPut("/api/jellyfin/sources/{id:int}/path-replacements", Name = "UpdateJellyfinPathReplacements")]
[Tags("Jellyfin")]
[EndpointSummary("Update Jellyfin path replacements")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateJellyfinPathReplacements(
int id,
[Required] [FromBody] UpdateJellyfinPathReplacementsRequest request,
@ -58,6 +63,7 @@ public class JellyfinController(IMediator mediator) : ControllerBase @@ -58,6 +63,7 @@ public class JellyfinController(IMediator mediator) : ControllerBase
[HttpPut("/api/jellyfin/library-preferences", Name = "UpdateJellyfinLibraryPreferences")]
[Tags("Jellyfin")]
[EndpointSummary("Update Jellyfin library preferences")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateJellyfinLibraryPreferences(
[Required] [FromBody] UpdateJellyfinLibraryPreferencesRequest request,
CancellationToken cancellationToken)
@ -72,6 +78,7 @@ public class JellyfinController(IMediator mediator) : ControllerBase @@ -72,6 +78,7 @@ public class JellyfinController(IMediator mediator) : ControllerBase
[HttpGet("/api/jellyfin/secrets", Name = "GetJellyfinSecrets")]
[Tags("Jellyfin")]
[EndpointSummary("Get Jellyfin secrets")]
[EndpointGroupName("general")]
public async Task<JellyfinSecrets> GetJellyfinSecrets(CancellationToken cancellationToken) =>
await mediator.Send(new GetJellyfinSecrets(), cancellationToken);
}

12
ErsatzTV/Controllers/Api/LibrariesController.cs

@ -11,24 +11,27 @@ using Microsoft.AspNetCore.Mvc; @@ -11,24 +11,27 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class LibrariesController(ITelevisionRepository televisionRepository, IMediator mediator) : ControllerBase
{
[HttpGet("/api/libraries", Name = "GetAllLibraries")]
[Tags("Libraries")]
[EndpointSummary("Get all configured libraries")]
[EndpointGroupName("general")]
public async Task<List<LibraryViewModel>> GetAllLibraries(CancellationToken cancellationToken) =>
await mediator.Send(new GetConfiguredLibraries(), cancellationToken);
[HttpGet("/api/libraries/local", Name = "GetLocalLibraries")]
[Tags("Libraries")]
[EndpointSummary("Get all local libraries")]
[EndpointGroupName("general")]
public async Task<List<LocalLibraryViewModel>> GetLocalLibraries(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllLocalLibraries(), cancellationToken);
[HttpGet("/api/libraries/local/{id:int}", Name = "GetLocalLibraryById")]
[Tags("Libraries")]
[EndpointSummary("Get local library by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetLocalLibraryById(int id, CancellationToken cancellationToken)
{
Option<LocalLibraryViewModel> result = await mediator.Send(new GetLocalLibraryById(id), cancellationToken);
@ -38,12 +41,14 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe @@ -38,12 +41,14 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe
[HttpGet("/api/libraries/local/{id:int}/paths", Name = "GetLocalLibraryPaths")]
[Tags("Libraries")]
[EndpointSummary("Get local library paths")]
[EndpointGroupName("general")]
public async Task<List<LocalLibraryPathViewModel>> GetLocalLibraryPaths(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetLocalLibraryPaths(id), cancellationToken);
[HttpPost("/api/libraries/local", Name = "CreateLocalLibrary")]
[Tags("Libraries")]
[EndpointSummary("Create a local library")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateLocalLibrary(
[Required] [FromBody] CreateLocalLibraryRequest request,
CancellationToken cancellationToken)
@ -56,6 +61,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe @@ -56,6 +61,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe
[HttpPut("/api/libraries/local/{id:int}", Name = "UpdateLocalLibrary")]
[Tags("Libraries")]
[EndpointSummary("Update a local library")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateLocalLibrary(
int id,
[Required] [FromBody] UpdateLocalLibraryRequest request,
@ -70,6 +76,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe @@ -70,6 +76,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe
[HttpDelete("/api/libraries/local/{id:int}", Name = "DeleteLocalLibrary")]
[Tags("Libraries")]
[EndpointSummary("Delete a local library")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteLocalLibrary(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteLocalLibrary(id), cancellationToken);
@ -79,6 +86,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe @@ -79,6 +86,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe
[HttpPost("/api/libraries/{id:int}/scan", Name = "ScanLibrary")]
[Tags("Libraries")]
[EndpointSummary("Scan library")]
[EndpointGroupName("general")]
public async Task<IActionResult> ScanLibrary(int id) =>
await mediator.Send(new QueueLibraryScanByLibraryId(id))
? Ok()
@ -87,6 +95,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe @@ -87,6 +95,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe
[HttpPost("/api/libraries/{id:int}/scan-show", Name = "ScanShow")]
[Tags("Libraries")]
[EndpointSummary("Scan show")]
[EndpointGroupName("general")]
public async Task<IActionResult> ScanShow(int id, [FromBody] ScanShowRequest request)
{
if (string.IsNullOrWhiteSpace(request.ShowTitle))
@ -112,6 +121,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe @@ -112,6 +121,7 @@ public class LibrariesController(ITelevisionRepository televisionRepository, IMe
[HttpGet("/api/libraries/external-collections", Name = "GetExternalCollections")]
[Tags("Libraries")]
[EndpointSummary("Get external collections")]
[EndpointGroupName("general")]
public async Task<List<LibraryViewModel>> GetExternalCollections(CancellationToken cancellationToken) =>
await mediator.Send(new GetExternalCollections(), cancellationToken);
}

6
ErsatzTV/Controllers/Api/MaintenanceController.cs

@ -6,12 +6,13 @@ using Microsoft.AspNetCore.Mvc; @@ -6,12 +6,13 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class MaintenanceController(IMediator mediator) : ControllerBase
{
[HttpPost("/api/maintenance/gc", Name = "GarbageCollection")]
[Tags("Maintenance")]
[EndpointSummary("Garbage collect")]
[EndpointGroupName("general")]
public async Task<IActionResult> GarbageCollection([FromQuery] bool force = false)
{
await mediator.Send(new ReleaseMemory(force));
@ -21,6 +22,7 @@ public class MaintenanceController(IMediator mediator) : ControllerBase @@ -21,6 +22,7 @@ public class MaintenanceController(IMediator mediator) : ControllerBase
[HttpPost("/api/maintenance/empty-trash", Name = "EmptyTrash")]
[Tags("Maintenance")]
[EndpointSummary("Empty trash")]
[EndpointGroupName("general")]
public async Task<IActionResult> EmptyTrash()
{
Either<BaseError, Unit> result = await mediator.Send(new EmptyTrash());
@ -30,6 +32,7 @@ public class MaintenanceController(IMediator mediator) : ControllerBase @@ -30,6 +32,7 @@ public class MaintenanceController(IMediator mediator) : ControllerBase
[HttpPost("/api/maintenance/delete-orphaned-artwork", Name = "DeleteOrphanedArtwork")]
[Tags("Maintenance")]
[EndpointSummary("Delete orphaned artwork")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteOrphanedArtwork()
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteOrphanedArtwork());
@ -39,6 +42,7 @@ public class MaintenanceController(IMediator mediator) : ControllerBase @@ -39,6 +42,7 @@ public class MaintenanceController(IMediator mediator) : ControllerBase
[HttpPost("/api/maintenance/delete-orphaned-subtitles", Name = "DeleteOrphanedSubtitles")]
[Tags("Maintenance")]
[EndpointSummary("Delete orphaned subtitles")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteOrphanedSubtitles()
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteOrphanedSubtitles());

8
ErsatzTV/Controllers/Api/MediaController.cs

@ -9,13 +9,14 @@ using Microsoft.AspNetCore.Mvc; @@ -9,13 +9,14 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class MediaController(IMediator mediator) : ControllerBase
{
// Movies
[HttpGet("/api/media/movies/{id:int}", Name = "GetMovieById")]
[Tags("Media")]
[EndpointSummary("Get movie by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetMovieById(int id, CancellationToken cancellationToken)
{
Option<MovieViewModel> result = await mediator.Send(new GetMovieById(id), cancellationToken);
@ -26,6 +27,7 @@ public class MediaController(IMediator mediator) : ControllerBase @@ -26,6 +27,7 @@ public class MediaController(IMediator mediator) : ControllerBase
[HttpGet("/api/media/shows/{id:int}", Name = "GetShowById")]
[Tags("Media")]
[EndpointSummary("Get TV show by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetShowById(int id, CancellationToken cancellationToken)
{
Option<TelevisionShowViewModel> result = await mediator.Send(new GetTelevisionShowById(id), cancellationToken);
@ -35,6 +37,7 @@ public class MediaController(IMediator mediator) : ControllerBase @@ -35,6 +37,7 @@ public class MediaController(IMediator mediator) : ControllerBase
[HttpGet("/api/media/shows/{id:int}/seasons", Name = "GetShowSeasons")]
[Tags("Media")]
[EndpointSummary("Get seasons for a TV show")]
[EndpointGroupName("general")]
public async Task<TelevisionSeasonCardResultsViewModel> GetShowSeasons(
int id,
[FromQuery] int pageNumber = 0,
@ -45,6 +48,7 @@ public class MediaController(IMediator mediator) : ControllerBase @@ -45,6 +48,7 @@ public class MediaController(IMediator mediator) : ControllerBase
[HttpGet("/api/media/seasons/{id:int}", Name = "GetSeasonById")]
[Tags("Media")]
[EndpointSummary("Get season by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetSeasonById(int id, CancellationToken cancellationToken)
{
Option<TelevisionSeasonViewModel> result = await mediator.Send(new GetTelevisionSeasonById(id), cancellationToken);
@ -54,6 +58,7 @@ public class MediaController(IMediator mediator) : ControllerBase @@ -54,6 +58,7 @@ public class MediaController(IMediator mediator) : ControllerBase
[HttpGet("/api/media/seasons/{id:int}/episodes", Name = "GetSeasonEpisodes")]
[Tags("Media")]
[EndpointSummary("Get episodes for a season")]
[EndpointGroupName("general")]
public async Task<TelevisionEpisodeCardResultsViewModel> GetSeasonEpisodes(
int id,
[FromQuery] int pageNumber = 0,
@ -65,6 +70,7 @@ public class MediaController(IMediator mediator) : ControllerBase @@ -65,6 +70,7 @@ public class MediaController(IMediator mediator) : ControllerBase
[HttpGet("/api/media/artists/{id:int}", Name = "GetArtistById")]
[Tags("Media")]
[EndpointSummary("Get artist by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetArtistById(int id, CancellationToken cancellationToken)
{
Option<ArtistViewModel> result = await mediator.Send(new GetArtistById(id), cancellationToken);

62
ErsatzTV/Controllers/Api/PlaylistController.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
#nullable enable
using System.ComponentModel.DataAnnotations;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Tree;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
@ -11,19 +12,63 @@ using Microsoft.AspNetCore.Mvc; @@ -11,19 +12,63 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class PlaylistController(IMediator mediator) : ControllerBase
{
private static CollectionApiResponse? ToApiResponse(MediaCollectionViewModel? c) =>
c == null ? null : new(c.Id, c.Name, c.CollectionType, c.UseCustomPlaybackOrder, c.State);
private static SmartCollectionApiResponse? ToApiResponse(SmartCollectionViewModel? s) =>
s == null ? null : new(s.Id, s.Name, s.Query);
private static MultiCollectionApiResponse? ToApiResponse(MultiCollectionViewModel? mc) =>
mc == null ? null : new(
mc.Id,
mc.Name,
mc.Items?.Select(i => new MultiCollectionItemApiResponse(
i.MultiCollectionId,
i.Collection?.Id ?? 0,
i.Collection?.Name ?? "",
i.Collection?.CollectionType ?? CollectionType.Collection,
i.Collection?.UseCustomPlaybackOrder ?? false,
i.Collection?.State ?? MediaItemState.Normal,
i.ScheduleAsGroup,
i.PlaybackOrder)).ToList() ?? [],
mc.SmartItems?.Select(s => new MultiCollectionSmartItemApiResponse(
s.MultiCollectionId,
s.SmartCollection?.Id ?? 0,
s.SmartCollection?.Name ?? "",
s.SmartCollection?.Query ?? "",
s.ScheduleAsGroup,
s.PlaybackOrder)).ToList() ?? []);
private static MediaItemApiResponse? ToMediaItemApiResponse(NamedMediaItemViewModel? m) =>
m == null ? null : new(m.MediaItemId, m.Name);
private static PlaylistItemApiResponse ToApiResponse(PlaylistItemViewModel item) =>
new(
item.Id,
item.Index,
item.CollectionType,
ToApiResponse(item.Collection),
ToApiResponse(item.MultiCollection),
ToApiResponse(item.SmartCollection),
ToMediaItemApiResponse(item.MediaItem),
item.PlaybackOrder,
item.Count,
item.PlayAll,
item.IncludeInProgramGuide);
// Playlist Groups
[HttpGet("/api/playlists/groups", Name = "GetPlaylistGroups")]
[Tags("Playlists")]
[EndpointSummary("Get all playlist groups")]
[EndpointGroupName("general")]
public async Task<List<PlaylistGroupViewModel>> GetPlaylistGroups(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllPlaylistGroups(), cancellationToken);
[HttpPost("/api/playlists/groups", Name = "CreatePlaylistGroup")]
[Tags("Playlists")]
[EndpointSummary("Create a playlist group")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreatePlaylistGroup(
[Required] [FromBody] CreatePlaylistGroupRequest request,
CancellationToken cancellationToken)
@ -36,6 +81,7 @@ public class PlaylistController(IMediator mediator) : ControllerBase @@ -36,6 +81,7 @@ public class PlaylistController(IMediator mediator) : ControllerBase
[HttpDelete("/api/playlists/groups/{id:int}", Name = "DeletePlaylistGroup")]
[Tags("Playlists")]
[EndpointSummary("Delete a playlist group")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeletePlaylistGroup(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeletePlaylistGroup(id), cancellationToken);
@ -46,18 +92,21 @@ public class PlaylistController(IMediator mediator) : ControllerBase @@ -46,18 +92,21 @@ public class PlaylistController(IMediator mediator) : ControllerBase
[HttpGet("/api/playlists/groups/{groupId:int}/playlists", Name = "GetPlaylistsByGroup")]
[Tags("Playlists")]
[EndpointSummary("Get playlists by group")]
[EndpointGroupName("general")]
public async Task<List<PlaylistViewModel>> GetPlaylistsByGroup(int groupId, CancellationToken cancellationToken) =>
await mediator.Send(new GetPlaylistsByPlaylistGroupId(groupId), cancellationToken);
[HttpGet("/api/playlists/tree", Name = "GetPlaylistTree")]
[Tags("Playlists")]
[EndpointSummary("Get playlist tree")]
[EndpointGroupName("general")]
public async Task<TreeViewModel> GetPlaylistTree(CancellationToken cancellationToken) =>
await mediator.Send(new GetPlaylistTree(), cancellationToken);
[HttpGet("/api/playlists/{id:int}", Name = "GetPlaylistById")]
[Tags("Playlists")]
[EndpointSummary("Get playlist by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetPlaylistById(int id, CancellationToken cancellationToken)
{
Option<PlaylistViewModel> result = await mediator.Send(new GetPlaylistById(id), cancellationToken);
@ -67,12 +116,17 @@ public class PlaylistController(IMediator mediator) : ControllerBase @@ -67,12 +116,17 @@ public class PlaylistController(IMediator mediator) : ControllerBase
[HttpGet("/api/playlists/{id:int}/items", Name = "GetPlaylistItems")]
[Tags("Playlists")]
[EndpointSummary("Get playlist items")]
public async Task<List<PlaylistItemViewModel>> GetPlaylistItems(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetPlaylistItems(id), cancellationToken);
[EndpointGroupName("general")]
public async Task<List<PlaylistItemApiResponse>> GetPlaylistItems(int id, CancellationToken cancellationToken)
{
List<PlaylistItemViewModel> items = await mediator.Send(new GetPlaylistItems(id), cancellationToken);
return items.Select(ToApiResponse).ToList();
}
[HttpPost("/api/playlists", Name = "CreatePlaylistApi")]
[Tags("Playlists")]
[EndpointSummary("Create a playlist")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreatePlaylist(
[Required] [FromBody] CreatePlaylistRequest request,
CancellationToken cancellationToken)
@ -85,6 +139,7 @@ public class PlaylistController(IMediator mediator) : ControllerBase @@ -85,6 +139,7 @@ public class PlaylistController(IMediator mediator) : ControllerBase
[HttpDelete("/api/playlists/{id:int}", Name = "DeletePlaylist")]
[Tags("Playlists")]
[EndpointSummary("Delete a playlist")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeletePlaylist(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeletePlaylist(id), cancellationToken);
@ -94,6 +149,7 @@ public class PlaylistController(IMediator mediator) : ControllerBase @@ -94,6 +149,7 @@ public class PlaylistController(IMediator mediator) : ControllerBase
[HttpPut("/api/playlists/{id:int}/items", Name = "ReplacePlaylistItems")]
[Tags("Playlists")]
[EndpointSummary("Replace playlist items")]
[EndpointGroupName("general")]
public async Task<IActionResult> ReplacePlaylistItems(
int id,
[Required] [FromBody] ReplacePlaylistItemsRequest request,

16
ErsatzTV/Controllers/Api/PlayoutController.cs

@ -12,12 +12,13 @@ using Microsoft.AspNetCore.Mvc; @@ -12,12 +12,13 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerChannel, IMediator mediator) : ControllerBase
{
[HttpGet("/api/playouts", Name = "GetAllPlayouts")]
[Tags("Playouts")]
[EndpointSummary("Get all playouts (paginated)")]
[EndpointGroupName("general")]
public async Task<PagedPlayoutsViewModel> GetAllPlayouts(
[FromQuery] string query = "",
[FromQuery] int pageNumber = 0,
@ -28,12 +29,14 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -28,12 +29,14 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpGet("/api/playouts/block", Name = "GetBlockPlayouts")]
[Tags("Playouts")]
[EndpointSummary("Get all block playouts")]
[EndpointGroupName("general")]
public async Task<List<PlayoutNameViewModel>> GetBlockPlayouts(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllBlockPlayouts(), cancellationToken);
[HttpGet("/api/playouts/{id:int}", Name = "GetPlayoutById")]
[Tags("Playouts")]
[EndpointSummary("Get playout by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetPlayoutById(int id, CancellationToken cancellationToken)
{
Option<PlayoutNameViewModel> result = await mediator.Send(new GetPlayoutById(id), cancellationToken);
@ -43,6 +46,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -43,6 +46,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpGet("/api/playouts/{id:int}/items", Name = "GetPlayoutItems")]
[Tags("Playouts")]
[EndpointSummary("Get playout items")]
[EndpointGroupName("general")]
public async Task<PagedPlayoutItemsViewModel> GetPlayoutItems(
int id,
[FromQuery] bool showFiller = false,
@ -54,6 +58,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -54,6 +58,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/classic", Name = "CreateClassicPlayout")]
[Tags("Playouts")]
[EndpointSummary("Create a classic playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateClassicPlayout(
[Required] [FromBody] CreateClassicPlayoutRequest request,
CancellationToken cancellationToken)
@ -66,6 +71,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -66,6 +71,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/block", Name = "CreateBlockPlayout")]
[Tags("Playouts")]
[EndpointSummary("Create a block playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateBlockPlayout(
[Required] [FromBody] CreateBlockPlayoutRequest request,
CancellationToken cancellationToken)
@ -78,6 +84,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -78,6 +84,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/sequential", Name = "CreateSequentialPlayout")]
[Tags("Playouts")]
[EndpointSummary("Create a sequential playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateSequentialPlayout(
[Required] [FromBody] CreateSequentialPlayoutRequest request,
CancellationToken cancellationToken)
@ -90,6 +97,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -90,6 +97,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/scripted", Name = "CreateScriptedPlayout")]
[Tags("Playouts")]
[EndpointSummary("Create a scripted playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateScriptedPlayout(
[Required] [FromBody] CreateScriptedPlayoutRequest request,
CancellationToken cancellationToken)
@ -102,6 +110,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -102,6 +110,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/external-json", Name = "CreateExternalJsonPlayout")]
[Tags("Playouts")]
[EndpointSummary("Create an external JSON playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateExternalJsonPlayout(
[Required] [FromBody] CreateExternalJsonPlayoutRequest request,
CancellationToken cancellationToken)
@ -114,6 +123,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -114,6 +123,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpDelete("/api/playouts/{id:int}", Name = "DeletePlayout")]
[Tags("Playouts")]
[EndpointSummary("Delete a playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeletePlayout(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeletePlayout(id), cancellationToken);
@ -123,6 +133,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -123,6 +133,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/{id:int}/build", Name = "BuildPlayout")]
[Tags("Playouts")]
[EndpointSummary("Build a playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> BuildPlayout(int id)
{
await workerChannel.WriteAsync(new BuildPlayout(id, PlayoutBuildMode.Refresh));
@ -132,6 +143,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -132,6 +143,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/{id:int}/reset", Name = "ResetPlayout")]
[Tags("Playouts")]
[EndpointSummary("Reset a playout")]
[EndpointGroupName("general")]
public async Task<IActionResult> ResetPlayout(int id)
{
await workerChannel.WriteAsync(new BuildPlayout(id, PlayoutBuildMode.Reset));
@ -141,6 +153,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -141,6 +153,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpPost("/api/playouts/reset-all", Name = "ResetAllPlayouts")]
[Tags("Playouts")]
[EndpointSummary("Reset all playouts")]
[EndpointGroupName("general")]
public async Task<IActionResult> ResetAllPlayouts(CancellationToken cancellationToken)
{
await mediator.Send(new ResetAllPlayouts(), cancellationToken);
@ -150,6 +163,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh @@ -150,6 +163,7 @@ public class PlayoutController(ChannelWriter<IBackgroundServiceRequest> workerCh
[HttpGet("/api/playouts/warnings/count", Name = "GetPlayoutWarningsCount")]
[Tags("Playouts")]
[EndpointSummary("Get playout warnings count")]
[EndpointGroupName("general")]
public async Task<int> GetPlayoutWarningsCount(CancellationToken cancellationToken) =>
await mediator.Send(new GetPlayoutWarningsCount(), cancellationToken);
}

8
ErsatzTV/Controllers/Api/PlexController.cs

@ -9,18 +9,20 @@ using Microsoft.AspNetCore.Mvc; @@ -9,18 +9,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class PlexController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/plex/sources", Name = "GetPlexMediaSources")]
[Tags("Plex")]
[EndpointSummary("Get all Plex media sources")]
[EndpointGroupName("general")]
public async Task<List<PlexMediaSourceViewModel>> GetPlexMediaSources(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllPlexMediaSources(), cancellationToken);
[HttpGet("/api/plex/sources/{id:int}", Name = "GetPlexMediaSourceById")]
[Tags("Plex")]
[EndpointSummary("Get Plex media source by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetPlexMediaSourceById(int id, CancellationToken cancellationToken)
{
Option<PlexMediaSourceViewModel> result = await mediator.Send(new GetPlexMediaSourceById(id), cancellationToken);
@ -30,18 +32,21 @@ public class PlexController(IMediator mediator) : ControllerBase @@ -30,18 +32,21 @@ public class PlexController(IMediator mediator) : ControllerBase
[HttpGet("/api/plex/sources/{id:int}/libraries", Name = "GetPlexLibraries")]
[Tags("Plex")]
[EndpointSummary("Get Plex libraries by source")]
[EndpointGroupName("general")]
public async Task<List<PlexLibraryViewModel>> GetPlexLibraries(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetPlexLibrariesBySourceId(id), cancellationToken);
[HttpGet("/api/plex/sources/{id:int}/path-replacements", Name = "GetPlexPathReplacements")]
[Tags("Plex")]
[EndpointSummary("Get Plex path replacements by source")]
[EndpointGroupName("general")]
public async Task<List<PlexPathReplacementViewModel>> GetPlexPathReplacements(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetPlexPathReplacementsBySourceId(id), cancellationToken);
[HttpPut("/api/plex/sources/{id:int}/path-replacements", Name = "UpdatePlexPathReplacements")]
[Tags("Plex")]
[EndpointSummary("Update Plex path replacements")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdatePlexPathReplacements(
int id,
[Required] [FromBody] UpdatePlexPathReplacementsRequest request,
@ -57,6 +62,7 @@ public class PlexController(IMediator mediator) : ControllerBase @@ -57,6 +62,7 @@ public class PlexController(IMediator mediator) : ControllerBase
[HttpPut("/api/plex/library-preferences", Name = "UpdatePlexLibraryPreferences")]
[Tags("Plex")]
[EndpointSummary("Update Plex library preferences")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdatePlexLibraryPreferences(
[Required] [FromBody] UpdatePlexLibraryPreferencesRequest request,
CancellationToken cancellationToken)

6
ErsatzTV/Controllers/Api/ResolutionController.cs

@ -7,18 +7,20 @@ using Microsoft.AspNetCore.Mvc; @@ -7,18 +7,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class ResolutionController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/resolutions", Name = "GetAllResolutions")]
[Tags("Resolutions")]
[EndpointSummary("Get all resolutions")]
[EndpointGroupName("general")]
public async Task<List<ResolutionViewModel>> GetAllResolutions(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllResolutions(), cancellationToken);
[HttpGet("/api/resolutions/by-name/{name}", Name = "GetResolutionByName")]
[Tags("Resolutions")]
[EndpointSummary("Get resolution by name")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetResolutionByName(string name, CancellationToken cancellationToken)
{
Option<ResolutionViewModel> result = await mediator.Send(new GetResolutionByName(name), cancellationToken);
@ -28,6 +30,7 @@ public class ResolutionController(IMediator mediator) : ControllerBase @@ -28,6 +30,7 @@ public class ResolutionController(IMediator mediator) : ControllerBase
[HttpPost("/api/resolutions", Name = "CreateCustomResolution")]
[Tags("Resolutions")]
[EndpointSummary("Create a custom resolution")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateCustomResolution(
[Required] [FromBody] CreateCustomResolutionRequest request,
CancellationToken cancellationToken)
@ -40,6 +43,7 @@ public class ResolutionController(IMediator mediator) : ControllerBase @@ -40,6 +43,7 @@ public class ResolutionController(IMediator mediator) : ControllerBase
[HttpDelete("/api/resolutions/{id:int}", Name = "DeleteCustomResolution")]
[Tags("Resolutions")]
[EndpointSummary("Delete a custom resolution")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteCustomResolution(int id, CancellationToken cancellationToken)
{
Option<Core.BaseError> result = await mediator.Send(new DeleteCustomResolution(id), cancellationToken);

3
ErsatzTV/Controllers/Api/ScannerController.cs

@ -15,6 +15,7 @@ public class ScannerController( @@ -15,6 +15,7 @@ public class ScannerController(
{
[HttpPost("progress")]
[EndpointSummary("Scanner progress update")]
[EndpointGroupName("general")]
public async Task<IActionResult> Progress(Guid scanId, [FromBody] decimal percentComplete)
{
await scannerProxyService.Progress(scanId, percentComplete);
@ -23,6 +24,7 @@ public class ScannerController( @@ -23,6 +24,7 @@ public class ScannerController(
[HttpPost("items/reindex")]
[EndpointSummary("Scanner reindex items in search index")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateItems(
Guid scanId,
[FromBody] List<int> itemsToUpdate,
@ -38,6 +40,7 @@ public class ScannerController( @@ -38,6 +40,7 @@ public class ScannerController(
[HttpPost("items/remove")]
[EndpointSummary("Scanner remove items from search index")]
[EndpointGroupName("general")]
public async Task<IActionResult> RemoveItems(
Guid scanId,
[FromBody] List<int> itemsToRemove,

107
ErsatzTV/Controllers/Api/ScheduleController.cs

@ -1,6 +1,13 @@ @@ -1,6 +1,13 @@
#nullable enable
using System.ComponentModel.DataAnnotations;
using ErsatzTV.Application.Filler;
using ErsatzTV.Application.Graphics;
using ErsatzTV.Application.MediaCollections;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.ProgramSchedules;
using ErsatzTV.Application.Watermarks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling;
using LanguageExt;
using MediatR;
@ -9,12 +16,97 @@ using Microsoft.AspNetCore.Mvc; @@ -9,12 +16,97 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class ScheduleController(IMediator mediator) : ControllerBase
{
private static CollectionApiResponse? ToApiResponse(MediaCollectionViewModel? c) =>
c == null ? null : new(c.Id, c.Name, c.CollectionType, c.UseCustomPlaybackOrder, c.State);
private static SmartCollectionApiResponse? ToApiResponse(SmartCollectionViewModel? s) =>
s == null ? null : new(s.Id, s.Name, s.Query);
private static MultiCollectionApiResponse? ToApiResponse(MultiCollectionViewModel? mc) =>
mc == null ? null : new(
mc.Id,
mc.Name,
mc.Items?.Select(i => new MultiCollectionItemApiResponse(
i.MultiCollectionId,
i.Collection?.Id ?? 0,
i.Collection?.Name ?? "",
i.Collection?.CollectionType ?? CollectionType.Collection,
i.Collection?.UseCustomPlaybackOrder ?? false,
i.Collection?.State ?? MediaItemState.Normal,
i.ScheduleAsGroup,
i.PlaybackOrder)).ToList() ?? [],
mc.SmartItems?.Select(s => new MultiCollectionSmartItemApiResponse(
s.MultiCollectionId,
s.SmartCollection?.Id ?? 0,
s.SmartCollection?.Name ?? "",
s.SmartCollection?.Query ?? "",
s.ScheduleAsGroup,
s.PlaybackOrder)).ToList() ?? []);
private static MediaItemApiResponse? ToMediaItemApiResponse(NamedMediaItemViewModel? m) =>
m == null ? null : new(m.MediaItemId, m.Name);
private static RerunCollectionApiResponse? ToApiResponse(RerunCollectionViewModel? r) =>
r == null ? null : new(r.Id, r.Name);
private static PlaylistApiResponse? ToApiResponse(PlaylistViewModel? p) =>
p == null ? null : new(p.Id, p.PlaylistGroupId, p.Name, p.IsSystem);
private static FillerPresetApiResponse? ToApiResponse(FillerPresetViewModel? f) =>
f == null ? null : new(f.Id, f.Name);
private static WatermarkApiResponse ToApiResponse(WatermarkViewModel w) =>
new(w.Id, w.Name, w.Image?.Path, w.Mode, w.ImageSource, w.Location, w.Size, w.Width,
w.HorizontalMargin, w.VerticalMargin, w.FrequencyMinutes, w.DurationSeconds,
w.Opacity, w.PlaceWithinSourceContent, w.OpacityExpression, w.ZIndex);
private static GraphicsElementApiResponse ToApiResponse(GraphicsElementViewModel g) =>
new(g.Id, g.Name, g.FileName);
private static ScheduleItemApiResponse ToApiResponse(ProgramScheduleItemViewModel item) =>
new(
item.Id,
item.Index,
item.StartType,
item.StartTime,
item.FixedStartTimeBehavior,
item.PlayoutMode,
item.CollectionType,
ToApiResponse(item.Collection),
ToApiResponse(item.MultiCollection),
ToApiResponse(item.SmartCollection),
ToApiResponse(item.RerunCollection),
ToApiResponse(item.Playlist),
ToMediaItemApiResponse(item.MediaItem),
item.SearchTitle,
item.SearchQuery,
item.PlaybackOrder,
item.MarathonGroupBy,
item.MarathonShuffleGroups,
item.MarathonShuffleItems,
item.MarathonBatchSize,
item.FillWithGroupMode,
item.CustomTitle,
item.GuideMode,
ToApiResponse(item.PreRollFiller),
ToApiResponse(item.MidRollFiller),
ToApiResponse(item.PostRollFiller),
ToApiResponse(item.TailFiller),
ToApiResponse(item.FallbackFiller),
item.Watermarks?.Select(ToApiResponse).ToList() ?? [],
item.GraphicsElements?.Select(ToApiResponse).ToList() ?? [],
item.PreferredAudioLanguageCode,
item.PreferredAudioTitle,
item.PreferredSubtitleLanguageCode,
item.SubtitleMode,
item is ProgramScheduleItemMultipleViewModel multiple ? multiple.Count : null,
item is ProgramScheduleItemDurationViewModel duration ? duration.PlayoutDuration : null);
[HttpGet("/api/schedules", Name = "GetAllSchedules")]
[Tags("Schedules")]
[EndpointSummary("Get all schedules (paginated)")]
[EndpointGroupName("general")]
public async Task<PagedProgramSchedulesViewModel> GetAllSchedules(
[FromQuery] string query = "",
[FromQuery] int pageNumber = 0,
@ -25,12 +117,14 @@ public class ScheduleController(IMediator mediator) : ControllerBase @@ -25,12 +117,14 @@ public class ScheduleController(IMediator mediator) : ControllerBase
[HttpGet("/api/schedules/all", Name = "GetAllSchedulesList")]
[Tags("Schedules")]
[EndpointSummary("Get all schedules")]
[EndpointGroupName("general")]
public async Task<List<ProgramScheduleViewModel>> GetAllSchedulesList(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllProgramSchedules(), cancellationToken);
[HttpGet("/api/schedules/{id:int}", Name = "GetScheduleById")]
[Tags("Schedules")]
[EndpointSummary("Get schedule by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetScheduleById(int id, CancellationToken cancellationToken)
{
Option<ProgramScheduleViewModel> result = await mediator.Send(new GetProgramScheduleById(id), cancellationToken);
@ -40,12 +134,17 @@ public class ScheduleController(IMediator mediator) : ControllerBase @@ -40,12 +134,17 @@ public class ScheduleController(IMediator mediator) : ControllerBase
[HttpGet("/api/schedules/{id:int}/items", Name = "GetScheduleItems")]
[Tags("Schedules")]
[EndpointSummary("Get schedule items")]
public async Task<List<ProgramScheduleItemViewModel>> GetScheduleItems(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetProgramScheduleItems(id), cancellationToken);
[EndpointGroupName("general")]
public async Task<List<ScheduleItemApiResponse>> GetScheduleItems(int id, CancellationToken cancellationToken)
{
List<ProgramScheduleItemViewModel> items = await mediator.Send(new GetProgramScheduleItems(id), cancellationToken);
return items.Select(ToApiResponse).ToList();
}
[HttpPost("/api/schedules", Name = "CreateSchedule")]
[Tags("Schedules")]
[EndpointSummary("Create a schedule")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateSchedule(
[Required] [FromBody] CreateScheduleRequest request,
CancellationToken cancellationToken)
@ -65,6 +164,7 @@ public class ScheduleController(IMediator mediator) : ControllerBase @@ -65,6 +164,7 @@ public class ScheduleController(IMediator mediator) : ControllerBase
[HttpDelete("/api/schedules/{id:int}", Name = "DeleteSchedule")]
[Tags("Schedules")]
[EndpointSummary("Delete a schedule")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteSchedule(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteProgramSchedule(id), cancellationToken);
@ -74,6 +174,7 @@ public class ScheduleController(IMediator mediator) : ControllerBase @@ -74,6 +174,7 @@ public class ScheduleController(IMediator mediator) : ControllerBase
[HttpPost("/api/schedules/{id:int}/copy", Name = "CopySchedule")]
[Tags("Schedules")]
[EndpointSummary("Copy a schedule")]
[EndpointGroupName("general")]
public async Task<IActionResult> CopySchedule(
int id,
[Required] [FromBody] CopyScheduleRequest request,

28
ErsatzTV/Controllers/Api/ScriptedScheduleController.cs

@ -16,6 +16,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -16,6 +16,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpGet("context", Name = "GetContext")]
[Tags("Scripted Metadata")]
[EndpointSummary("Get the current context")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> GetContext([FromRoute] Guid buildId)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -30,6 +31,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -30,6 +31,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_collection", Name = "AddCollection")]
[Tags("Scripted Content")]
[EndpointSummary("Add a collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddCollection(
[FromRoute]
Guid buildId,
@ -55,6 +57,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -55,6 +57,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_marathon", Name = "AddMarathon")]
[Tags("Scripted Content")]
[EndpointSummary("Add a marathon")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddMarathon([FromRoute] Guid buildId, [FromBody] ContentMarathon request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -82,6 +85,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -82,6 +85,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_multi_collection", Name = "AddMultiCollection")]
[Tags("Scripted Content")]
[EndpointSummary("Add a multi-collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddMultiCollection(
[FromRoute]
Guid buildId,
@ -107,6 +111,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -107,6 +111,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_playlist", Name = "AddPlaylist")]
[Tags("Scripted Content")]
[EndpointSummary("Add a playlist")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddPlaylist(
[FromRoute]
Guid buildId,
@ -127,6 +132,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -127,6 +132,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("create_playlist", Name="CreatePlaylist")]
[Tags("Scripted Content")]
[EndpointSummary("Create a playlist")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreatePlaylist(
[FromRoute]
Guid buildId,
@ -150,6 +156,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -150,6 +156,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_search", Name = "AddSearch")]
[Tags("Scripted Content")]
[EndpointSummary("Add a search query")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddSearch(
[FromRoute]
Guid buildId,
@ -175,6 +182,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -175,6 +182,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_smart_collection", Name = "AddSmartCollection")]
[Tags("Scripted Content")]
[EndpointSummary("Add a smart collection")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddSmartCollection(
[FromRoute]
Guid buildId,
@ -200,6 +208,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -200,6 +208,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_show", Name = "AddShow")]
[Tags("Scripted Content")]
[EndpointSummary("Add a show")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddShow(
[FromRoute]
Guid buildId,
@ -225,6 +234,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -225,6 +234,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_all", Name = "AddAll")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Add all content")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> AddAll([FromRoute] Guid buildId, [FromBody] ContentAll request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -246,6 +256,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -246,6 +256,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_count", Name = "AddCount")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Add a specific number of content items")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> AddCount(
[FromRoute]
Guid buildId,
@ -276,6 +287,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -276,6 +287,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("add_duration", Name = "AddDuration")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Add content for a specific duration")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> AddDuration(
[FromRoute]
Guid buildId,
@ -311,6 +323,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -311,6 +323,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("pad_to_next", Name = "PadToNext")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Add content until a specific minutes interval")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> PadToNext(
[FromRoute] Guid buildId,
[FromBody] PlayoutPadToNext request)
@ -344,6 +357,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -344,6 +357,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("pad_until", Name = "PadUntil")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Add content until a specified time of day")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> PadUntil(
[FromRoute] Guid buildId,
[FromBody] PlayoutPadUntil request)
@ -378,6 +392,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -378,6 +392,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("pad_until_exact", Name = "PadUntilExact")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Add content until an exact time")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> PadUntilExact(
[FromRoute]
Guid buildId,
@ -413,6 +428,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -413,6 +428,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpGet("peek_next/{content}", Name="PeekNext")]
[Tags("Scripted Scheduling")]
[EndpointSummary("Peek the next content item")]
[EndpointGroupName("general")]
public ActionResult<PeekItemDuration> PeekNext(Guid buildId, string content)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -437,6 +453,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -437,6 +453,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("start_epg_group", Name = "StartEpgGroup")]
[Tags("Scripted Control")]
[EndpointSummary("Start an EPG group")]
[EndpointGroupName("general")]
public IActionResult StartEpgGroup([FromRoute] Guid buildId, [FromBody] ControlStartEpgGroup request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -452,6 +469,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -452,6 +469,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("stop_epg_group", Name = "StopEpgGroup")]
[Tags("Scripted Control")]
[EndpointSummary("Finish an EPG group")]
[EndpointGroupName("general")]
public IActionResult StopEpgGroup([FromRoute] Guid buildId)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -467,6 +485,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -467,6 +485,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("graphics_on", Name = "GraphicsOn")]
[Tags("Scripted Control")]
[EndpointSummary("Turn on graphics elements")]
[EndpointGroupName("general")]
public async Task<IActionResult> GraphicsOn(
[FromRoute]
Guid buildId,
@ -487,6 +506,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -487,6 +506,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("graphics_off", Name = "GraphicsOff")]
[Tags("Scripted Control")]
[EndpointSummary("Turn off graphics elements")]
[EndpointGroupName("general")]
public async Task<IActionResult> GraphicsOff(
[FromRoute]
Guid buildId,
@ -507,6 +527,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -507,6 +527,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("watermark_on", Name = "WatermarkOn")]
[Tags("Scripted Control")]
[EndpointSummary("Turn on watermarks")]
[EndpointGroupName("general")]
public async Task<IActionResult> WatermarkOn([FromRoute] Guid buildId, [FromBody] ControlWatermarkOn request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -522,6 +543,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -522,6 +543,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("watermark_off", Name = "WatermarkOff")]
[Tags("Scripted Control")]
[EndpointSummary("Turn off watermarks")]
[EndpointGroupName("general")]
public async Task<IActionResult> WatermarkOff([FromRoute] Guid buildId, [FromBody] ControlWatermarkOff request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -537,6 +559,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -537,6 +559,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("pre_roll_on", Name = "PreRollOn")]
[Tags("Scripted Control")]
[EndpointSummary("Turn on pre-roll playlist")]
[EndpointGroupName("general")]
public IActionResult PreRollOn([FromRoute] Guid buildId, [FromBody] ControlPreRollOn request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -552,6 +575,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -552,6 +575,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("pre_roll_off", Name = "PreRollOff")]
[Tags("Scripted Control")]
[EndpointSummary("Turn off pre-roll playlist")]
[EndpointGroupName("general")]
public IActionResult PreRollOff([FromRoute] Guid buildId)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -567,6 +591,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -567,6 +591,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("skip_items", Name = "SkipItems")]
[Tags("Scripted Control")]
[EndpointSummary("Skip a specific number of items")]
[EndpointGroupName("general")]
public IActionResult SkipItems([FromRoute] Guid buildId, [FromBody] ControlSkipItems request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -582,6 +607,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -582,6 +607,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("skip_to_item", Name = "SkipToItem")]
[Tags("Scripted Control")]
[EndpointSummary("Skip to a specific episode")]
[EndpointGroupName("general")]
public IActionResult SkipToItem([FromRoute] Guid buildId, [FromBody] ControlSkipToItem request)
{
ISchedulingEngine engine = scriptedPlayoutBuilderService.GetEngine(buildId);
@ -597,6 +623,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -597,6 +623,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("wait_until_exact", Name = "WaitUntilExact")]
[Tags("Scripted Control")]
[EndpointSummary("Wait until an exact time")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> WaitUntilExact(
[FromRoute]
Guid buildId,
@ -617,6 +644,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP @@ -617,6 +644,7 @@ public class ScriptedScheduleController(IScriptedPlayoutBuilderService scriptedP
[HttpPost("wait_until", Name = "WaitUntil")]
[Tags("Scripted Control")]
[EndpointSummary("Wait until the specified time of day")]
[EndpointGroupName("general")]
public ActionResult<PlayoutContext> WaitUntilTime(
[FromRoute]
Guid buildId,

11
ErsatzTV/Controllers/Api/SearchController.cs

@ -6,12 +6,13 @@ using Microsoft.AspNetCore.Mvc; @@ -6,12 +6,13 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class SearchController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/search", Name = "SearchAll")]
[Tags("Search")]
[EndpointSummary("Search all media types")]
[EndpointGroupName("general")]
public async Task<SearchResultAllItemsViewModel> SearchAll(
[FromQuery] string query,
CancellationToken cancellationToken) =>
@ -20,6 +21,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -20,6 +21,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/movies", Name = "SearchMovies")]
[Tags("Search")]
[EndpointSummary("Search movies")]
[EndpointGroupName("general")]
public async Task<MovieCardResultsViewModel> SearchMovies(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -30,6 +32,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -30,6 +32,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/shows", Name = "SearchShows")]
[Tags("Search")]
[EndpointSummary("Search TV shows")]
[EndpointGroupName("general")]
public async Task<TelevisionShowCardResultsViewModel> SearchShows(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -40,6 +43,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -40,6 +43,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/episodes", Name = "SearchEpisodes")]
[Tags("Search")]
[EndpointSummary("Search episodes")]
[EndpointGroupName("general")]
public async Task<TelevisionEpisodeCardResultsViewModel> SearchEpisodes(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -50,6 +54,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -50,6 +54,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/artists", Name = "SearchArtists")]
[Tags("Search")]
[EndpointSummary("Search artists")]
[EndpointGroupName("general")]
public async Task<ArtistCardResultsViewModel> SearchArtists(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -60,6 +65,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -60,6 +65,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/music-videos", Name = "SearchMusicVideos")]
[Tags("Search")]
[EndpointSummary("Search music videos")]
[EndpointGroupName("general")]
public async Task<MusicVideoCardResultsViewModel> SearchMusicVideos(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -70,6 +76,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -70,6 +76,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/songs", Name = "SearchSongs")]
[Tags("Search")]
[EndpointSummary("Search songs")]
[EndpointGroupName("general")]
public async Task<SongCardResultsViewModel> SearchSongs(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -80,6 +87,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -80,6 +87,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpGet("/api/search/images", Name = "SearchImages")]
[Tags("Search")]
[EndpointSummary("Search images")]
[EndpointGroupName("general")]
public async Task<ImageCardResultsViewModel> SearchImages(
[FromQuery] string query,
[FromQuery] int pageNumber = 0,
@ -90,6 +98,7 @@ public class SearchController(IMediator mediator) : ControllerBase @@ -90,6 +98,7 @@ public class SearchController(IMediator mediator) : ControllerBase
[HttpPost("/api/search/rebuild", Name = "RebuildSearchIndex")]
[Tags("Search")]
[EndpointSummary("Rebuild search index")]
[EndpointGroupName("general")]
public async Task<IActionResult> RebuildSearchIndex(CancellationToken cancellationToken)
{
await mediator.Send(new RebuildSearchIndex(), cancellationToken);

4
ErsatzTV/Controllers/Api/SessionController.cs

@ -5,17 +5,19 @@ using Microsoft.AspNetCore.Mvc; @@ -5,17 +5,19 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class SessionController(IFFmpegSegmenterService ffmpegSegmenterService)
{
[HttpGet("api/sessions")]
[Tags("Sessions")]
[EndpointSummary("Get sessions")]
[EndpointGroupName("general")]
public List<HlsSessionModel> GetSessions() => ffmpegSegmenterService.Workers.Map(w => w.GetModel()).ToList();
[HttpDelete("api/session/{channelNumber}")]
[Tags("Sessions")]
[EndpointSummary("Stop session")]
[EndpointGroupName("general")]
public async Task<IActionResult> StopSession(string channelNumber, CancellationToken cancellationToken)
{
if (await ffmpegSegmenterService.StopChannel(channelNumber, cancellationToken))

5
ErsatzTV/Controllers/Api/SmartCollectionController.cs

@ -8,14 +8,15 @@ using Microsoft.AspNetCore.Mvc; @@ -8,14 +8,15 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class SmartCollectionController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/collections/smart", Name="GetSmartCollections")]
[EndpointGroupName("general")]
public async Task<List<SmartCollectionResponseModel>> GetAll() =>
await mediator.Send(new GetAllSmartCollectionsForApi());
[HttpPost("/api/collections/smart/new", Name = "CreateSmartCollection")]
[EndpointGroupName("general")]
public async Task<IActionResult> AddOne(
[Required] [FromBody]
CreateSmartCollection request)
@ -26,6 +27,7 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase @@ -26,6 +27,7 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
}
[HttpPut("/api/collections/smart/update", Name="UpdateSmartCollection")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateOne(
[Required] [FromBody]
UpdateSmartCollection request)
@ -35,6 +37,7 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase @@ -35,6 +37,7 @@ public class SmartCollectionController(IMediator mediator) : ControllerBase
}
[HttpDelete("/api/collections/smart/delete/{id:int}", Name="DeleteSmartCollection")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteSmartCollection(int id)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteSmartCollection(id));

13
ErsatzTV/Controllers/Api/TemplateController.cs

@ -9,19 +9,21 @@ using Microsoft.AspNetCore.Mvc; @@ -9,19 +9,21 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class TemplateController(IMediator mediator) : ControllerBase
{
// Template Groups
[HttpGet("/api/templates/groups", Name = "GetTemplateGroups")]
[Tags("Templates")]
[EndpointSummary("Get all template groups")]
[EndpointGroupName("general")]
public async Task<List<TemplateGroupViewModel>> GetTemplateGroups(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllTemplateGroups(), cancellationToken);
[HttpPost("/api/templates/groups", Name = "CreateTemplateGroup")]
[Tags("Templates")]
[EndpointSummary("Create a template group")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateTemplateGroup(
[Required] [FromBody] CreateTemplateGroupRequest request,
CancellationToken cancellationToken)
@ -34,6 +36,7 @@ public class TemplateController(IMediator mediator) : ControllerBase @@ -34,6 +36,7 @@ public class TemplateController(IMediator mediator) : ControllerBase
[HttpDelete("/api/templates/groups/{id:int}", Name = "DeleteTemplateGroup")]
[Tags("Templates")]
[EndpointSummary("Delete a template group")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteTemplateGroup(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteTemplateGroup(id), cancellationToken);
@ -44,18 +47,21 @@ public class TemplateController(IMediator mediator) : ControllerBase @@ -44,18 +47,21 @@ public class TemplateController(IMediator mediator) : ControllerBase
[HttpGet("/api/templates/groups/{groupId:int}/templates", Name = "GetTemplatesByGroup")]
[Tags("Templates")]
[EndpointSummary("Get templates by group")]
[EndpointGroupName("general")]
public async Task<List<TemplateViewModel>> GetTemplatesByGroup(int groupId, CancellationToken cancellationToken) =>
await mediator.Send(new GetTemplatesByTemplateGroupId(groupId), cancellationToken);
[HttpGet("/api/templates", Name = "GetAllTemplates")]
[Tags("Templates")]
[EndpointSummary("Get all templates")]
[EndpointGroupName("general")]
public async Task<List<TemplateViewModel>> GetAllTemplates(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllTemplates(), cancellationToken);
[HttpGet("/api/templates/{id:int}", Name = "GetTemplateById")]
[Tags("Templates")]
[EndpointSummary("Get template by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetTemplateById(int id, CancellationToken cancellationToken)
{
Option<TemplateViewModel> result = await mediator.Send(new GetTemplateById(id), cancellationToken);
@ -65,12 +71,14 @@ public class TemplateController(IMediator mediator) : ControllerBase @@ -65,12 +71,14 @@ public class TemplateController(IMediator mediator) : ControllerBase
[HttpGet("/api/templates/{id:int}/items", Name = "GetTemplateItems")]
[Tags("Templates")]
[EndpointSummary("Get template items")]
[EndpointGroupName("general")]
public async Task<List<TemplateItemViewModel>> GetTemplateItems(int id, CancellationToken cancellationToken) =>
await mediator.Send(new GetTemplateItems(id), cancellationToken);
[HttpPost("/api/templates", Name = "CreateTemplate")]
[Tags("Templates")]
[EndpointSummary("Create a template")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateTemplate(
[Required] [FromBody] CreateTemplateRequest request,
CancellationToken cancellationToken)
@ -83,6 +91,7 @@ public class TemplateController(IMediator mediator) : ControllerBase @@ -83,6 +91,7 @@ public class TemplateController(IMediator mediator) : ControllerBase
[HttpDelete("/api/templates/{id:int}", Name = "DeleteTemplate")]
[Tags("Templates")]
[EndpointSummary("Delete a template")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteTemplate(int id, CancellationToken cancellationToken)
{
Option<BaseError> result = await mediator.Send(new DeleteTemplate(id), cancellationToken);
@ -92,6 +101,7 @@ public class TemplateController(IMediator mediator) : ControllerBase @@ -92,6 +101,7 @@ public class TemplateController(IMediator mediator) : ControllerBase
[HttpPost("/api/templates/{id:int}/copy", Name = "CopyTemplate")]
[Tags("Templates")]
[EndpointSummary("Copy a template")]
[EndpointGroupName("general")]
public async Task<IActionResult> CopyTemplate(
int id,
[Required] [FromBody] CopyTemplateRequest request,
@ -105,6 +115,7 @@ public class TemplateController(IMediator mediator) : ControllerBase @@ -105,6 +115,7 @@ public class TemplateController(IMediator mediator) : ControllerBase
[HttpPut("/api/templates/{id:int}/items", Name = "ReplaceTemplateItems")]
[Tags("Templates")]
[EndpointSummary("Replace template items")]
[EndpointGroupName("general")]
public async Task<IActionResult> ReplaceTemplateItems(
int id,
[Required] [FromBody] ReplaceTemplateItemsRequest request,

7
ErsatzTV/Controllers/Api/TraktController.cs

@ -8,12 +8,13 @@ using Microsoft.AspNetCore.Mvc; @@ -8,12 +8,13 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class TraktController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/trakt/lists", Name = "GetTraktLists")]
[Tags("Trakt")]
[EndpointSummary("Get all Trakt lists (paginated)")]
[EndpointGroupName("general")]
public async Task<PagedTraktListsViewModel> GetTraktLists(
[FromQuery] int pageNumber = 0,
[FromQuery] int pageSize = 50,
@ -23,6 +24,7 @@ public class TraktController(IMediator mediator) : ControllerBase @@ -23,6 +24,7 @@ public class TraktController(IMediator mediator) : ControllerBase
[HttpGet("/api/trakt/lists/{id:int}", Name = "GetTraktListById")]
[Tags("Trakt")]
[EndpointSummary("Get Trakt list by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetTraktListById(int id, CancellationToken cancellationToken)
{
Option<TraktListViewModel> result = await mediator.Send(new GetTraktListById(id), cancellationToken);
@ -32,6 +34,7 @@ public class TraktController(IMediator mediator) : ControllerBase @@ -32,6 +34,7 @@ public class TraktController(IMediator mediator) : ControllerBase
[HttpPut("/api/trakt/lists/{id:int}", Name = "UpdateTraktList")]
[Tags("Trakt")]
[EndpointSummary("Update a Trakt list")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateTraktList(
int id,
[Required] [FromBody] UpdateTraktListRequest request,
@ -45,6 +48,7 @@ public class TraktController(IMediator mediator) : ControllerBase @@ -45,6 +48,7 @@ public class TraktController(IMediator mediator) : ControllerBase
[HttpDelete("/api/trakt/lists/{id:int}", Name = "DeleteTraktList")]
[Tags("Trakt")]
[EndpointSummary("Delete a Trakt list")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteTraktList(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteTraktList(id), cancellationToken);
@ -54,6 +58,7 @@ public class TraktController(IMediator mediator) : ControllerBase @@ -54,6 +58,7 @@ public class TraktController(IMediator mediator) : ControllerBase
[HttpPost("/api/trakt/lists/{id:int}/match", Name = "MatchTraktListItems")]
[Tags("Trakt")]
[EndpointSummary("Match Trakt list items")]
[EndpointGroupName("general")]
public async Task<IActionResult> MatchTraktListItems(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new MatchTraktListItems(id), cancellationToken);

3
ErsatzTV/Controllers/Api/VersionController.cs

@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc; @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class VersionController
{
private static readonly CombinedVersion Version;
@ -19,6 +19,7 @@ public class VersionController @@ -19,6 +19,7 @@ public class VersionController
[HttpGet("/api/version", Name="GetVersion")]
[Tags("Version")]
[EndpointSummary("Get version")]
[EndpointGroupName("general")]
public CombinedVersion GetVersion() => Version;
public record CombinedVersion(int ApiVersion, string AppVersion);

8
ErsatzTV/Controllers/Api/WatermarkController.cs

@ -12,18 +12,20 @@ using Microsoft.AspNetCore.Mvc; @@ -12,18 +12,20 @@ using Microsoft.AspNetCore.Mvc;
namespace ErsatzTV.Controllers.Api;
[ApiController]
[EndpointGroupName("general")]
public class WatermarkController(IMediator mediator) : ControllerBase
{
[HttpGet("/api/watermarks", Name = "GetAllWatermarks")]
[Tags("Watermarks")]
[EndpointSummary("Get all watermarks")]
[EndpointGroupName("general")]
public async Task<List<WatermarkViewModel>> GetAllWatermarks(CancellationToken cancellationToken) =>
await mediator.Send(new GetAllWatermarks(), cancellationToken);
[HttpGet("/api/watermarks/{id:int}", Name = "GetWatermarkById")]
[Tags("Watermarks")]
[EndpointSummary("Get watermark by ID")]
[EndpointGroupName("general")]
public async Task<IActionResult> GetWatermarkById(int id, CancellationToken cancellationToken)
{
Option<WatermarkViewModel> result = await mediator.Send(new GetWatermarkById(id), cancellationToken);
@ -33,6 +35,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase @@ -33,6 +35,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase
[HttpPost("/api/watermarks", Name = "CreateWatermark")]
[Tags("Watermarks")]
[EndpointSummary("Create a watermark")]
[EndpointGroupName("general")]
public async Task<IActionResult> CreateWatermark(
[Required] [FromBody] CreateWatermarkRequest request,
CancellationToken cancellationToken)
@ -65,6 +68,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase @@ -65,6 +68,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase
[HttpPut("/api/watermarks/{id:int}", Name = "UpdateWatermark")]
[Tags("Watermarks")]
[EndpointSummary("Update a watermark")]
[EndpointGroupName("general")]
public async Task<IActionResult> UpdateWatermark(
int id,
[Required] [FromBody] UpdateWatermarkRequest request,
@ -99,6 +103,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase @@ -99,6 +103,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase
[HttpDelete("/api/watermarks/{id:int}", Name = "DeleteWatermark")]
[Tags("Watermarks")]
[EndpointSummary("Delete a watermark")]
[EndpointGroupName("general")]
public async Task<IActionResult> DeleteWatermark(int id, CancellationToken cancellationToken)
{
Either<BaseError, Unit> result = await mediator.Send(new DeleteWatermark(id), cancellationToken);
@ -108,6 +113,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase @@ -108,6 +113,7 @@ public class WatermarkController(IMediator mediator) : ControllerBase
[HttpPost("/api/watermarks/{id:int}/copy", Name = "CopyWatermark")]
[Tags("Watermarks")]
[EndpointSummary("Copy a watermark")]
[EndpointGroupName("general")]
public async Task<IActionResult> CopyWatermark(
int id,
[Required] [FromBody] CopyWatermarkRequest request,

Loading…
Cancel
Save