first pass at block scheduling (#1548)
* add blocks, block groups * basic block and block item editing * add template groups and basic template editing (name) * add blocks to template calendar * edit playout templates * add calendar preview to playout templates * add basic block playout building * add mysql migration * update changelog
This commit is contained in:
@@ -20,6 +20,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
- If found, ErsatzTV will run ffprobe to get statistics immediately before streaming from disk
|
- If found, ErsatzTV will run ffprobe to get statistics immediately before streaming from disk
|
||||||
- When local files are unavailable, ErsatzTV must be logged into the same Plex server as DizqueTV
|
- When local files are unavailable, ErsatzTV must be logged into the same Plex server as DizqueTV
|
||||||
- ErsatzTV will ask Plex for statistics immediately before streaming from Plex
|
- ErsatzTV will ask Plex for statistics immediately before streaming from Plex
|
||||||
|
- Add new *experimental* playout type `Block`
|
||||||
|
- **This playout type is under active development and updates may reset or delete related playout data**
|
||||||
|
- Many planned features are missing, incomplete, or result in errors. This is expected.
|
||||||
|
- Block playouts consist of:
|
||||||
|
- `Blocks` - ordered list of items to play within the specified duration
|
||||||
|
- `Templates` - a generic "day" that consists of blocks scheduled at specific times
|
||||||
|
- `Playout Templates` - templates to schedule using the specified criteria. Only one template will be selected each day
|
||||||
|
- Much more to come on this feature as development continues
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fix error loading path replacements when using MySql
|
- Fix error loading path replacements when using MySql
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<InformationalVersion>develop</InformationalVersion>
|
<InformationalVersion>develop</InformationalVersion>
|
||||||
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -35,6 +35,8 @@
|
|||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=programschedules_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=programschedules_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resolutions_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resolutions_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resolutions_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resolutions_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=scheduling_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=scheduling_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=search_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=search_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=search_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=search_005Cqueries/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=streaming_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=streaming_005Ccommands/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
|||||||
@@ -22,12 +22,16 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
|||||||
private readonly IEntityLocker _entityLocker;
|
private readonly IEntityLocker _entityLocker;
|
||||||
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
|
||||||
private readonly IPlayoutBuilder _playoutBuilder;
|
private readonly IPlayoutBuilder _playoutBuilder;
|
||||||
|
private readonly IBlockPlayoutBuilder _blockPlayoutBuilder;
|
||||||
|
private readonly IExternalJsonPlayoutBuilder _externalJsonPlayoutBuilder;
|
||||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||||
|
|
||||||
public BuildPlayoutHandler(
|
public BuildPlayoutHandler(
|
||||||
IClient client,
|
IClient client,
|
||||||
IDbContextFactory<TvContext> dbContextFactory,
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
IPlayoutBuilder playoutBuilder,
|
IPlayoutBuilder playoutBuilder,
|
||||||
|
IBlockPlayoutBuilder blockPlayoutBuilder,
|
||||||
|
IExternalJsonPlayoutBuilder externalJsonPlayoutBuilder,
|
||||||
IFFmpegSegmenterService ffmpegSegmenterService,
|
IFFmpegSegmenterService ffmpegSegmenterService,
|
||||||
IEntityLocker entityLocker,
|
IEntityLocker entityLocker,
|
||||||
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
||||||
@@ -35,6 +39,8 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
|||||||
_client = client;
|
_client = client;
|
||||||
_dbContextFactory = dbContextFactory;
|
_dbContextFactory = dbContextFactory;
|
||||||
_playoutBuilder = playoutBuilder;
|
_playoutBuilder = playoutBuilder;
|
||||||
|
_blockPlayoutBuilder = blockPlayoutBuilder;
|
||||||
|
_externalJsonPlayoutBuilder = externalJsonPlayoutBuilder;
|
||||||
_ffmpegSegmenterService = ffmpegSegmenterService;
|
_ffmpegSegmenterService = ffmpegSegmenterService;
|
||||||
_entityLocker = entityLocker;
|
_entityLocker = entityLocker;
|
||||||
_workerChannel = workerChannel;
|
_workerChannel = workerChannel;
|
||||||
@@ -59,7 +65,20 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
|||||||
{
|
{
|
||||||
_entityLocker.LockPlayout(playout.Id);
|
_entityLocker.LockPlayout(playout.Id);
|
||||||
|
|
||||||
await _playoutBuilder.Build(playout, request.Mode, cancellationToken);
|
switch (playout.ProgramSchedulePlayoutType)
|
||||||
|
{
|
||||||
|
case ProgramSchedulePlayoutType.Block:
|
||||||
|
await _blockPlayoutBuilder.Build(playout, request.Mode, cancellationToken);
|
||||||
|
break;
|
||||||
|
case ProgramSchedulePlayoutType.ExternalJson:
|
||||||
|
await _externalJsonPlayoutBuilder.Build(playout, request.Mode, cancellationToken);
|
||||||
|
break;
|
||||||
|
case ProgramSchedulePlayoutType.None:
|
||||||
|
case ProgramSchedulePlayoutType.Flood:
|
||||||
|
default:
|
||||||
|
await _playoutBuilder.Build(playout, request.Mode, cancellationToken);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// let any active segmenter processes know that the playout has been modified
|
// let any active segmenter processes know that the playout has been modified
|
||||||
// and therefore the segmenter may need to seek into the next item instead of
|
// and therefore the segmenter may need to seek into the next item instead of
|
||||||
@@ -135,6 +154,12 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
|||||||
dbContext.Playouts
|
dbContext.Playouts
|
||||||
.Include(p => p.Channel)
|
.Include(p => p.Channel)
|
||||||
.Include(p => p.Items)
|
.Include(p => p.Items)
|
||||||
|
.Include(p => p.PlayoutHistory)
|
||||||
|
.Include(p => p.Templates)
|
||||||
|
.ThenInclude(t => t.Template)
|
||||||
|
.ThenInclude(t => t.Items)
|
||||||
|
.ThenInclude(i => i.Block)
|
||||||
|
.ThenInclude(b => b.Items)
|
||||||
.Include(p => p.FillGroupIndices)
|
.Include(p => p.FillGroupIndices)
|
||||||
.ThenInclude(fgi => fgi.EnumeratorState)
|
.ThenInclude(fgi => fgi.EnumeratorState)
|
||||||
.Include(p => p.ProgramScheduleAlternates)
|
.Include(p => p.ProgramScheduleAlternates)
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
using ErsatzTV.Application.Channels;
|
||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Channel = ErsatzTV.Core.Domain.Channel;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Playouts;
|
||||||
|
|
||||||
|
public class CreateBlockPlayoutHandler(
|
||||||
|
ChannelWriter<IBackgroundServiceRequest> channel,
|
||||||
|
IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<CreateBlockPlayout, Either<BaseError, CreatePlayoutResponse>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, CreatePlayoutResponse>> Handle(
|
||||||
|
CreateBlockPlayout request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, Playout> validation = await Validate(dbContext, request);
|
||||||
|
return await validation.Apply(playout => PersistPlayout(dbContext, playout));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CreatePlayoutResponse> PersistPlayout(TvContext dbContext, Playout playout)
|
||||||
|
{
|
||||||
|
await dbContext.Playouts.AddAsync(playout);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
await channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset));
|
||||||
|
await channel.WriteAsync(new RefreshChannelList());
|
||||||
|
return new CreatePlayoutResponse(playout.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, Playout>> Validate(
|
||||||
|
TvContext dbContext,
|
||||||
|
CreateBlockPlayout request) =>
|
||||||
|
(await ValidateChannel(dbContext, request), ValidatePlayoutType(request))
|
||||||
|
.Apply(
|
||||||
|
(channel, playoutType) => new Playout
|
||||||
|
{
|
||||||
|
ChannelId = channel.Id,
|
||||||
|
ProgramSchedulePlayoutType = playoutType
|
||||||
|
});
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Channel>> ValidateChannel(
|
||||||
|
TvContext dbContext,
|
||||||
|
CreateBlockPlayout createBlockPlayout) =>
|
||||||
|
dbContext.Channels
|
||||||
|
.Include(c => c.Playouts)
|
||||||
|
.SelectOneAsync(c => c.Id, c => c.Id == createBlockPlayout.ChannelId)
|
||||||
|
.Map(o => o.ToValidation<BaseError>("Channel does not exist"))
|
||||||
|
.BindT(ChannelMustNotHavePlayouts);
|
||||||
|
|
||||||
|
private static Validation<BaseError, Channel> ChannelMustNotHavePlayouts(Channel channel) =>
|
||||||
|
Optional(channel.Playouts.Count)
|
||||||
|
.Filter(count => count == 0)
|
||||||
|
.Map(_ => channel)
|
||||||
|
.ToValidation<BaseError>("Channel already has one playout");
|
||||||
|
|
||||||
|
private static Validation<BaseError, ProgramSchedulePlayoutType> ValidatePlayoutType(
|
||||||
|
CreateBlockPlayout createBlockPlayout) =>
|
||||||
|
Optional(createBlockPlayout.ProgramSchedulePlayoutType)
|
||||||
|
.Filter(playoutType => playoutType == ProgramSchedulePlayoutType.Block)
|
||||||
|
.ToValidation<BaseError>("[ProgramSchedulePlayoutType] must be Block");
|
||||||
|
}
|
||||||
@@ -9,5 +9,8 @@ public record CreatePlayout(int ChannelId, ProgramSchedulePlayoutType ProgramSch
|
|||||||
public record CreateFloodPlayout(int ChannelId, int ProgramScheduleId)
|
public record CreateFloodPlayout(int ChannelId, int ProgramScheduleId)
|
||||||
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Flood);
|
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Flood);
|
||||||
|
|
||||||
|
public record CreateBlockPlayout(int ChannelId)
|
||||||
|
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Block);
|
||||||
|
|
||||||
public record CreateExternalJsonPlayout(int ChannelId, string ExternalJsonFile)
|
public record CreateExternalJsonPlayout(int ChannelId, string ExternalJsonFile)
|
||||||
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.ExternalJson);
|
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.ExternalJson);
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ public class DeletePlayoutHandler : IRequestHandler<DeletePlayout, Either<BaseEr
|
|||||||
_localFileSystem = localFileSystem;
|
_localFileSystem = localFileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Either<BaseError, Unit>> Handle(
|
public async Task<Either<BaseError, Unit>> Handle(DeletePlayout request, CancellationToken cancellationToken)
|
||||||
DeletePlayout request,
|
|
||||||
CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class GetProgramScheduleItemsHandler :
|
|||||||
.Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList());
|
.Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
// shuffled schedule items supports a limited set of properly values
|
// shuffled schedule items supports a limited set of property values
|
||||||
private static ProgramScheduleItemViewModel EnforceProperties(
|
private static ProgramScheduleItemViewModel EnforceProperties(
|
||||||
Option<ProgramSchedule> maybeProgramSchedule,
|
Option<ProgramSchedule> maybeProgramSchedule,
|
||||||
ProgramScheduleItemViewModel item)
|
ProgramScheduleItemViewModel item)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record BlockGroupViewModel(int Id, string Name, int BlockCount);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using ErsatzTV.Application.MediaCollections;
|
||||||
|
using ErsatzTV.Application.MediaItems;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record BlockItemViewModel(
|
||||||
|
int Id,
|
||||||
|
int Index,
|
||||||
|
ProgramScheduleItemCollectionType CollectionType,
|
||||||
|
MediaCollectionViewModel Collection,
|
||||||
|
MultiCollectionViewModel MultiCollection,
|
||||||
|
SmartCollectionViewModel SmartCollection,
|
||||||
|
NamedMediaItemViewModel MediaItem,
|
||||||
|
PlaybackOrder PlaybackOrder);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record BlockViewModel(int Id, string Name, int Minutes);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record CreateBlock(int BlockGroupId, string Name) : IRequest<Either<BaseError, BlockViewModel>>;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record CreateBlockGroup(string Name) : IRequest<Either<BaseError, BlockGroupViewModel>>;
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class CreateBlockGroupHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<CreateBlockGroup, Either<BaseError, BlockGroupViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, BlockGroupViewModel>> Handle(CreateBlockGroup request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, BlockGroup> validation = await Validate(request);
|
||||||
|
return await validation.Apply(profile => PersistBlockGroup(dbContext, profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<BlockGroupViewModel> PersistBlockGroup(TvContext dbContext, BlockGroup blockGroup)
|
||||||
|
{
|
||||||
|
await dbContext.BlockGroups.AddAsync(blockGroup);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
return Mapper.ProjectToViewModel(blockGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, BlockGroup>> Validate(CreateBlockGroup request) =>
|
||||||
|
Task.FromResult(ValidateName(request).Map(name => new BlockGroup { Name = name, Blocks = [] }));
|
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateBlockGroup createBlockGroup) =>
|
||||||
|
createBlockGroup.NotEmpty(x => x.Name)
|
||||||
|
.Bind(_ => createBlockGroup.NotLongerThan(50)(x => x.Name));
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class CreateBlockHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<CreateBlock, Either<BaseError, BlockViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, BlockViewModel>> Handle(
|
||||||
|
CreateBlock request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, Block> validation = await Validate(request);
|
||||||
|
return await validation.Apply(profile => PersistBlock(dbContext, profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<BlockViewModel> PersistBlock(TvContext dbContext, Block block)
|
||||||
|
{
|
||||||
|
await dbContext.Blocks.AddAsync(block);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
return Mapper.ProjectToViewModel(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Block>> Validate(CreateBlock request) =>
|
||||||
|
Task.FromResult(
|
||||||
|
ValidateName(request).Map(
|
||||||
|
name => new Block
|
||||||
|
{
|
||||||
|
BlockGroupId = request.BlockGroupId,
|
||||||
|
Name = name,
|
||||||
|
Minutes = 30
|
||||||
|
}));
|
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateBlock createBlock) =>
|
||||||
|
createBlock.NotEmpty(x => x.Name)
|
||||||
|
.Bind(_ => createBlock.NotLongerThan(50)(x => x.Name));
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record CreateTemplate(int TemplateGroupId, string Name) : IRequest<Either<BaseError, TemplateViewModel>>;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record CreateTemplateGroup(string Name) : IRequest<Either<BaseError, TemplateGroupViewModel>>;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class CreateTemplateGroupHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<CreateTemplateGroup, Either<BaseError, TemplateGroupViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, TemplateGroupViewModel>> Handle(
|
||||||
|
CreateTemplateGroup request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, TemplateGroup> validation = await Validate(request);
|
||||||
|
return await validation.Apply(profile => PersistTemplateGroup(dbContext, profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<TemplateGroupViewModel> PersistTemplateGroup(
|
||||||
|
TvContext dbContext,
|
||||||
|
TemplateGroup templateGroup)
|
||||||
|
{
|
||||||
|
await dbContext.TemplateGroups.AddAsync(templateGroup);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
return Mapper.ProjectToViewModel(templateGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, TemplateGroup>> Validate(CreateTemplateGroup request) =>
|
||||||
|
Task.FromResult(ValidateName(request).Map(name => new TemplateGroup { Name = name, Templates = [] }));
|
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateTemplateGroup createTemplateGroup) =>
|
||||||
|
createTemplateGroup.NotEmpty(x => x.Name)
|
||||||
|
.Bind(_ => createTemplateGroup.NotLongerThan(50)(x => x.Name));
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class CreateTemplateHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<CreateTemplate, Either<BaseError, TemplateViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, TemplateViewModel>> Handle(
|
||||||
|
CreateTemplate request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, Template> validation = await Validate(request);
|
||||||
|
return await validation.Apply(profile => PersistTemplate(dbContext, profile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<TemplateViewModel> PersistTemplate(TvContext dbContext, Template template)
|
||||||
|
{
|
||||||
|
await dbContext.Templates.AddAsync(template);
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
return Mapper.ProjectToViewModel(template);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Template>> Validate(CreateTemplate request) =>
|
||||||
|
Task.FromResult(
|
||||||
|
ValidateName(request).Map(
|
||||||
|
name => new Template
|
||||||
|
{
|
||||||
|
TemplateGroupId = request.TemplateGroupId,
|
||||||
|
Name = name
|
||||||
|
}));
|
||||||
|
|
||||||
|
private static Validation<BaseError, string> ValidateName(CreateTemplate createTemplate) =>
|
||||||
|
createTemplate.NotEmpty(x => x.Name)
|
||||||
|
.Bind(_ => createTemplate.NotLongerThan(50)(x => x.Name));
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record DeleteBlock(int BlockId) : IRequest<Option<BaseError>>;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record DeleteBlockGroup(int BlockGroupId) : IRequest<Option<BaseError>>;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class DeleteBlockGroupHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<DeleteBlockGroup, Option<BaseError>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BaseError>> Handle(DeleteBlockGroup request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<BlockGroup> maybeBlockGroup = await dbContext.BlockGroups
|
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.BlockGroupId);
|
||||||
|
|
||||||
|
foreach (BlockGroup blockGroup in maybeBlockGroup)
|
||||||
|
{
|
||||||
|
dbContext.BlockGroups.Remove(blockGroup);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maybeBlockGroup.Match(
|
||||||
|
_ => Option<BaseError>.None,
|
||||||
|
() => BaseError.New($"BlockGroup {request.BlockGroupId} does not exist."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class DeleteBlockHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<DeleteBlock, Option<BaseError>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BaseError>> Handle(DeleteBlock request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<Block> maybeBlock = await dbContext.Blocks
|
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.BlockId);
|
||||||
|
|
||||||
|
foreach (Block block in maybeBlock)
|
||||||
|
{
|
||||||
|
dbContext.Blocks.Remove(block);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maybeBlock.Match(
|
||||||
|
_ => Option<BaseError>.None,
|
||||||
|
() => BaseError.New($"Block {request.BlockId} does not exist."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record DeleteTemplate(int TemplateId) : IRequest<Option<BaseError>>;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record DeleteTemplateGroup(int TemplateGroupId) : IRequest<Option<BaseError>>;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class DeleteTemplateGroupHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<DeleteTemplateGroup, Option<BaseError>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BaseError>> Handle(DeleteTemplateGroup request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<TemplateGroup> maybeTemplateGroup = await dbContext.TemplateGroups
|
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.TemplateGroupId);
|
||||||
|
|
||||||
|
foreach (TemplateGroup templateGroup in maybeTemplateGroup)
|
||||||
|
{
|
||||||
|
dbContext.TemplateGroups.Remove(templateGroup);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maybeTemplateGroup.Match(
|
||||||
|
_ => Option<BaseError>.None,
|
||||||
|
() => BaseError.New($"TemplateGroup {request.TemplateGroupId} does not exist."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class DeleteTemplateHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<DeleteTemplate, Option<BaseError>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BaseError>> Handle(DeleteTemplate request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<Template> maybeTemplate = await dbContext.Templates
|
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.TemplateId);
|
||||||
|
|
||||||
|
foreach (Template template in maybeTemplate)
|
||||||
|
{
|
||||||
|
dbContext.Templates.Remove(template);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return maybeTemplate.Match(
|
||||||
|
_ => Option<BaseError>.None,
|
||||||
|
() => BaseError.New($"Template {request.TemplateId} does not exist."));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record ReplaceBlockItem(
|
||||||
|
int Index,
|
||||||
|
ProgramScheduleItemCollectionType CollectionType,
|
||||||
|
int? CollectionId,
|
||||||
|
int? MultiCollectionId,
|
||||||
|
int? SmartCollectionId,
|
||||||
|
int? MediaItemId,
|
||||||
|
PlaybackOrder PlaybackOrder);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record ReplaceBlockItems(int BlockId, string Name, int Minutes, List<ReplaceBlockItem> Items)
|
||||||
|
: IRequest<Either<BaseError, List<BlockItemViewModel>>>;
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class ReplaceBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<ReplaceBlockItems, Either<BaseError, List<BlockItemViewModel>>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, List<BlockItemViewModel>>> Handle(
|
||||||
|
ReplaceBlockItems request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, Block> validation = await Validate(dbContext, request);
|
||||||
|
return await validation.Apply(ps => Persist(dbContext, request, ps));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<BlockItemViewModel>> Persist(
|
||||||
|
TvContext dbContext,
|
||||||
|
ReplaceBlockItems request,
|
||||||
|
Block block)
|
||||||
|
{
|
||||||
|
block.Name = request.Name;
|
||||||
|
block.Minutes = request.Minutes;
|
||||||
|
|
||||||
|
dbContext.RemoveRange(block.Items);
|
||||||
|
block.Items = request.Items.Map(i => BuildItem(block, i.Index, i)).ToList();
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
// TODO: refresh any playouts that use this schedule
|
||||||
|
// foreach (Playout playout in programSchedule.Playouts)
|
||||||
|
// {
|
||||||
|
// await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Refresh));
|
||||||
|
// }
|
||||||
|
|
||||||
|
return block.Items.Map(Mapper.ProjectToViewModel).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockItem BuildItem(Block block, int index, ReplaceBlockItem item) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
BlockId = block.Id,
|
||||||
|
Index = index,
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
CollectionId = item.CollectionId,
|
||||||
|
MultiCollectionId = item.MultiCollectionId,
|
||||||
|
SmartCollectionId = item.SmartCollectionId,
|
||||||
|
MediaItemId = item.MediaItemId,
|
||||||
|
PlaybackOrder = item.PlaybackOrder
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Block>> Validate(TvContext dbContext, ReplaceBlockItems request) =>
|
||||||
|
BlockMustExist(dbContext, request.BlockId)
|
||||||
|
.BindT(programSchedule => CollectionTypesMustBeValid(request, programSchedule));
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Block>> BlockMustExist(TvContext dbContext, int blockId) =>
|
||||||
|
dbContext.Blocks
|
||||||
|
.Include(b => b.Items)
|
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == blockId)
|
||||||
|
.Map(o => o.ToValidation<BaseError>("[BlockId] does not exist."));
|
||||||
|
|
||||||
|
private static Validation<BaseError, Block> CollectionTypesMustBeValid(ReplaceBlockItems request, Block block) =>
|
||||||
|
request.Items.Map(item => CollectionTypeMustBeValid(item, block)).Sequence().Map(_ => block);
|
||||||
|
|
||||||
|
private static Validation<BaseError, Block> CollectionTypeMustBeValid(ReplaceBlockItem item, Block block)
|
||||||
|
{
|
||||||
|
switch (item.CollectionType)
|
||||||
|
{
|
||||||
|
case ProgramScheduleItemCollectionType.Collection:
|
||||||
|
if (item.CollectionId is null)
|
||||||
|
{
|
||||||
|
return BaseError.New("[Collection] is required for collection type 'Collection'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ProgramScheduleItemCollectionType.TelevisionShow:
|
||||||
|
if (item.MediaItemId is null)
|
||||||
|
{
|
||||||
|
return BaseError.New("[MediaItem] is required for collection type 'TelevisionShow'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ProgramScheduleItemCollectionType.TelevisionSeason:
|
||||||
|
if (item.MediaItemId is null)
|
||||||
|
{
|
||||||
|
return BaseError.New("[MediaItem] is required for collection type 'TelevisionSeason'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ProgramScheduleItemCollectionType.Artist:
|
||||||
|
if (item.MediaItemId is null)
|
||||||
|
{
|
||||||
|
return BaseError.New("[MediaItem] is required for collection type 'Artist'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ProgramScheduleItemCollectionType.MultiCollection:
|
||||||
|
if (item.MultiCollectionId is null)
|
||||||
|
{
|
||||||
|
return BaseError.New("[MultiCollection] is required for collection type 'MultiCollection'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ProgramScheduleItemCollectionType.SmartCollection:
|
||||||
|
if (item.SmartCollectionId is null)
|
||||||
|
{
|
||||||
|
return BaseError.New("[SmartCollection] is required for collection type 'SmartCollection'");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ProgramScheduleItemCollectionType.FakeCollection:
|
||||||
|
default:
|
||||||
|
return BaseError.New("[CollectionType] is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record ReplacePlayoutTemplate(
|
||||||
|
int Id,
|
||||||
|
int Index,
|
||||||
|
int TemplateId,
|
||||||
|
List<DayOfWeek> DaysOfWeek,
|
||||||
|
List<int> DaysOfMonth,
|
||||||
|
List<int> MonthsOfYear);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record ReplacePlayoutTemplateItems(int PlayoutId, List<ReplacePlayoutTemplate> Items)
|
||||||
|
: IRequest<Option<BaseError>>;
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class ReplacePlayoutTemplateItemsHandler(
|
||||||
|
IDbContextFactory<TvContext> dbContextFactory,
|
||||||
|
ILogger<ReplacePlayoutTemplateItemsHandler> logger)
|
||||||
|
: IRequestHandler<ReplacePlayoutTemplateItems, Option<BaseError>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BaseError>> Handle(
|
||||||
|
ReplacePlayoutTemplateItems request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
Option<Playout> maybePlayout = await dbContext.Playouts
|
||||||
|
.Include(p => p.ProgramSchedule)
|
||||||
|
.Include(p => p.Templates)
|
||||||
|
.ThenInclude(t => t.Template)
|
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId);
|
||||||
|
|
||||||
|
foreach (Playout playout in maybePlayout)
|
||||||
|
{
|
||||||
|
PlayoutTemplate[] existing = playout.Templates.ToArray();
|
||||||
|
|
||||||
|
List<ReplacePlayoutTemplate> incoming = request.Items;
|
||||||
|
|
||||||
|
var toAdd = incoming.Filter(x => existing.All(e => e.Id != x.Id)).ToList();
|
||||||
|
var toRemove = existing.Filter(e => incoming.All(m => m.Id != e.Id)).ToList();
|
||||||
|
var toUpdate = incoming.Except(toAdd).ToList();
|
||||||
|
|
||||||
|
foreach (PlayoutTemplate remove in toRemove)
|
||||||
|
{
|
||||||
|
playout.Templates.Remove(remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ReplacePlayoutTemplate add in toAdd)
|
||||||
|
{
|
||||||
|
playout.Templates.Add(
|
||||||
|
new PlayoutTemplate
|
||||||
|
{
|
||||||
|
PlayoutId = playout.Id,
|
||||||
|
Index = add.Index,
|
||||||
|
TemplateId = add.TemplateId,
|
||||||
|
DaysOfWeek = add.DaysOfWeek,
|
||||||
|
DaysOfMonth = add.DaysOfMonth,
|
||||||
|
MonthsOfYear = add.MonthsOfYear
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ReplacePlayoutTemplate update in toUpdate)
|
||||||
|
{
|
||||||
|
foreach (PlayoutTemplate ex in existing.Filter(x => x.Id == update.Id))
|
||||||
|
{
|
||||||
|
ex.Index = update.Index;
|
||||||
|
ex.TemplateId = update.TemplateId;
|
||||||
|
ex.DaysOfWeek = update.DaysOfWeek;
|
||||||
|
ex.DaysOfMonth = update.DaysOfMonth;
|
||||||
|
ex.MonthsOfYear = update.MonthsOfYear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Option<BaseError>.None;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error saving playout template items");
|
||||||
|
return BaseError.New(ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record ReplaceTemplateItem(int BlockId, TimeSpan StartTime);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record ReplaceTemplateItems(int TemplateId, string Name, List<ReplaceTemplateItem> Items)
|
||||||
|
: IRequest<Either<BaseError, List<TemplateItemViewModel>>>;
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using ErsatzTV.Core;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class ReplaceTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<ReplaceTemplateItems, Either<BaseError, List<TemplateItemViewModel>>>
|
||||||
|
{
|
||||||
|
public async Task<Either<BaseError, List<TemplateItemViewModel>>> Handle(
|
||||||
|
ReplaceTemplateItems request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
Validation<BaseError, Template> validation = await Validate(dbContext, request);
|
||||||
|
return await validation.Apply(ps => Persist(dbContext, request, ps));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<List<TemplateItemViewModel>> Persist(
|
||||||
|
TvContext dbContext,
|
||||||
|
ReplaceTemplateItems request,
|
||||||
|
Template template)
|
||||||
|
{
|
||||||
|
template.Name = request.Name;
|
||||||
|
|
||||||
|
dbContext.RemoveRange(template.Items);
|
||||||
|
template.Items = request.Items.Map(i => BuildItem(template, i)).ToList();
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
// TODO: refresh any playouts that use this schedule
|
||||||
|
// foreach (Playout playout in programSchedule.Playouts)
|
||||||
|
// {
|
||||||
|
// await _channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Refresh));
|
||||||
|
// }
|
||||||
|
|
||||||
|
await dbContext.Entry(template)
|
||||||
|
.Collection(t => t.Items)
|
||||||
|
.Query()
|
||||||
|
.Include(i => i.Block)
|
||||||
|
.LoadAsync();
|
||||||
|
|
||||||
|
return template.Items.Map(Mapper.ProjectToViewModel).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TemplateItem BuildItem(Template template, ReplaceTemplateItem item) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
TemplateId = template.Id,
|
||||||
|
BlockId = item.BlockId,
|
||||||
|
StartTime = item.StartTime
|
||||||
|
};
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Template>> Validate(TvContext dbContext, ReplaceTemplateItems request) =>
|
||||||
|
TemplateMustExist(dbContext, request.TemplateId);
|
||||||
|
|
||||||
|
private static Task<Validation<BaseError, Template>> TemplateMustExist(TvContext dbContext, int templateId) =>
|
||||||
|
dbContext.Templates
|
||||||
|
.Include(b => b.Items)
|
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == templateId)
|
||||||
|
.Map(o => o.ToValidation<BaseError>("[TemplateId] does not exist."));
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
internal static class Mapper
|
||||||
|
{
|
||||||
|
internal static BlockGroupViewModel ProjectToViewModel(BlockGroup blockGroup) =>
|
||||||
|
new(blockGroup.Id, blockGroup.Name, blockGroup.Blocks.Count);
|
||||||
|
|
||||||
|
internal static BlockViewModel ProjectToViewModel(Block block) =>
|
||||||
|
new(block.Id, block.Name, block.Minutes);
|
||||||
|
|
||||||
|
internal static BlockItemViewModel ProjectToViewModel(BlockItem blockItem) =>
|
||||||
|
new(
|
||||||
|
blockItem.Id,
|
||||||
|
blockItem.Index,
|
||||||
|
blockItem.CollectionType,
|
||||||
|
blockItem.Collection is not null ? MediaCollections.Mapper.ProjectToViewModel(blockItem.Collection) : null,
|
||||||
|
blockItem.MultiCollection is not null
|
||||||
|
? MediaCollections.Mapper.ProjectToViewModel(blockItem.MultiCollection)
|
||||||
|
: null,
|
||||||
|
blockItem.SmartCollection is not null
|
||||||
|
? MediaCollections.Mapper.ProjectToViewModel(blockItem.SmartCollection)
|
||||||
|
: null,
|
||||||
|
blockItem.MediaItem switch
|
||||||
|
{
|
||||||
|
Show show => MediaItems.Mapper.ProjectToViewModel(show),
|
||||||
|
Season season => MediaItems.Mapper.ProjectToViewModel(season),
|
||||||
|
Artist artist => MediaItems.Mapper.ProjectToViewModel(artist),
|
||||||
|
_ => null
|
||||||
|
},
|
||||||
|
blockItem.PlaybackOrder);
|
||||||
|
|
||||||
|
internal static TemplateGroupViewModel ProjectToViewModel(TemplateGroup templateGroup) =>
|
||||||
|
new(templateGroup.Id, templateGroup.Name, templateGroup.Templates.Count);
|
||||||
|
|
||||||
|
internal static TemplateViewModel ProjectToViewModel(Template template) =>
|
||||||
|
new(template.Id, template.TemplateGroupId, template.Name);
|
||||||
|
|
||||||
|
internal static TemplateItemViewModel ProjectToViewModel(TemplateItem templateItem)
|
||||||
|
{
|
||||||
|
DateTime startTime = DateTime.Today.Add(templateItem.StartTime);
|
||||||
|
DateTime endTime = startTime.AddMinutes(templateItem.Block.Minutes);
|
||||||
|
return new TemplateItemViewModel(templateItem.BlockId, templateItem.Block.Name, startTime, endTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static PlayoutTemplateViewModel ProjectToViewModel(PlayoutTemplate playoutTemplate) =>
|
||||||
|
new(
|
||||||
|
playoutTemplate.Id,
|
||||||
|
ProjectToViewModel(playoutTemplate.Template),
|
||||||
|
playoutTemplate.Index,
|
||||||
|
playoutTemplate.DaysOfWeek,
|
||||||
|
playoutTemplate.DaysOfMonth,
|
||||||
|
playoutTemplate.MonthsOfYear);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record PlayoutTemplateViewModel(
|
||||||
|
int Id,
|
||||||
|
TemplateViewModel Template,
|
||||||
|
int Index,
|
||||||
|
ICollection<DayOfWeek> DaysOfWeek,
|
||||||
|
ICollection<int> DaysOfMonth,
|
||||||
|
ICollection<int> MonthsOfYear);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetAllBlockGroups : IRequest<List<BlockGroupViewModel>>;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetAllBlockGroupsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetAllBlockGroups, List<BlockGroupViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<BlockGroupViewModel>> Handle(GetAllBlockGroups request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
List<BlockGroup> blockGroups = await dbContext.BlockGroups
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(g => g.Blocks)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return blockGroups.Map(Mapper.ProjectToViewModel).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetAllTemplateGroups : IRequest<List<TemplateGroupViewModel>>;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetAllTemplateGroupsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetAllTemplateGroups, List<TemplateGroupViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<TemplateGroupViewModel>> Handle(
|
||||||
|
GetAllTemplateGroups request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
List<TemplateGroup> blockGroups = await dbContext.TemplateGroups
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(g => g.Templates)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return blockGroups.Map(Mapper.ProjectToViewModel).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetBlockById(int BlockId) : IRequest<Option<BlockViewModel>>;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetBlockByIdHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetBlockById, Option<BlockViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<Option<BlockViewModel>> Handle(GetBlockById request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
return await dbContext.Blocks
|
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == request.BlockId)
|
||||||
|
.MapT(Mapper.ProjectToViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetBlockItems(int BlockId) : IRequest<List<BlockItemViewModel>>;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetBlockItems, List<BlockItemViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<BlockItemViewModel>> Handle(GetBlockItems request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
return await dbContext.BlockItems
|
||||||
|
.AsNoTracking()
|
||||||
|
.Filter(i => i.BlockId == request.BlockId)
|
||||||
|
.Include(i => i.Collection)
|
||||||
|
.Include(i => i.MultiCollection)
|
||||||
|
.Include(i => i.SmartCollection)
|
||||||
|
.Include(i => i.MediaItem)
|
||||||
|
.ThenInclude(i => (i as Season).SeasonMetadata)
|
||||||
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.Include(i => i.MediaItem)
|
||||||
|
.ThenInclude(i => (i as Season).Show)
|
||||||
|
.ThenInclude(s => s.ShowMetadata)
|
||||||
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.Include(i => i.MediaItem)
|
||||||
|
.ThenInclude(i => (i as Show).ShowMetadata)
|
||||||
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.Include(i => i.MediaItem)
|
||||||
|
.ThenInclude(i => (i as Artist).ArtistMetadata)
|
||||||
|
.ThenInclude(am => am.Artwork)
|
||||||
|
.ToListAsync(cancellationToken)
|
||||||
|
.Map(items => items.Map(Mapper.ProjectToViewModel).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetBlocksByBlockGroupId(int BlockGroupId) : IRequest<List<BlockViewModel>>;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetBlocksByBlockGroupIdHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetBlocksByBlockGroupId, List<BlockViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<BlockViewModel>> Handle(GetBlocksByBlockGroupId request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
List<Block> blocks = await dbContext.Blocks
|
||||||
|
.Filter(b => b.BlockGroupId == request.BlockGroupId)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return blocks.Map(Mapper.ProjectToViewModel).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetPlayoutTemplates(int PlayoutId) : IRequest<List<PlayoutTemplateViewModel>>;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetPlayoutTemplatesHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetPlayoutTemplates, List<PlayoutTemplateViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<PlayoutTemplateViewModel>> Handle(
|
||||||
|
GetPlayoutTemplates request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
List<PlayoutTemplate> playoutTemplates = await dbContext.PlayoutTemplates
|
||||||
|
.AsNoTracking()
|
||||||
|
.Filter(t => t.PlayoutId == request.PlayoutId)
|
||||||
|
.Include(t => t.Template)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return playoutTemplates.Map(Mapper.ProjectToViewModel).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetTemplateById(int TemplateId) : IRequest<Option<TemplateViewModel>>;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using ErsatzTV.Infrastructure.Extensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetTemplateByIdHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetTemplateById, Option<TemplateViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<Option<TemplateViewModel>> Handle(GetTemplateById request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
return await dbContext.Templates
|
||||||
|
.SelectOneAsync(b => b.Id, b => b.Id == request.TemplateId)
|
||||||
|
.MapT(Mapper.ProjectToViewModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetTemplateItems(int TemplateId) : IRequest<List<TemplateItemViewModel>>;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetTemplateItemsHandler(IDbContextFactory<TvContext> dbContextFactory) : IRequestHandler<GetTemplateItems, List<TemplateItemViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<TemplateItemViewModel>> Handle(GetTemplateItems request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
return await dbContext.TemplateItems
|
||||||
|
.AsNoTracking()
|
||||||
|
.Filter(i => i.TemplateId == request.TemplateId)
|
||||||
|
.Include(i => i.Block)
|
||||||
|
.ToListAsync(cancellationToken)
|
||||||
|
.Map(items => items.Map(Mapper.ProjectToViewModel).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record GetTemplatesByTemplateGroupId(int TemplateGroupId) : IRequest<List<TemplateViewModel>>;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using ErsatzTV.Infrastructure.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public class GetTemplatesByTemplateGroupIdHandler(IDbContextFactory<TvContext> dbContextFactory)
|
||||||
|
: IRequestHandler<GetTemplatesByTemplateGroupId, List<TemplateViewModel>>
|
||||||
|
{
|
||||||
|
public async Task<List<TemplateViewModel>> Handle(
|
||||||
|
GetTemplatesByTemplateGroupId request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
return await dbContext.Templates
|
||||||
|
.AsNoTracking()
|
||||||
|
.Filter(i => i.TemplateGroupId == request.TemplateGroupId)
|
||||||
|
.ToListAsync(cancellationToken)
|
||||||
|
.Map(items => items.Map(Mapper.ProjectToViewModel).ToList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record TemplateGroupViewModel(int Id, string Name, int TemplateCount);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record TemplateItemViewModel(int BlockId, string BlockName, DateTime StartTime, DateTime EndTime);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace ErsatzTV.Application.Scheduling;
|
||||||
|
|
||||||
|
public record TemplateViewModel(int Id, int TemplateGroupId, string Name);
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Domain;
|
||||||
|
|
||||||
public class Playout
|
public class Playout
|
||||||
{
|
{
|
||||||
@@ -14,5 +16,7 @@ public class Playout
|
|||||||
public PlayoutAnchor Anchor { get; set; }
|
public PlayoutAnchor Anchor { get; set; }
|
||||||
public List<PlayoutProgramScheduleAnchor> ProgramScheduleAnchors { get; set; }
|
public List<PlayoutProgramScheduleAnchor> ProgramScheduleAnchors { get; set; }
|
||||||
public List<PlayoutScheduleItemFillGroupIndex> FillGroupIndices { get; set; }
|
public List<PlayoutScheduleItemFillGroupIndex> FillGroupIndices { get; set; }
|
||||||
|
public ICollection<PlayoutTemplate> Templates { get; set; }
|
||||||
|
public ICollection<PlayoutHistory> PlayoutHistory { get; set; }
|
||||||
public TimeSpan? DailyRebuildTime { get; set; }
|
public TimeSpan? DailyRebuildTime { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ public enum ProgramSchedulePlayoutType
|
|||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
Flood = 1,
|
Flood = 1,
|
||||||
|
Block = 2,
|
||||||
|
|
||||||
ExternalJson = 20
|
ExternalJson = 20
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class Block
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int BlockGroupId { get; set; }
|
||||||
|
public BlockGroup BlockGroup { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public int Minutes { get; set; }
|
||||||
|
public ICollection<BlockItem> Items { get; set; }
|
||||||
|
public ICollection<TemplateItem> TemplateItems { get; set; }
|
||||||
|
public ICollection<PlayoutHistory> PlayoutHistory { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class BlockGroup
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public ICollection<Block> Blocks { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class BlockItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
public int BlockId { get; set; }
|
||||||
|
public Block Block { get; set; }
|
||||||
|
public ProgramScheduleItemCollectionType CollectionType { get; set; }
|
||||||
|
public int? CollectionId { get; set; }
|
||||||
|
public Collection Collection { get; set; }
|
||||||
|
public int? MediaItemId { get; set; }
|
||||||
|
public MediaItem MediaItem { get; set; }
|
||||||
|
public int? MultiCollectionId { get; set; }
|
||||||
|
public MultiCollection MultiCollection { get; set; }
|
||||||
|
public int? SmartCollectionId { get; set; }
|
||||||
|
public SmartCollection SmartCollection { get; set; }
|
||||||
|
public PlaybackOrder PlaybackOrder { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class PlayoutHistory
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public int PlayoutId { get; set; }
|
||||||
|
public Playout Playout { get; set; }
|
||||||
|
|
||||||
|
public int BlockId { get; set; }
|
||||||
|
public Block Block { get; set; }
|
||||||
|
|
||||||
|
// something that uniquely identifies the collection within the block
|
||||||
|
public string Key { get; set; }
|
||||||
|
|
||||||
|
// last occurence of an item from this collection in the playout
|
||||||
|
public DateTime When { get; set; }
|
||||||
|
|
||||||
|
// details about the item
|
||||||
|
public string Details { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class PlayoutTemplate
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int PlayoutId { get; set; }
|
||||||
|
public Playout Playout { get; set; }
|
||||||
|
public int TemplateId { get; set; }
|
||||||
|
public Template Template { get; set; }
|
||||||
|
public int Index { get; set; }
|
||||||
|
public ICollection<DayOfWeek> DaysOfWeek { get; set; }
|
||||||
|
public ICollection<int> DaysOfMonth { get; set; }
|
||||||
|
public ICollection<int> MonthsOfYear { get; set; }
|
||||||
|
public DateTimeOffset StartDate { get; set; }
|
||||||
|
public DateTimeOffset EndDate { get; set; }
|
||||||
|
|
||||||
|
// TODO: ICollection<DateTimeOffset> AdditionalDays { get; set; }
|
||||||
|
|
||||||
|
public static List<DayOfWeek> AllDaysOfWeek() =>
|
||||||
|
[
|
||||||
|
DayOfWeek.Monday,
|
||||||
|
DayOfWeek.Tuesday,
|
||||||
|
DayOfWeek.Wednesday,
|
||||||
|
DayOfWeek.Thursday,
|
||||||
|
DayOfWeek.Friday,
|
||||||
|
DayOfWeek.Saturday,
|
||||||
|
DayOfWeek.Sunday
|
||||||
|
];
|
||||||
|
|
||||||
|
public static List<int> AllDaysOfMonth() => Enumerable.Range(1, 31).ToList();
|
||||||
|
public static List<int> AllMonthsOfYear() => Enumerable.Range(1, 12).ToList();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class Template
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int TemplateGroupId { get; set; }
|
||||||
|
public TemplateGroup TemplateGroup { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public ICollection<TemplateItem> Items { get; set; }
|
||||||
|
public ICollection<PlayoutTemplate> PlayoutTemplates { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class TemplateGroup
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public ICollection<Template> Templates { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
public class TemplateItem
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int TemplateId { get; set; }
|
||||||
|
public Template Template { get; set; }
|
||||||
|
public int BlockId { get; set; }
|
||||||
|
public Block Block { get; set; }
|
||||||
|
public TimeSpan StartTime { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
|
||||||
|
public interface IBlockPlayoutBuilder
|
||||||
|
{
|
||||||
|
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
|
||||||
|
public interface IExternalJsonPlayoutBuilder
|
||||||
|
{
|
||||||
|
Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Domain.Filler;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using ErsatzTV.Core.Extensions;
|
||||||
|
using ErsatzTV.Core.Interfaces.Repositories;
|
||||||
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
|
public class BlockPlayoutBuilder(
|
||||||
|
IConfigElementRepository configElementRepository,
|
||||||
|
IMediaCollectionRepository mediaCollectionRepository,
|
||||||
|
ITelevisionRepository televisionRepository,
|
||||||
|
IArtistRepository artistRepository,
|
||||||
|
ILogger<BlockPlayoutBuilder> logger)
|
||||||
|
: IBlockPlayoutBuilder
|
||||||
|
{
|
||||||
|
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
logger.LogDebug(
|
||||||
|
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
|
||||||
|
playout.Id,
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
DateTimeOffset start = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
// get blocks to schedule
|
||||||
|
List<RealBlock> blocksToSchedule = await GetBlocksToSchedule(playout, start);
|
||||||
|
|
||||||
|
// get all collection items for the playout
|
||||||
|
Map<CollectionKey, List<MediaItem>> collectionMediaItems = await GetCollectionMediaItems(blocksToSchedule);
|
||||||
|
|
||||||
|
// TODO: REMOVE THIS !!!
|
||||||
|
playout.Items.Clear();
|
||||||
|
|
||||||
|
// TODO: REMOVE THIS !!!
|
||||||
|
var historyToRemove = playout.PlayoutHistory
|
||||||
|
.Filter(h => h.When > start.UtcDateTime)
|
||||||
|
.ToList();
|
||||||
|
foreach (PlayoutHistory remove in historyToRemove)
|
||||||
|
{
|
||||||
|
playout.PlayoutHistory.Remove(remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (RealBlock realBlock in blocksToSchedule)
|
||||||
|
{
|
||||||
|
logger.LogDebug(
|
||||||
|
"Will schedule block {Block} at {Start}",
|
||||||
|
realBlock.Block.Name,
|
||||||
|
realBlock.Start);
|
||||||
|
|
||||||
|
DateTimeOffset currentTime = realBlock.Start;
|
||||||
|
|
||||||
|
foreach (BlockItem blockItem in realBlock.Block.Items)
|
||||||
|
{
|
||||||
|
// TODO: support other playback orders
|
||||||
|
if (blockItem.PlaybackOrder is not PlaybackOrder.SeasonEpisode and not PlaybackOrder.Chronological)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check if change is needed - if not, skip building
|
||||||
|
// - block can change
|
||||||
|
// - template can change
|
||||||
|
// - playout templates can change
|
||||||
|
|
||||||
|
// TODO: check for playout history for this collection
|
||||||
|
string historyKey = HistoryKey.ForBlockItem(blockItem);
|
||||||
|
logger.LogDebug("History key for block item {Item} is {Key}", blockItem.Id, historyKey);
|
||||||
|
|
||||||
|
DateTime historyTime = currentTime.UtcDateTime;
|
||||||
|
Option<PlayoutHistory> maybeHistory = playout.PlayoutHistory
|
||||||
|
.Filter(h => h.BlockId == blockItem.BlockId)
|
||||||
|
.Filter(h => h.Key == historyKey)
|
||||||
|
.Filter(h => h.When < historyTime)
|
||||||
|
.OrderByDescending(h => h.When)
|
||||||
|
.HeadOrNone();
|
||||||
|
|
||||||
|
var state = new CollectionEnumeratorState { Seed = 0, Index = 0 };
|
||||||
|
|
||||||
|
var collectionKey = CollectionKey.ForBlockItem(blockItem);
|
||||||
|
List<MediaItem> collectionItems = collectionMediaItems[collectionKey];
|
||||||
|
// get enumerator
|
||||||
|
var enumerator = new SeasonEpisodeMediaCollectionEnumerator(collectionItems, state);
|
||||||
|
|
||||||
|
// seek to the appropriate place in the collection enumerator
|
||||||
|
foreach (PlayoutHistory history in maybeHistory)
|
||||||
|
{
|
||||||
|
logger.LogDebug("History is applicable: {When}: {History}", history.When, history.Details);
|
||||||
|
|
||||||
|
// find next media item
|
||||||
|
HistoryDetails.Details details = JsonConvert.DeserializeObject<HistoryDetails.Details>(history.Details);
|
||||||
|
if (details.SeasonNumber.HasValue && details.EpisodeNumber.HasValue)
|
||||||
|
{
|
||||||
|
Option<MediaItem> maybeMatchedItem = Optional(
|
||||||
|
collectionItems.Find(
|
||||||
|
ci => ci is Episode e &&
|
||||||
|
e.EpisodeMetadata.Any(em => em.EpisodeNumber == details.EpisodeNumber.Value) &&
|
||||||
|
e.Season.SeasonNumber == details.SeasonNumber.Value));
|
||||||
|
|
||||||
|
var copy = collectionItems.ToList();
|
||||||
|
|
||||||
|
if (maybeMatchedItem.IsNone)
|
||||||
|
{
|
||||||
|
var fakeItem = new Episode
|
||||||
|
{
|
||||||
|
Season = new Season { SeasonNumber = details.SeasonNumber.Value },
|
||||||
|
EpisodeMetadata =
|
||||||
|
[
|
||||||
|
new EpisodeMetadata
|
||||||
|
{
|
||||||
|
EpisodeNumber = details.EpisodeNumber.Value,
|
||||||
|
ReleaseDate = details.ReleaseDate
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
copy.Add(fakeItem);
|
||||||
|
maybeMatchedItem = fakeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (MediaItem matchedItem in maybeMatchedItem)
|
||||||
|
{
|
||||||
|
IComparer<MediaItem> comparer = blockItem.PlaybackOrder switch
|
||||||
|
{
|
||||||
|
PlaybackOrder.Chronological => new ChronologicalMediaComparer(),
|
||||||
|
_ => new SeasonEpisodeMediaComparer()
|
||||||
|
};
|
||||||
|
|
||||||
|
copy.Sort(comparer);
|
||||||
|
|
||||||
|
state.Index = copy.IndexOf(matchedItem);
|
||||||
|
enumerator.ResetState(state);
|
||||||
|
enumerator.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (MediaItem mediaItem in enumerator.Current)
|
||||||
|
{
|
||||||
|
logger.LogDebug("current item: {Id} / {Title}", mediaItem.Id, mediaItem is Episode e ? GetTitle(e) : string.Empty);
|
||||||
|
|
||||||
|
TimeSpan itemDuration = DurationForMediaItem(mediaItem);
|
||||||
|
|
||||||
|
// TODO: create a playout item
|
||||||
|
var playoutItem = new PlayoutItem
|
||||||
|
{
|
||||||
|
MediaItemId = mediaItem.Id,
|
||||||
|
Start = currentTime.UtcDateTime,
|
||||||
|
Finish = currentTime.UtcDateTime + itemDuration,
|
||||||
|
InPoint = TimeSpan.Zero,
|
||||||
|
OutPoint = itemDuration,
|
||||||
|
FillerKind = FillerKind.None,
|
||||||
|
//CustomTitle = scheduleItem.CustomTitle,
|
||||||
|
//WatermarkId = scheduleItem.WatermarkId,
|
||||||
|
//PreferredAudioLanguageCode = scheduleItem.PreferredAudioLanguageCode,
|
||||||
|
//PreferredAudioTitle = scheduleItem.PreferredAudioTitle,
|
||||||
|
//PreferredSubtitleLanguageCode = scheduleItem.PreferredSubtitleLanguageCode,
|
||||||
|
//SubtitleMode = scheduleItem.SubtitleMode
|
||||||
|
};
|
||||||
|
|
||||||
|
playout.Items.Add(playoutItem);
|
||||||
|
|
||||||
|
// TODO: create a playout history record
|
||||||
|
var nextHistory = new PlayoutHistory
|
||||||
|
{
|
||||||
|
PlayoutId = playout.Id,
|
||||||
|
BlockId = blockItem.BlockId,
|
||||||
|
When = currentTime.UtcDateTime,
|
||||||
|
Key = historyKey,
|
||||||
|
Details = HistoryDetails.ForMediaItem(mediaItem)
|
||||||
|
};
|
||||||
|
|
||||||
|
playout.PlayoutHistory.Add(nextHistory);
|
||||||
|
|
||||||
|
currentTime += itemDuration;
|
||||||
|
enumerator.MoveNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanUpHistory(playout, start);
|
||||||
|
|
||||||
|
return playout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetTitle(Episode e)
|
||||||
|
{
|
||||||
|
string showTitle = e.Season.Show.ShowMetadata.HeadOrNone()
|
||||||
|
.Map(sm => $"{sm.Title} - ").IfNone(string.Empty);
|
||||||
|
var episodeNumbers = e.EpisodeMetadata.Map(em => em.EpisodeNumber).ToList();
|
||||||
|
var episodeTitles = e.EpisodeMetadata.Map(em => em.Title).ToList();
|
||||||
|
if (episodeNumbers.Count == 0 || episodeTitles.Count == 0)
|
||||||
|
{
|
||||||
|
return "[unknown episode]";
|
||||||
|
}
|
||||||
|
|
||||||
|
var numbersString = $"e{string.Join('e', episodeNumbers.Map(n => $"{n:00}"))}";
|
||||||
|
var titlesString = $"{string.Join('/', episodeTitles)}";
|
||||||
|
|
||||||
|
return $"{showTitle}s{e.Season.SeasonNumber:00}{numbersString} - {titlesString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<RealBlock>> GetBlocksToSchedule(Playout playout, DateTimeOffset start)
|
||||||
|
{
|
||||||
|
int daysToBuild = await configElementRepository.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
||||||
|
.IfNoneAsync(2);
|
||||||
|
|
||||||
|
DateTimeOffset finish = start.AddDays(daysToBuild);
|
||||||
|
|
||||||
|
var realBlocks = new List<RealBlock>();
|
||||||
|
DateTimeOffset current = start.Date;
|
||||||
|
while (current < finish)
|
||||||
|
{
|
||||||
|
foreach (PlayoutTemplate playoutTemplate in PlayoutTemplateSelector.GetPlayoutTemplateFor(playout.Templates, current))
|
||||||
|
{
|
||||||
|
// logger.LogDebug(
|
||||||
|
// "Will schedule day {Date} using template {Template}",
|
||||||
|
// current,
|
||||||
|
// playoutTemplate.Template.Name);
|
||||||
|
|
||||||
|
foreach (TemplateItem templateItem in playoutTemplate.Template.Items)
|
||||||
|
{
|
||||||
|
var realBlock = new RealBlock(
|
||||||
|
templateItem.Block,
|
||||||
|
new DateTimeOffset(
|
||||||
|
current.Year,
|
||||||
|
current.Month,
|
||||||
|
current.Day,
|
||||||
|
templateItem.StartTime.Hours,
|
||||||
|
templateItem.StartTime.Minutes,
|
||||||
|
0,
|
||||||
|
start.Offset));
|
||||||
|
|
||||||
|
realBlocks.Add(realBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current.AddDays(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realBlocks.RemoveAll(b => b.Start.AddMinutes(b.Block.Minutes) < start || b.Start > finish);
|
||||||
|
|
||||||
|
return realBlocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanUpHistory(Playout playout, DateTimeOffset start)
|
||||||
|
{
|
||||||
|
var groups = new Dictionary<string, List<PlayoutHistory>>();
|
||||||
|
foreach (PlayoutHistory history in playout.PlayoutHistory)
|
||||||
|
{
|
||||||
|
var key = $"{history.BlockId}-{history.Key}";
|
||||||
|
if (!groups.TryGetValue(key, out List<PlayoutHistory> group))
|
||||||
|
{
|
||||||
|
group = [];
|
||||||
|
groups[key] = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Add(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((string key, List<PlayoutHistory> group) in groups)
|
||||||
|
{
|
||||||
|
logger.LogDebug("History key {Key} has {Count} items in group", key, group.Count);
|
||||||
|
|
||||||
|
IEnumerable<PlayoutHistory> toDelete = group
|
||||||
|
.Filter(h => h.When < start.UtcDateTime)
|
||||||
|
.OrderByDescending(h => h.When)
|
||||||
|
.Tail();
|
||||||
|
|
||||||
|
foreach (PlayoutHistory delete in toDelete)
|
||||||
|
{
|
||||||
|
playout.PlayoutHistory.Remove(delete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Map<CollectionKey, List<MediaItem>>> GetCollectionMediaItems(List<RealBlock> realBlocks)
|
||||||
|
{
|
||||||
|
var collectionKeys = realBlocks.Map(b => b.Block.Items)
|
||||||
|
.Flatten()
|
||||||
|
.DistinctBy(i => i.Id)
|
||||||
|
.Map(CollectionKey.ForBlockItem)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
IEnumerable<Tuple<CollectionKey, List<MediaItem>>> tuples = await collectionKeys.Map(
|
||||||
|
async collectionKey => Tuple(
|
||||||
|
collectionKey,
|
||||||
|
await MediaItemsForCollection.Collect(
|
||||||
|
mediaCollectionRepository,
|
||||||
|
televisionRepository,
|
||||||
|
artistRepository,
|
||||||
|
collectionKey))).SequenceParallel();
|
||||||
|
|
||||||
|
return LanguageExt.Map.createRange(tuples);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TimeSpan DurationForMediaItem(MediaItem mediaItem)
|
||||||
|
{
|
||||||
|
MediaVersion version = mediaItem.GetHeadVersion();
|
||||||
|
return version.Duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record RealBlock(Block Block, DateTimeOffset Start);
|
||||||
|
|
||||||
|
private static class HistoryKey
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerSettings Settings = new()
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string ForBlockItem(BlockItem blockItem)
|
||||||
|
{
|
||||||
|
dynamic key = new
|
||||||
|
{
|
||||||
|
blockItem.BlockId,
|
||||||
|
blockItem.PlaybackOrder,
|
||||||
|
blockItem.CollectionType,
|
||||||
|
blockItem.CollectionId,
|
||||||
|
blockItem.MultiCollectionId,
|
||||||
|
blockItem.SmartCollectionId,
|
||||||
|
blockItem.MediaItemId
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(key, Formatting.None, Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HistoryDetails
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerSettings Settings = new()
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string ForMediaItem(MediaItem mediaItem)
|
||||||
|
{
|
||||||
|
Details details = mediaItem switch
|
||||||
|
{
|
||||||
|
Episode e => ForEpisode(e),
|
||||||
|
_ => new Details(mediaItem.Id, null, null, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(details, Formatting.None, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Details ForEpisode(Episode e)
|
||||||
|
{
|
||||||
|
int? episodeNumber = null;
|
||||||
|
DateTime? releaseDate = null;
|
||||||
|
foreach (EpisodeMetadata episodeMetadata in e.EpisodeMetadata.HeadOrNone())
|
||||||
|
{
|
||||||
|
episodeNumber = episodeMetadata.EpisodeNumber;
|
||||||
|
releaseDate = episodeMetadata.ReleaseDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Details(e.Id, releaseDate, e.Season.SeasonNumber, episodeNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Details(int? MediaItemId, DateTime? ReleaseDate, int? SeasonNumber, int? EpisodeNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using ErsatzTV.Core.Domain.Filler;
|
using ErsatzTV.Core.Domain.Filler;
|
||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
namespace ErsatzTV.Core.Scheduling;
|
namespace ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
@@ -11,6 +12,46 @@ public class CollectionKey : Record<CollectionKey>
|
|||||||
public int? SmartCollectionId { get; set; }
|
public int? SmartCollectionId { get; set; }
|
||||||
public int? MediaItemId { get; set; }
|
public int? MediaItemId { get; set; }
|
||||||
public string FakeCollectionKey { get; set; }
|
public string FakeCollectionKey { get; set; }
|
||||||
|
|
||||||
|
public static CollectionKey ForBlockItem(BlockItem item) =>
|
||||||
|
item.CollectionType switch
|
||||||
|
{
|
||||||
|
ProgramScheduleItemCollectionType.Collection => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
CollectionId = item.CollectionId,
|
||||||
|
},
|
||||||
|
ProgramScheduleItemCollectionType.TelevisionShow => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
MediaItemId = item.MediaItemId,
|
||||||
|
},
|
||||||
|
ProgramScheduleItemCollectionType.TelevisionSeason => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
MediaItemId = item.MediaItemId,
|
||||||
|
},
|
||||||
|
ProgramScheduleItemCollectionType.Artist => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
MediaItemId = item.MediaItemId,
|
||||||
|
},
|
||||||
|
ProgramScheduleItemCollectionType.MultiCollection => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
MultiCollectionId = item.MultiCollectionId,
|
||||||
|
},
|
||||||
|
ProgramScheduleItemCollectionType.SmartCollection => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
SmartCollectionId = item.SmartCollectionId,
|
||||||
|
},
|
||||||
|
ProgramScheduleItemCollectionType.FakeCollection => new CollectionKey
|
||||||
|
{
|
||||||
|
CollectionType = item.CollectionType,
|
||||||
|
},
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(item))
|
||||||
|
};
|
||||||
|
|
||||||
public static CollectionKey ForScheduleItem(ProgramScheduleItem item) =>
|
public static CollectionKey ForScheduleItem(ProgramScheduleItem item) =>
|
||||||
item.CollectionType switch
|
item.CollectionType switch
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using ErsatzTV.Core.Domain;
|
||||||
|
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
|
public class ExternalJsonPlayoutBuilder(ILogger<ExternalJsonPlayoutBuilder> logger) : IExternalJsonPlayoutBuilder
|
||||||
|
{
|
||||||
|
// nothing to do for external json playouts
|
||||||
|
public Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
logger.LogDebug(
|
||||||
|
"Building external json playout for channel {Number} - {Name}",
|
||||||
|
playout.Channel.Number,
|
||||||
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
return Task.FromResult(playout);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,10 +43,11 @@ public class PlayoutBuilder : IPlayoutBuilder
|
|||||||
|
|
||||||
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
|
public async Task<Playout> Build(Playout playout, PlayoutBuildMode mode, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (playout.ProgramSchedulePlayoutType is ProgramSchedulePlayoutType.ExternalJson)
|
if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Flood)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(
|
_logger.LogWarning(
|
||||||
"Skipping external json playout build on channel {Number} - {Name}",
|
"Skipping playout build with type {Type} on channel {Number} - {Name}",
|
||||||
|
playout.ProgramSchedulePlayoutType,
|
||||||
playout.Channel.Number,
|
playout.Channel.Number,
|
||||||
playout.Channel.Name);
|
playout.Channel.Name);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Scheduling;
|
||||||
|
|
||||||
|
public static class PlayoutTemplateSelector
|
||||||
|
{
|
||||||
|
public static Option<PlayoutTemplate> GetPlayoutTemplateFor(
|
||||||
|
IEnumerable<PlayoutTemplate> templates,
|
||||||
|
DateTimeOffset date)
|
||||||
|
{
|
||||||
|
foreach (PlayoutTemplate template in templates.OrderBy(x => x.Index))
|
||||||
|
{
|
||||||
|
bool daysOfWeek = template.DaysOfWeek.Contains(date.DayOfWeek);
|
||||||
|
if (!daysOfWeek)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool daysOfMonth = template.DaysOfMonth.Contains(date.Day);
|
||||||
|
if (!daysOfMonth)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool monthOfYear = template.MonthsOfYear.Contains(date.Month);
|
||||||
|
if (monthOfYear)
|
||||||
|
{
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Option<PlayoutTemplate>.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
|
||||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
+4895
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,355 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_BlockScheduling : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BlockGroup",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BlockGroup", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TemplateGroup",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TemplateGroup", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Block",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
BlockGroupId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Minutes = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Block", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Block_BlockGroup_BlockGroupId",
|
||||||
|
column: x => x.BlockGroupId,
|
||||||
|
principalTable: "BlockGroup",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Template",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
TemplateGroupId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "varchar(255)", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Template", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Template_TemplateGroup_TemplateGroupId",
|
||||||
|
column: x => x.TemplateGroupId,
|
||||||
|
principalTable: "TemplateGroup",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BlockItem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Index = table.Column<int>(type: "int", nullable: false),
|
||||||
|
BlockId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
CollectionType = table.Column<int>(type: "int", nullable: false),
|
||||||
|
CollectionId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
MediaItemId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
MultiCollectionId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
SmartCollectionId = table.Column<int>(type: "int", nullable: true),
|
||||||
|
PlaybackOrder = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BlockItem", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_Block_BlockId",
|
||||||
|
column: x => x.BlockId,
|
||||||
|
principalTable: "Block",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_Collection_CollectionId",
|
||||||
|
column: x => x.CollectionId,
|
||||||
|
principalTable: "Collection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_MediaItem_MediaItemId",
|
||||||
|
column: x => x.MediaItemId,
|
||||||
|
principalTable: "MediaItem",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_MultiCollection_MultiCollectionId",
|
||||||
|
column: x => x.MultiCollectionId,
|
||||||
|
principalTable: "MultiCollection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_SmartCollection_SmartCollectionId",
|
||||||
|
column: x => x.SmartCollectionId,
|
||||||
|
principalTable: "SmartCollection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlayoutHistory",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
PlayoutId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
BlockId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Key = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
When = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Details = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4")
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlayoutHistory", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutHistory_Block_BlockId",
|
||||||
|
column: x => x.BlockId,
|
||||||
|
principalTable: "Block",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutHistory_Playout_PlayoutId",
|
||||||
|
column: x => x.PlayoutId,
|
||||||
|
principalTable: "Playout",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlayoutTemplate",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
PlayoutId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
TemplateId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
Index = table.Column<int>(type: "int", nullable: false),
|
||||||
|
DaysOfWeek = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
DaysOfMonth = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
MonthsOfYear = table.Column<string>(type: "longtext", nullable: true)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
StartDate = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
|
||||||
|
EndDate = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlayoutTemplate", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutTemplate_Playout_PlayoutId",
|
||||||
|
column: x => x.PlayoutId,
|
||||||
|
principalTable: "Playout",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutTemplate_Template_TemplateId",
|
||||||
|
column: x => x.TemplateId,
|
||||||
|
principalTable: "Template",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TemplateItem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
TemplateId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
BlockId = table.Column<int>(type: "int", nullable: false),
|
||||||
|
StartTime = table.Column<TimeSpan>(type: "time(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TemplateItem", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_TemplateItem_Block_BlockId",
|
||||||
|
column: x => x.BlockId,
|
||||||
|
principalTable: "Block",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_TemplateItem_Template_TemplateId",
|
||||||
|
column: x => x.TemplateId,
|
||||||
|
principalTable: "Template",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Block_BlockGroupId",
|
||||||
|
table: "Block",
|
||||||
|
column: "BlockGroupId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Block_Name",
|
||||||
|
table: "Block",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockGroup_Name",
|
||||||
|
table: "BlockGroup",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_BlockId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "BlockId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_CollectionId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "CollectionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_MediaItemId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "MediaItemId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_MultiCollectionId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "MultiCollectionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_SmartCollectionId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "SmartCollectionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutHistory_BlockId",
|
||||||
|
table: "PlayoutHistory",
|
||||||
|
column: "BlockId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutHistory_PlayoutId",
|
||||||
|
table: "PlayoutHistory",
|
||||||
|
column: "PlayoutId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutTemplate_PlayoutId",
|
||||||
|
table: "PlayoutTemplate",
|
||||||
|
column: "PlayoutId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutTemplate_TemplateId",
|
||||||
|
table: "PlayoutTemplate",
|
||||||
|
column: "TemplateId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Template_Name",
|
||||||
|
table: "Template",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Template_TemplateGroupId",
|
||||||
|
table: "Template",
|
||||||
|
column: "TemplateGroupId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TemplateGroup_Name",
|
||||||
|
table: "TemplateGroup",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TemplateItem_BlockId",
|
||||||
|
table: "TemplateItem",
|
||||||
|
column: "BlockId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TemplateItem_TemplateId",
|
||||||
|
table: "TemplateItem",
|
||||||
|
column: "TemplateId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BlockItem");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlayoutHistory");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlayoutTemplate");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TemplateItem");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Block");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Template");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BlockGroup");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TemplateGroup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "8.0.0")
|
.HasAnnotation("ProductVersion", "8.0.1")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||||
@@ -1820,6 +1820,225 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.ToTable("Resolution", (string)null);
|
b.ToTable("Resolution", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Block", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("BlockGroupId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Minutes")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockGroupId");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Block", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockGroup", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("BlockGroup", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("BlockId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("CollectionId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("CollectionType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("MediaItemId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("MultiCollectionId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("PlaybackOrder")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int?>("SmartCollectionId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockId");
|
||||||
|
|
||||||
|
b.HasIndex("CollectionId");
|
||||||
|
|
||||||
|
b.HasIndex("MediaItemId");
|
||||||
|
|
||||||
|
b.HasIndex("MultiCollectionId");
|
||||||
|
|
||||||
|
b.HasIndex("SmartCollectionId");
|
||||||
|
|
||||||
|
b.ToTable("BlockItem", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("BlockId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Details")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("PlayoutId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("When")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockId");
|
||||||
|
|
||||||
|
b.HasIndex("PlayoutId");
|
||||||
|
|
||||||
|
b.ToTable("PlayoutHistory", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutTemplate", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("DaysOfMonth")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("DaysOfWeek")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("EndDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("MonthsOfYear")
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("PlayoutId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("StartDate")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<int>("TemplateId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PlayoutId");
|
||||||
|
|
||||||
|
b.HasIndex("TemplateId");
|
||||||
|
|
||||||
|
b.ToTable("PlayoutTemplate", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Template", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.Property<int>("TemplateGroupId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("TemplateGroupId");
|
||||||
|
|
||||||
|
b.ToTable("Template", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateGroup", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("varchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TemplateGroup", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("BlockId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("StartTime")
|
||||||
|
.HasColumnType("time(6)");
|
||||||
|
|
||||||
|
b.Property<int>("TemplateId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockId");
|
||||||
|
|
||||||
|
b.HasIndex("TemplateId");
|
||||||
|
|
||||||
|
b.ToTable("TemplateItem", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -3634,6 +3853,124 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.Navigation("Watermark");
|
b.Navigation("Watermark");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Block", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.BlockGroup", "BlockGroup")
|
||||||
|
.WithMany("Blocks")
|
||||||
|
.HasForeignKey("BlockGroupId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("BlockGroup");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Block", "Block")
|
||||||
|
.WithMany("Items")
|
||||||
|
.HasForeignKey("BlockId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MediaItemId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.MultiCollection", "MultiCollection")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MultiCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.SmartCollection", "SmartCollection")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SmartCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.Navigation("Block");
|
||||||
|
|
||||||
|
b.Navigation("Collection");
|
||||||
|
|
||||||
|
b.Navigation("MediaItem");
|
||||||
|
|
||||||
|
b.Navigation("MultiCollection");
|
||||||
|
|
||||||
|
b.Navigation("SmartCollection");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Block", "Block")
|
||||||
|
.WithMany("PlayoutHistory")
|
||||||
|
.HasForeignKey("BlockId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout")
|
||||||
|
.WithMany("PlayoutHistory")
|
||||||
|
.HasForeignKey("PlayoutId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Block");
|
||||||
|
|
||||||
|
b.Navigation("Playout");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutTemplate", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout")
|
||||||
|
.WithMany("Templates")
|
||||||
|
.HasForeignKey("PlayoutId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Template", "Template")
|
||||||
|
.WithMany("PlayoutTemplates")
|
||||||
|
.HasForeignKey("TemplateId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playout");
|
||||||
|
|
||||||
|
b.Navigation("Template");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Template", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.TemplateGroup", "TemplateGroup")
|
||||||
|
.WithMany("Templates")
|
||||||
|
.HasForeignKey("TemplateGroupId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("TemplateGroup");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Block", "Block")
|
||||||
|
.WithMany("TemplateItems")
|
||||||
|
.HasForeignKey("BlockId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Template", "Template")
|
||||||
|
.WithMany("Items")
|
||||||
|
.HasForeignKey("TemplateId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Block");
|
||||||
|
|
||||||
|
b.Navigation("Template");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
|
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
|
||||||
@@ -4353,9 +4690,13 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
|
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
||||||
|
b.Navigation("PlayoutHistory");
|
||||||
|
|
||||||
b.Navigation("ProgramScheduleAlternates");
|
b.Navigation("ProgramScheduleAlternates");
|
||||||
|
|
||||||
b.Navigation("ProgramScheduleAnchors");
|
b.Navigation("ProgramScheduleAnchors");
|
||||||
|
|
||||||
|
b.Navigation("Templates");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b =>
|
||||||
@@ -4367,6 +4708,32 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
|||||||
b.Navigation("ProgramScheduleAlternates");
|
b.Navigation("ProgramScheduleAlternates");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Block", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Items");
|
||||||
|
|
||||||
|
b.Navigation("PlayoutHistory");
|
||||||
|
|
||||||
|
b.Navigation("TemplateItems");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockGroup", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Blocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Template", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Items");
|
||||||
|
|
||||||
|
b.Navigation("PlayoutTemplates");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateGroup", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Templates");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Actors");
|
b.Navigation("Actors");
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dapper" Version="2.1.24" />
|
<PackageReference Include="Dapper" Version="2.1.28" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+4673
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,153 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Block : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BlockGroup",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BlockGroup", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Block",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
BlockGroupId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Minutes = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Block", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Block_BlockGroup_BlockGroupId",
|
||||||
|
column: x => x.BlockGroupId,
|
||||||
|
principalTable: "BlockGroup",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "BlockItem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
BlockId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
CollectionType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
CollectionId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
MediaItemId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
MultiCollectionId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
SmartCollectionId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
|
PlaybackOrder = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_BlockItem", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_Block_BlockId",
|
||||||
|
column: x => x.BlockId,
|
||||||
|
principalTable: "Block",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_Collection_CollectionId",
|
||||||
|
column: x => x.CollectionId,
|
||||||
|
principalTable: "Collection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_MediaItem_MediaItemId",
|
||||||
|
column: x => x.MediaItemId,
|
||||||
|
principalTable: "MediaItem",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_MultiCollection_MultiCollectionId",
|
||||||
|
column: x => x.MultiCollectionId,
|
||||||
|
principalTable: "MultiCollection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_BlockItem_SmartCollection_SmartCollectionId",
|
||||||
|
column: x => x.SmartCollectionId,
|
||||||
|
principalTable: "SmartCollection",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Block_BlockGroupId",
|
||||||
|
table: "Block",
|
||||||
|
column: "BlockGroupId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Block_Name",
|
||||||
|
table: "Block",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockGroup_Name",
|
||||||
|
table: "BlockGroup",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_BlockId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "BlockId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_CollectionId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "CollectionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_MediaItemId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "MediaItemId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_MultiCollectionId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "MultiCollectionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_BlockItem_SmartCollectionId",
|
||||||
|
table: "BlockItem",
|
||||||
|
column: "SmartCollectionId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BlockItem");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Block");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "BlockGroup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4789
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_Template : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TemplateGroup",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TemplateGroup", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Template",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
TemplateGroupId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
PlayoutId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Template", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Template_Playout_PlayoutId",
|
||||||
|
column: x => x.PlayoutId,
|
||||||
|
principalTable: "Playout",
|
||||||
|
principalColumn: "Id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Template_TemplateGroup_TemplateGroupId",
|
||||||
|
column: x => x.TemplateGroupId,
|
||||||
|
principalTable: "TemplateGroup",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TemplateItem",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
TemplateId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
BlockId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
StartTime = table.Column<TimeSpan>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TemplateItem", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_TemplateItem_Block_BlockId",
|
||||||
|
column: x => x.BlockId,
|
||||||
|
principalTable: "Block",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_TemplateItem_Template_TemplateId",
|
||||||
|
column: x => x.TemplateId,
|
||||||
|
principalTable: "Template",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Template_Name",
|
||||||
|
table: "Template",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Template_PlayoutId",
|
||||||
|
table: "Template",
|
||||||
|
column: "PlayoutId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Template_TemplateGroupId",
|
||||||
|
table: "Template",
|
||||||
|
column: "TemplateGroupId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TemplateGroup_Name",
|
||||||
|
table: "TemplateGroup",
|
||||||
|
column: "Name",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TemplateItem_BlockId",
|
||||||
|
table: "TemplateItem",
|
||||||
|
column: "BlockId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TemplateItem_TemplateId",
|
||||||
|
table: "TemplateItem",
|
||||||
|
column: "TemplateId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TemplateItem");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Template");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TemplateGroup");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4837
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_PlayoutTemplate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Template_Playout_PlayoutId",
|
||||||
|
table: "Template");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Template_PlayoutId",
|
||||||
|
table: "Template");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PlayoutId",
|
||||||
|
table: "Template");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlayoutTemplate",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
PlayoutId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
TemplateId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Index = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
DaysOfWeek = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
MonthsOfYear = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
StartDate = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
EndDate = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlayoutTemplate", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutTemplate_Playout_PlayoutId",
|
||||||
|
column: x => x.PlayoutId,
|
||||||
|
principalTable: "Playout",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutTemplate_Template_TemplateId",
|
||||||
|
column: x => x.TemplateId,
|
||||||
|
principalTable: "Template",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutTemplate_PlayoutId",
|
||||||
|
table: "PlayoutTemplate",
|
||||||
|
column: "PlayoutId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutTemplate_TemplateId",
|
||||||
|
table: "PlayoutTemplate",
|
||||||
|
column: "TemplateId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlayoutTemplate");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "PlayoutId",
|
||||||
|
table: "Template",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Template_PlayoutId",
|
||||||
|
table: "Template",
|
||||||
|
column: "PlayoutId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Template_Playout_PlayoutId",
|
||||||
|
table: "Template",
|
||||||
|
column: "PlayoutId",
|
||||||
|
principalTable: "Playout",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ErsatzTV.Infrastructure.Sqlite/Migrations/20240113140741_Add_PlayoutTemplate_DaysOfMonth.Designer.cs
Generated
+4840
File diff suppressed because it is too large
Load Diff
+28
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_PlayoutTemplate_DaysOfMonth : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "DaysOfMonth",
|
||||||
|
table: "PlayoutTemplate",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "DaysOfMonth",
|
||||||
|
table: "PlayoutTemplate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4893
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_PlayoutHistory : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PlayoutHistory",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
PlayoutId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
BlockId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
Key = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
When = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
Details = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PlayoutHistory", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutHistory_Block_BlockId",
|
||||||
|
column: x => x.BlockId,
|
||||||
|
principalTable: "Block",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PlayoutHistory_Playout_PlayoutId",
|
||||||
|
column: x => x.PlayoutId,
|
||||||
|
principalTable: "Playout",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutHistory_BlockId",
|
||||||
|
table: "PlayoutHistory",
|
||||||
|
column: "BlockId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PlayoutHistory_PlayoutId",
|
||||||
|
table: "PlayoutHistory",
|
||||||
|
column: "PlayoutId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PlayoutHistory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||||
{
|
{
|
||||||
@@ -715,7 +715,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("SongMetadataId");
|
b.HasIndex("SongMetadataId");
|
||||||
|
|
||||||
b.ToTable("Genre");
|
b.ToTable("Genre", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinCollection", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinCollection", b =>
|
||||||
@@ -1168,7 +1168,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ArtistMetadataId");
|
b.HasIndex("ArtistMetadataId");
|
||||||
|
|
||||||
b.ToTable("Mood");
|
b.ToTable("Mood", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b =>
|
||||||
@@ -1276,7 +1276,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("SmartCollectionId");
|
b.HasIndex("SmartCollectionId");
|
||||||
|
|
||||||
b.ToTable("MultiCollectionSmartItem");
|
b.ToTable("MultiCollectionSmartItem", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoArtist", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoArtist", b =>
|
||||||
@@ -1295,7 +1295,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("MusicVideoMetadataId");
|
b.HasIndex("MusicVideoMetadataId");
|
||||||
|
|
||||||
b.ToTable("MusicVideoArtist");
|
b.ToTable("MusicVideoArtist", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
||||||
@@ -1818,6 +1818,225 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.ToTable("Resolution", (string)null);
|
b.ToTable("Resolution", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Block", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("BlockGroupId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Minutes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockGroupId");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Block", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockGroup", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("BlockGroup", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("BlockId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("CollectionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CollectionType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MediaItemId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MultiCollectionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PlaybackOrder")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("SmartCollectionId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockId");
|
||||||
|
|
||||||
|
b.HasIndex("CollectionId");
|
||||||
|
|
||||||
|
b.HasIndex("MediaItemId");
|
||||||
|
|
||||||
|
b.HasIndex("MultiCollectionId");
|
||||||
|
|
||||||
|
b.HasIndex("SmartCollectionId");
|
||||||
|
|
||||||
|
b.ToTable("BlockItem", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutHistory", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("BlockId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Details")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("PlayoutId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("When")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockId");
|
||||||
|
|
||||||
|
b.HasIndex("PlayoutId");
|
||||||
|
|
||||||
|
b.ToTable("PlayoutHistory", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutTemplate", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("DaysOfMonth")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DaysOfWeek")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("EndDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Index")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("MonthsOfYear")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("PlayoutId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("StartDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TemplateId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PlayoutId");
|
||||||
|
|
||||||
|
b.HasIndex("TemplateId");
|
||||||
|
|
||||||
|
b.ToTable("PlayoutTemplate", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Template", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TemplateGroupId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("TemplateGroupId");
|
||||||
|
|
||||||
|
b.ToTable("Template", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateGroup", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Name")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TemplateGroup", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateItem", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("BlockId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<TimeSpan>("StartTime")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("TemplateId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BlockId");
|
||||||
|
|
||||||
|
b.HasIndex("TemplateId");
|
||||||
|
|
||||||
|
b.ToTable("TemplateItem", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -2055,7 +2274,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("ArtistMetadataId");
|
b.HasIndex("ArtistMetadataId");
|
||||||
|
|
||||||
b.ToTable("Style");
|
b.ToTable("Style", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Subtitle", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Subtitle", b =>
|
||||||
@@ -2203,7 +2422,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("SongMetadataId");
|
b.HasIndex("SongMetadataId");
|
||||||
|
|
||||||
b.ToTable("Tag");
|
b.ToTable("Tag", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.TraktList", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.TraktList", b =>
|
||||||
@@ -2344,7 +2563,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("EmbyLibraryId");
|
b.HasIndex("EmbyLibraryId");
|
||||||
|
|
||||||
b.ToTable("EmbyPathInfo");
|
b.ToTable("EmbyPathInfo", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Jellyfin.JellyfinPathInfo", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Jellyfin.JellyfinPathInfo", b =>
|
||||||
@@ -2366,7 +2585,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.HasIndex("JellyfinLibraryId");
|
b.HasIndex("JellyfinLibraryId");
|
||||||
|
|
||||||
b.ToTable("JellyfinPathInfo");
|
b.ToTable("JellyfinPathInfo", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyLibrary", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyLibrary", b =>
|
||||||
@@ -3326,7 +3545,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
.HasForeignKey("ProgramScheduleId")
|
.HasForeignKey("ProgramScheduleId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.OwnsOne("ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 =>
|
b.OwnsOne("ErsatzTV.Core.Domain.Playout.Anchor#ErsatzTV.Core.Domain.PlayoutAnchor", "Anchor", b1 =>
|
||||||
{
|
{
|
||||||
b1.Property<int>("PlayoutId")
|
b1.Property<int>("PlayoutId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
@@ -3356,7 +3575,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b1.WithOwner()
|
b1.WithOwner()
|
||||||
.HasForeignKey("PlayoutId");
|
.HasForeignKey("PlayoutId");
|
||||||
|
|
||||||
b1.OwnsOne("ErsatzTV.Core.Domain.CollectionEnumeratorState", "ScheduleItemsEnumeratorState", b2 =>
|
b1.OwnsOne("ErsatzTV.Core.Domain.Playout.Anchor#ErsatzTV.Core.Domain.PlayoutAnchor.ScheduleItemsEnumeratorState#ErsatzTV.Core.Domain.CollectionEnumeratorState", "ScheduleItemsEnumeratorState", b2 =>
|
||||||
{
|
{
|
||||||
b2.Property<int>("PlayoutAnchorPlayoutId")
|
b2.Property<int>("PlayoutAnchorPlayoutId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
@@ -3439,7 +3658,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
.HasForeignKey("SmartCollectionId")
|
.HasForeignKey("SmartCollectionId")
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
b.OwnsOne("ErsatzTV.Core.Domain.CollectionEnumeratorState", "EnumeratorState", b1 =>
|
b.OwnsOne("ErsatzTV.Core.Domain.PlayoutProgramScheduleAnchor.EnumeratorState#ErsatzTV.Core.Domain.CollectionEnumeratorState", "EnumeratorState", b1 =>
|
||||||
{
|
{
|
||||||
b1.Property<int>("PlayoutProgramScheduleAnchorId")
|
b1.Property<int>("PlayoutProgramScheduleAnchorId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
@@ -3485,7 +3704,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.OwnsOne("ErsatzTV.Core.Domain.CollectionEnumeratorState", "EnumeratorState", b1 =>
|
b.OwnsOne("ErsatzTV.Core.Domain.PlayoutScheduleItemFillGroupIndex.EnumeratorState#ErsatzTV.Core.Domain.CollectionEnumeratorState", "EnumeratorState", b1 =>
|
||||||
{
|
{
|
||||||
b1.Property<int>("PlayoutScheduleItemFillGroupIndexId")
|
b1.Property<int>("PlayoutScheduleItemFillGroupIndexId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
@@ -3632,6 +3851,124 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.Navigation("Watermark");
|
b.Navigation("Watermark");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Block", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.BlockGroup", "BlockGroup")
|
||||||
|
.WithMany("Blocks")
|
||||||
|
.HasForeignKey("BlockGroupId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("BlockGroup");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Block", "Block")
|
||||||
|
.WithMany("Items")
|
||||||
|
.HasForeignKey("BlockId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Collection", "Collection")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.MediaItem", "MediaItem")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MediaItemId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.MultiCollection", "MultiCollection")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("MultiCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.SmartCollection", "SmartCollection")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SmartCollectionId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.Navigation("Block");
|
||||||
|
|
||||||
|
b.Navigation("Collection");
|
||||||
|
|
||||||
|
b.Navigation("MediaItem");
|
||||||
|
|
||||||
|
b.Navigation("MultiCollection");
|
||||||
|
|
||||||
|
b.Navigation("SmartCollection");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutHistory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Block", "Block")
|
||||||
|
.WithMany("PlayoutHistory")
|
||||||
|
.HasForeignKey("BlockId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout")
|
||||||
|
.WithMany("PlayoutHistory")
|
||||||
|
.HasForeignKey("PlayoutId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Block");
|
||||||
|
|
||||||
|
b.Navigation("Playout");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.PlayoutTemplate", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Playout", "Playout")
|
||||||
|
.WithMany("Templates")
|
||||||
|
.HasForeignKey("PlayoutId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Template", "Template")
|
||||||
|
.WithMany("PlayoutTemplates")
|
||||||
|
.HasForeignKey("TemplateId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Playout");
|
||||||
|
|
||||||
|
b.Navigation("Template");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Template", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.TemplateGroup", "TemplateGroup")
|
||||||
|
.WithMany("Templates")
|
||||||
|
.HasForeignKey("TemplateGroupId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("TemplateGroup");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateItem", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Block", "Block")
|
||||||
|
.WithMany("TemplateItems")
|
||||||
|
.HasForeignKey("BlockId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.Scheduling.Template", "Template")
|
||||||
|
.WithMany("Items")
|
||||||
|
.HasForeignKey("TemplateId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Block");
|
||||||
|
|
||||||
|
b.Navigation("Template");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
|
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
|
||||||
@@ -4351,9 +4688,13 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
|
|
||||||
b.Navigation("Items");
|
b.Navigation("Items");
|
||||||
|
|
||||||
|
b.Navigation("PlayoutHistory");
|
||||||
|
|
||||||
b.Navigation("ProgramScheduleAlternates");
|
b.Navigation("ProgramScheduleAlternates");
|
||||||
|
|
||||||
b.Navigation("ProgramScheduleAnchors");
|
b.Navigation("ProgramScheduleAnchors");
|
||||||
|
|
||||||
|
b.Navigation("Templates");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b =>
|
||||||
@@ -4365,6 +4706,32 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
|||||||
b.Navigation("ProgramScheduleAlternates");
|
b.Navigation("ProgramScheduleAlternates");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Block", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Items");
|
||||||
|
|
||||||
|
b.Navigation("PlayoutHistory");
|
||||||
|
|
||||||
|
b.Navigation("TemplateItems");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.BlockGroup", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Blocks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.Template", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Items");
|
||||||
|
|
||||||
|
b.Navigation("PlayoutTemplates");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Scheduling.TemplateGroup", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Templates");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Actors");
|
b.Navigation("Actors");
|
||||||
|
|||||||
@@ -34,5 +34,15 @@ public class PlayoutConfiguration : IEntityTypeConfiguration<Playout>
|
|||||||
.WithOne(i => i.Playout)
|
.WithOne(i => i.Playout)
|
||||||
.HasForeignKey(i => i.PlayoutId)
|
.HasForeignKey(i => i.PlayoutId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(p => p.Templates)
|
||||||
|
.WithOne(i => i.Playout)
|
||||||
|
.HasForeignKey(i => i.PlayoutId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(p => p.PlayoutHistory)
|
||||||
|
.WithOne(h => h.Playout)
|
||||||
|
.HasForeignKey(h => h.PlayoutId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class BlockConfiguration : IEntityTypeConfiguration<Block>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Block> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Block");
|
||||||
|
|
||||||
|
builder.HasIndex(b => b.Name)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
builder.HasMany(b => b.Items)
|
||||||
|
.WithOne(i => i.Block)
|
||||||
|
.HasForeignKey(i => i.BlockId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(b => b.TemplateItems)
|
||||||
|
.WithOne(i => i.Block)
|
||||||
|
.HasForeignKey(i => i.BlockId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(b => b.PlayoutHistory)
|
||||||
|
.WithOne(h => h.Block)
|
||||||
|
.HasForeignKey(h => h.BlockId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class BlockGroupConfiguration : IEntityTypeConfiguration<BlockGroup>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<BlockGroup> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("BlockGroup");
|
||||||
|
|
||||||
|
builder.HasIndex(b => b.Name)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
builder.HasMany(b => b.Blocks)
|
||||||
|
.WithOne(i => i.BlockGroup)
|
||||||
|
.HasForeignKey(i => i.BlockGroupId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class BlockItemConfiguration : IEntityTypeConfiguration<BlockItem>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<BlockItem> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("BlockItem");
|
||||||
|
|
||||||
|
builder.HasOne(i => i.Collection)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(i => i.CollectionId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder.HasOne(i => i.MediaItem)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(i => i.MediaItemId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder.HasOne(i => i.MultiCollection)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(i => i.MultiCollectionId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder.HasOne(i => i.SmartCollection)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(i => i.SmartCollectionId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class PlayoutHistoryConfiguration : IEntityTypeConfiguration<PlayoutHistory>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PlayoutHistory> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("PlayoutHistory");
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class PlayoutTemplateConfiguration : IEntityTypeConfiguration<PlayoutTemplate>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PlayoutTemplate> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("PlayoutTemplate");
|
||||||
|
|
||||||
|
builder.Property(t => t.DaysOfMonth)
|
||||||
|
.HasConversion<IntCollectionValueConverter, CollectionValueComparer<int>>();
|
||||||
|
|
||||||
|
builder.Property(t => t.MonthsOfYear)
|
||||||
|
.HasConversion<IntCollectionValueConverter, CollectionValueComparer<int>>();
|
||||||
|
|
||||||
|
builder.Property(t => t.DaysOfWeek)
|
||||||
|
.HasConversion<EnumCollectionJsonValueConverter<DayOfWeek>, CollectionValueComparer<DayOfWeek>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class TemplateConfiguration : IEntityTypeConfiguration<Template>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Template> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Template");
|
||||||
|
|
||||||
|
builder.HasIndex(b => b.Name)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
builder.HasMany(b => b.Items)
|
||||||
|
.WithOne(i => i.Template)
|
||||||
|
.HasForeignKey(i => i.TemplateId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(t => t.PlayoutTemplates)
|
||||||
|
.WithOne(t => t.Template)
|
||||||
|
.HasForeignKey(t => t.TemplateId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using ErsatzTV.Core.Domain.Scheduling;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Data.Configurations.Scheduling;
|
||||||
|
|
||||||
|
public class TemplateGroupConfiguration : IEntityTypeConfiguration<TemplateGroup>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<TemplateGroup> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("TemplateGroup");
|
||||||
|
|
||||||
|
builder.HasIndex(b => b.Name)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
builder.HasMany(b => b.Templates)
|
||||||
|
.WithOne(i => i.TemplateGroup)
|
||||||
|
.HasForeignKey(i => i.TemplateGroupId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user