Enhance channel management: add support for preferred weight multipliers and preferred hours in channel shows, update related data models and API endpoints, and implement validation for new properties. Refactor scheduling logic to utilize preferred hours for weight adjustments during show planning.
This commit is contained in:
@@ -209,7 +209,9 @@ public static class ChannelEndpoints
|
||||
body.Weight,
|
||||
body.BlockMode,
|
||||
body.BlockValue,
|
||||
body.IsEnabled
|
||||
body.IsEnabled,
|
||||
body.PreferredWeightMultiplier,
|
||||
body.PreferredHours ?? []
|
||||
),
|
||||
cancellationToken
|
||||
);
|
||||
@@ -401,7 +403,8 @@ public static class ChannelEndpoints
|
||||
body.NextLabel,
|
||||
body.Line1,
|
||||
body.Line2,
|
||||
body.Trigger
|
||||
body.Trigger,
|
||||
body.Weight
|
||||
),
|
||||
cancellationToken
|
||||
);
|
||||
@@ -604,7 +607,9 @@ public sealed record UpdateChannelShowBody(
|
||||
int Weight,
|
||||
BlockMode BlockMode,
|
||||
int BlockValue,
|
||||
bool IsEnabled
|
||||
bool IsEnabled,
|
||||
int PreferredWeightMultiplier,
|
||||
IReadOnlyList<HourWindowInput> PreferredHours
|
||||
);
|
||||
|
||||
public sealed record AddChannelAdBody(Guid MediaAssetId);
|
||||
@@ -622,7 +627,8 @@ public sealed record UpdateBumperVariantBody(
|
||||
string NextLabel,
|
||||
string Line1,
|
||||
string Line2,
|
||||
BumperTrigger Trigger
|
||||
BumperTrigger Trigger,
|
||||
int Weight
|
||||
);
|
||||
|
||||
public sealed record UpdateBumperTemplateBody(
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@ using TeleWave.Domain.Broadcast;
|
||||
|
||||
namespace TeleWave.Application.Broadcast.Bumpers;
|
||||
|
||||
/// <summary>Обновить подблок: имя, режим текста, текст и правило показа.</summary>
|
||||
/// <summary>Обновить подблок: имя, режим текста, текст, правило показа и вес.</summary>
|
||||
public sealed record UpdateBumperTextVariantCommand(
|
||||
Guid ChannelId,
|
||||
Guid TemplateId,
|
||||
@@ -15,5 +15,6 @@ public sealed record UpdateBumperTextVariantCommand(
|
||||
string NextLabel,
|
||||
string Line1,
|
||||
string Line2,
|
||||
BumperTrigger Trigger
|
||||
BumperTrigger Trigger,
|
||||
int Weight
|
||||
) : ICommand<Result>;
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ public sealed class UpdateBumperTextVariantCommandHandler(IAppDbContext dbContex
|
||||
command.NextLabel,
|
||||
command.Line1,
|
||||
command.Line2,
|
||||
command.Trigger
|
||||
command.Trigger,
|
||||
command.Weight
|
||||
);
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
+1
@@ -12,5 +12,6 @@ public sealed class UpdateBumperTextVariantCommandValidator
|
||||
RuleFor(x => x.NextLabel).MaximumLength(64);
|
||||
RuleFor(x => x.Line1).MaximumLength(120);
|
||||
RuleFor(x => x.Line2).MaximumLength(120);
|
||||
RuleFor(x => x.Weight).InclusiveBetween(0, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,14 @@ public sealed record ChannelShowDto(
|
||||
BlockMode BlockMode,
|
||||
int BlockValue,
|
||||
bool IsEnabled,
|
||||
int NextEpisodeIndex
|
||||
int NextEpisodeIndex,
|
||||
int PreferredWeightMultiplier,
|
||||
IReadOnlyList<HourWindowDto> PreferredHours
|
||||
);
|
||||
|
||||
/// <summary>Окно предпочтительных часов [StartHour, EndHour) суток (UTC).</summary>
|
||||
public sealed record HourWindowDto(int StartHour, int EndHour);
|
||||
|
||||
public sealed record ChannelAdDto(Guid Id, Guid MediaAssetId, string? AssetName, int Position);
|
||||
|
||||
public sealed record OverrideShowDto(Guid ShowId, string ShowName, int Weight);
|
||||
@@ -31,10 +36,12 @@ public sealed record ProgrammingOverrideDto(
|
||||
public sealed record BumperSettingsDto(
|
||||
BumperFont Font,
|
||||
int MinIntervalMinutes,
|
||||
BumperSelection Selection
|
||||
BumperSelection Selection,
|
||||
double ShowChangeChance,
|
||||
double EpisodeChangeChance
|
||||
);
|
||||
|
||||
/// <summary>Подблок (текст-вариант): свой текст + правило показа поверх стиля/звука блока.</summary>
|
||||
/// <summary>Подблок (текст-вариант): свой текст + правило показа + вес поверх стиля/звука блока.</summary>
|
||||
public sealed record BumperTextVariantDto(
|
||||
Guid Id,
|
||||
int Position,
|
||||
@@ -44,7 +51,8 @@ public sealed record BumperTextVariantDto(
|
||||
string NextLabel,
|
||||
string Line1,
|
||||
string Line2,
|
||||
BumperTrigger Trigger
|
||||
BumperTrigger Trigger,
|
||||
int Weight
|
||||
);
|
||||
|
||||
/// <summary>Блок заставки: своё оформление + звук + подблоки. <see cref="AudioDurationSeconds"/> — длина звука (сек).</summary>
|
||||
|
||||
@@ -15,6 +15,7 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
|
||||
{
|
||||
var channel = await dbContext.Channels.AsNoTracking()
|
||||
.Include(c => c.Shows)
|
||||
.ThenInclude(s => s.PreferredHours)
|
||||
.Include(c => c.Ads)
|
||||
.Include(c => c.BumperTemplates)
|
||||
.ThenInclude(t => t.Variants)
|
||||
@@ -51,7 +52,12 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
|
||||
s.BlockMode,
|
||||
s.BlockValue,
|
||||
s.IsEnabled,
|
||||
s.NextEpisodeIndex
|
||||
s.NextEpisodeIndex,
|
||||
s.PreferredWeightMultiplier,
|
||||
s.PreferredHours
|
||||
.OrderBy(h => h.StartHour)
|
||||
.Select(h => new HourWindowDto(h.StartHour, h.EndHour))
|
||||
.ToList()
|
||||
))
|
||||
.ToList();
|
||||
|
||||
@@ -90,7 +96,8 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
|
||||
v.NextLabel,
|
||||
v.Line1,
|
||||
v.Line2,
|
||||
v.Trigger
|
||||
v.Trigger,
|
||||
v.Weight
|
||||
))
|
||||
.ToList()
|
||||
))
|
||||
@@ -121,7 +128,9 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
|
||||
new BumperSettingsDto(
|
||||
channel.BumperFont,
|
||||
channel.BumperMinIntervalMinutes,
|
||||
channel.BumperSelection
|
||||
channel.BumperSelection,
|
||||
channel.BumperShowChangeChance,
|
||||
channel.BumperEpisodeChangeChance
|
||||
),
|
||||
bumperTemplates,
|
||||
channel.FillerAssetId,
|
||||
|
||||
+72
-13
@@ -37,6 +37,19 @@ public sealed class GetChannelScheduleQueryHandler(IAppDbContext dbContext)
|
||||
.Select(s => new { s.Id, s.Name })
|
||||
.ToDictionaryAsync(s => s.Id, s => s.Name, cancellationToken);
|
||||
|
||||
// Подблоки заставок в окне — чтобы показать в расписании, какая именно заставка и с каким текстом.
|
||||
var variantIds = entries
|
||||
.Where(e => e.Kind == Domain.Broadcast.ScheduleEntryKind.Bumper && e.BumperVariantId != null)
|
||||
.Select(e => e.BumperVariantId!.Value)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
var variants = variantIds.Count == 0
|
||||
? []
|
||||
: await dbContext.BumperTextVariants.AsNoTracking()
|
||||
.Where(v => variantIds.Contains(v.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
var variantsById = variants.ToDictionary(v => v.Id);
|
||||
|
||||
// Имена ассетов программ — чтобы показать реальную метку S16E03 в расписании админки.
|
||||
var assetIds = entries
|
||||
.Where(e => e.Kind == Domain.Broadcast.ScheduleEntryKind.Program)
|
||||
@@ -48,20 +61,66 @@ public sealed class GetChannelScheduleQueryHandler(IAppDbContext dbContext)
|
||||
.Select(a => new { a.Id, a.OriginalFileName })
|
||||
.ToDictionaryAsync(a => a.Id, a => a.OriginalFileName, cancellationToken);
|
||||
|
||||
var dtos = entries
|
||||
.Select(e => new ScheduleEntryDto(
|
||||
e.Id,
|
||||
e.Kind,
|
||||
e.MediaAssetId,
|
||||
e.StartsAtUtc,
|
||||
e.EndsAtUtc,
|
||||
e.ShowId,
|
||||
e.ShowId is { } sid ? showNames.GetValueOrDefault(sid) : null,
|
||||
e.EpisodeIndex,
|
||||
assetNames.TryGetValue(e.MediaAssetId, out var name) ? EpisodeName.ParseLabel(name) : null
|
||||
))
|
||||
.ToList();
|
||||
// «Из какого шоу» для заставки берём из ближайшей предыдущей программы в упорядоченном окне.
|
||||
Guid? prevProgramShowId = null;
|
||||
var dtos = new List<ScheduleEntryDto>(entries.Count);
|
||||
foreach (var e in entries)
|
||||
{
|
||||
string? bumperName = null;
|
||||
string? bumperText = null;
|
||||
if (
|
||||
e.Kind == Domain.Broadcast.ScheduleEntryKind.Bumper
|
||||
&& e.BumperVariantId is { } vid
|
||||
&& variantsById.TryGetValue(vid, out var variant)
|
||||
)
|
||||
{
|
||||
bumperName = variant.Name;
|
||||
bumperText = BumperText(variant, prevProgramShowId, e.ShowId, showNames);
|
||||
}
|
||||
|
||||
dtos.Add(
|
||||
new ScheduleEntryDto(
|
||||
e.Id,
|
||||
e.Kind,
|
||||
e.MediaAssetId,
|
||||
e.StartsAtUtc,
|
||||
e.EndsAtUtc,
|
||||
e.ShowId,
|
||||
e.ShowId is { } sid ? showNames.GetValueOrDefault(sid) : null,
|
||||
e.EpisodeIndex,
|
||||
assetNames.TryGetValue(e.MediaAssetId, out var name) ? EpisodeName.ParseLabel(name) : null,
|
||||
bumperName,
|
||||
bumperText
|
||||
)
|
||||
);
|
||||
|
||||
if (e.Kind == Domain.Broadcast.ScheduleEntryKind.Program)
|
||||
prevProgramShowId = e.ShowId;
|
||||
}
|
||||
|
||||
return Result.Success<IReadOnlyList<ScheduleEntryDto>>(dtos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Текст заставки для метки в расписании: для «Сейчас/Далее» — подписи + названия шоу (из→в),
|
||||
/// для свободного текста — заданные строки. Возвращает null, если показывать нечего.
|
||||
/// </summary>
|
||||
private static string? BumperText(
|
||||
Domain.Broadcast.BumperTextVariant variant,
|
||||
Guid? fromShowId,
|
||||
Guid? toShowId,
|
||||
IReadOnlyDictionary<Guid, string> showNames
|
||||
)
|
||||
{
|
||||
if (variant.Kind == Domain.Broadcast.BumperTextKind.Free)
|
||||
{
|
||||
var parts = new[] { variant.Line1, variant.Line2 }
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.ToArray();
|
||||
return parts.Length == 0 ? null : string.Join(" · ", parts);
|
||||
}
|
||||
|
||||
string Name(Guid? id) => id is { } g ? showNames.GetValueOrDefault(g, "…") : "…";
|
||||
return $"{variant.NowLabel} {Name(fromShowId)} · {variant.NextLabel} {Name(toShowId)}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,8 @@ public sealed record ScheduleEntryDto(
|
||||
Guid? ShowId,
|
||||
string? ShowName,
|
||||
int? EpisodeIndex,
|
||||
string? SeasonEpisode
|
||||
string? SeasonEpisode,
|
||||
// Для заставок (Kind == Bumper): имя подблока и его текст — для метки в админ-расписании.
|
||||
string? BumperName = null,
|
||||
string? BumperText = null
|
||||
);
|
||||
|
||||
@@ -51,6 +51,7 @@ public sealed class ScheduleGenerator(
|
||||
{
|
||||
var channel = await dbContext.Channels
|
||||
.Include(c => c.Shows)
|
||||
.ThenInclude(s => s.PreferredHours)
|
||||
.Include(c => c.Ads)
|
||||
.Include(c => c.BumperTemplates)
|
||||
.ThenInclude(t => t.Variants)
|
||||
@@ -161,7 +162,14 @@ public sealed class ScheduleGenerator(
|
||||
)
|
||||
return null;
|
||||
|
||||
return ScheduleEntry.Bumper(channelId, assetId, entry.StartsAtUtc, entry.EndsAtUtc, entry.ToShowId);
|
||||
return ScheduleEntry.Bumper(
|
||||
channelId,
|
||||
assetId,
|
||||
entry.StartsAtUtc,
|
||||
entry.EndsAtUtc,
|
||||
entry.ToShowId,
|
||||
entry.BumperVariantId
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -525,7 +533,11 @@ public sealed class ScheduleGenerator(
|
||||
channelShow.BlockMode,
|
||||
channelShow.BlockValue,
|
||||
ready,
|
||||
channelShow.NextEpisodeIndex
|
||||
channelShow.NextEpisodeIndex,
|
||||
channelShow.PreferredHours
|
||||
.Select(h => new PlannerHourWindow(h.StartHour, h.EndHour))
|
||||
.ToList(),
|
||||
channelShow.PreferredWeightMultiplier
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -544,7 +556,7 @@ public sealed class ScheduleGenerator(
|
||||
var dur = TimeSpan.FromSeconds(AlignedDurationSeconds(TemplateDurationSeconds(t)));
|
||||
return t.Variants
|
||||
.OrderBy(v => v.Position)
|
||||
.Select(v => new PlannerBumperVariant(v.Id, t.Id, dur, v.Trigger));
|
||||
.Select(v => new PlannerBumperVariant(v.Id, t.Id, dur, v.Trigger, v.Weight));
|
||||
})
|
||||
.ToList();
|
||||
|
||||
@@ -561,7 +573,9 @@ public sealed class ScheduleGenerator(
|
||||
channel.BumpersEnabled,
|
||||
TimeSpan.FromMinutes(channel.BumperMinIntervalMinutes),
|
||||
channel.BumperSelection,
|
||||
bumperVariants
|
||||
bumperVariants,
|
||||
channel.BumperShowChangeChance,
|
||||
channel.BumperEpisodeChangeChance
|
||||
);
|
||||
|
||||
return new PlannerInput(
|
||||
|
||||
+3
-1
@@ -19,5 +19,7 @@ public sealed record UpdateChannelSettingsCommand(
|
||||
public sealed record BumperSettingsInput(
|
||||
BumperFont Font,
|
||||
int MinIntervalMinutes,
|
||||
BumperSelection Selection
|
||||
BumperSelection Selection,
|
||||
double ShowChangeChance,
|
||||
double EpisodeChangeChance
|
||||
);
|
||||
|
||||
+3
-1
@@ -38,7 +38,9 @@ public sealed class UpdateChannelSettingsCommandHandler(IAppDbContext dbContext)
|
||||
channel.UpdateBumperSettings(
|
||||
command.Bumper.Font,
|
||||
command.Bumper.MinIntervalMinutes,
|
||||
command.Bumper.Selection
|
||||
command.Bumper.Selection,
|
||||
command.Bumper.ShowChangeChance,
|
||||
command.Bumper.EpisodeChangeChance
|
||||
);
|
||||
return Result.Success();
|
||||
}
|
||||
|
||||
+2
@@ -11,5 +11,7 @@ public sealed class UpdateChannelSettingsCommandValidator
|
||||
RuleFor(x => x.AdsPerBreak).InclusiveBetween(0, 10);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -10,5 +10,10 @@ public sealed record UpdateChannelShowCommand(
|
||||
int Weight,
|
||||
BlockMode BlockMode,
|
||||
int BlockValue,
|
||||
bool IsEnabled
|
||||
bool IsEnabled,
|
||||
int PreferredWeightMultiplier,
|
||||
IReadOnlyList<HourWindowInput> PreferredHours
|
||||
) : ICommand<Result>;
|
||||
|
||||
/// <summary>Окно предпочтительных часов [StartHour, EndHour) суток (UTC).</summary>
|
||||
public sealed record HourWindowInput(int StartHour, int EndHour);
|
||||
|
||||
+5
@@ -15,6 +15,7 @@ public sealed class UpdateChannelShowCommandHandler(IAppDbContext dbContext)
|
||||
{
|
||||
var channel = await dbContext.Channels
|
||||
.Include(c => c.Shows)
|
||||
.ThenInclude(s => s.PreferredHours)
|
||||
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
||||
if (channel is null)
|
||||
return Result.Failure(ChannelErrors.NotFound);
|
||||
@@ -24,6 +25,10 @@ public sealed class UpdateChannelShowCommandHandler(IAppDbContext dbContext)
|
||||
return Result.Failure(ChannelErrors.ChannelShowNotFound);
|
||||
|
||||
channelShow.Update(command.Weight, command.BlockMode, command.BlockValue, command.IsEnabled);
|
||||
channelShow.SetPreferredHours(
|
||||
command.PreferredWeightMultiplier,
|
||||
command.PreferredHours.Select(h => (h.StartHour, h.EndHour))
|
||||
);
|
||||
return Result.Success();
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -8,5 +8,13 @@ public sealed class UpdateChannelShowCommandValidator : AbstractValidator<Update
|
||||
{
|
||||
RuleFor(x => x.Weight).InclusiveBetween(1, 1000);
|
||||
RuleFor(x => x.BlockValue).InclusiveBetween(1, 10000);
|
||||
RuleFor(x => x.PreferredWeightMultiplier).InclusiveBetween(1, 100);
|
||||
RuleForEach(x => x.PreferredHours).ChildRules(w =>
|
||||
{
|
||||
w.RuleFor(h => h.StartHour).InclusiveBetween(0, 23);
|
||||
w.RuleFor(h => h.EndHour).InclusiveBetween(1, 24);
|
||||
w.RuleFor(h => h).Must(h => h.StartHour < h.EndHour)
|
||||
.WithMessage("Начало окна должно быть раньше конца.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public interface IAppDbContext
|
||||
DbSet<Show> Shows { get; }
|
||||
DbSet<Channel> Channels { get; }
|
||||
DbSet<ScheduleEntry> ScheduleEntries { get; }
|
||||
DbSet<BumperTextVariant> BumperTextVariants { get; }
|
||||
DbSet<BumperAsset> BumperAssets { get; }
|
||||
DbSet<AppSetting> AppSettings { get; }
|
||||
DbSet<Image> Images { get; }
|
||||
|
||||
@@ -6,9 +6,12 @@ public enum BumperSelection
|
||||
/// <summary>По кругу в порядке блоков (курсор <see cref="Channel.NextBumperIndex"/>).</summary>
|
||||
Rotation,
|
||||
|
||||
/// <summary>Случайный блок на каждом переходе.</summary>
|
||||
/// <summary>Случайный блок на каждом переходе (равновероятно).</summary>
|
||||
Random,
|
||||
|
||||
/// <summary>Всегда первый (дефолтный) блок.</summary>
|
||||
AlwaysFirst,
|
||||
|
||||
/// <summary>Случайный блок с учётом веса подблока (<see cref="BumperTextVariant.Weight"/>).</summary>
|
||||
WeightedRandom,
|
||||
}
|
||||
|
||||
@@ -24,8 +24,13 @@ public class BumperTextVariant
|
||||
|
||||
public BumperTrigger Trigger { get; private set; }
|
||||
|
||||
/// <summary>Вес при стратегии <see cref="BumperSelection.WeightedRandom"/> (0 — не выбирается). Иначе игнорируется.</summary>
|
||||
public int Weight { get; private set; } = DefaultWeight;
|
||||
|
||||
public DateTimeOffset CreatedAt { get; private set; }
|
||||
|
||||
public const int DefaultWeight = 1;
|
||||
|
||||
public const string DefaultNowLabel = "СЕЙЧАС";
|
||||
public const string DefaultNextLabel = "ДАЛЕЕ";
|
||||
|
||||
@@ -49,6 +54,7 @@ public class BumperTextVariant
|
||||
Line1 = string.Empty,
|
||||
Line2 = string.Empty,
|
||||
Trigger = trigger,
|
||||
Weight = DefaultWeight,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
@@ -59,7 +65,8 @@ public class BumperTextVariant
|
||||
string nextLabel,
|
||||
string line1,
|
||||
string line2,
|
||||
BumperTrigger trigger
|
||||
BumperTrigger trigger,
|
||||
int weight
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
@@ -69,6 +76,7 @@ public class BumperTextVariant
|
||||
Line1 = line1;
|
||||
Line2 = line2;
|
||||
Trigger = trigger;
|
||||
Weight = Math.Max(0, weight);
|
||||
}
|
||||
|
||||
/// <summary>Подходит ли подблок для перехода: <paramref name="isShowChange"/> — сменилось ли шоу.</summary>
|
||||
|
||||
@@ -39,6 +39,12 @@ public class Channel
|
||||
/// <summary>Не вставлять заставку чаще, чем раз в N минут (0 — на каждом подходящем переходе).</summary>
|
||||
public int BumperMinIntervalMinutes { get; private set; }
|
||||
|
||||
/// <summary>Вероятность заставки на смене шоу (0..1; 1 — на каждой смене, 0 — никогда).</summary>
|
||||
public double BumperShowChangeChance { get; private set; } = 1.0;
|
||||
|
||||
/// <summary>Вероятность заставки между блоками одного шоу (0..1; напр. 0.3 — примерно в 30% случаев).</summary>
|
||||
public double BumperEpisodeChangeChance { get; private set; } = 1.0;
|
||||
|
||||
private const string DefaultTemplateName = "Заставка 1";
|
||||
|
||||
/// <summary>Ассет-заглушка на случай пустого расписания (аварийная подстраховка).</summary>
|
||||
@@ -76,6 +82,8 @@ public class Channel
|
||||
NextBumperIndex = 0,
|
||||
BumperFont = BumperFont.Sans,
|
||||
BumperMinIntervalMinutes = 0,
|
||||
BumperShowChangeChance = 1.0,
|
||||
BumperEpisodeChangeChance = 1.0,
|
||||
NextAdIndex = 0,
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
@@ -101,16 +109,23 @@ public class Channel
|
||||
FillerAssetId = fillerAssetId;
|
||||
}
|
||||
|
||||
/// <summary>Общие настройки ТВ-заставок канала: шрифт, мин. интервал и стратегия выбора подблока.</summary>
|
||||
/// <summary>
|
||||
/// Общие настройки ТВ-заставок канала: шрифт, мин. интервал, стратегия выбора подблока и
|
||||
/// вероятности появления на смене шоу / между блоками одного шоу (0..1).
|
||||
/// </summary>
|
||||
public void UpdateBumperSettings(
|
||||
BumperFont font,
|
||||
int minIntervalMinutes,
|
||||
BumperSelection selection
|
||||
BumperSelection selection,
|
||||
double showChangeChance,
|
||||
double episodeChangeChance
|
||||
)
|
||||
{
|
||||
BumperFont = font;
|
||||
BumperMinIntervalMinutes = Math.Max(0, minIntervalMinutes);
|
||||
BumperSelection = selection;
|
||||
BumperShowChangeChance = Math.Clamp(showChangeChance, 0.0, 1.0);
|
||||
BumperEpisodeChangeChance = Math.Clamp(episodeChangeChance, 0.0, 1.0);
|
||||
}
|
||||
|
||||
/// <summary>Добавить блок заставки в конец списка. Возвращает созданный блок.</summary>
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace TeleWave.Domain.Broadcast;
|
||||
/// </summary>
|
||||
public class ChannelShow
|
||||
{
|
||||
private readonly List<ChannelShowHour> _preferredHours = new();
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
public Guid ChannelId { get; private set; }
|
||||
public Guid ShowId { get; private set; }
|
||||
@@ -21,6 +23,14 @@ public class ChannelShow
|
||||
/// <summary>Индекс следующей серии для этого канала (0-based в упорядоченном списке серий шоу).</summary>
|
||||
public int NextEpisodeIndex { get; private set; }
|
||||
|
||||
/// <summary>Во сколько раз усиливать вес шоу в предпочтительные часы (1 — без буста).</summary>
|
||||
public int PreferredWeightMultiplier { get; private set; } = DefaultPreferredWeightMultiplier;
|
||||
|
||||
/// <summary>Окна предпочтительных часов (пусто — шоу без предпочтений, вес не меняется).</summary>
|
||||
public IReadOnlyList<ChannelShowHour> PreferredHours => _preferredHours;
|
||||
|
||||
public const int DefaultPreferredWeightMultiplier = 3;
|
||||
|
||||
private ChannelShow() { }
|
||||
|
||||
internal static ChannelShow Create(
|
||||
@@ -40,6 +50,7 @@ public class ChannelShow
|
||||
BlockValue = blockValue,
|
||||
IsEnabled = true,
|
||||
NextEpisodeIndex = 0,
|
||||
PreferredWeightMultiplier = DefaultPreferredWeightMultiplier,
|
||||
};
|
||||
|
||||
public void Update(int weight, BlockMode blockMode, int blockValue, bool isEnabled)
|
||||
@@ -50,6 +61,25 @@ public class ChannelShow
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Задаёт множитель веса и полностью заменяет набор окон предпочтительных часов. Окна нормализуются:
|
||||
/// отбрасываются некорректные ([0,24], start < end), совпадающие схлопываются.
|
||||
/// </summary>
|
||||
public void SetPreferredHours(int multiplier, IEnumerable<(int StartHour, int EndHour)> windows)
|
||||
{
|
||||
PreferredWeightMultiplier = Math.Max(1, multiplier);
|
||||
_preferredHours.Clear();
|
||||
foreach (var (start, end) in windows
|
||||
.Where(w => w.StartHour >= 0 && w.EndHour <= 24 && w.StartHour < w.EndHour)
|
||||
.Distinct())
|
||||
{
|
||||
_preferredHours.Add(ChannelShowHour.Create(Id, start, end));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Час суток (0..23) попадает в одно из окон предпочтительных часов.</summary>
|
||||
public bool IsPreferredAt(int hour) => _preferredHours.Any(w => w.Contains(hour));
|
||||
|
||||
/// <summary>Планировщик двигает курсор по мере постановки серий в расписание.</summary>
|
||||
public void SetNextEpisodeIndex(int index) => NextEpisodeIndex = index;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace TeleWave.Domain.Broadcast;
|
||||
|
||||
/// <summary>
|
||||
/// Окно предпочтительных часов для шоу на канале: полуинтервал часов суток [StartHour, EndHour)
|
||||
/// в UTC. В эти часы вес шоу в ротации умножается на <see cref="ChannelShow.PreferredWeightMultiplier"/>.
|
||||
/// Ночные окна задаются двумя записями (напр. 22–24 и 0–2), заворот через полночь не поддерживается.
|
||||
/// </summary>
|
||||
public class ChannelShowHour
|
||||
{
|
||||
public Guid Id { get; private set; }
|
||||
public Guid ChannelShowId { get; private set; }
|
||||
|
||||
/// <summary>Начало окна — час суток (0..23).</summary>
|
||||
public int StartHour { get; private set; }
|
||||
|
||||
/// <summary>Конец окна (исключительно) — час суток (1..24).</summary>
|
||||
public int EndHour { get; private set; }
|
||||
|
||||
private ChannelShowHour() { }
|
||||
|
||||
internal static ChannelShowHour Create(Guid channelShowId, int startHour, int endHour) =>
|
||||
new()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
ChannelShowId = channelShowId,
|
||||
StartHour = startHour,
|
||||
EndHour = endHour,
|
||||
};
|
||||
|
||||
/// <summary>Попадает ли час суток (0..23) в это окно.</summary>
|
||||
public bool Contains(int hour) => hour >= StartHour && hour < EndHour;
|
||||
}
|
||||
@@ -19,6 +19,9 @@ public class ScheduleEntry
|
||||
/// <summary>Индекс серии в упорядоченном списке шоу (для EPG).</summary>
|
||||
public int? EpisodeIndex { get; private set; }
|
||||
|
||||
/// <summary>Подблок заставки (<see cref="BumperTextVariant"/>), которым отрендерена запись — для метки в админ-расписании.</summary>
|
||||
public Guid? BumperVariantId { get; private set; }
|
||||
|
||||
private ScheduleEntry() { }
|
||||
|
||||
public static ScheduleEntry Program(
|
||||
@@ -63,7 +66,8 @@ public class ScheduleEntry
|
||||
Guid mediaAssetId,
|
||||
DateTimeOffset startsAtUtc,
|
||||
DateTimeOffset endsAtUtc,
|
||||
Guid? showId
|
||||
Guid? showId,
|
||||
Guid? bumperVariantId
|
||||
) =>
|
||||
new()
|
||||
{
|
||||
@@ -74,5 +78,6 @@ public class ScheduleEntry
|
||||
StartsAtUtc = startsAtUtc,
|
||||
EndsAtUtc = endsAtUtc,
|
||||
ShowId = showId,
|
||||
BumperVariantId = bumperVariantId,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ public static class SchedulePlanner
|
||||
var pick = WeightedPick(candidates, random);
|
||||
|
||||
// ТВ-заставка на переходе. Из подходящих подблоков (по правилу показа vs контексту)
|
||||
// резервируем слот выбранного блока — ассет подставит оркестратор.
|
||||
// резервируем слот выбранного блока — ассет подставит оркестратор. Само появление
|
||||
// ограничено мин. интервалом и вероятностью для типа перехода (смена шоу / между блоками).
|
||||
if (
|
||||
prevShowId is { } prev
|
||||
&& input.Bumpers is { Enabled: true } bumper
|
||||
@@ -51,9 +52,14 @@ public static class SchedulePlanner
|
||||
)
|
||||
)
|
||||
{
|
||||
var bumperStart = cursor;
|
||||
if (TryPlaceBumper(entries, bumper, prev, pick.ShowId, random, ref nextBumper, ref cursor))
|
||||
lastBumperAt = bumperStart;
|
||||
var isShowChange = prev != pick.ShowId;
|
||||
var chance = isShowChange ? bumper.ShowChangeChance : bumper.EpisodeChangeChance;
|
||||
if (RollChance(chance, random))
|
||||
{
|
||||
var bumperStart = cursor;
|
||||
if (TryPlaceBumper(entries, bumper, prev, pick.ShowId, random, ref nextBumper, ref cursor))
|
||||
lastBumperAt = bumperStart;
|
||||
}
|
||||
}
|
||||
|
||||
var blockStart = cursor;
|
||||
@@ -121,6 +127,9 @@ public static class SchedulePlanner
|
||||
case BumperSelection.Random:
|
||||
variant = eligible[random.Next(eligible.Count)];
|
||||
break;
|
||||
case BumperSelection.WeightedRandom:
|
||||
variant = WeightedPickVariant(eligible, random);
|
||||
break;
|
||||
case BumperSelection.AlwaysFirst:
|
||||
variant = eligible[0];
|
||||
break;
|
||||
@@ -158,6 +167,38 @@ public static class SchedulePlanner
|
||||
_ => true,
|
||||
};
|
||||
|
||||
/// <summary>Прошла ли проверка вероятности появления (chance 0..1). 1 — всегда, 0 — никогда.</summary>
|
||||
private static bool RollChance(double chance, IRandomSource random)
|
||||
{
|
||||
if (chance >= 1.0)
|
||||
return true;
|
||||
if (chance <= 0.0)
|
||||
return false;
|
||||
return random.Next(10000) < (int)Math.Round(chance * 10000);
|
||||
}
|
||||
|
||||
/// <summary>Взвешенный случайный выбор подблока по <see cref="PlannerBumperVariant.Weight"/> (нулевые веса → равновероятно).</summary>
|
||||
private static PlannerBumperVariant WeightedPickVariant(
|
||||
List<PlannerBumperVariant> eligible,
|
||||
IRandomSource random
|
||||
)
|
||||
{
|
||||
var total = eligible.Sum(v => Math.Max(0, v.Weight));
|
||||
if (total <= 0)
|
||||
return eligible[random.Next(eligible.Count)];
|
||||
|
||||
var roll = random.Next(total);
|
||||
var acc = 0;
|
||||
foreach (var v in eligible)
|
||||
{
|
||||
acc += Math.Max(0, v.Weight);
|
||||
if (roll < acc)
|
||||
return v;
|
||||
}
|
||||
|
||||
return eligible[^1];
|
||||
}
|
||||
|
||||
private static List<(PlannerShow Show, int Weight)> ResolvePolicy(
|
||||
DateTimeOffset moment,
|
||||
PlannerInput input,
|
||||
@@ -182,12 +223,25 @@ public static class SchedulePlanner
|
||||
// Override ссылается на пустые/неготовые шоу — откатываемся к базовой ротации.
|
||||
}
|
||||
|
||||
var hour = moment.UtcDateTime.Hour;
|
||||
return input.Shows
|
||||
.Where(s => s.Weight > 0 && s.EpisodeAssetIds.Count > 0)
|
||||
.Select(s => (s, s.Weight))
|
||||
.Select(s => (s, EffectiveWeight(s, hour)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>Вес шоу с учётом предпочтительных часов: в окне — усиливается множителем, иначе базовый.</summary>
|
||||
private static int EffectiveWeight(PlannerShow show, int hour)
|
||||
{
|
||||
if (
|
||||
show.PreferredWeightMultiplier > 1
|
||||
&& show.PreferredHours is { Count: > 0 } windows
|
||||
&& windows.Any(w => w.Contains(hour))
|
||||
)
|
||||
return show.Weight * show.PreferredWeightMultiplier;
|
||||
return show.Weight;
|
||||
}
|
||||
|
||||
private static PlannerShow WeightedPick(
|
||||
List<(PlannerShow Show, int Weight)> candidates,
|
||||
IRandomSource random
|
||||
|
||||
@@ -8,9 +8,17 @@ public sealed record PlannerShow(
|
||||
BlockMode BlockMode,
|
||||
int BlockValue,
|
||||
IReadOnlyList<Guid> EpisodeAssetIds,
|
||||
int NextEpisodeIndex
|
||||
int NextEpisodeIndex,
|
||||
IReadOnlyList<PlannerHourWindow>? PreferredHours = null,
|
||||
int PreferredWeightMultiplier = 1
|
||||
);
|
||||
|
||||
/// <summary>Окно предпочтительных часов [StartHour, EndHour) суток (UTC) для планировщика.</summary>
|
||||
public sealed record PlannerHourWindow(int StartHour, int EndHour)
|
||||
{
|
||||
public bool Contains(int hour) => hour >= StartHour && hour < EndHour;
|
||||
}
|
||||
|
||||
/// <summary>Override в терминах планировщика: окно + режим + шоу с весами.</summary>
|
||||
public sealed record PlannerOverride(
|
||||
DateTimeOffset StartsAtUtc,
|
||||
@@ -25,23 +33,28 @@ public sealed record PlannerOverrideShow(Guid ShowId, int Weight);
|
||||
/// Политика ТВ-заставок на переходах. Планировщик из подходящих подблоков (<see cref="Variants"/>,
|
||||
/// фильтр по <see cref="PlannerBumperVariant.Trigger"/> и контексту перехода) выбирает один по стратегии
|
||||
/// <see cref="Selection"/> и резервирует слот длины его блока. Ассет подставляет оркестратор.
|
||||
/// <see cref="ShowChangeChance"/>/<see cref="EpisodeChangeChance"/> — вероятность самого появления
|
||||
/// заставки на смене шоу / между блоками одного шоу (0..1).
|
||||
/// </summary>
|
||||
public sealed record PlannerBumperConfig(
|
||||
bool Enabled,
|
||||
TimeSpan MinInterval,
|
||||
BumperSelection Selection,
|
||||
IReadOnlyList<PlannerBumperVariant> Variants
|
||||
IReadOnlyList<PlannerBumperVariant> Variants,
|
||||
double ShowChangeChance = 1.0,
|
||||
double EpisodeChangeChance = 1.0
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Подблок заставки в терминах планировщика: id варианта + id родительского блока (стиль/звук) +
|
||||
/// длительность слота (кратна сегменту) + правило показа.
|
||||
/// длительность слота (кратна сегменту) + правило показа + вес (для <see cref="BumperSelection.WeightedRandom"/>).
|
||||
/// </summary>
|
||||
public sealed record PlannerBumperVariant(
|
||||
Guid VariantId,
|
||||
Guid TemplateId,
|
||||
TimeSpan Duration,
|
||||
BumperTrigger Trigger
|
||||
BumperTrigger Trigger,
|
||||
int Weight = 1
|
||||
);
|
||||
|
||||
/// <summary>Полный вход планировщика для одного прогона по каналу.</summary>
|
||||
|
||||
+956
@@ -0,0 +1,956 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TeleWave.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TeleWave.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20260725105210_BumperChancesWeightsAndScheduleVariant")]
|
||||
partial class BumperChancesWeightsAndScheduleVariant
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Auth.RefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperAsset", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("FromShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Signature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<Guid>("ToShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FromShowId", "ToShowId", "Signature");
|
||||
|
||||
b.ToTable("BumperAssets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AccentColor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<double?>("AudioDurationSeconds")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("AudioExtension")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("BackgroundColor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("BackgroundColor2")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<Guid?>("BackgroundImageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("TextColor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "Position");
|
||||
|
||||
b.ToTable("BumperTemplate");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTextVariant", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("BumperTemplateId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Line1")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<string>("Line2")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("NextLabel")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("NowLabel")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Trigger")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BumperTemplateId", "Position");
|
||||
|
||||
b.ToTable("BumperTextVariants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("AdInsertion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("AdsPerBreak")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("BumperEpisodeChangeChance")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<int>("BumperFont")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("BumperMinIntervalMinutes")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("BumperSelection")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("BumperShowChangeChance")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<bool>("BumpersEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("EpochUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("FillerAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int>("NextAdIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("NextBumperIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelAd", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "Position");
|
||||
|
||||
b.ToTable("ChannelAd");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("BlockMode")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("BlockValue")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("NextEpisodeIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "ShowId");
|
||||
|
||||
b.ToTable("ChannelShow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ProgrammingOverrideId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ProgrammingOverrideId");
|
||||
|
||||
b.ToTable("OverrideShow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("EndsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("Mode")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("StartsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "StartsAtUtc", "EndsAtUtc");
|
||||
|
||||
b.ToTable("ProgrammingOverride");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ScheduleEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("BumperVariantId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("EndsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("EpisodeIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("StartsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "EndsAtUtc");
|
||||
|
||||
b.HasIndex("ChannelId", "ShowId");
|
||||
|
||||
b.HasIndex("ChannelId", "StartsAtUtc");
|
||||
|
||||
b.ToTable("ScheduleEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Images.Image", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Category")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("FileExtension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Category", "CreatedAt");
|
||||
|
||||
b.ToTable("Images");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("MetadataExternalId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MetadataProvider")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("OriginalName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid?>("PosterImageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.ShowEpisode", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateOnly?>("AirDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("Episode")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("Season")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("StillImageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MediaAssetId");
|
||||
|
||||
b.HasIndex("ShowId", "Position");
|
||||
|
||||
b.ToTable("ShowEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Media.MediaAsset", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AudioCodec")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<TimeSpan?>("Duration")
|
||||
.HasColumnType("interval");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("Height")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("OriginalExtension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int?>("SegmentCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("SegmentSeconds")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Source")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("VideoCodec")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<int?>("Width")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("MediaAssets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Settings.AppSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("AppSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Infrastructure.Identity.AppRole", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsSystem")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Infrastructure.Identity.AppUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsBlocked")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("BumperTemplates")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTextVariant", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.BumperTemplate", null)
|
||||
.WithMany("Variants")
|
||||
.HasForeignKey("BumperTemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelAd", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("Ads")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("Shows")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.ProgrammingOverride", null)
|
||||
.WithMany("Shows")
|
||||
.HasForeignKey("ProgrammingOverrideId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("Overrides")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.ShowEpisode", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Library.Show", null)
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ShowId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
|
||||
{
|
||||
b.Navigation("Variants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
|
||||
{
|
||||
b.Navigation("Ads");
|
||||
|
||||
b.Navigation("BumperTemplates");
|
||||
|
||||
b.Navigation("Overrides");
|
||||
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TeleWave.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BumperChancesWeightsAndScheduleVariant : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BumperTextVariant_BumperTemplate_BumperTemplateId",
|
||||
table: "BumperTextVariant");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_BumperTextVariant",
|
||||
table: "BumperTextVariant");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "BumperTextVariant",
|
||||
newName: "BumperTextVariants");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_BumperTextVariant_BumperTemplateId_Position",
|
||||
table: "BumperTextVariants",
|
||||
newName: "IX_BumperTextVariants_BumperTemplateId_Position");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "BumperVariantId",
|
||||
table: "ScheduleEntries",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
// Существующим каналам — 1.0 (заставка на каждом подходящем переходе, как было до фичи).
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "BumperEpisodeChangeChance",
|
||||
table: "Channels",
|
||||
type: "double precision",
|
||||
nullable: false,
|
||||
defaultValue: 1.0);
|
||||
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "BumperShowChangeChance",
|
||||
table: "Channels",
|
||||
type: "double precision",
|
||||
nullable: false,
|
||||
defaultValue: 1.0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Weight",
|
||||
table: "BumperTextVariants",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 1);
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_BumperTextVariants",
|
||||
table: "BumperTextVariants",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BumperTextVariants_BumperTemplate_BumperTemplateId",
|
||||
table: "BumperTextVariants",
|
||||
column: "BumperTemplateId",
|
||||
principalTable: "BumperTemplate",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_BumperTextVariants_BumperTemplate_BumperTemplateId",
|
||||
table: "BumperTextVariants");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_BumperTextVariants",
|
||||
table: "BumperTextVariants");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BumperVariantId",
|
||||
table: "ScheduleEntries");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BumperEpisodeChangeChance",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BumperShowChangeChance",
|
||||
table: "Channels");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Weight",
|
||||
table: "BumperTextVariants");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "BumperTextVariants",
|
||||
newName: "BumperTextVariant");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_BumperTextVariants_BumperTemplateId_Position",
|
||||
table: "BumperTextVariant",
|
||||
newName: "IX_BumperTextVariant_BumperTemplateId_Position");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_BumperTextVariant",
|
||||
table: "BumperTextVariant",
|
||||
column: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_BumperTextVariant_BumperTemplate_BumperTemplateId",
|
||||
table: "BumperTextVariant",
|
||||
column: "BumperTemplateId",
|
||||
principalTable: "BumperTemplate",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+996
@@ -0,0 +1,996 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using TeleWave.Infrastructure.Persistence;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TeleWave.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDbContext))]
|
||||
[Migration("20260725154034_ChannelShowPreferredHours")]
|
||||
partial class ChannelShowPreferredHours
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Auth.RefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperAsset", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("FromShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Signature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<Guid>("ToShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FromShowId", "ToShowId", "Signature");
|
||||
|
||||
b.ToTable("BumperAssets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AccentColor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<double?>("AudioDurationSeconds")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<string>("AudioExtension")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("BackgroundColor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<string>("BackgroundColor2")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<Guid?>("BackgroundImageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("TextColor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "Position");
|
||||
|
||||
b.ToTable("BumperTemplate");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTextVariant", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("BumperTemplateId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Line1")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<string>("Line2")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("character varying(120)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("NextLabel")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("NowLabel")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Trigger")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BumperTemplateId", "Position");
|
||||
|
||||
b.ToTable("BumperTextVariants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("AdInsertion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("AdsPerBreak")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("BumperEpisodeChangeChance")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<int>("BumperFont")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("BumperMinIntervalMinutes")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("BumperSelection")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("BumperShowChangeChance")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<bool>("BumpersEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("EpochUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("FillerAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int>("NextAdIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("NextBumperIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Channels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelAd", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "Position");
|
||||
|
||||
b.ToTable("ChannelAd");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("BlockMode")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("BlockValue")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("NextEpisodeIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("PreferredWeightMultiplier")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(3);
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "ShowId");
|
||||
|
||||
b.ToTable("ChannelShow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShowHour", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("EndHour")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("StartHour")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelShowId");
|
||||
|
||||
b.ToTable("ChannelShowHour");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ProgrammingOverrideId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ProgrammingOverrideId");
|
||||
|
||||
b.ToTable("OverrideShow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("EndsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("Mode")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("StartsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "StartsAtUtc", "EndsAtUtc");
|
||||
|
||||
b.ToTable("ProgrammingOverride");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ScheduleEntry", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("BumperVariantId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("EndsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("EpisodeIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("StartsAtUtc")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelId", "EndsAtUtc");
|
||||
|
||||
b.HasIndex("ChannelId", "ShowId");
|
||||
|
||||
b.HasIndex("ChannelId", "StartsAtUtc");
|
||||
|
||||
b.ToTable("ScheduleEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Images.Image", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("Category")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("FileExtension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Category", "CreatedAt");
|
||||
|
||||
b.ToTable("Images");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int>("Kind")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("MetadataExternalId")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("MetadataProvider")
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("OriginalName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<Guid?>("PosterImageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int?>("Year")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.ShowEpisode", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateOnly?>("AirDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("Episode")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("MediaAssetId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Overview")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<int>("Position")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("Season")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("StillImageId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MediaAssetId");
|
||||
|
||||
b.HasIndex("ShowId", "Position");
|
||||
|
||||
b.ToTable("ShowEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Media.MediaAsset", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("AudioCodec")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<TimeSpan?>("Duration")
|
||||
.HasColumnType("interval");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<int?>("Height")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("OriginalExtension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(16)
|
||||
.HasColumnType("character varying(16)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<int?>("SegmentCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("SegmentSeconds")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Source")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("VideoCodec")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)");
|
||||
|
||||
b.Property<int?>("Width")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("MediaAssets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Settings.AppSetting", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("AppSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Infrastructure.Identity.AppRole", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsSystem")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Infrastructure.Identity.AppUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("IsBlocked")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Infrastructure.Identity.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("BumperTemplates")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTextVariant", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.BumperTemplate", null)
|
||||
.WithMany("Variants")
|
||||
.HasForeignKey("BumperTemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelAd", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("Ads")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("Shows")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShowHour", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.ChannelShow", null)
|
||||
.WithMany("PreferredHours")
|
||||
.HasForeignKey("ChannelShowId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.ProgrammingOverride", null)
|
||||
.WithMany("Shows")
|
||||
.HasForeignKey("ProgrammingOverrideId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.Channel", null)
|
||||
.WithMany("Overrides")
|
||||
.HasForeignKey("ChannelId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.ShowEpisode", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Library.Show", null)
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ShowId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.BumperTemplate", b =>
|
||||
{
|
||||
b.Navigation("Variants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
|
||||
{
|
||||
b.Navigation("Ads");
|
||||
|
||||
b.Navigation("BumperTemplates");
|
||||
|
||||
b.Navigation("Overrides");
|
||||
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
|
||||
{
|
||||
b.Navigation("PreferredHours");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Library.Show", b =>
|
||||
{
|
||||
b.Navigation("Episodes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TeleWave.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChannelShowPreferredHours : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "PreferredWeightMultiplier",
|
||||
table: "ChannelShow",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 3);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChannelShowHour",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ChannelShowId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
StartHour = table.Column<int>(type: "integer", nullable: false),
|
||||
EndHour = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChannelShowHour", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ChannelShowHour_ChannelShow_ChannelShowId",
|
||||
column: x => x.ChannelShowId,
|
||||
principalTable: "ChannelShow",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChannelShowHour_ChannelShowId",
|
||||
table: "ChannelShowHour",
|
||||
column: "ChannelShowId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChannelShowHour");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PreferredWeightMultiplier",
|
||||
table: "ChannelShow");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,11 +292,16 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.Property<int>("Trigger")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Weight")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(1);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BumperTemplateId", "Position");
|
||||
|
||||
b.ToTable("BumperTextVariant");
|
||||
b.ToTable("BumperTextVariants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.Channel", b =>
|
||||
@@ -310,6 +315,9 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.Property<int>("AdsPerBreak")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("BumperEpisodeChangeChance")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<int>("BumperFont")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@@ -319,6 +327,9 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.Property<int>("BumperSelection")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<double>("BumperShowChangeChance")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<bool>("BumpersEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
@@ -399,6 +410,11 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.Property<int>("NextEpisodeIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("PreferredWeightMultiplier")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasDefaultValue(3);
|
||||
|
||||
b.Property<Guid>("ShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@@ -412,6 +428,27 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.ToTable("ChannelShow");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShowHour", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelShowId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<int>("EndHour")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("StartHour")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChannelShowId");
|
||||
|
||||
b.ToTable("ChannelShowHour");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -462,6 +499,9 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.Property<Guid>("Id")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid?>("BumperVariantId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("ChannelId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
@@ -881,6 +921,15 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShowHour", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.ChannelShow", null)
|
||||
.WithMany("PreferredHours")
|
||||
.HasForeignKey("ChannelShowId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.OverrideShow", b =>
|
||||
{
|
||||
b.HasOne("TeleWave.Domain.Broadcast.ProgrammingOverride", null)
|
||||
@@ -924,6 +973,11 @@ namespace TeleWave.Infrastructure.Migrations
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ChannelShow", b =>
|
||||
{
|
||||
b.Navigation("PreferredHours");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TeleWave.Domain.Broadcast.ProgrammingOverride", b =>
|
||||
{
|
||||
b.Navigation("Shows");
|
||||
|
||||
@@ -25,6 +25,7 @@ public class AppDbContext(DbContextOptions<AppDbContext> options)
|
||||
public DbSet<Show> Shows => Set<Show>();
|
||||
public DbSet<Channel> Channels => Set<Channel>();
|
||||
public DbSet<ScheduleEntry> ScheduleEntries => Set<ScheduleEntry>();
|
||||
public DbSet<BumperTextVariant> BumperTextVariants => Set<BumperTextVariant>();
|
||||
public DbSet<BumperAsset> BumperAssets => Set<BumperAsset>();
|
||||
public DbSet<AppSetting> AppSettings => Set<AppSetting>();
|
||||
public DbSet<Image> Images => Set<Image>();
|
||||
|
||||
+18
@@ -48,6 +48,23 @@ public class ChannelShowConfiguration : IEntityTypeConfiguration<ChannelShow>
|
||||
public void Configure(EntityTypeBuilder<ChannelShow> builder)
|
||||
{
|
||||
builder.HasIndex(x => new { x.ChannelId, x.ShowId });
|
||||
builder.Property(x => x.PreferredWeightMultiplier)
|
||||
.HasDefaultValue(ChannelShow.DefaultPreferredWeightMultiplier);
|
||||
|
||||
builder
|
||||
.HasMany(x => x.PreferredHours)
|
||||
.WithOne()
|
||||
.HasForeignKey(h => h.ChannelShowId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
builder.Navigation(x => x.PreferredHours).UsePropertyAccessMode(PropertyAccessMode.Field);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelShowHourConfiguration : IEntityTypeConfiguration<ChannelShowHour>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ChannelShowHour> builder)
|
||||
{
|
||||
builder.HasIndex(x => x.ChannelShowId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +107,7 @@ public class BumperTextVariantConfiguration : IEntityTypeConfiguration<BumperTex
|
||||
builder.Property(x => x.NextLabel).IsRequired().HasMaxLength(64);
|
||||
builder.Property(x => x.Line1).IsRequired().HasMaxLength(120);
|
||||
builder.Property(x => x.Line2).IsRequired().HasMaxLength(120);
|
||||
builder.Property(x => x.Weight).HasDefaultValue(BumperTextVariant.DefaultWeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -415,6 +415,108 @@ public class SchedulePlannerTests
|
||||
Assert.DoesNotContain(result.Entries, e => e.Kind == ScheduleEntryKind.Bumper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_ShowChangeChanceZero_SuppressesBumpersOnShowChange()
|
||||
{
|
||||
// Два шоу, ротация выбора подблока (без random в пути заставки) → чередование гарантировано,
|
||||
// каждый переход — смена шоу. ShowChangeChance=0 глушит их, EpisodeChangeChance=1 не при чём.
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20));
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(120)) with
|
||||
{
|
||||
Bumpers = Bumper(true, BumperTrigger.Both, TimeSpan.Zero) with { ShowChangeChance = 0.0 },
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1));
|
||||
|
||||
Assert.DoesNotContain(result.Entries, e => e.Kind == ScheduleEntryKind.Bumper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_EpisodeChangeChanceZero_SuppressesBumpersBetweenEpisodes()
|
||||
{
|
||||
// Одно шоу → каждый переход между блоками одного шоу. EpisodeChangeChance=0 глушит все заставки.
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20));
|
||||
|
||||
var input = BaseInput([a], durations, Start.AddMinutes(120)) with
|
||||
{
|
||||
Bumpers = Bumper(true, BumperTrigger.Both, TimeSpan.Zero) with { EpisodeChangeChance = 0.0 },
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0));
|
||||
|
||||
Assert.DoesNotContain(result.Entries, e => e.Kind == ScheduleEntryKind.Bumper);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Bumpers_WeightedRandom_NeverPicksZeroWeightVariant()
|
||||
{
|
||||
// Взвешенный выбор: подблок с весом 0 не выбирается никогда, весь трафик уходит на t1 (вес 5).
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20));
|
||||
Guid t0 = Guid.NewGuid(),
|
||||
t1 = Guid.NewGuid();
|
||||
|
||||
var input = BaseInput([a], durations, Start.AddMinutes(120)) with
|
||||
{
|
||||
Bumpers = Bumper(
|
||||
true,
|
||||
BumperTrigger.Both,
|
||||
TimeSpan.Zero,
|
||||
BumperSelection.WeightedRandom,
|
||||
new PlannerBumperVariant(Guid.NewGuid(), t0, TimeSpan.FromSeconds(8), BumperTrigger.Both, 0),
|
||||
new PlannerBumperVariant(Guid.NewGuid(), t1, TimeSpan.FromSeconds(8), BumperTrigger.Both, 5)
|
||||
),
|
||||
};
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(0, 1, 2, 3, 4));
|
||||
|
||||
var bumpers = result.Entries.Where(e => e.Kind == ScheduleEntryKind.Bumper).ToList();
|
||||
Assert.NotEmpty(bumpers);
|
||||
Assert.All(bumpers, e => Assert.Equal(t1, e.BumperTemplateId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreferredHours_BoostsWeight_InsideWindow()
|
||||
{
|
||||
// Час старта — 0 (UTC). Окно b покрывает его, множитель 100 → b доминирует, хотя базовые веса равны.
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(
|
||||
Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0,
|
||||
PreferredHours: [new PlannerHourWindow(0, 24)], PreferredWeightMultiplier: 100);
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20));
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(60));
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(50));
|
||||
|
||||
var programs = result.Entries.Where(e => e.Kind == ScheduleEntryKind.Program).ToList();
|
||||
Assert.NotEmpty(programs);
|
||||
Assert.All(programs, e => Assert.Equal(b.ShowId, e.ShowId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreferredHours_OutsideWindow_KeepsBaseWeight()
|
||||
{
|
||||
// Час старта — 0, окно b — [10,12): не покрывает → буста нет, при FixedRandom(50) берётся первое шоу (a).
|
||||
var a = new PlannerShow(Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0);
|
||||
var b = new PlannerShow(
|
||||
Guid.NewGuid(), Guid.NewGuid(), 1, BlockMode.Count, 1, [Guid.NewGuid()], 0,
|
||||
PreferredHours: [new PlannerHourWindow(10, 12)], PreferredWeightMultiplier: 100);
|
||||
var durations = Durations((a.EpisodeAssetIds[0], 20), (b.EpisodeAssetIds[0], 20));
|
||||
|
||||
var input = BaseInput([a, b], durations, Start.AddMinutes(60));
|
||||
|
||||
var result = SchedulePlanner.Plan(input, new FixedRandom(50));
|
||||
|
||||
var programs = result.Entries.Where(e => e.Kind == ScheduleEntryKind.Program).ToList();
|
||||
Assert.NotEmpty(programs);
|
||||
Assert.All(programs, e => Assert.Equal(a.ShowId, e.ShowId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoPlayableShows_ReturnsEmpty()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user