Update scheduling parameters and refactor channel endpoints: extend HorizonDays to 7 and RetentionDays to 90 in appsettings.json. Consolidate channel-related endpoint logic by removing obsolete files and enhancing the ShowEndpoints with audience and genre management capabilities. Improve error handling and streamline command handlers for channel operations.
This commit is contained in:
+477
-294
@@ -1,294 +1,477 @@
|
||||
export type ApiError = {
|
||||
title: string
|
||||
detail: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export type CurrentUser = {
|
||||
id: string
|
||||
userName: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export type AuthResponse = {
|
||||
accessToken: string
|
||||
expiresAt: string
|
||||
user: CurrentUser
|
||||
}
|
||||
|
||||
export type RegistrationStatus = { enabled: boolean }
|
||||
|
||||
export type SiteSettings = { registrationEnabled: boolean; preferredAudioLanguages: string }
|
||||
|
||||
export type RoleDto = {
|
||||
id: string
|
||||
name: string
|
||||
isSystem: boolean
|
||||
}
|
||||
|
||||
export type UserSummaryDto = {
|
||||
id: string
|
||||
userName: string
|
||||
role: string
|
||||
isBlocked: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type PagedList<T> = {
|
||||
items: T[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export type CreatedIdResponse = { id: string }
|
||||
|
||||
// ── Медиа ────────────────────────────────────────────────────────────────
|
||||
export type MediaAssetStatus = 'Pending' | 'Processing' | 'Ready' | 'Failed'
|
||||
export type MediaSource = 'Upload' | 'Inbox'
|
||||
|
||||
export type MediaAssetDto = {
|
||||
id: string
|
||||
originalFileName: string
|
||||
source: MediaSource
|
||||
status: MediaAssetStatus
|
||||
durationSeconds: number | null
|
||||
width: number | null
|
||||
height: number | null
|
||||
errorMessage: string | null
|
||||
processingSeconds: number | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type MediaStatsDto = {
|
||||
queued: number
|
||||
processing: number
|
||||
averageProcessingSeconds: number | null
|
||||
}
|
||||
|
||||
// ── Библиотека (шоу) ───────────────────────────────────────────────────────
|
||||
export type ShowKind = 'Series' | 'Single'
|
||||
|
||||
/** Категория аудитории: обычное / детское / взрослое. */
|
||||
export type ShowAudience = 'General' | 'Kids' | 'Adult'
|
||||
|
||||
export type ShowSummaryDto = {
|
||||
id: string
|
||||
name: string
|
||||
originalName: string | null
|
||||
kind: ShowKind
|
||||
audience: ShowAudience
|
||||
episodeCount: number
|
||||
seasonCount: number
|
||||
year: number | null
|
||||
hasPoster: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type MetadataCandidate = {
|
||||
externalId: string
|
||||
title: string
|
||||
year: number | null
|
||||
overview: string | null
|
||||
posterUrl: string | null
|
||||
}
|
||||
|
||||
export type SeasonGapDto = {
|
||||
season: number
|
||||
expected: number | null
|
||||
loaded: number
|
||||
missing: number[]
|
||||
}
|
||||
|
||||
export type MissingEpisodesReport = {
|
||||
seasons: SeasonGapDto[]
|
||||
}
|
||||
|
||||
export type EpisodeDto = {
|
||||
id: string
|
||||
mediaAssetId: string
|
||||
position: number
|
||||
assetName: string | null
|
||||
assetStatus: MediaAssetStatus | null
|
||||
durationSeconds: number | null
|
||||
season: number | null
|
||||
episode: number | null
|
||||
title: string | null
|
||||
overview: string | null
|
||||
stillImageId: string | null
|
||||
}
|
||||
|
||||
export type ImageCategory = 'Library' | 'ShowPoster' | 'EpisodeStill' | 'BumperBackground'
|
||||
|
||||
export type ImageDto = {
|
||||
id: string
|
||||
category: ImageCategory
|
||||
originalFileName: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type ShowDto = {
|
||||
id: string
|
||||
name: string
|
||||
originalName: string | null
|
||||
kind: ShowKind
|
||||
audience: ShowAudience
|
||||
description: string | null
|
||||
metadataProvider: string | null
|
||||
metadataExternalId: string | null
|
||||
year: number | null
|
||||
posterImageId: string | null
|
||||
episodes: EpisodeDto[]
|
||||
}
|
||||
|
||||
// ── Каналы ────────────────────────────────────────────────────────────────
|
||||
export type BlockMode = 'Count' | 'Duration'
|
||||
export type AdInsertion = 'BetweenBlocks' | 'BetweenEpisodes'
|
||||
export type OverrideMode = 'Exclusive' | 'Boost'
|
||||
export type ScheduleEntryKind = 'Program' | 'Ad' | 'Bumper'
|
||||
export type BumperFont = 'Sans' | 'Serif'
|
||||
export type BumperSelection = 'Rotation' | 'Random' | 'AlwaysFirst' | 'WeightedRandom'
|
||||
export type BumperTextKind = 'NowNext' | 'Free'
|
||||
export type BumperTrigger = 'OnShowChange' | 'BetweenEpisodes' | 'Both'
|
||||
|
||||
/** Общие для канала настройки заставок (стиль/звук/текст — на блоках и подблоках). */
|
||||
export type BumperSettings = {
|
||||
font: BumperFont
|
||||
minIntervalMinutes: number
|
||||
selection: BumperSelection
|
||||
/** Вероятность заставки на смене шоу (0..1). */
|
||||
showChangeChance: number
|
||||
/** Вероятность заставки между блоками одного шоу (0..1). */
|
||||
episodeChangeChance: number
|
||||
}
|
||||
|
||||
/** Подблок (текст-вариант): свой текст + правило показа поверх стиля/звука блока. */
|
||||
export type BumperTextVariantDto = {
|
||||
id: string
|
||||
position: number
|
||||
name: string
|
||||
kind: BumperTextKind
|
||||
nowLabel: string
|
||||
nextLabel: string
|
||||
line1: string
|
||||
line2: string
|
||||
trigger: BumperTrigger
|
||||
/** Вес при стратегии выбора «случайно взвешенный» (0 — не выбирается). */
|
||||
weight: number
|
||||
}
|
||||
|
||||
export type BumperTemplateDto = {
|
||||
id: string
|
||||
position: number
|
||||
isDefault: boolean
|
||||
name: string
|
||||
backgroundColor: string
|
||||
backgroundColor2: string
|
||||
accentColor: string
|
||||
textColor: string
|
||||
backgroundImageId: string | null
|
||||
hasAudio: boolean
|
||||
audioDurationSeconds: number | null
|
||||
variants: BumperTextVariantDto[]
|
||||
}
|
||||
|
||||
export type ChannelSummaryDto = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
isEnabled: boolean
|
||||
}
|
||||
|
||||
/** Окно предпочтительных часов [startHour, endHour) суток (UTC). */
|
||||
export type HourWindow = { startHour: number; endHour: number }
|
||||
|
||||
export type ChannelShowDto = {
|
||||
id: string
|
||||
showId: string
|
||||
showName: string
|
||||
weight: number
|
||||
blockMode: BlockMode
|
||||
blockValue: number
|
||||
isEnabled: boolean
|
||||
/** Во сколько раз усиливать вес в предпочтительные часы (1 — без буста). */
|
||||
preferredWeightMultiplier: number
|
||||
preferredHours: HourWindow[]
|
||||
}
|
||||
|
||||
export type ChannelAdDto = {
|
||||
id: string
|
||||
mediaAssetId: string
|
||||
assetName: string | null
|
||||
position: number
|
||||
}
|
||||
|
||||
export type OverrideShowDto = { showId: string; showName: string; weight: number }
|
||||
|
||||
export type OverrideRecurrence = 'OneTime' | 'Weekly'
|
||||
|
||||
export type ProgrammingOverrideDto = {
|
||||
id: string
|
||||
mode: OverrideMode
|
||||
recurrence: OverrideRecurrence
|
||||
startsAtUtc: string | null
|
||||
endsAtUtc: string | null
|
||||
/** Weekly: день недели 0=Вс..6=Сб; окно минут суток (UTC). */
|
||||
dayOfWeek: number | null
|
||||
startMinute: number | null
|
||||
endMinute: number | null
|
||||
shows: OverrideShowDto[]
|
||||
}
|
||||
|
||||
export type ChannelDto = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
isEnabled: boolean
|
||||
adInsertion: AdInsertion
|
||||
adsPerBreak: number
|
||||
bumpersEnabled: boolean
|
||||
bumper: BumperSettings
|
||||
bumperTemplates: BumperTemplateDto[]
|
||||
fillerAssetId: string | null
|
||||
shows: ChannelShowDto[]
|
||||
ads: ChannelAdDto[]
|
||||
overrides: ProgrammingOverrideDto[]
|
||||
}
|
||||
|
||||
export type ScheduleEntryDto = {
|
||||
id: string
|
||||
kind: ScheduleEntryKind
|
||||
mediaAssetId: string
|
||||
startsAtUtc: string
|
||||
endsAtUtc: string
|
||||
showId: string | null
|
||||
showName: string | null
|
||||
episodeIndex: number | null
|
||||
seasonEpisode: string | null
|
||||
/** Для заставок: имя подблока и его текст — для метки в расписании. */
|
||||
bumperName: string | null
|
||||
bumperText: string | null
|
||||
}
|
||||
|
||||
// ── Публичный эфир ─────────────────────────────────────────────────────────
|
||||
export type PublicChannelDto = {
|
||||
id: string
|
||||
slug: string
|
||||
name: string
|
||||
currentShowId: string | null
|
||||
currentShowName: string | null
|
||||
currentShowPosterImageId: string | null
|
||||
}
|
||||
|
||||
export type PublicEpgEntryDto = {
|
||||
kind: ScheduleEntryKind
|
||||
startsAtUtc: string
|
||||
endsAtUtc: string
|
||||
showId: string | null
|
||||
showName: string | null
|
||||
showPosterImageId: string | null
|
||||
episodeId: string | null
|
||||
episodeTitle: string | null
|
||||
episodeOverview: string | null
|
||||
episodeStillImageId: string | null
|
||||
}
|
||||
export type ApiError = {
|
||||
title: string
|
||||
detail: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export type CurrentUser = {
|
||||
id: string
|
||||
userName: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export type AuthResponse = {
|
||||
accessToken: string
|
||||
expiresAt: string
|
||||
user: CurrentUser
|
||||
}
|
||||
|
||||
export type RegistrationStatus = { enabled: boolean }
|
||||
|
||||
export type SiteSettings = { registrationEnabled: boolean; preferredAudioLanguages: string }
|
||||
|
||||
export type RoleDto = {
|
||||
id: string
|
||||
name: string
|
||||
isSystem: boolean
|
||||
}
|
||||
|
||||
export type UserSummaryDto = {
|
||||
id: string
|
||||
userName: string
|
||||
role: string
|
||||
isBlocked: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type PagedList<T> = {
|
||||
items: T[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
|
||||
export type CreatedIdResponse = { id: string }
|
||||
|
||||
// ── Медиа ────────────────────────────────────────────────────────────────
|
||||
export type MediaAssetStatus = 'Pending' | 'Processing' | 'Ready' | 'Failed'
|
||||
export type MediaSource = 'Upload' | 'Inbox'
|
||||
|
||||
export type MediaAssetDto = {
|
||||
id: string
|
||||
originalFileName: string
|
||||
source: MediaSource
|
||||
status: MediaAssetStatus
|
||||
durationSeconds: number | null
|
||||
width: number | null
|
||||
height: number | null
|
||||
errorMessage: string | null
|
||||
processingSeconds: number | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type MediaStatsDto = {
|
||||
queued: number
|
||||
processing: number
|
||||
averageProcessingSeconds: number | null
|
||||
}
|
||||
|
||||
// ── Библиотека (жанры) ─────────────────────────────────────────────────────
|
||||
export type GenreDto = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
sortOrder: number
|
||||
isSystem: boolean
|
||||
/** Варианты написания для сопоставления с метаданными провайдеров. */
|
||||
aliases: string[]
|
||||
/** Сколько шоу используют жанр — при ненулевом удаление заблокировано. */
|
||||
showCount: number
|
||||
}
|
||||
|
||||
// ── Библиотека (шоу) ───────────────────────────────────────────────────────
|
||||
export type ShowKind = 'Series' | 'Single'
|
||||
|
||||
/** Возрастная категория, по возрастанию строгости — порядок значим для правил планировщика. */
|
||||
export type ShowAudience = 'Kids' | 'Family' | 'Teen' | 'General' | 'Adult'
|
||||
|
||||
/** Тот же порядок для селектов и списков. */
|
||||
export const SHOW_AUDIENCES: ShowAudience[] = ['Kids', 'Family', 'Teen', 'General', 'Adult']
|
||||
|
||||
export type ShowSummaryDto = {
|
||||
id: string
|
||||
name: string
|
||||
originalName: string | null
|
||||
kind: ShowKind
|
||||
audience: ShowAudience
|
||||
episodeCount: number
|
||||
seasonCount: number
|
||||
year: number | null
|
||||
hasPoster: boolean
|
||||
createdAt: string
|
||||
/** Основной жанр — в списке показывается только он. */
|
||||
primaryGenre: string | null
|
||||
}
|
||||
|
||||
export type ShowGenreDto = {
|
||||
id: string
|
||||
name: string
|
||||
isPrimary: boolean
|
||||
}
|
||||
|
||||
// ── Библиотека (коллекции) ─────────────────────────────────────────────────
|
||||
export type CollectionSummaryDto = {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
posterImageId: string | null
|
||||
/** Сколько частей в коллекции. */
|
||||
itemCount: number
|
||||
/** Сколько единиц воспроизведения суммарно — у сериала внутри коллекции их больше одной. */
|
||||
unitCount: number
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type CollectionItemDto = {
|
||||
showId: string
|
||||
position: number
|
||||
showName: string
|
||||
showKind: ShowKind
|
||||
showAudience: ShowAudience
|
||||
episodeCount: number
|
||||
year: number | null
|
||||
posterImageId: string | null
|
||||
}
|
||||
|
||||
export type CollectionDto = {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
posterImageId: string | null
|
||||
createdAt: string
|
||||
items: CollectionItemDto[]
|
||||
}
|
||||
|
||||
/** Коллекция, в которую входит шоу — для блока на экране шоу. */
|
||||
export type ShowCollectionRefDto = {
|
||||
id: string
|
||||
name: string
|
||||
position: number
|
||||
}
|
||||
|
||||
// ── Группы контента (планировщик) ──────────────────────────────────────────
|
||||
export type GroupElementKind = 'Show' | 'Collection'
|
||||
|
||||
/** Правило быстрого набора состава. Пустые поля не ограничивают. */
|
||||
export type GroupFilter = {
|
||||
elementKinds?: GroupElementKind[] | null
|
||||
showKinds?: ShowKind[] | null
|
||||
genreIds?: string[] | null
|
||||
maxAudience?: ShowAudience | null
|
||||
yearMin?: number | null
|
||||
yearMax?: number | null
|
||||
unitMinutesMin?: number | null
|
||||
unitMinutesMax?: number | null
|
||||
}
|
||||
|
||||
export type GroupSummaryDto = {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
itemCount: number
|
||||
unitCount: number
|
||||
totalDurationSeconds: number
|
||||
hasFilter: boolean
|
||||
statsComputedAt: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type GroupItemDto = {
|
||||
id: string
|
||||
elementKind: GroupElementKind
|
||||
elementId: string
|
||||
elementName: string
|
||||
weight: number
|
||||
position: number
|
||||
unitCount: number
|
||||
showKind: ShowKind | null
|
||||
audience: ShowAudience | null
|
||||
year: number | null
|
||||
posterImageId: string | null
|
||||
}
|
||||
|
||||
export type GroupDto = {
|
||||
id: string
|
||||
name: string
|
||||
description: string | null
|
||||
filter: GroupFilter | null
|
||||
itemCount: number
|
||||
unitCount: number
|
||||
totalDurationSeconds: number
|
||||
statsComputedAt: string | null
|
||||
items: GroupItemDto[]
|
||||
}
|
||||
|
||||
/** Кандидат, найденный правилом набора. */
|
||||
export type GroupCandidateDto = {
|
||||
elementKind: GroupElementKind
|
||||
elementId: string
|
||||
elementName: string
|
||||
unitCount: number
|
||||
showKind: ShowKind | null
|
||||
audience: ShowAudience | null
|
||||
year: number | null
|
||||
posterImageId: string | null
|
||||
alreadyInGroup: boolean
|
||||
}
|
||||
|
||||
export type MetadataCandidate = {
|
||||
externalId: string
|
||||
title: string
|
||||
year: number | null
|
||||
overview: string | null
|
||||
posterUrl: string | null
|
||||
}
|
||||
|
||||
export type SeasonGapDto = {
|
||||
season: number
|
||||
expected: number | null
|
||||
loaded: number
|
||||
missing: number[]
|
||||
}
|
||||
|
||||
export type MissingEpisodesReport = {
|
||||
seasons: SeasonGapDto[]
|
||||
}
|
||||
|
||||
export type EpisodeDto = {
|
||||
id: string
|
||||
mediaAssetId: string
|
||||
position: number
|
||||
assetName: string | null
|
||||
assetStatus: MediaAssetStatus | null
|
||||
durationSeconds: number | null
|
||||
season: number | null
|
||||
episode: number | null
|
||||
title: string | null
|
||||
overview: string | null
|
||||
stillImageId: string | null
|
||||
}
|
||||
|
||||
export type ImageCategory = 'Library' | 'ShowPoster' | 'EpisodeStill' | 'BumperBackground'
|
||||
|
||||
export type ImageDto = {
|
||||
id: string
|
||||
category: ImageCategory
|
||||
originalFileName: string | null
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
export type ShowDto = {
|
||||
id: string
|
||||
name: string
|
||||
originalName: string | null
|
||||
kind: ShowKind
|
||||
audience: ShowAudience
|
||||
description: string | null
|
||||
metadataProvider: string | null
|
||||
metadataExternalId: string | null
|
||||
year: number | null
|
||||
posterImageId: string | null
|
||||
episodes: EpisodeDto[]
|
||||
genres: ShowGenreDto[]
|
||||
collections: ShowCollectionRefDto[]
|
||||
}
|
||||
|
||||
// ── Каналы ────────────────────────────────────────────────────────────────
|
||||
export type ScheduleEntryKind = 'Program' | 'Ad' | 'Bumper' | 'Fallback' | 'SignOff'
|
||||
export type BumperFont = 'Sans' | 'Serif'
|
||||
export type BumperSelection = 'Rotation' | 'Random' | 'AlwaysFirst' | 'WeightedRandom'
|
||||
export type BumperTextKind = 'NowNext' | 'Free'
|
||||
export type BumperTrigger = 'OnShowChange' | 'BetweenEpisodes' | 'Both'
|
||||
|
||||
/** Общие для канала настройки заставок (стиль/звук/текст — на блоках и подблоках). */
|
||||
export type BumperSettings = {
|
||||
font: BumperFont
|
||||
minIntervalMinutes: number
|
||||
selection: BumperSelection
|
||||
/** Вероятность заставки на смене шоу (0..1). */
|
||||
showChangeChance: number
|
||||
/** Вероятность заставки между блоками одного шоу (0..1). */
|
||||
episodeChangeChance: number
|
||||
}
|
||||
|
||||
/** Подблок (текст-вариант): свой текст + правило показа поверх стиля/звука блока. */
|
||||
export type BumperTextVariantDto = {
|
||||
id: string
|
||||
position: number
|
||||
name: string
|
||||
kind: BumperTextKind
|
||||
nowLabel: string
|
||||
nextLabel: string
|
||||
line1: string
|
||||
line2: string
|
||||
trigger: BumperTrigger
|
||||
/** Вес при стратегии выбора «случайно взвешенный» (0 — не выбирается). */
|
||||
weight: number
|
||||
}
|
||||
|
||||
export type BumperTemplateDto = {
|
||||
id: string
|
||||
position: number
|
||||
isDefault: boolean
|
||||
name: string
|
||||
backgroundColor: string
|
||||
backgroundColor2: string
|
||||
accentColor: string
|
||||
textColor: string
|
||||
backgroundImageId: string | null
|
||||
hasAudio: boolean
|
||||
audioDurationSeconds: number | null
|
||||
variants: BumperTextVariantDto[]
|
||||
}
|
||||
|
||||
export type ChannelSummaryDto = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
isEnabled: boolean
|
||||
}
|
||||
|
||||
export type ChannelDto = {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
isEnabled: boolean
|
||||
/** Номер канала для переключения по номерам (null — не задан). */
|
||||
number: number | null
|
||||
/** Смещение времени канала от UTC, минуты (180 — московское). */
|
||||
utcOffsetMinutes: number
|
||||
/** Начало вещательных суток в времени канала («06:00:00»). */
|
||||
dayStartTime: string
|
||||
templateId: string | null
|
||||
bumpersEnabled: boolean
|
||||
bumper: BumperSettings
|
||||
bumperTemplates: BumperTemplateDto[]
|
||||
fillerAssetId: string | null
|
||||
}
|
||||
|
||||
// ── Сетка канала (шаблон → слои → слоты) ──────────────────────────────────
|
||||
export type Daypart = 'Morning' | 'Day' | 'Prime' | 'Night'
|
||||
export type SlotKind = 'Content' | 'Repeat' | 'SignOff'
|
||||
export type SlotBlockMode = 'Count' | 'Duration' | 'FillSlot'
|
||||
export type OverflowPolicy = 'ContinueNext' | 'ExtendSlot' | 'SkipIfNotFits'
|
||||
export type SlotStrategyType = 'Sequential' | 'RandomWithCooldown' | 'Fixed'
|
||||
export type CooldownFallback = 'OldestFirst' | 'IgnoreCooldown'
|
||||
|
||||
export type SlotStrategy = {
|
||||
type: SlotStrategyType
|
||||
restartOnEnd: boolean
|
||||
cooldownDays: number
|
||||
fallback: CooldownFallback
|
||||
fixedElementId?: string | null
|
||||
}
|
||||
|
||||
/** Что повторяет слот-повтор: точка в уже записанной ленте того же канала. */
|
||||
export type RepeatSource = { daysAgo: number; time: string; durationMinutes: number }
|
||||
|
||||
export type SlotDto = {
|
||||
id: string
|
||||
layerId: string
|
||||
/** День недели вещательных суток (0=Вс..6=Сб) или null — каждый день. */
|
||||
weekday: number | null
|
||||
targetStart: string
|
||||
targetDurationMinutes: number
|
||||
title: string
|
||||
daypart: Daypart
|
||||
slotKind: SlotKind
|
||||
groupId: string | null
|
||||
groupName: string | null
|
||||
strategy: SlotStrategy | null
|
||||
repeatSource: RepeatSource | null
|
||||
blockMode: SlotBlockMode
|
||||
blockValue: number
|
||||
overflowPolicy: OverflowPolicy
|
||||
isAnchor: boolean
|
||||
maxDriftMinutes: number
|
||||
/** Округление старта до кратного N минут (5/10/15/30) или null. */
|
||||
snapToMinutes: number | null
|
||||
}
|
||||
|
||||
export type DateRange = { from: string; to: string }
|
||||
export type AnnualRange = { fromMonth: number; fromDay: number; toMonth: number; toDay: number }
|
||||
|
||||
/** Когда действует слой. Пустая применимость — слой действует всегда. */
|
||||
export type LayerApplicability = {
|
||||
weekdays?: number[] | null
|
||||
dateRanges?: DateRange[] | null
|
||||
annualRanges?: AnnualRange[] | null
|
||||
specificDates?: string[] | null
|
||||
}
|
||||
|
||||
export type GridLayerDto = {
|
||||
id: string
|
||||
name: string
|
||||
priority: number
|
||||
isEnabled: boolean
|
||||
isBackground: boolean
|
||||
applicability: LayerApplicability | null
|
||||
slots: SlotDto[]
|
||||
}
|
||||
|
||||
export type ScheduleTemplateDto = {
|
||||
id: string
|
||||
channelId: string
|
||||
name: string
|
||||
fallbackGroupId: string | null
|
||||
revision: number
|
||||
appliedRevision: number
|
||||
/** Есть ли правки правил, не применённые к эфиру. */
|
||||
hasPendingChanges: boolean
|
||||
utcOffsetMinutes: number
|
||||
dayStartTime: string
|
||||
layers: GridLayerDto[]
|
||||
}
|
||||
|
||||
export type PlanningWarningKind =
|
||||
| 'SlotEmpty'
|
||||
| 'DriftExceeded'
|
||||
| 'CooldownExhausted'
|
||||
| 'RepeatSourceEmpty'
|
||||
| 'FallbackEmpty'
|
||||
|
||||
export type PlanningWarningDto = {
|
||||
kind: PlanningWarningKind
|
||||
slotId: string | null
|
||||
details: string
|
||||
}
|
||||
|
||||
export type ApplyResultDto = { added: number; warnings: PlanningWarningDto[] }
|
||||
|
||||
export type ScheduleEntryDto = {
|
||||
id: string
|
||||
kind: ScheduleEntryKind
|
||||
mediaAssetId: string
|
||||
startsAtUtc: string
|
||||
endsAtUtc: string
|
||||
showId: string | null
|
||||
showName: string | null
|
||||
episodeIndex: number | null
|
||||
seasonEpisode: string | null
|
||||
/** Для заставок: имя подблока и его текст — для метки в расписании. */
|
||||
bumperName: string | null
|
||||
bumperText: string | null
|
||||
}
|
||||
|
||||
// ── Публичный эфир ─────────────────────────────────────────────────────────
|
||||
export type PublicChannelDto = {
|
||||
id: string
|
||||
slug: string
|
||||
name: string
|
||||
currentShowId: string | null
|
||||
currentShowName: string | null
|
||||
currentShowPosterImageId: string | null
|
||||
}
|
||||
|
||||
export type PublicEpgEntryDto = {
|
||||
kind: ScheduleEntryKind
|
||||
startsAtUtc: string
|
||||
endsAtUtc: string
|
||||
showId: string | null
|
||||
showName: string | null
|
||||
showPosterImageId: string | null
|
||||
episodeId: string | null
|
||||
episodeTitle: string | null
|
||||
episodeOverview: string | null
|
||||
episodeStillImageId: string | null
|
||||
}
|
||||
|
||||
+1040
-756
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user