diff --git a/ErsatzTV.Application/Filler/Mapper.cs b/ErsatzTV.Application/Filler/Mapper.cs index 6a4ad35eb..1b72b5200 100644 --- a/ErsatzTV.Application/Filler/Mapper.cs +++ b/ErsatzTV.Application/Filler/Mapper.cs @@ -1,9 +1,13 @@ -using ErsatzTV.Core.Domain.Filler; +using ErsatzTV.Core.Api.Filler; +using ErsatzTV.Core.Domain.Filler; namespace ErsatzTV.Application.Filler; internal static class Mapper { + internal static FillerPresetResponseModel ProjectToResponseModel(FillerPreset fillerPreset) => + new(fillerPreset.Id, fillerPreset.Name); + internal static FillerPresetViewModel ProjectToViewModel(FillerPreset fillerPreset) => new( fillerPreset.Id, diff --git a/ErsatzTV.Application/Filler/Queries/GetAllFillerPresetsForApi.cs b/ErsatzTV.Application/Filler/Queries/GetAllFillerPresetsForApi.cs new file mode 100644 index 000000000..3252aae1f --- /dev/null +++ b/ErsatzTV.Application/Filler/Queries/GetAllFillerPresetsForApi.cs @@ -0,0 +1,5 @@ +using ErsatzTV.Core.Api.Filler; + +namespace ErsatzTV.Application.Filler; + +public record GetAllFillerPresetsForApi : IRequest>; diff --git a/ErsatzTV.Application/Filler/Queries/GetAllFillerPresetsForApiHandler.cs b/ErsatzTV.Application/Filler/Queries/GetAllFillerPresetsForApiHandler.cs new file mode 100644 index 000000000..b0c6edaf6 --- /dev/null +++ b/ErsatzTV.Application/Filler/Queries/GetAllFillerPresetsForApiHandler.cs @@ -0,0 +1,22 @@ +using ErsatzTV.Core.Api.Filler; +using ErsatzTV.Core.Domain.Filler; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using static ErsatzTV.Application.Filler.Mapper; + +namespace ErsatzTV.Application.Filler; + +public class GetAllFillerPresetsForApiHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> +{ + public async Task> Handle( + GetAllFillerPresetsForApi request, + CancellationToken cancellationToken) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + List fillerPresets = await dbContext.FillerPresets + .AsNoTracking() + .ToListAsync(cancellationToken); + return fillerPresets.Map(ProjectToResponseModel).ToList(); + } +} diff --git a/ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsForApi.cs b/ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsForApi.cs new file mode 100644 index 000000000..534ecf73f --- /dev/null +++ b/ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsForApi.cs @@ -0,0 +1,5 @@ +using ErsatzTV.Core.Api.Graphics; + +namespace ErsatzTV.Application.Graphics; + +public record GetAllGraphicsElementsForApi : IRequest>; diff --git a/ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsForApiHandler.cs b/ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsForApiHandler.cs new file mode 100644 index 000000000..c6131d4ab --- /dev/null +++ b/ErsatzTV.Application/Graphics/Queries/GetAllGraphicsElementsForApiHandler.cs @@ -0,0 +1,27 @@ +using ErsatzTV.Core.Api.Graphics; +using ErsatzTV.Core.Domain; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using static ErsatzTV.Application.Graphics.Mapper; + +namespace ErsatzTV.Application.Graphics; + +public class GetAllGraphicsElementsForApiHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> +{ + public async Task> Handle( + GetAllGraphicsElementsForApi request, + CancellationToken cancellationToken) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + List graphicsElements = await dbContext.GraphicsElements + .AsNoTracking() + .ToListAsync(cancellationToken); + return graphicsElements + .Map(ProjectToViewModel) + .OrderBy(e => e.Name == e.FileName) + .ThenBy(e => e.Name) + .Select(vm => new GraphicsElementResponseModel(vm.Id, vm.Name)) + .ToList(); + } +} diff --git a/ErsatzTV.Application/Watermarks/Mapper.cs b/ErsatzTV.Application/Watermarks/Mapper.cs index 537932caa..4869d2ddc 100644 --- a/ErsatzTV.Application/Watermarks/Mapper.cs +++ b/ErsatzTV.Application/Watermarks/Mapper.cs @@ -1,10 +1,14 @@ using ErsatzTV.Application.Artworks; +using ErsatzTV.Core.Api.Watermarks; using ErsatzTV.Core.Domain; namespace ErsatzTV.Application.Watermarks; internal static class Mapper { + internal static WatermarkResponseModel ProjectToResponseModel(ChannelWatermark watermark) => + new(watermark.Id, watermark.Name); + public static WatermarkViewModel ProjectToViewModel(ChannelWatermark watermark) => new( watermark.Id, diff --git a/ErsatzTV.Application/Watermarks/Queries/GetAllWatermarksForApi.cs b/ErsatzTV.Application/Watermarks/Queries/GetAllWatermarksForApi.cs new file mode 100644 index 000000000..acf3b86a9 --- /dev/null +++ b/ErsatzTV.Application/Watermarks/Queries/GetAllWatermarksForApi.cs @@ -0,0 +1,5 @@ +using ErsatzTV.Core.Api.Watermarks; + +namespace ErsatzTV.Application.Watermarks; + +public record GetAllWatermarksForApi : IRequest>; diff --git a/ErsatzTV.Application/Watermarks/Queries/GetAllWatermarksForApiHandler.cs b/ErsatzTV.Application/Watermarks/Queries/GetAllWatermarksForApiHandler.cs new file mode 100644 index 000000000..758fe6349 --- /dev/null +++ b/ErsatzTV.Application/Watermarks/Queries/GetAllWatermarksForApiHandler.cs @@ -0,0 +1,22 @@ +using ErsatzTV.Core.Api.Watermarks; +using ErsatzTV.Core.Domain; +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; +using static ErsatzTV.Application.Watermarks.Mapper; + +namespace ErsatzTV.Application.Watermarks; + +public class GetAllWatermarksForApiHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> +{ + public async Task> Handle( + GetAllWatermarksForApi request, + CancellationToken cancellationToken) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + List watermarks = await dbContext.ChannelWatermarks + .AsNoTracking() + .ToListAsync(cancellationToken); + return watermarks.Map(ProjectToResponseModel).ToList(); + } +} diff --git a/ErsatzTV.Core/Api/Filler/FillerPresetResponseModel.cs b/ErsatzTV.Core/Api/Filler/FillerPresetResponseModel.cs new file mode 100644 index 000000000..a237a0b3b --- /dev/null +++ b/ErsatzTV.Core/Api/Filler/FillerPresetResponseModel.cs @@ -0,0 +1,3 @@ +namespace ErsatzTV.Core.Api.Filler; + +public record FillerPresetResponseModel(int Id, string Name); diff --git a/ErsatzTV.Core/Api/Graphics/GraphicsElementResponseModel.cs b/ErsatzTV.Core/Api/Graphics/GraphicsElementResponseModel.cs new file mode 100644 index 000000000..fef16a496 --- /dev/null +++ b/ErsatzTV.Core/Api/Graphics/GraphicsElementResponseModel.cs @@ -0,0 +1,3 @@ +namespace ErsatzTV.Core.Api.Graphics; + +public record GraphicsElementResponseModel(int Id, string Name); diff --git a/ErsatzTV.Core/Api/Watermarks/WatermarkResponseModel.cs b/ErsatzTV.Core/Api/Watermarks/WatermarkResponseModel.cs new file mode 100644 index 000000000..656e5b550 --- /dev/null +++ b/ErsatzTV.Core/Api/Watermarks/WatermarkResponseModel.cs @@ -0,0 +1,3 @@ +namespace ErsatzTV.Core.Api.Watermarks; + +public record WatermarkResponseModel(int Id, string Name); diff --git a/ErsatzTV.Tests/Application/Filler/FillerPresetHandlerTests.cs b/ErsatzTV.Tests/Application/Filler/FillerPresetHandlerTests.cs new file mode 100644 index 000000000..6c810cffd --- /dev/null +++ b/ErsatzTV.Tests/Application/Filler/FillerPresetHandlerTests.cs @@ -0,0 +1,63 @@ +using ErsatzTV.Application.Filler; +using ErsatzTV.Core.Api.Filler; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Domain.Filler; +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Tests.Support; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Application.Filler; + +[TestFixture] +public class FillerPresetHandlerTests +{ + private InMemoryTvContext _db = null!; + + [SetUp] + public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync(); + + [TearDown] + public async Task TearDown() => await _db.DisposeAsync(); + + [Test] + public async Task GetAllFillerPresetsForApi_Should_Return_All_Presets() + { + await SeedPreset(1, "Intro"); + await SeedPreset(2, "Outro"); + + var handler = new GetAllFillerPresetsForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllFillerPresetsForApi(), CancellationToken.None); + + result.Count.ShouldBe(2); + result.ShouldContain(new FillerPresetResponseModel(1, "Intro")); + result.ShouldContain(new FillerPresetResponseModel(2, "Outro")); + } + + [Test] + public async Task GetAllFillerPresetsForApi_Should_Return_Empty_List_When_None_Exist() + { + var handler = new GetAllFillerPresetsForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllFillerPresetsForApi(), CancellationToken.None); + + result.ShouldBeEmpty(); + } + + private async Task SeedPreset(int id, string name) + { + await using TvContext context = _db.CreateContext(); + context.FillerPresets.Add(new FillerPreset + { + Id = id, + Name = name, + FillerKind = FillerKind.PreRoll, + FillerMode = FillerMode.Duration, + CollectionType = CollectionType.Collection + }); + await context.SaveChangesAsync(); + } +} diff --git a/ErsatzTV.Tests/Application/Graphics/GraphicsElementHandlerTests.cs b/ErsatzTV.Tests/Application/Graphics/GraphicsElementHandlerTests.cs new file mode 100644 index 000000000..d68fa1ca0 --- /dev/null +++ b/ErsatzTV.Tests/Application/Graphics/GraphicsElementHandlerTests.cs @@ -0,0 +1,77 @@ +using ErsatzTV.Application.Graphics; +using ErsatzTV.Core.Api.Graphics; +using ErsatzTV.Core.Domain; +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Tests.Support; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Application.Graphics; + +[TestFixture] +public class GraphicsElementHandlerTests +{ + private InMemoryTvContext _db = null!; + + [SetUp] + public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync(); + + [TearDown] + public async Task TearDown() => await _db.DisposeAsync(); + + [Test] + public async Task GetAllGraphicsElementsForApi_Should_Return_All_Elements() + { + await SeedElement(1, "watermark.png", GraphicsElementKind.Image, "Custom Watermark"); + await SeedElement(2, "clock.png", GraphicsElementKind.Image, string.Empty); + + var handler = new GetAllGraphicsElementsForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllGraphicsElementsForApi(), CancellationToken.None); + + result.Count.ShouldBe(2); + result.Select(e => e.Id).ShouldBe([1, 2]); + } + + [Test] + public async Task GetAllGraphicsElementsForApi_Should_Order_Named_Elements_Before_Unnamed() + { + // unnamed element's projected Name equals its FileName -> should sort after named elements + await SeedElement(1, "aaa.png", GraphicsElementKind.Image, string.Empty); + await SeedElement(2, "zzz.png", GraphicsElementKind.Image, "A Custom Name"); + + var handler = new GetAllGraphicsElementsForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllGraphicsElementsForApi(), CancellationToken.None); + + result.Count.ShouldBe(2); + result[0].Id.ShouldBe(2); + result[1].Id.ShouldBe(1); + } + + [Test] + public async Task GetAllGraphicsElementsForApi_Should_Return_Empty_List_When_None_Exist() + { + var handler = new GetAllGraphicsElementsForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllGraphicsElementsForApi(), CancellationToken.None); + + result.ShouldBeEmpty(); + } + + private async Task SeedElement(int id, string path, GraphicsElementKind kind, string name) + { + await using TvContext context = _db.CreateContext(); + context.GraphicsElements.Add(new GraphicsElement + { + Id = id, + Path = path, + Name = name, + Kind = kind + }); + await context.SaveChangesAsync(); + } +} diff --git a/ErsatzTV.Tests/Application/Watermarks/WatermarkHandlerTests.cs b/ErsatzTV.Tests/Application/Watermarks/WatermarkHandlerTests.cs new file mode 100644 index 000000000..173ce4798 --- /dev/null +++ b/ErsatzTV.Tests/Application/Watermarks/WatermarkHandlerTests.cs @@ -0,0 +1,71 @@ +using ErsatzTV.Application.Watermarks; +using ErsatzTV.Core.Api.Watermarks; +using ErsatzTV.Core.Domain; +using ErsatzTV.FFmpeg.State; +using ErsatzTV.Infrastructure.Data; +using ErsatzTV.Tests.Support; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Application.Watermarks; + +[TestFixture] +public class WatermarkHandlerTests +{ + private InMemoryTvContext _db = null!; + + [SetUp] + public async Task SetUp() => _db = await InMemoryTvContext.CreateAsync(); + + [TearDown] + public async Task TearDown() => await _db.DisposeAsync(); + + [Test] + public async Task GetAllWatermarksForApi_Should_Return_All_Watermarks() + { + await SeedWatermark(1, "Bug"); + await SeedWatermark(2, "Logo"); + + var handler = new GetAllWatermarksForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllWatermarksForApi(), CancellationToken.None); + + result.Count.ShouldBe(2); + result.ShouldContain(new WatermarkResponseModel(1, "Bug")); + result.ShouldContain(new WatermarkResponseModel(2, "Logo")); + } + + [Test] + public async Task GetAllWatermarksForApi_Should_Return_Empty_List_When_None_Exist() + { + var handler = new GetAllWatermarksForApiHandler(_db.Factory); + + List result = + await handler.Handle(new GetAllWatermarksForApi(), CancellationToken.None); + + result.ShouldBeEmpty(); + } + + private async Task SeedWatermark(int id, string name) + { + await using TvContext context = _db.CreateContext(); + context.ChannelWatermarks.Add(new ChannelWatermark + { + Id = id, + Name = name, + Mode = ChannelWatermarkMode.Permanent, + ImageSource = ChannelWatermarkImageSource.Custom, + Image = "watermark.png", + Location = WatermarkLocation.BottomRight, + Size = WatermarkSize.Scaled, + WidthPercent = 10, + HorizontalMarginPercent = 2, + VerticalMarginPercent = 2, + FrequencyMinutes = 15, + DurationSeconds = 30, + Opacity = 100 + }); + await context.SaveChangesAsync(); + } +} diff --git a/ErsatzTV.Tests/Controllers/FillerPresetControllerTests.cs b/ErsatzTV.Tests/Controllers/FillerPresetControllerTests.cs new file mode 100644 index 000000000..5747efc03 --- /dev/null +++ b/ErsatzTV.Tests/Controllers/FillerPresetControllerTests.cs @@ -0,0 +1,63 @@ +using System.Reflection; +using ErsatzTV.Application.Filler; +using ErsatzTV.Controllers.Api; +using ErsatzTV.Core.Api.Filler; +using MediatR; +using Microsoft.AspNetCore.Mvc.Routing; +using NSubstitute; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Controllers; + +[TestFixture] +public class FillerPresetControllerTests +{ + private FillerPresetController _controller = null!; + private IMediator _mediator = null!; + + [SetUp] + public void SetUp() + { + _mediator = Substitute.For(); + _controller = new FillerPresetController(_mediator); + } + + [Test] + public void Controller_Should_Expose_Idiomatic_Rest_Route() + { + MethodInfo action = typeof(FillerPresetController).GetMethod(nameof(FillerPresetController.GetAll)) + ?? throw new AssertionException("Missing action GetAll"); + + HttpMethodAttribute attribute = action.GetCustomAttributes(inherit: true).Single(); + attribute.HttpMethods.ShouldContain("GET"); + attribute.Template.ShouldBe("/api/filler-presets"); + } + + [Test] + public async Task GetAll_Should_Return_FillerPresets() + { + List models = + [ + new FillerPresetResponseModel(1, "Intro"), + new FillerPresetResponseModel(2, "Outro") + ]; + _mediator.Send(Arg.Any(), Arg.Any()) + .Returns(models); + + List result = await _controller.GetAll(CancellationToken.None); + + result.ShouldBe(models); + } + + [Test] + public async Task GetAll_Should_Return_Empty_List_When_None_Exist() + { + _mediator.Send(Arg.Any(), Arg.Any()) + .Returns([]); + + List result = await _controller.GetAll(CancellationToken.None); + + result.ShouldBeEmpty(); + } +} diff --git a/ErsatzTV.Tests/Controllers/GraphicsElementControllerTests.cs b/ErsatzTV.Tests/Controllers/GraphicsElementControllerTests.cs new file mode 100644 index 000000000..8a366e9d4 --- /dev/null +++ b/ErsatzTV.Tests/Controllers/GraphicsElementControllerTests.cs @@ -0,0 +1,63 @@ +using System.Reflection; +using ErsatzTV.Application.Graphics; +using ErsatzTV.Controllers.Api; +using ErsatzTV.Core.Api.Graphics; +using MediatR; +using Microsoft.AspNetCore.Mvc.Routing; +using NSubstitute; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Controllers; + +[TestFixture] +public class GraphicsElementControllerTests +{ + private GraphicsElementController _controller = null!; + private IMediator _mediator = null!; + + [SetUp] + public void SetUp() + { + _mediator = Substitute.For(); + _controller = new GraphicsElementController(_mediator); + } + + [Test] + public void Controller_Should_Expose_Idiomatic_Rest_Route() + { + MethodInfo action = typeof(GraphicsElementController).GetMethod(nameof(GraphicsElementController.GetAll)) + ?? throw new AssertionException("Missing action GetAll"); + + HttpMethodAttribute attribute = action.GetCustomAttributes(inherit: true).Single(); + attribute.HttpMethods.ShouldContain("GET"); + attribute.Template.ShouldBe("/api/graphics-elements"); + } + + [Test] + public async Task GetAll_Should_Return_GraphicsElements() + { + List models = + [ + new GraphicsElementResponseModel(1, "Lower Third (lower-third.png)"), + new GraphicsElementResponseModel(2, "bug.png") + ]; + _mediator.Send(Arg.Any(), Arg.Any()) + .Returns(models); + + List result = await _controller.GetAll(CancellationToken.None); + + result.ShouldBe(models); + } + + [Test] + public async Task GetAll_Should_Return_Empty_List_When_None_Exist() + { + _mediator.Send(Arg.Any(), Arg.Any()) + .Returns([]); + + List result = await _controller.GetAll(CancellationToken.None); + + result.ShouldBeEmpty(); + } +} diff --git a/ErsatzTV.Tests/Controllers/WatermarkControllerTests.cs b/ErsatzTV.Tests/Controllers/WatermarkControllerTests.cs new file mode 100644 index 000000000..1a2bafa9b --- /dev/null +++ b/ErsatzTV.Tests/Controllers/WatermarkControllerTests.cs @@ -0,0 +1,63 @@ +using System.Reflection; +using ErsatzTV.Application.Watermarks; +using ErsatzTV.Controllers.Api; +using ErsatzTV.Core.Api.Watermarks; +using MediatR; +using Microsoft.AspNetCore.Mvc.Routing; +using NSubstitute; +using NUnit.Framework; +using Shouldly; + +namespace ErsatzTV.Tests.Controllers; + +[TestFixture] +public class WatermarkControllerTests +{ + private WatermarkController _controller = null!; + private IMediator _mediator = null!; + + [SetUp] + public void SetUp() + { + _mediator = Substitute.For(); + _controller = new WatermarkController(_mediator); + } + + [Test] + public void Controller_Should_Expose_Idiomatic_Rest_Route() + { + MethodInfo action = typeof(WatermarkController).GetMethod(nameof(WatermarkController.GetAll)) + ?? throw new AssertionException("Missing action GetAll"); + + HttpMethodAttribute attribute = action.GetCustomAttributes(inherit: true).Single(); + attribute.HttpMethods.ShouldContain("GET"); + attribute.Template.ShouldBe("/api/watermarks"); + } + + [Test] + public async Task GetAll_Should_Return_Watermarks() + { + List models = + [ + new WatermarkResponseModel(1, "Corner Logo"), + new WatermarkResponseModel(2, "Ticker") + ]; + _mediator.Send(Arg.Any(), Arg.Any()) + .Returns(models); + + List result = await _controller.GetAll(CancellationToken.None); + + result.ShouldBe(models); + } + + [Test] + public async Task GetAll_Should_Return_Empty_List_When_None_Exist() + { + _mediator.Send(Arg.Any(), Arg.Any()) + .Returns([]); + + List result = await _controller.GetAll(CancellationToken.None); + + result.ShouldBeEmpty(); + } +} diff --git a/ErsatzTV/Controllers/Api/FillerPresetController.cs b/ErsatzTV/Controllers/Api/FillerPresetController.cs new file mode 100644 index 000000000..ba4d8de77 --- /dev/null +++ b/ErsatzTV/Controllers/Api/FillerPresetController.cs @@ -0,0 +1,19 @@ +using ErsatzTV.Application.Filler; +using ErsatzTV.Core.Api.Filler; +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace ErsatzTV.Controllers.Api; + +[ApiController] +public class FillerPresetController(IMediator mediator) : ControllerBase +{ + [HttpGet("/api/filler-presets", Name = "GetFillerPresets")] + [Tags("Filler Presets")] + [EndpointSummary("Get all filler presets")] + [EndpointGroupName("general")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task> GetAll(CancellationToken cancellationToken) => + await mediator.Send(new GetAllFillerPresetsForApi(), cancellationToken); +} diff --git a/ErsatzTV/Controllers/Api/GraphicsElementController.cs b/ErsatzTV/Controllers/Api/GraphicsElementController.cs new file mode 100644 index 000000000..7196f1f14 --- /dev/null +++ b/ErsatzTV/Controllers/Api/GraphicsElementController.cs @@ -0,0 +1,19 @@ +using ErsatzTV.Application.Graphics; +using ErsatzTV.Core.Api.Graphics; +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace ErsatzTV.Controllers.Api; + +[ApiController] +public class GraphicsElementController(IMediator mediator) : ControllerBase +{ + [HttpGet("/api/graphics-elements", Name = "GetGraphicsElements")] + [Tags("Graphics Elements")] + [EndpointSummary("Get all graphics elements")] + [EndpointGroupName("general")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task> GetAll(CancellationToken cancellationToken) => + await mediator.Send(new GetAllGraphicsElementsForApi(), cancellationToken); +} diff --git a/ErsatzTV/Controllers/Api/WatermarkController.cs b/ErsatzTV/Controllers/Api/WatermarkController.cs new file mode 100644 index 000000000..4b2259a7f --- /dev/null +++ b/ErsatzTV/Controllers/Api/WatermarkController.cs @@ -0,0 +1,19 @@ +using ErsatzTV.Application.Watermarks; +using ErsatzTV.Core.Api.Watermarks; +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace ErsatzTV.Controllers.Api; + +[ApiController] +public class WatermarkController(IMediator mediator) : ControllerBase +{ + [HttpGet("/api/watermarks", Name = "GetWatermarks")] + [Tags("Watermarks")] + [EndpointSummary("Get all watermarks")] + [EndpointGroupName("general")] + [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] + public async Task> GetAll(CancellationToken cancellationToken) => + await mediator.Send(new GetAllWatermarksForApi(), cancellationToken); +} diff --git a/ErsatzTV/wwwroot/openapi/v1.json b/ErsatzTV/wwwroot/openapi/v1.json index e52fa2841..3df42d662 100644 --- a/ErsatzTV/wwwroot/openapi/v1.json +++ b/ErsatzTV/wwwroot/openapi/v1.json @@ -1353,6 +1353,86 @@ } } }, + "/api/filler-presets": { + "get": { + "tags": [ + "Filler Presets" + ], + "summary": "Get all filler presets", + "operationId": "GetFillerPresets", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FillerPresetResponseModel" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FillerPresetResponseModel" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FillerPresetResponseModel" + } + } + } + } + } + } + } + }, + "/api/graphics-elements": { + "get": { + "tags": [ + "Graphics Elements" + ], + "summary": "Get all graphics elements", + "operationId": "GetGraphicsElements", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphicsElementResponseModel" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphicsElementResponseModel" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GraphicsElementResponseModel" + } + } + } + } + } + } + } + }, "/api/libraries/{id}/scan": { "post": { "tags": [ @@ -2906,6 +2986,46 @@ } } } + }, + "/api/watermarks": { + "get": { + "tags": [ + "Watermarks" + ], + "summary": "Get all watermarks", + "operationId": "GetWatermarks", + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WatermarkResponseModel" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WatermarkResponseModel" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WatermarkResponseModel" + } + } + } + } + } + } + } } }, "components": { @@ -4017,6 +4137,25 @@ ], "type": "string" }, + "FillerPresetResponseModel": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ] + } + } + }, "FillerPresetViewModel": { "required": [ "id", @@ -4145,6 +4284,25 @@ ], "type": "string" }, + "GraphicsElementResponseModel": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ] + } + } + }, "GraphicsElementViewModel": { "required": [ "id", @@ -5660,6 +5818,25 @@ "WatermarkLocation": { "type": "integer" }, + "WatermarkResponseModel": { + "required": [ + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": [ + "null", + "string" + ] + } + } + }, "WatermarkSize": { "type": "integer" }, @@ -5763,6 +5940,12 @@ { "name": "FFmpeg Profiles" }, + { + "name": "Filler Presets" + }, + { + "name": "Graphics Elements" + }, { "name": "Libraries" }, @@ -5786,6 +5969,9 @@ }, { "name": "Version" + }, + { + "name": "Watermarks" } ] } \ No newline at end of file