diff --git a/CHANGELOG.md b/CHANGELOG.md index f2edba51..2d521025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Remove some unnecessary API calls related to media server scanning and paging - Improve trakt list URL validation; non-trakt URLs will no longer be requested +- Prevent saving block templates when blocks are overlapping + - This can happen if block durations are changed for blocks that are already on the template ## [0.8.7-beta] - 2024-06-26 ### Added diff --git a/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs b/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs index 8fd283fd..b0965efb 100644 --- a/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs @@ -55,11 +55,58 @@ public class ReplaceTemplateItemsHandler(IDbContextFactory dbContextF }; private static Task> Validate(TvContext dbContext, ReplaceTemplateItems request) => - TemplateMustExist(dbContext, request.TemplateId); + TemplateMustExist(dbContext, request.TemplateId) + .BindT(template => TemplateItemsMustBeValid(dbContext, template, request)); + + private static async Task> TemplateItemsMustBeValid( + TvContext dbContext, + Template template, + ReplaceTemplateItems request) + { + var allBlockIds = request.Items.Map(i => i.BlockId).Distinct().ToList(); + + Dictionary allBlocks = await dbContext.Blocks + .AsNoTracking() + .Filter(b => allBlockIds.Contains(b.Id)) + .ToListAsync() + .Map(list => list.ToDictionary(b => b.Id, b => b)); + + var allTemplateItems = request.Items.Map( + i => + { + Block block = allBlocks[i.BlockId]; + return new BlockTemplateItem( + i.BlockId, + i.StartTime, + i.StartTime + TimeSpan.FromMinutes(block.Minutes)); + }) + .ToList(); + + foreach (BlockTemplateItem item in allTemplateItems) + { + foreach (BlockTemplateItem otherItem in allTemplateItems) + { + if (item == otherItem) + { + continue; + } + + if (item.StartTime < otherItem.EndTime && otherItem.StartTime < item.EndTime) + { + return BaseError.New( + $"Block from {item.StartTime} to {item.EndTime} intersects block from {otherItem.StartTime} to {otherItem.EndTime}"); + } + } + } + + return template; + } private static Task> TemplateMustExist(TvContext dbContext, int templateId) => dbContext.Templates .Include(b => b.Items) .SelectOneAsync(b => b.Id, b => b.Id == templateId) .Map(o => o.ToValidation("[TemplateId] does not exist.")); + + private sealed record BlockTemplateItem(int BlockId, TimeSpan StartTime, TimeSpan EndTime); }