mirror of https://github.com/ErsatzTV/ErsatzTV.git
6 changed files with 227 additions and 13 deletions
@ -0,0 +1,6 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.ProgramSchedules; |
||||||
|
|
||||||
|
public record CopyProgramSchedule |
||||||
|
(int ProgramScheduleId, string Name) : IRequest<Either<BaseError, ProgramScheduleViewModel>>; |
||||||
@ -0,0 +1,113 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Infrastructure.Data; |
||||||
|
using ErsatzTV.Infrastructure.Extensions; |
||||||
|
using Microsoft.EntityFrameworkCore; |
||||||
|
using Microsoft.EntityFrameworkCore.ChangeTracking; |
||||||
|
using static ErsatzTV.Application.ProgramSchedules.Mapper; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.ProgramSchedules; |
||||||
|
|
||||||
|
public class |
||||||
|
CopyProgramScheduleHandler : IRequestHandler<CopyProgramSchedule, Either<BaseError, ProgramScheduleViewModel>> |
||||||
|
{ |
||||||
|
private readonly IDbContextFactory<TvContext> _dbContextFactory; |
||||||
|
|
||||||
|
public CopyProgramScheduleHandler(IDbContextFactory<TvContext> dbContextFactory) => |
||||||
|
_dbContextFactory = dbContextFactory; |
||||||
|
|
||||||
|
public async Task<Either<BaseError, ProgramScheduleViewModel>> Handle( |
||||||
|
CopyProgramSchedule request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
try |
||||||
|
{ |
||||||
|
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); |
||||||
|
Validation<BaseError, ProgramSchedule> validation = await Validate(dbContext, request); |
||||||
|
return await validation.Apply(p => PerformCopy(dbContext, p, request, cancellationToken)); |
||||||
|
} |
||||||
|
catch (Exception ex) |
||||||
|
{ |
||||||
|
return BaseError.New(ex.Message); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<ProgramScheduleViewModel> PerformCopy( |
||||||
|
TvContext dbContext, |
||||||
|
ProgramSchedule schedule, |
||||||
|
CopyProgramSchedule request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
var clone = new ProgramSchedule(); |
||||||
|
await dbContext.AddAsync(clone, cancellationToken); |
||||||
|
|
||||||
|
clone.Name = request.Name; |
||||||
|
clone.RandomStartPoint = schedule.RandomStartPoint; |
||||||
|
clone.ShuffleScheduleItems = schedule.ShuffleScheduleItems; |
||||||
|
clone.TreatCollectionsAsShows = schedule.TreatCollectionsAsShows; |
||||||
|
clone.KeepMultiPartEpisodesTogether = schedule.KeepMultiPartEpisodesTogether; |
||||||
|
|
||||||
|
// no playouts, no alternates
|
||||||
|
clone.Playouts = new List<Playout>(); |
||||||
|
clone.ProgramScheduleAlternates = new List<ProgramScheduleAlternate>(); |
||||||
|
|
||||||
|
// clone all items
|
||||||
|
clone.Items = new List<ProgramScheduleItem>(); |
||||||
|
foreach (ProgramScheduleItem item in schedule.Items) |
||||||
|
{ |
||||||
|
PropertyValues itemValues = dbContext.Entry(item).CurrentValues.Clone(); |
||||||
|
itemValues["Id"] = 0; |
||||||
|
|
||||||
|
ProgramScheduleItem itemClone = item switch |
||||||
|
{ |
||||||
|
ProgramScheduleItemFlood => new ProgramScheduleItemFlood(), |
||||||
|
ProgramScheduleItemDuration => new ProgramScheduleItemDuration(), |
||||||
|
ProgramScheduleItemMultiple => new ProgramScheduleItemMultiple(), |
||||||
|
_ => new ProgramScheduleItemOne() |
||||||
|
}; |
||||||
|
|
||||||
|
await dbContext.AddAsync(itemClone, cancellationToken); |
||||||
|
dbContext.Entry(itemClone).CurrentValues.SetValues(itemValues); |
||||||
|
|
||||||
|
itemClone.ProgramScheduleId = 0; |
||||||
|
itemClone.ProgramSchedule = clone; |
||||||
|
} |
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken); |
||||||
|
|
||||||
|
return ProjectToViewModel(clone); |
||||||
|
} |
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, ProgramSchedule>> Validate( |
||||||
|
TvContext dbContext, |
||||||
|
CopyProgramSchedule request) => |
||||||
|
(await ScheduleMustExist(dbContext, request), await ValidateName(dbContext, request)) |
||||||
|
.Apply((programSchedule, _) => programSchedule); |
||||||
|
|
||||||
|
private static Task<Validation<BaseError, ProgramSchedule>> ScheduleMustExist( |
||||||
|
TvContext dbContext, |
||||||
|
CopyProgramSchedule request) => |
||||||
|
dbContext.ProgramSchedules |
||||||
|
.AsNoTracking() |
||||||
|
.Include(ps => ps.Items) |
||||||
|
.SelectOneAsync(p => p.Id, p => p.Id == request.ProgramScheduleId) |
||||||
|
.Map(o => o.ToValidation<BaseError>("Schedule does not exist.")); |
||||||
|
|
||||||
|
private static async Task<Validation<BaseError, string>> ValidateName( |
||||||
|
TvContext dbContext, |
||||||
|
CopyProgramSchedule request) |
||||||
|
{ |
||||||
|
List<string> allNames = await dbContext.ProgramSchedules |
||||||
|
.Map(ps => ps.Name) |
||||||
|
.ToListAsync(); |
||||||
|
|
||||||
|
Validation<BaseError, string> result1 = request.NotEmpty(c => c.Name) |
||||||
|
.Bind(_ => request.NotLongerThan(50)(c => c.Name)); |
||||||
|
|
||||||
|
var result2 = Optional(request.Name) |
||||||
|
.Where(name => !allNames.Contains(name)) |
||||||
|
.ToValidation<BaseError>("Schedule name must be unique"); |
||||||
|
|
||||||
|
return (result1, result2).Apply((_, _) => request.Name); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
@using ErsatzTV.Application.ProgramSchedules |
||||||
|
@implements IDisposable |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject ILogger<CopyScheduleDialog> Logger |
||||||
|
|
||||||
|
<MudDialog> |
||||||
|
<DialogContent> |
||||||
|
<EditForm Model="@_dummyModel" OnSubmit="@(_ => Submit())"> |
||||||
|
<MudContainer Class="mb-6"> |
||||||
|
<MudText> |
||||||
|
Enter a name for the new Schedule |
||||||
|
</MudText> |
||||||
|
</MudContainer> |
||||||
|
<MudTextField T="string" Label="New Schedule Name" |
||||||
|
@bind-Text="@_newName" |
||||||
|
Class="mb-6 mx-4"> |
||||||
|
</MudTextField> |
||||||
|
</EditForm> |
||||||
|
</DialogContent> |
||||||
|
<DialogActions> |
||||||
|
<MudButton OnClick="Cancel" ButtonType="ButtonType.Reset">Cancel</MudButton> |
||||||
|
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Submit"> |
||||||
|
Copy Schedule |
||||||
|
</MudButton> |
||||||
|
</DialogActions> |
||||||
|
</MudDialog> |
||||||
|
|
||||||
|
@code { |
||||||
|
private readonly CancellationTokenSource _cts = new(); |
||||||
|
|
||||||
|
[CascadingParameter] |
||||||
|
MudDialogInstance MudDialog { get; set; } |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int ProgramScheduleId { get; set; } |
||||||
|
|
||||||
|
private record DummyModel; |
||||||
|
|
||||||
|
private readonly DummyModel _dummyModel = new(); |
||||||
|
|
||||||
|
private string _newName; |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
_cts.Cancel(); |
||||||
|
_cts.Dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
private bool CanSubmit() => !string.IsNullOrWhiteSpace(_newName); |
||||||
|
|
||||||
|
private async Task Submit() |
||||||
|
{ |
||||||
|
if (!CanSubmit()) |
||||||
|
{ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
Either<BaseError, ProgramScheduleViewModel> maybeResult = |
||||||
|
await Mediator.Send(new CopyProgramSchedule(ProgramScheduleId, _newName), _cts.Token); |
||||||
|
|
||||||
|
maybeResult.Match( |
||||||
|
schedule => { MudDialog.Close(DialogResult.Ok(schedule)); }, |
||||||
|
error => |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Error copying Schedule: {Error}", error.Value); |
||||||
|
MudDialog.Close(DialogResult.Cancel()); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
private void Cancel(MouseEventArgs e) => MudDialog.Cancel(); |
||||||
|
} |
||||||
Loading…
Reference in new issue