Add interstitial endpoints and enhance media and template functionalities: implement MapInterstitialEndpoints in Program.cs, add preview functionality for media assets in MediaEndpoints, and introduce template preview capabilities in TemplateEndpoints. Remove obsolete bumper settings from Channel and related classes to streamline configuration.
build / backend (push) Successful in 5m58s
build / frontend (push) Successful in 47s
tests / backend-tests (push) Successful in 5m57s

This commit is contained in:
Leonid Pershin
2026-07-26 14:03:53 +03:00
parent 66040a8841
commit 7b12a06d1b
53 changed files with 4076 additions and 469 deletions
@@ -5,13 +5,7 @@ namespace TeleWave.Application.Broadcast;
public sealed record ChannelSummaryDto(Guid Id, string Name, string Slug, bool IsEnabled);
/// <summary>Общие для канала настройки ТВ-заставок (стиль/звук/текст — на блоках/подблоках).</summary>
public sealed record BumperSettingsDto(
BumperFont Font,
int MinIntervalMinutes,
BumperSelection Selection,
double ShowChangeChance,
double EpisodeChangeChance
);
public sealed record BumperSettingsDto(BumperFont Font, BumperSelection Selection);
/// <summary>Подблок (текст-вариант): свой текст + правило показа + вес поверх стиля/звука блока.</summary>
public sealed record BumperTextVariantDto(
@@ -1,80 +1,74 @@
using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
namespace TeleWave.Application.Broadcast.GetChannel;
public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
: IQueryHandler<GetChannelQuery, Result<ChannelDto>>
{
public async Task<Result<ChannelDto>> Handle(
GetChannelQuery query,
CancellationToken cancellationToken
)
{
// Что и когда идёт в эфире, лежит в шаблоне сетки и запрашивается отдельно
// (GetChannelTemplateQuery) — здесь только собственные свойства канала.
var channel = await dbContext
.Channels.AsNoTracking()
.Include(c => c.BumperTemplates)
.ThenInclude(t => t.Variants)
.FirstOrDefaultAsync(c => c.Id == query.Id, cancellationToken);
if (channel is null)
return Result.Failure<ChannelDto>(ChannelErrors.NotFound);
var bumperTemplates = channel
.BumperTemplates.OrderBy(t => t.Position)
.Select(t => new BumperTemplateDto(
t.Id,
t.Position,
t.IsDefault,
t.Name,
t.BackgroundColor,
t.BackgroundColor2,
t.AccentColor,
t.TextColor,
t.BackgroundImageId,
t.AudioExtension is not null,
t.AudioDurationSeconds,
t.Variants.OrderBy(v => v.Position)
.Select(v => new BumperTextVariantDto(
v.Id,
v.Position,
v.Name,
v.Kind,
v.NowLabel,
v.NextLabel,
v.Line1,
v.Line2,
v.Trigger,
v.Weight
))
.ToList()
))
.ToList();
return Result.Success(
new ChannelDto(
channel.Id,
channel.Name,
channel.Slug,
channel.IsEnabled,
channel.Number,
channel.UtcOffsetMinutes,
channel.DayStartTime,
channel.TemplateId,
channel.BumpersEnabled,
new BumperSettingsDto(
channel.BumperFont,
channel.BumperMinIntervalMinutes,
channel.BumperSelection,
channel.BumperShowChangeChance,
channel.BumperEpisodeChangeChance
),
bumperTemplates,
channel.FillerAssetId
)
);
}
}
using LiteCqrs;
using Microsoft.EntityFrameworkCore;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Common.Models;
namespace TeleWave.Application.Broadcast.GetChannel;
public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
: IQueryHandler<GetChannelQuery, Result<ChannelDto>>
{
public async Task<Result<ChannelDto>> Handle(
GetChannelQuery query,
CancellationToken cancellationToken
)
{
// Что и когда идёт в эфире, лежит в шаблоне сетки и запрашивается отдельно
// (GetChannelTemplateQuery) — здесь только собственные свойства канала.
var channel = await dbContext
.Channels.AsNoTracking()
.Include(c => c.BumperTemplates)
.ThenInclude(t => t.Variants)
.FirstOrDefaultAsync(c => c.Id == query.Id, cancellationToken);
if (channel is null)
return Result.Failure<ChannelDto>(ChannelErrors.NotFound);
var bumperTemplates = channel
.BumperTemplates.OrderBy(t => t.Position)
.Select(t => new BumperTemplateDto(
t.Id,
t.Position,
t.IsDefault,
t.Name,
t.BackgroundColor,
t.BackgroundColor2,
t.AccentColor,
t.TextColor,
t.BackgroundImageId,
t.AudioExtension is not null,
t.AudioDurationSeconds,
t.Variants.OrderBy(v => v.Position)
.Select(v => new BumperTextVariantDto(
v.Id,
v.Position,
v.Name,
v.Kind,
v.NowLabel,
v.NextLabel,
v.Line1,
v.Line2,
v.Trigger,
v.Weight
))
.ToList()
))
.ToList();
return Result.Success(
new ChannelDto(
channel.Id,
channel.Name,
channel.Slug,
channel.IsEnabled,
channel.Number,
channel.UtcOffsetMinutes,
channel.DayStartTime,
channel.TemplateId,
channel.BumpersEnabled,
new BumperSettingsDto(channel.BumperFont, channel.BumperSelection),
bumperTemplates,
channel.FillerAssetId
)
);
}
}
@@ -13,11 +13,6 @@ public sealed record UpdateChannelSettingsCommand(
Guid? FillerAssetId
) : ICommand<Result>;
/// <summary>Общие настройки ТВ-заставок канала (см. <c>Channel.UpdateBumperSettings</c>).</summary>
public sealed record BumperSettingsInput(
BumperFont Font,
int MinIntervalMinutes,
BumperSelection Selection,
double ShowChangeChance,
double EpisodeChangeChance
);
/// <summary>Общие настройки ТВ-заставок канала (см. <c>Channel.UpdateBumperSettings</c>). Условия
/// показа сюда не входят — они задаются на элементе стыка.</summary>
public sealed record BumperSettingsInput(BumperFont Font, BumperSelection Selection);
@@ -36,13 +36,7 @@ public sealed class UpdateChannelSettingsCommandHandler(IAppDbContext dbContext)
command.BumpersEnabled,
command.FillerAssetId
);
channel.UpdateBumperSettings(
command.Bumper.Font,
command.Bumper.MinIntervalMinutes,
command.Bumper.Selection,
command.Bumper.ShowChangeChance,
command.Bumper.EpisodeChangeChance
);
channel.UpdateBumperSettings(command.Bumper.Font, command.Bumper.Selection);
return Result.Success();
}
}
@@ -8,9 +8,5 @@ public sealed class UpdateChannelSettingsCommandValidator
public UpdateChannelSettingsCommandValidator()
{
RuleFor(x => x.Name).NotEmpty().MaximumLength(256);
RuleFor(x => x.Bumper.MinIntervalMinutes).InclusiveBetween(0, 1440);
RuleFor(x => x.Bumper.ShowChangeChance).InclusiveBetween(0.0, 1.0);
RuleFor(x => x.Bumper.EpisodeChangeChance).InclusiveBetween(0.0, 1.0);
}
}