Database redesign (#31)

* starting database redesign

* set season and episode numbers

* use datetimes in db (utc); update movie metadata

* get movie cards from new table

* copy show/episode metadata

* remove old movie metadata type

* rename new movie metadata type

* code cleanup

* start to remove old television classes

* remove old television tables from database

* fix playout building

* fix collection views

* fix show/season views

* clean up movie metadata table

* fix scanner tests

* add libraries ui

* code cleanup

* fix movie scanning/metadata

* add library scan button to ui

* delete library path from ui

* temp disable movie scanning

* remove orphan media items and prevent duplicate paths

* attach artwork to metadata

* fix split show/season display

* fix television artwork

* store year distinct from release date

* fix collections ui

* code cleanup

* add library paths from ui

* fix adding to collections from ui

* fix schedule items loading

* schedule editing works again

* remove some todos

* more cleanup

* fix unit tests

* fix episode sorting

* fix deleting show library paths

* remove unused class

* fix playout list in ui

* fix log viewer

* start to use version/file instead of statistics

* clean up old columns

* fix playout display (time zone)

* fix playback

* fix channel guide time zone

* cascade more deletes

* fix compiler warnings

* fix adding new seasons

* use artwork for channel logo

* clean cache folder on startup (move channel logos, delete everything else)

* log database migration

* update homepage docs for libraries

* fix adding new channel with logo

* fix episode numbers in epg
This commit is contained in:
Jason Dove
2021-02-28 17:48:01 +00:00
committed by GitHub
parent e25b9edd01
commit f392bab118
392 changed files with 52108 additions and 4079 deletions
@@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddEpisodeToCollection(int CollectionId, int EpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -0,0 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddEpisodeToCollectionHandler : MediatR.IRequestHandler<AddEpisodeToCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddEpisodeToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddEpisodeToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(_ => ApplyAddTelevisionEpisodeRequest(request))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddTelevisionEpisodeRequest(AddEpisodeToCollection request) =>
_mediaCollectionRepository.AddMediaItem(request.CollectionId, request.EpisodeId);
private async Task<Validation<BaseError, Unit>> Validate(AddEpisodeToCollection request) =>
(await CollectionMustExist(request), await ValidateEpisode(request))
.Apply((_, _) => Unit.Default);
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddEpisodeToCollection request) =>
_mediaCollectionRepository.Get(request.CollectionId)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Unit>> ValidateEpisode(AddEpisodeToCollection request) =>
LoadTelevisionEpisode(request)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Episode does not exist"));
private Task<Option<int>> LoadTelevisionEpisode(AddEpisodeToCollection request) =>
_televisionRepository.GetEpisode(request.EpisodeId).MapT(e => e.Id);
}
}
@@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddMovieToCollection(int CollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -0,0 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class AddMovieToCollectionHandler : MediatR.IRequestHandler<AddMovieToCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMovieRepository _movieRepository;
public AddMovieToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
IMovieRepository movieRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_movieRepository = movieRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddMovieToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(_ => ApplyAddMoviesRequest(request))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddMoviesRequest(AddMovieToCollection request) =>
_mediaCollectionRepository.AddMediaItem(request.CollectionId, request.MovieId);
private async Task<Validation<BaseError, Unit>> Validate(AddMovieToCollection request) =>
(await CollectionMustExist(request), await ValidateMovies(request))
.Apply((_, _) => Unit.Default);
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddMovieToCollection request) =>
_mediaCollectionRepository.GetCollectionWithItems(request.CollectionId)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Unit>> ValidateMovies(AddMovieToCollection request) =>
LoadMovie(request)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Movie does not exist"));
private Task<Option<Movie>> LoadMovie(AddMovieToCollection request) =>
_movieRepository.GetMovie(request.MovieId);
}
}
@@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddMovieToSimpleMediaCollection
(int MediaCollectionId, int MovieId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -1,62 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddMovieToSimpleMediaCollectionHandler : MediatR.IRequestHandler<AddMovieToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMovieRepository _movieRepository;
public AddMovieToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
IMovieRepository movieRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_movieRepository = movieRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddMovieToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddMoviesRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddMoviesRequest(RequestParameters parameters)
{
parameters.Collection.Movies.Add(parameters.MovieToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddMovieToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateMovies(request))
.Apply(
(simpleMediaCollectionToUpdate, movieToAdd) =>
new RequestParameters(simpleMediaCollectionToUpdate, movieToAdd));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddMovieToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, MovieMediaItem>> ValidateMovies(
AddMovieToSimpleMediaCollection request) =>
LoadMovie(request)
.Map(v => v.ToValidation<BaseError>("MovieMediaItem does not exist"));
private Task<Option<MovieMediaItem>> LoadMovie(AddMovieToSimpleMediaCollection request) =>
_movieRepository.GetMovie(request.MovieId);
private record RequestParameters(SimpleMediaCollection Collection, MovieMediaItem MovieToAdd);
}
}
@@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddSeasonToCollection(int CollectionId, int SeasonId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -0,0 +1,51 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class AddSeasonToCollectionHandler : MediatR.IRequestHandler<AddSeasonToCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddSeasonToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddSeasonToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(_ => ApplyAddTelevisionSeasonRequest(request))
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionSeasonRequest(AddSeasonToCollection request) =>
await _mediaCollectionRepository.AddMediaItem(request.CollectionId, request.SeasonId);
private async Task<Validation<BaseError, Unit>> Validate(AddSeasonToCollection request) =>
(await CollectionMustExist(request), await ValidateSeason(request))
.Apply((_, _) => Unit.Default);
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddSeasonToCollection request) =>
_mediaCollectionRepository.GetCollectionWithItems(request.CollectionId)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Unit>> ValidateSeason(AddSeasonToCollection request) =>
LoadTelevisionSeason(request)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Season does not exist"));
private Task<Option<Season>> LoadTelevisionSeason(
AddSeasonToCollection request) =>
_televisionRepository.GetSeason(request.SeasonId);
}
}
@@ -0,0 +1,7 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddShowToCollection(int CollectionId, int ShowId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -0,0 +1,50 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class AddShowToCollectionHandler : MediatR.IRequestHandler<AddShowToCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddShowToCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddShowToCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(_ => ApplyAddTelevisionShowRequest(request))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddTelevisionShowRequest(AddShowToCollection request)
=> _mediaCollectionRepository.AddMediaItem(request.CollectionId, request.ShowId);
private async Task<Validation<BaseError, Unit>> Validate(AddShowToCollection request) =>
(await CollectionMustExist(request), await ValidateShow(request))
.Apply((_, _) => Unit.Default);
private Task<Validation<BaseError, Unit>> CollectionMustExist(AddShowToCollection request) =>
_mediaCollectionRepository.GetCollectionWithItems(request.CollectionId)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Task<Validation<BaseError, Unit>> ValidateShow(AddShowToCollection request) =>
LoadTelevisionShow(request)
.MapT(_ => Unit.Default)
.Map(v => v.ToValidation<BaseError>("Show does not exist"));
private Task<Option<Show>> LoadTelevisionShow(AddShowToCollection request) =>
_televisionRepository.GetShow(request.ShowId);
}
}
@@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddTelevisionEpisodeToSimpleMediaCollection
(int MediaCollectionId, int TelevisionEpisodeId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -1,68 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddTelevisionEpisodeToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
AddTelevisionEpisodeToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddTelevisionEpisodeToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddTelevisionEpisodeToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionEpisodeRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionEpisodeRequest(RequestParameters parameters)
{
if (parameters.Collection.TelevisionEpisodes.All(s => s.Id != parameters.EpisodeToAdd.Id))
{
parameters.Collection.TelevisionEpisodes.Add(parameters.EpisodeToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddTelevisionEpisodeToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateEpisode(request))
.Apply(
(simpleMediaCollectionToUpdate, episode) =>
new RequestParameters(simpleMediaCollectionToUpdate, episode));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddTelevisionEpisodeToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, TelevisionEpisodeMediaItem>> ValidateEpisode(
AddTelevisionEpisodeToSimpleMediaCollection request) =>
LoadTelevisionEpisode(request)
.Map(v => v.ToValidation<BaseError>("TelevisionEpisode does not exist"));
private Task<Option<TelevisionEpisodeMediaItem>> LoadTelevisionEpisode(
AddTelevisionEpisodeToSimpleMediaCollection request) =>
_televisionRepository.GetEpisode(request.TelevisionEpisodeId);
private record RequestParameters(SimpleMediaCollection Collection, TelevisionEpisodeMediaItem EpisodeToAdd);
}
}
@@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddTelevisionSeasonToSimpleMediaCollection
(int MediaCollectionId, int TelevisionSeasonId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -1,68 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddTelevisionSeasonToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
AddTelevisionSeasonToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddTelevisionSeasonToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddTelevisionSeasonToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionSeasonRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionSeasonRequest(RequestParameters parameters)
{
if (parameters.Collection.TelevisionSeasons.All(s => s.Id != parameters.SeasonToAdd.Id))
{
parameters.Collection.TelevisionSeasons.Add(parameters.SeasonToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddTelevisionSeasonToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateSeason(request))
.Apply(
(simpleMediaCollectionToUpdate, season) =>
new RequestParameters(simpleMediaCollectionToUpdate, season));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddTelevisionSeasonToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, TelevisionSeason>> ValidateSeason(
AddTelevisionSeasonToSimpleMediaCollection request) =>
LoadTelevisionSeason(request)
.Map(v => v.ToValidation<BaseError>("TelevisionSeason does not exist"));
private Task<Option<TelevisionSeason>> LoadTelevisionSeason(
AddTelevisionSeasonToSimpleMediaCollection request) =>
_televisionRepository.GetSeason(request.TelevisionSeasonId);
private record RequestParameters(SimpleMediaCollection Collection, TelevisionSeason SeasonToAdd);
}
}
@@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record AddTelevisionShowToSimpleMediaCollection
(int MediaCollectionId, int TelevisionShowId) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -1,67 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
AddTelevisionShowToSimpleMediaCollectionHandler : MediatR.IRequestHandler<
AddTelevisionShowToSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly ITelevisionRepository _televisionRepository;
public AddTelevisionShowToSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository,
ITelevisionRepository televisionRepository)
{
_mediaCollectionRepository = mediaCollectionRepository;
_televisionRepository = televisionRepository;
}
public Task<Either<BaseError, Unit>> Handle(
AddTelevisionShowToSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(ApplyAddTelevisionShowRequest)
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyAddTelevisionShowRequest(RequestParameters parameters)
{
if (parameters.Collection.TelevisionShows.All(s => s.Id != parameters.ShowToAdd.Id))
{
parameters.Collection.TelevisionShows.Add(parameters.ShowToAdd);
await _mediaCollectionRepository.Update(parameters.Collection);
}
return Unit.Default;
}
private async Task<Validation<BaseError, RequestParameters>>
Validate(AddTelevisionShowToSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), await ValidateShow(request))
.Apply(
(simpleMediaCollectionToUpdate, show) =>
new RequestParameters(simpleMediaCollectionToUpdate, show));
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
AddTelevisionShowToSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Task<Validation<BaseError, TelevisionShow>> ValidateShow(
AddTelevisionShowToSimpleMediaCollection request) =>
LoadTelevisionShow(request)
.Map(v => v.ToValidation<BaseError>("TelevisionShow does not exist"));
private Task<Option<TelevisionShow>> LoadTelevisionShow(AddTelevisionShowToSimpleMediaCollection request) =>
_televisionRepository.GetShow(request.TelevisionShowId);
private record RequestParameters(SimpleMediaCollection Collection, TelevisionShow ShowToAdd);
}
}
@@ -0,0 +1,8 @@
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record CreateCollection(string Name) : IRequest<Either<BaseError, MediaCollectionViewModel>>;
}
@@ -12,36 +12,33 @@ using static LanguageExt.Prelude;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class CreateSimpleMediaCollectionHandler : IRequestHandler<CreateSimpleMediaCollection,
Either<BaseError, MediaCollectionViewModel>>
public class
CreateCollectionHandler : IRequestHandler<CreateCollection, Either<BaseError, MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public CreateSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public CreateCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, MediaCollectionViewModel>> Handle(
CreateSimpleMediaCollection request,
CreateCollection request,
CancellationToken cancellationToken) =>
Validate(request).MapT(PersistCollection).Bind(v => v.ToEitherAsync());
private Task<MediaCollectionViewModel> PersistCollection(SimpleMediaCollection c) =>
private Task<MediaCollectionViewModel> PersistCollection(Collection c) =>
_mediaCollectionRepository.Add(c).Map(ProjectToViewModel);
private Task<Validation<BaseError, SimpleMediaCollection>> Validate(CreateSimpleMediaCollection request) =>
private Task<Validation<BaseError, Collection>> Validate(CreateCollection request) =>
ValidateName(request).MapT(
name => new SimpleMediaCollection
name => new Collection
{
Name = name,
Movies = new List<MovieMediaItem>(),
TelevisionShows = new List<TelevisionShow>(),
TelevisionEpisodes = new List<TelevisionEpisodeMediaItem>(),
TelevisionSeasons = new List<TelevisionSeason>()
MediaItems = new List<MediaItem>()
});
private async Task<Validation<BaseError, string>> ValidateName(CreateSimpleMediaCollection createCollection)
private async Task<Validation<BaseError, string>> ValidateName(CreateCollection createCollection)
{
List<string> allNames = await _mediaCollectionRepository.GetSimpleMediaCollections()
List<string> allNames = await _mediaCollectionRepository.GetAll()
.Map(list => list.Map(c => c.Name).ToList());
Validation<BaseError, string> result1 = createCollection.NotEmpty(c => c.Name)
@@ -49,7 +46,7 @@ namespace ErsatzTV.Application.MediaCollections.Commands
var result2 = Optional(createCollection.Name)
.Filter(name => !allNames.Contains(name))
.ToValidation<BaseError>("Media collection name must be unique");
.ToValidation<BaseError>("Collection name must be unique");
return (result1, result2).Apply((_, _) => createCollection.Name);
}
@@ -1,9 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record CreateSimpleMediaCollection
(string Name) : IRequest<Either<BaseError, MediaCollectionViewModel>>;
}
@@ -5,5 +5,5 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record DeleteSimpleMediaCollection(int SimpleMediaCollectionId) : IRequest<Either<BaseError, Task>>;
public record DeleteCollection(int CollectionId) : IRequest<Either<BaseError, Task>>;
}
@@ -7,28 +7,27 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
DeleteSimpleMediaCollectionHandler : IRequestHandler<DeleteSimpleMediaCollection, Either<BaseError, Task>>
public class DeleteCollectionHandler : IRequestHandler<DeleteCollection, Either<BaseError, Task>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public DeleteSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public DeleteCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public async Task<Either<BaseError, Task>> Handle(
DeleteSimpleMediaCollection request,
DeleteCollection request,
CancellationToken cancellationToken) =>
(await SimpleMediaCollectionMustExist(request))
(await CollectionMustExist(request))
.Map(DoDeletion)
.ToEither<Task>();
private Task DoDeletion(int mediaCollectionId) => _mediaCollectionRepository.Delete(mediaCollectionId);
private async Task<Validation<BaseError, int>> SimpleMediaCollectionMustExist(
DeleteSimpleMediaCollection deleteMediaCollection) =>
(await _mediaCollectionRepository.GetSimpleMediaCollection(deleteMediaCollection.SimpleMediaCollectionId))
private async Task<Validation<BaseError, int>> CollectionMustExist(
DeleteCollection deleteMediaCollection) =>
(await _mediaCollectionRepository.Get(deleteMediaCollection.CollectionId))
.ToValidation<BaseError>(
$"SimpleMediaCollection {deleteMediaCollection.SimpleMediaCollectionId} does not exist.")
$"Collection {deleteMediaCollection.CollectionId} does not exist.")
.Map(c => c.Id);
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record RemoveItemsFromCollection(int MediaCollectionId) : MediatR.IRequest<Either<BaseError, Unit>>
{
public List<int> MediaItemIds { get; set; } = new();
}
}
@@ -0,0 +1,51 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
RemoveItemsFromCollectionHandler : MediatR.IRequestHandler<RemoveItemsFromCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public RemoveItemsFromCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
RemoveItemsFromCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddTelevisionEpisodeRequest(
RemoveItemsFromCollection request,
Collection collection)
{
var itemsToRemove = collection.MediaItems
.Filter(m => request.MediaItemIds.Contains(m.Id))
.ToList();
itemsToRemove.ForEach(m => collection.MediaItems.Remove(m));
return itemsToRemove.Any()
? _mediaCollectionRepository.Update(collection).ToUnit()
: Task.FromResult(Unit.Default);
}
private Task<Validation<BaseError, Collection>> Validate(
RemoveItemsFromCollection request) =>
CollectionMustExist(request);
private Task<Validation<BaseError, Collection>> CollectionMustExist(
RemoveItemsFromCollection updateCollection) =>
_mediaCollectionRepository.GetCollectionWithItems(updateCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
}
}
@@ -1,15 +0,0 @@
using System.Collections.Generic;
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record RemoveItemsFromSimpleMediaCollection
(int MediaCollectionId) : MediatR.IRequest<Either<BaseError, Unit>>
{
public List<int> MovieIds { get; set; } = new();
public List<int> TelevisionShowIds { get; set; } = new();
public List<int> TelevisionSeasonIds { get; set; } = new();
public List<int> TelevisionEpisodeIds { get; set; } = new();
}
}
@@ -1,74 +0,0 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
RemoveItemsFromSimpleMediaCollectionHandler : MediatR.IRequestHandler<
RemoveItemsFromSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public RemoveItemsFromSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
RemoveItemsFromSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection))
.Bind(v => v.ToEitherAsync());
private Task<Unit> ApplyAddTelevisionEpisodeRequest(
RemoveItemsFromSimpleMediaCollection request,
SimpleMediaCollection collection)
{
var moviesToRemove = collection.Movies
.Filter(m => request.MovieIds.Contains(m.Id))
.ToList();
moviesToRemove.ForEach(m => collection.Movies.Remove(m));
var showsToRemove = collection.TelevisionShows
.Filter(s => request.TelevisionShowIds.Contains(s.Id))
.ToList();
showsToRemove.ForEach(s => collection.TelevisionShows.Remove(s));
var seasonsToRemove = collection.TelevisionSeasons
.Filter(s => request.TelevisionSeasonIds.Contains(s.Id))
.ToList();
seasonsToRemove.ForEach(s => collection.TelevisionSeasons.Remove(s));
var episodesToRemove = collection.TelevisionEpisodes
.Filter(e => request.TelevisionEpisodeIds.Contains(e.Id))
.ToList();
episodesToRemove.ForEach(e => collection.TelevisionEpisodes.Remove(e));
if (moviesToRemove.Any() || showsToRemove.Any() || seasonsToRemove.Any() || episodesToRemove.Any())
{
return _mediaCollectionRepository.Update(collection).ToUnit();
}
return Task.FromResult(Unit.Default);
}
private Task<Validation<BaseError, SimpleMediaCollection>> Validate(
RemoveItemsFromSimpleMediaCollection request) =>
SimpleMediaCollectionMustExist(request);
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
RemoveItemsFromSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
}
}
@@ -0,0 +1,8 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record UpdateCollection
(int CollectionId, string Name) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -0,0 +1,45 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class UpdateCollectionHandler : MediatR.IRequestHandler<UpdateCollection, Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public UpdateCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
UpdateCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(c => ApplyUpdateRequest(c, request))
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyUpdateRequest(Collection c, UpdateCollection update)
{
c.Name = update.Name;
await _mediaCollectionRepository.Update(c);
return Unit.Default;
}
private async Task<Validation<BaseError, Collection>>
Validate(UpdateCollection request) =>
(await CollectionMustExist(request), ValidateName(request))
.Apply((collectionToUpdate, _) => collectionToUpdate);
private Task<Validation<BaseError, Collection>> CollectionMustExist(
UpdateCollection updateCollection) =>
_mediaCollectionRepository.Get(updateCollection.CollectionId)
.Map(v => v.ToValidation<BaseError>("Collection does not exist."));
private Validation<BaseError, string> ValidateName(UpdateCollection updateSimpleMediaCollection) =>
updateSimpleMediaCollection.NotEmpty(c => c.Name)
.Bind(_ => updateSimpleMediaCollection.NotLongerThan(50)(c => c.Name));
}
}
@@ -1,8 +0,0 @@
using ErsatzTV.Core;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public record UpdateSimpleMediaCollection
(int MediaCollectionId, string Name) : MediatR.IRequest<Either<BaseError, Unit>>;
}
@@ -1,47 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt;
namespace ErsatzTV.Application.MediaCollections.Commands
{
public class
UpdateSimpleMediaCollectionHandler : MediatR.IRequestHandler<UpdateSimpleMediaCollection,
Either<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public UpdateSimpleMediaCollectionHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
UpdateSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(c => ApplyUpdateRequest(c, request))
.Bind(v => v.ToEitherAsync());
private async Task<Unit> ApplyUpdateRequest(SimpleMediaCollection c, UpdateSimpleMediaCollection update)
{
c.Name = update.Name;
await _mediaCollectionRepository.Update(c);
return Unit.Default;
}
private async Task<Validation<BaseError, SimpleMediaCollection>>
Validate(UpdateSimpleMediaCollection request) =>
(await SimpleMediaCollectionMustExist(request), ValidateName(request))
.Apply((simpleMediaCollectionToUpdate, _) => simpleMediaCollectionToUpdate);
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
UpdateSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollection(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
private Validation<BaseError, string> ValidateName(UpdateSimpleMediaCollection updateSimpleMediaCollection) =>
updateSimpleMediaCollection.NotEmpty(c => c.Name)
.Bind(_ => updateSimpleMediaCollection.NotLongerThan(50)(c => c.Name));
}
}
@@ -4,7 +4,7 @@ namespace ErsatzTV.Application.MediaCollections
{
internal static class Mapper
{
internal static MediaCollectionViewModel ProjectToViewModel(MediaCollection mediaCollection) =>
new(mediaCollection.Id, mediaCollection.Name);
internal static MediaCollectionViewModel ProjectToViewModel(Collection collection) =>
new(collection.Id, collection.Name);
}
}
@@ -3,5 +3,5 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetAllMediaCollections : IRequest<List<MediaCollectionViewModel>>;
public record GetAllCollections : IRequest<List<MediaCollectionViewModel>>;
}
@@ -9,15 +9,15 @@ using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class GetAllMediaCollectionsHandler : IRequestHandler<GetAllMediaCollections, List<MediaCollectionViewModel>>
public class GetAllCollectionsHandler : IRequestHandler<GetAllCollections, List<MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetAllMediaCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public GetAllCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<List<MediaCollectionViewModel>> Handle(
GetAllMediaCollections request,
GetAllCollections request,
CancellationToken cancellationToken) =>
_mediaCollectionRepository.GetAll().Map(list => list.Map(ProjectToViewModel).ToList());
}
@@ -1,7 +0,0 @@
using System.Collections.Generic;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetAllSimpleMediaCollections : IRequest<List<MediaCollectionViewModel>>;
}
@@ -1,25 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErsatzTV.Core.Interfaces.Repositories;
using MediatR;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class
GetAllSimpleMediaCollectionsHandler : IRequestHandler<GetAllSimpleMediaCollections,
List<MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetAllSimpleMediaCollectionsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public async Task<List<MediaCollectionViewModel>> Handle(
GetAllSimpleMediaCollections request,
CancellationToken cancellationToken) =>
(await _mediaCollectionRepository.GetSimpleMediaCollections()).Map(ProjectToViewModel).ToList();
}
}
@@ -0,0 +1,7 @@
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetCollectionById(int Id) : IRequest<Option<MediaCollectionViewModel>>;
}
@@ -8,18 +8,18 @@ using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class
GetSimpleMediaCollectionByIdHandler : IRequestHandler<GetSimpleMediaCollectionById,
GetCollectionByIdHandler : IRequestHandler<GetCollectionById,
Option<MediaCollectionViewModel>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetSimpleMediaCollectionByIdHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public GetCollectionByIdHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Option<MediaCollectionViewModel>> Handle(
GetSimpleMediaCollectionById request,
GetCollectionById request,
CancellationToken cancellationToken) =>
_mediaCollectionRepository.GetSimpleMediaCollection(request.Id)
_mediaCollectionRepository.Get(request.Id)
.MapT(ProjectToViewModel);
}
}
@@ -5,5 +5,5 @@ using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetSimpleMediaCollectionItems(int Id) : IRequest<Option<IEnumerable<MediaItemViewModel>>>;
public record GetCollectionItems(int Id) : IRequest<Option<IEnumerable<MediaItemViewModel>>>;
}
@@ -9,18 +9,18 @@ using static ErsatzTV.Application.MediaItems.Mapper;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public class GetSimpleMediaCollectionItemsHandler : IRequestHandler<GetSimpleMediaCollectionItems,
public class GetCollectionItemsHandler : IRequestHandler<GetCollectionItems,
Option<IEnumerable<MediaItemViewModel>>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public GetSimpleMediaCollectionItemsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
public GetCollectionItemsHandler(IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Option<IEnumerable<MediaItemViewModel>>> Handle(
GetSimpleMediaCollectionItems request,
GetCollectionItems request,
CancellationToken cancellationToken) =>
_mediaCollectionRepository.GetSimpleMediaCollectionItems(request.Id)
_mediaCollectionRepository.GetItems(request.Id)
.MapT(mediaItems => mediaItems.Map(ProjectToViewModel));
}
}
@@ -1,7 +0,0 @@
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.MediaCollections.Queries
{
public record GetSimpleMediaCollectionById(int Id) : IRequest<Option<MediaCollectionViewModel>>;
}