using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Errors; using ErsatzTV.Core.Interfaces.Search; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Watermarks; public class UpdateWatermarkHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; private readonly ISearchTargets _searchTargets; public UpdateWatermarkHandler(IDbContextFactory dbContextFactory, ISearchTargets searchTargets) { _dbContextFactory = dbContextFactory; _searchTargets = searchTargets; } public async Task> Handle( UpdateWatermark request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Option maybeWatermark = await WatermarkMustExist(dbContext, request, cancellationToken); // must-exist maps to a NotFoundError Either directly (not via Validation, which // aggregates errors and loses the subtype the API layer maps to 404) return await maybeWatermark.Match( Some: async watermark => { Validation validation = await ValidateName(dbContext, request); return await validation.Apply((string _) => ApplyUpdateRequest(dbContext, watermark, request)); }, None: () => Task.FromResult>( new NotFoundError("Watermark does not exist."))); } private async Task ApplyUpdateRequest( TvContext dbContext, ChannelWatermark p, UpdateWatermark update) { p.Name = update.Name; p.Image = null; p.OriginalContentType = null; if (update.ImageSource == ChannelWatermarkImageSource.Custom) { p.Image = update.Image?.Path; p.OriginalContentType = update.Image?.ContentType; } p.Mode = update.Mode; p.ImageSource = update.ImageSource; p.Location = update.Location; p.Size = update.Size; p.WidthPercent = update.Width; p.HorizontalMarginPercent = update.HorizontalMargin; p.VerticalMarginPercent = update.VerticalMargin; p.FrequencyMinutes = update.FrequencyMinutes; p.DurationSeconds = update.DurationSeconds; p.Opacity = update.Opacity; p.PlaceWithinSourceContent = update.PlaceWithinSourceContent; p.OpacityExpression = update.Mode is ChannelWatermarkMode.OpacityExpression ? update.OpacityExpression : null; p.ZIndex = update.ZIndex; await dbContext.SaveChangesAsync(); _searchTargets.SearchTargetsChanged(); return new UpdateWatermarkResult(p.Id); } private static Task> WatermarkMustExist( TvContext dbContext, UpdateWatermark updateWatermark, CancellationToken cancellationToken) => dbContext.ChannelWatermarks .SelectOneAsync(p => p.Id, p => p.Id == updateWatermark.Id, cancellationToken); private static async Task> ValidateName( TvContext dbContext, UpdateWatermark updateWatermark) { bool duplicateName = await dbContext.ChannelWatermarks .AnyAsync(wm => wm.Id != updateWatermark.Id && wm.Name == updateWatermark.Name); Validation result2 = duplicateName ? Fail("ChannelWatermark name must be unique") : Success(Unit.Default); Validation result1 = updateWatermark.NotEmpty(c => c.Name) .Bind(_ => updateWatermark.NotLongerThan(50)(c => c.Name)); return (result1, result2).Apply((_, _) => updateWatermark.Name); } }