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
|
||||
- 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
|
||||
- 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
|
||||
- Fix error loading path replacements when using MySql
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<InformationalVersion>develop</InformationalVersion>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||
</PropertyGroup>
|
||||
</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/=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/=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_005Cqueries/@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 IFFmpegSegmenterService _ffmpegSegmenterService;
|
||||
private readonly IPlayoutBuilder _playoutBuilder;
|
||||
private readonly IBlockPlayoutBuilder _blockPlayoutBuilder;
|
||||
private readonly IExternalJsonPlayoutBuilder _externalJsonPlayoutBuilder;
|
||||
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
|
||||
|
||||
public BuildPlayoutHandler(
|
||||
IClient client,
|
||||
IDbContextFactory<TvContext> dbContextFactory,
|
||||
IPlayoutBuilder playoutBuilder,
|
||||
IBlockPlayoutBuilder blockPlayoutBuilder,
|
||||
IExternalJsonPlayoutBuilder externalJsonPlayoutBuilder,
|
||||
IFFmpegSegmenterService ffmpegSegmenterService,
|
||||
IEntityLocker entityLocker,
|
||||
ChannelWriter<IBackgroundServiceRequest> workerChannel)
|
||||
@@ -35,6 +39,8 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
_client = client;
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_playoutBuilder = playoutBuilder;
|
||||
_blockPlayoutBuilder = blockPlayoutBuilder;
|
||||
_externalJsonPlayoutBuilder = externalJsonPlayoutBuilder;
|
||||
_ffmpegSegmenterService = ffmpegSegmenterService;
|
||||
_entityLocker = entityLocker;
|
||||
_workerChannel = workerChannel;
|
||||
@@ -59,7 +65,20 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
|
||||
{
|
||||
_entityLocker.LockPlayout(playout.Id);
|
||||
|
||||
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
|
||||
// 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
|
||||
.Include(p => p.Channel)
|
||||
.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)
|
||||
.ThenInclude(fgi => fgi.EnumeratorState)
|
||||
.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)
|
||||
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Flood);
|
||||
|
||||
public record CreateBlockPlayout(int ChannelId)
|
||||
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.Block);
|
||||
|
||||
public record CreateExternalJsonPlayout(int ChannelId, string ExternalJsonFile)
|
||||
: CreatePlayout(ChannelId, ProgramSchedulePlayoutType.ExternalJson);
|
||||
|
||||
@@ -25,9 +25,7 @@ public class DeletePlayoutHandler : IRequestHandler<DeletePlayout, Either<BaseEr
|
||||
_localFileSystem = localFileSystem;
|
||||
}
|
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle(
|
||||
DeletePlayout request,
|
||||
CancellationToken cancellationToken)
|
||||
public async Task<Either<BaseError, Unit>> Handle(DeletePlayout request, CancellationToken cancellationToken)
|
||||
{
|
||||
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ public class GetProgramScheduleItemsHandler :
|
||||
.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(
|
||||
Option<ProgramSchedule> maybeProgramSchedule,
|
||||
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
|
||||
{
|
||||
@@ -14,5 +16,7 @@ public class Playout
|
||||
public PlayoutAnchor Anchor { get; set; }
|
||||
public List<PlayoutProgramScheduleAnchor> ProgramScheduleAnchors { 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; }
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ public enum ProgramSchedulePlayoutType
|
||||
{
|
||||
None = 0,
|
||||
Flood = 1,
|
||||
Block = 2,
|
||||
|
||||
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.Filler;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling;
|
||||
|
||||
@@ -12,6 +13,46 @@ public class CollectionKey : Record<CollectionKey>
|
||||
public int? MediaItemId { 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) =>
|
||||
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)
|
||||
{
|
||||
if (playout.ProgramSchedulePlayoutType is ProgramSchedulePlayoutType.ExternalJson)
|
||||
if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Flood)
|
||||
{
|
||||
_logger.LogDebug(
|
||||
"Skipping external json playout build on channel {Number} - {Name}",
|
||||
_logger.LogWarning(
|
||||
"Skipping playout build with type {Type} on channel {Number} - {Name}",
|
||||
playout.ProgramSchedulePlayoutType,
|
||||
playout.Channel.Number,
|
||||
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>
|
||||
<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" />
|
||||
</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
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.0")
|
||||
.HasAnnotation("ProductVersion", "8.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||
@@ -1820,6 +1820,225 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -3634,6 +3853,124 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
|
||||
@@ -4353,9 +4690,13 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
|
||||
b.Navigation("Items");
|
||||
|
||||
b.Navigation("PlayoutHistory");
|
||||
|
||||
b.Navigation("ProgramScheduleAlternates");
|
||||
|
||||
b.Navigation("ProgramScheduleAnchors");
|
||||
|
||||
b.Navigation("Templates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b =>
|
||||
@@ -4367,6 +4708,32 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Navigation("Actors");
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.24" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
|
||||
<PackageReference Include="Dapper" Version="2.1.28" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
|
||||
</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)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||
{
|
||||
@@ -715,7 +715,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("SongMetadataId");
|
||||
|
||||
b.ToTable("Genre");
|
||||
b.ToTable("Genre", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.JellyfinCollection", b =>
|
||||
@@ -1168,7 +1168,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("ArtistMetadataId");
|
||||
|
||||
b.ToTable("Mood");
|
||||
b.ToTable("Mood", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b =>
|
||||
@@ -1276,7 +1276,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("SmartCollectionId");
|
||||
|
||||
b.ToTable("MultiCollectionSmartItem");
|
||||
b.ToTable("MultiCollectionSmartItem", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoArtist", b =>
|
||||
@@ -1295,7 +1295,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("MusicVideoMetadataId");
|
||||
|
||||
b.ToTable("MusicVideoArtist");
|
||||
b.ToTable("MusicVideoArtist", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MusicVideoMetadata", b =>
|
||||
@@ -1818,6 +1818,225 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -2055,7 +2274,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("ArtistMetadataId");
|
||||
|
||||
b.ToTable("Style");
|
||||
b.ToTable("Style", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Subtitle", b =>
|
||||
@@ -2203,7 +2422,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("SongMetadataId");
|
||||
|
||||
b.ToTable("Tag");
|
||||
b.ToTable("Tag", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.TraktList", b =>
|
||||
@@ -2344,7 +2563,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("EmbyLibraryId");
|
||||
|
||||
b.ToTable("EmbyPathInfo");
|
||||
b.ToTable("EmbyPathInfo", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Jellyfin.JellyfinPathInfo", b =>
|
||||
@@ -2366,7 +2585,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.HasIndex("JellyfinLibraryId");
|
||||
|
||||
b.ToTable("JellyfinPathInfo");
|
||||
b.ToTable("JellyfinPathInfo", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EmbyLibrary", b =>
|
||||
@@ -3326,7 +3545,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.HasForeignKey("ProgramScheduleId")
|
||||
.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")
|
||||
.HasColumnType("INTEGER");
|
||||
@@ -3356,7 +3575,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b1.WithOwner()
|
||||
.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")
|
||||
.HasColumnType("INTEGER");
|
||||
@@ -3439,7 +3658,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.HasForeignKey("SmartCollectionId")
|
||||
.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")
|
||||
.HasColumnType("INTEGER");
|
||||
@@ -3485,7 +3704,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.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")
|
||||
.HasColumnType("INTEGER");
|
||||
@@ -3632,6 +3851,124 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.HasOne("ErsatzTV.Core.Domain.Season", "Season")
|
||||
@@ -4351,9 +4688,13 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
|
||||
b.Navigation("Items");
|
||||
|
||||
b.Navigation("PlayoutHistory");
|
||||
|
||||
b.Navigation("ProgramScheduleAlternates");
|
||||
|
||||
b.Navigation("ProgramScheduleAnchors");
|
||||
|
||||
b.Navigation("Templates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ProgramSchedule", b =>
|
||||
@@ -4365,6 +4706,32 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
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 =>
|
||||
{
|
||||
b.Navigation("Actors");
|
||||
|
||||
@@ -34,5 +34,15 @@ public class PlayoutConfiguration : IEntityTypeConfiguration<Playout>
|
||||
.WithOne(i => i.Playout)
|
||||
.HasForeignKey(i => i.PlayoutId)
|
||||
.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