Update scheduling parameters and refactor channel endpoints: extend HorizonDays to 7 and RetentionDays to 90 in appsettings.json. Consolidate channel-related endpoint logic by removing obsolete files and enhancing the ShowEndpoints with audience and genre management capabilities. Improve error handling and streamline command handlers for channel operations.
build / backend (push) Successful in 7m40s
build / frontend (push) Failing after 39s
tests / backend-tests (push) Successful in 6m9s

This commit is contained in:
Leonid Pershin
2026-07-26 13:32:13 +03:00
parent c4ef954dea
commit 66040a8841
272 changed files with 27944 additions and 8699 deletions
@@ -0,0 +1,106 @@
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
using TeleWave.Domain.Programming;
namespace TeleWave.Application.Programming.Templates;
/// <summary>
/// Общая часть создания и правки слота: проверки, которые нельзя доверить валидатору, потому что
/// им нужны соседние слоты и справочник групп.
/// </summary>
public sealed class SlotWriter(IAppDbContext dbContext)
{
/// <summary>
/// Проверяет вход и применяет его к слоту. <paramref name="slot"/> = null — проверка перед
/// созданием.
/// </summary>
public async Task<Result> ApplyAsync(
GridLayer layer,
Slot? slot,
SlotInput input,
CancellationToken cancellationToken
)
{
// Перекрытие внутри слоя запрещено: два слота на одну минуту сделали бы выбор неоднозначным.
if (
layer.HasOverlap(
input.Weekday,
input.TargetStart,
input.TargetDurationMinutes,
slot?.Id
)
)
return Result.Failure(TemplateErrors.SlotsOverlap);
if (input.SlotKind == SlotKind.Content)
{
if (input.GroupId is not { } groupId)
return Result.Failure(TemplateErrors.GroupRequired);
if (!await dbContext.Groups.AnyAsync(g => g.Id == groupId, cancellationToken))
return Result.Failure(TemplateErrors.GroupNotFound);
}
if (input.SlotKind == SlotKind.Repeat && input.RepeatSource is null)
return Result.Failure(TemplateErrors.RepeatSourceRequired);
var target =
slot
?? layer.AddSlot(
input.Title,
input.TargetStart,
input.TargetDurationMinutes,
input.Daypart,
input.SlotKind,
input.Weekday
);
target.UpdateTiming(
input.Weekday,
input.TargetStart,
input.TargetDurationMinutes,
input.Daypart,
input.IsAnchor,
input.MaxDriftMinutes,
input.SnapToMinutes
);
target.UpdateContent(
input.Title,
input.SlotKind,
input.GroupId,
input.Strategy?.ToJson(),
input.RepeatSource?.ToJson(),
input.BlockMode,
input.BlockValue,
input.OverflowPolicy,
input.JunctionBetweenId,
input.JunctionAfterId
);
return Result.Success();
}
/// <summary>Загружает шаблон целиком по слою — правка слота меняет ревизию всего шаблона.</summary>
public Task<ScheduleTemplate?> LoadTemplateByLayerAsync(
Guid layerId,
CancellationToken cancellationToken
) =>
dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
/// <summary>Загружает шаблон по слоту.</summary>
public Task<ScheduleTemplate?> LoadTemplateBySlotAsync(
Guid slotId,
CancellationToken cancellationToken
) =>
dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.FirstOrDefaultAsync(
t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)),
cancellationToken
);
}