using ErsatzTV.Core; using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Scheduling; public class ReplaceDecoTemplateItemsHandler(IDbContextFactory dbContextFactory) : IRequestHandler>> { public async Task>> Handle( ReplaceDecoTemplateItems request, CancellationToken cancellationToken) { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); Validation validation = await Validate(dbContext, request, cancellationToken); return await validation.Apply(ps => Persist(dbContext, request, ps, cancellationToken)); } private static async Task> Persist( TvContext dbContext, ReplaceDecoTemplateItems request, DecoTemplate decoTemplate, CancellationToken cancellationToken) { decoTemplate.Name = request.Name; decoTemplate.DateUpdated = DateTime.UtcNow; dbContext.RemoveRange(decoTemplate.Items); // drop items that are invalid decoTemplate.Items = request.Items .Map(i => BuildItem(decoTemplate, i)) .Filter(i => i.StartTime < i.EndTime || i.EndTime == TimeSpan.Zero) .ToList(); await dbContext.SaveChangesAsync(cancellationToken); // TODO: refresh any playouts that use this schedule // foreach (Playout playout in programSchedule.Playouts) // { // await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Refresh)); // } await dbContext.Entry(decoTemplate) .Collection(t => t.Items) .Query() .Include(i => i.Deco) .LoadAsync(cancellationToken); return decoTemplate.Items.Map(Mapper.ProjectToViewModel).ToList(); } private static DecoTemplateItem BuildItem(DecoTemplate decoTemplate, ReplaceDecoTemplateItem item) => new() { DecoTemplateId = decoTemplate.Id, DecoId = item.DecoId, StartTime = item.StartTime, EndTime = item.EndTime }; private static Task> Validate( TvContext dbContext, ReplaceDecoTemplateItems request, CancellationToken cancellationToken) => DecoTemplateMustExist(dbContext, request.DecoTemplateId, cancellationToken) .BindT(decoTemplate => DecoTemplateNameMustBeValid(dbContext, decoTemplate, request)); private static Task> DecoTemplateMustExist( TvContext dbContext, int decoTemplateId, CancellationToken cancellationToken) => dbContext.DecoTemplates .Include(b => b.Items) .SelectOneAsync(b => b.Id, b => b.Id == decoTemplateId, cancellationToken) .Map(o => o.ToValidation("[DecoTemplateId] does not exist.")); private static async Task> DecoTemplateNameMustBeValid( TvContext dbContext, DecoTemplate decoTemplate, ReplaceDecoTemplateItems request) { if (request.Name.Length > 50) { return BaseError.New($"Deco template name \"{request.Name}\" is invalid"); } Option maybeExisting = await dbContext.DecoTemplates .AsNoTracking() .FirstOrDefaultAsync(d => d.Id != request.DecoTemplateId && d.DecoTemplateGroupId == request.DecoTemplateGroupId && d.Name == request.Name) .Map(Optional); return maybeExisting.IsSome ? BaseError.New($"A deco template named \"{request.Name}\" already exists in that deco template group") : Success(decoTemplate); } }