mirror of https://github.com/ErsatzTV/ErsatzTV.git
46 changed files with 2117 additions and 677 deletions
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries |
||||
{ |
||||
public record GetSimpleMediaCollectionCards |
||||
(int Id) : IRequest<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>>; |
||||
} |
||||
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using static ErsatzTV.Application.MediaCards.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards.Queries |
||||
{ |
||||
public class GetSimpleMediaCollectionCardsHandler : IRequestHandler<GetSimpleMediaCollectionCards, |
||||
Either<BaseError, SimpleMediaCollectionCardResultsViewModel>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public GetSimpleMediaCollectionCardsHandler(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public async Task<Either<BaseError, SimpleMediaCollectionCardResultsViewModel>> Handle( |
||||
GetSimpleMediaCollectionCards request, |
||||
CancellationToken cancellationToken) => |
||||
(await _mediaCollectionRepository.GetSimpleMediaCollectionWithItemsUntracked(request.Id)) |
||||
.ToEither(BaseError.New("Unable to load collection")) |
||||
.Map(ProjectToViewModel); |
||||
} |
||||
} |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record SimpleMediaCollectionCardResultsViewModel( |
||||
string Name, |
||||
List<MovieCardViewModel> MovieCards, |
||||
List<TelevisionShowCardViewModel> ShowCards, |
||||
List<TelevisionSeasonCardViewModel> SeasonCards, |
||||
List<TelevisionEpisodeCardViewModel> EpisodeCards); |
||||
} |
||||
@ -1,11 +1,17 @@
@@ -1,11 +1,17 @@
|
||||
namespace ErsatzTV.Application.MediaCards |
||||
{ |
||||
public record TelevisionSeasonCardViewModel |
||||
(int TelevisionSeasonId, string Title, string Subtitle, string SortTitle, string Poster, string Placeholder) : MediaCardViewModel( |
||||
Title, |
||||
Subtitle, |
||||
SortTitle, |
||||
Poster) |
||||
( |
||||
int TelevisionSeasonId, |
||||
string Title, |
||||
string Subtitle, |
||||
string SortTitle, |
||||
string Poster, |
||||
string Placeholder) : MediaCardViewModel( |
||||
Title, |
||||
Subtitle, |
||||
SortTitle, |
||||
Poster) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
@ -1,9 +0,0 @@
@@ -1,9 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddItemsToSimpleMediaCollection |
||||
(int MediaCollectionId, List<int> ItemIds) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddMoviesToSimpleMediaCollection |
||||
(int MediaCollectionId, List<int> MovieIds) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record AddTelevisionShowToSimpleMediaCollection |
||||
(int MediaCollectionId, int TelevisionShowId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||
} |
||||
@ -0,0 +1,67 @@
@@ -0,0 +1,67 @@
|
||||
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); |
||||
} |
||||
} |
||||
@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Application.MediaItems; |
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public record ReplaceSimpleMediaCollectionItems |
||||
(int MediaCollectionId, List<int> MediaItemIds) : IRequest<Either<BaseError, List<MediaItemViewModel>>>; |
||||
} |
||||
@ -1,65 +0,0 @@
@@ -1,65 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Application.MediaItems; |
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using LanguageExt.UnsafeValueAccess; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Commands |
||||
{ |
||||
public class ReplaceSimpleMediaCollectionItemsHandler : IRequestHandler<ReplaceSimpleMediaCollectionItems, |
||||
Either<BaseError, List<MediaItemViewModel>>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
private readonly IMediaItemRepository _mediaItemRepository; |
||||
|
||||
public ReplaceSimpleMediaCollectionItemsHandler( |
||||
IMediaCollectionRepository mediaCollectionRepository, |
||||
IMediaItemRepository mediaItemRepository) |
||||
{ |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
_mediaItemRepository = mediaItemRepository; |
||||
} |
||||
|
||||
public Task<Either<BaseError, List<MediaItemViewModel>>> Handle( |
||||
ReplaceSimpleMediaCollectionItems request, |
||||
CancellationToken cancellationToken) => |
||||
Validate(request) |
||||
.MapT(mediaItems => PersistItems(request, mediaItems)) |
||||
.Bind(v => v.ToEitherAsync()); |
||||
|
||||
private async Task<List<MediaItemViewModel>> PersistItems( |
||||
ReplaceSimpleMediaCollectionItems request, |
||||
List<MediaItem> mediaItems) |
||||
{ |
||||
await _mediaCollectionRepository.ReplaceItems(request.MediaCollectionId, mediaItems); |
||||
return mediaItems.Map(MediaItems.Mapper.ProjectToViewModel).ToList(); |
||||
} |
||||
|
||||
private Task<Validation<BaseError, List<MediaItem>>> Validate(ReplaceSimpleMediaCollectionItems request) => |
||||
MediaCollectionMustExist(request).BindT(_ => MediaItemsMustExist(request)); |
||||
|
||||
private async Task<Validation<BaseError, SimpleMediaCollection>> MediaCollectionMustExist( |
||||
ReplaceSimpleMediaCollectionItems request) => |
||||
(await _mediaCollectionRepository.GetSimpleMediaCollection(request.MediaCollectionId)) |
||||
.ToValidation<BaseError>("[MediaCollectionId] does not exist."); |
||||
|
||||
private async Task<Validation<BaseError, List<MediaItem>>> MediaItemsMustExist( |
||||
ReplaceSimpleMediaCollectionItems replaceItems) |
||||
{ |
||||
var allMediaItems = (await replaceItems.MediaItemIds.Map(i => _mediaItemRepository.Get(i)).Sequence()) |
||||
.ToList(); |
||||
if (allMediaItems.Any(x => x.IsNone)) |
||||
{ |
||||
return BaseError.New("[MediaItemId] does not exist"); |
||||
} |
||||
|
||||
return allMediaItems.Sequence().ValueUnsafe().ToList(); |
||||
} |
||||
} |
||||
} |
||||
@ -1,4 +1,10 @@
@@ -1,4 +1,10 @@
|
||||
namespace ErsatzTV.Application.MediaCollections |
||||
using ErsatzTV.Application.MediaCards; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections |
||||
{ |
||||
public record MediaCollectionViewModel(int Id, string Name); |
||||
public record MediaCollectionViewModel(int Id, string Name) : MediaCardViewModel( |
||||
Name, |
||||
string.Empty, |
||||
Name, |
||||
string.Empty); |
||||
} |
||||
|
||||
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public record GetMediaCollectionSummaries(string SearchString) : IRequest<List<MediaCollectionSummaryViewModel>>; |
||||
} |
||||
@ -1,27 +0,0 @@
@@ -1,27 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using static ErsatzTV.Application.MediaCollections.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public class |
||||
GetMediaCollectionSummariesHandler : IRequestHandler<GetMediaCollectionSummaries, |
||||
List<MediaCollectionSummaryViewModel>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public GetMediaCollectionSummariesHandler(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public Task<List<MediaCollectionSummaryViewModel>> Handle( |
||||
GetMediaCollectionSummaries request, |
||||
CancellationToken cancellationToken) => |
||||
_mediaCollectionRepository.GetSummaries(request.SearchString) |
||||
.Map(list => list.Map(ProjectToViewModel).ToList()); |
||||
} |
||||
} |
||||
@ -1,11 +0,0 @@
@@ -1,11 +0,0 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using ErsatzTV.Application.MediaItems; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public record GetSimpleMediaCollectionWithItemsById |
||||
(int Id) : IRequest<Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>>; |
||||
} |
||||
@ -1,37 +0,0 @@
@@ -1,37 +0,0 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Application.MediaItems; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using MediatR; |
||||
using static LanguageExt.Prelude; |
||||
using static ErsatzTV.Application.MediaCollections.Mapper; |
||||
using static ErsatzTV.Application.MediaItems.Mapper; |
||||
|
||||
namespace ErsatzTV.Application.MediaCollections.Queries |
||||
{ |
||||
public class GetSimpleMediaCollectionWithItemsByIdHandler : IRequestHandler<GetSimpleMediaCollectionWithItemsById, |
||||
Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>> |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public GetSimpleMediaCollectionWithItemsByIdHandler(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public async Task<Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>> Handle( |
||||
GetSimpleMediaCollectionWithItemsById request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
Option<SimpleMediaCollection> maybeCollection = |
||||
await _mediaCollectionRepository.GetSimpleMediaCollectionWithItems(request.Id); |
||||
|
||||
return maybeCollection.Match<Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>>>( |
||||
c => Tuple(ProjectToViewModel(c), c.Items.Map(ProjectToSearchViewModel).ToList()), |
||||
None); |
||||
} |
||||
} |
||||
} |
||||
@ -1,8 +1,11 @@
@@ -1,8 +1,11 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class MovieMediaItem : MediaItem |
||||
{ |
||||
public int MetadataId { get; set; } |
||||
public MovieMetadata Metadata { get; set; } |
||||
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; } |
||||
} |
||||
} |
||||
|
||||
@ -1,9 +1,12 @@
@@ -1,9 +1,12 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
using System.Collections.Generic; |
||||
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class TelevisionEpisodeMediaItem : MediaItem |
||||
{ |
||||
public int SeasonId { get; set; } |
||||
public TelevisionSeason Season { get; set; } |
||||
public TelevisionEpisodeMetadata Metadata { get; set; } |
||||
public List<SimpleMediaCollection> SimpleMediaCollections { get; set; } |
||||
} |
||||
} |
||||
|
||||
@ -1,8 +0,0 @@
@@ -1,8 +0,0 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public class TelevisionMediaCollection : MediaCollection |
||||
{ |
||||
public string ShowTitle { get; set; } |
||||
public int? SeasonNumber { get; set; } |
||||
} |
||||
} |
||||
@ -1,63 +0,0 @@
@@ -1,63 +0,0 @@
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading.Tasks; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Metadata; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
using static LanguageExt.Prelude; |
||||
|
||||
namespace ErsatzTV.Core.Metadata |
||||
{ |
||||
public class SmartCollectionBuilder : ISmartCollectionBuilder |
||||
{ |
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||
|
||||
public SmartCollectionBuilder(IMediaCollectionRepository mediaCollectionRepository) => |
||||
_mediaCollectionRepository = mediaCollectionRepository; |
||||
|
||||
public async Task<bool> RefreshSmartCollections(TelevisionEpisodeMediaItem mediaItem) |
||||
{ |
||||
var results = new List<bool>(); |
||||
|
||||
foreach (TelevisionMediaCollection collection in GetTelevisionCollections(mediaItem)) |
||||
{ |
||||
results.Add(await _mediaCollectionRepository.InsertOrIgnore(collection)); |
||||
} |
||||
|
||||
return results.Any(identity); |
||||
} |
||||
|
||||
private IEnumerable<TelevisionMediaCollection> GetTelevisionCollections(TelevisionEpisodeMediaItem mediaItem) |
||||
{ |
||||
var televisionMediaItems = new List<TelevisionEpisodeMediaItem> { mediaItem }; |
||||
|
||||
IEnumerable<TelevisionMediaCollection> televisionShowCollections = televisionMediaItems |
||||
.Map(c => c.Metadata.Title) |
||||
.Distinct().Map( |
||||
t => new TelevisionMediaCollection |
||||
{ |
||||
Name = $"{t} - All Seasons", |
||||
ShowTitle = t, |
||||
SeasonNumber = null |
||||
}); |
||||
|
||||
IEnumerable<TelevisionMediaCollection> televisionShowSeasonCollections = televisionMediaItems |
||||
.Map(c => new { c.Metadata.Title, c.Metadata.Season }).Distinct() |
||||
.Map( |
||||
ts => |
||||
{ |
||||
return Optional(ts.Season).Map( |
||||
sn => new TelevisionMediaCollection |
||||
{ |
||||
Name = $"{ts.Title} - Season {sn:00}", |
||||
ShowTitle = ts.Title, |
||||
SeasonNumber = sn |
||||
}); |
||||
}) |
||||
.Sequence().Flatten(); |
||||
|
||||
return Seq(televisionShowCollections, televisionShowSeasonCollections).Flatten(); |
||||
} |
||||
} |
||||
} |
||||
@ -1,17 +0,0 @@
@@ -1,17 +0,0 @@
|
||||
using ErsatzTV.Core.Domain; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Data.Configurations |
||||
{ |
||||
public class TelevisionMediaCollectionConfiguration : IEntityTypeConfiguration<TelevisionMediaCollection> |
||||
{ |
||||
public void Configure(EntityTypeBuilder<TelevisionMediaCollection> builder) |
||||
{ |
||||
builder.ToTable("TelevisionMediaCollections"); |
||||
|
||||
builder.HasIndex(c => new { c.ShowTitle, c.SeasonNumber }) |
||||
.IsUnique(); |
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,212 @@
@@ -0,0 +1,212 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Migrations |
||||
{ |
||||
public partial class CollectionsRework : Migration |
||||
{ |
||||
protected override void Up(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropTable( |
||||
"MediaItemSimpleMediaCollection"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"TelevisionMediaCollections"); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"SimpleMediaCollectionEpisodes", |
||||
table => new |
||||
{ |
||||
SimpleMediaCollectionsId = table.Column<int>("INTEGER", nullable: false), |
||||
TelevisionEpisodesId = table.Column<int>("INTEGER", nullable: false) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey( |
||||
"PK_SimpleMediaCollectionEpisodes", |
||||
x => new { x.SimpleMediaCollectionsId, x.TelevisionEpisodesId }); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionEpisodes_SimpleMediaCollections_SimpleMediaCollectionsId", |
||||
x => x.SimpleMediaCollectionsId, |
||||
"SimpleMediaCollections", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionEpisodes_TelevisionEpisodes_TelevisionEpisodesId", |
||||
x => x.TelevisionEpisodesId, |
||||
"TelevisionEpisodes", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"SimpleMediaCollectionMovies", |
||||
table => new |
||||
{ |
||||
MoviesId = table.Column<int>("INTEGER", nullable: false), |
||||
SimpleMediaCollectionsId = table.Column<int>("INTEGER", nullable: false) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey( |
||||
"PK_SimpleMediaCollectionMovies", |
||||
x => new { x.MoviesId, x.SimpleMediaCollectionsId }); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionMovies_Movies_MoviesId", |
||||
x => x.MoviesId, |
||||
"Movies", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionMovies_SimpleMediaCollections_SimpleMediaCollectionsId", |
||||
x => x.SimpleMediaCollectionsId, |
||||
"SimpleMediaCollections", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"SimpleMediaCollectionSeasons", |
||||
table => new |
||||
{ |
||||
SimpleMediaCollectionsId = table.Column<int>("INTEGER", nullable: false), |
||||
TelevisionSeasonsId = table.Column<int>("INTEGER", nullable: false) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey( |
||||
"PK_SimpleMediaCollectionSeasons", |
||||
x => new { x.SimpleMediaCollectionsId, x.TelevisionSeasonsId }); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionSeasons_SimpleMediaCollections_SimpleMediaCollectionsId", |
||||
x => x.SimpleMediaCollectionsId, |
||||
"SimpleMediaCollections", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionSeasons_TelevisionSeasons_TelevisionSeasonsId", |
||||
x => x.TelevisionSeasonsId, |
||||
"TelevisionSeasons", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"SimpleMediaCollectionShows", |
||||
table => new |
||||
{ |
||||
SimpleMediaCollectionsId = table.Column<int>("INTEGER", nullable: false), |
||||
TelevisionShowsId = table.Column<int>("INTEGER", nullable: false) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey( |
||||
"PK_SimpleMediaCollectionShows", |
||||
x => new { x.SimpleMediaCollectionsId, x.TelevisionShowsId }); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionShows_SimpleMediaCollections_SimpleMediaCollectionsId", |
||||
x => x.SimpleMediaCollectionsId, |
||||
"SimpleMediaCollections", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
"FK_SimpleMediaCollectionShows_TelevisionShows_TelevisionShowsId", |
||||
x => x.TelevisionShowsId, |
||||
"TelevisionShows", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
"IX_SimpleMediaCollectionEpisodes_TelevisionEpisodesId", |
||||
"SimpleMediaCollectionEpisodes", |
||||
"TelevisionEpisodesId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
"IX_SimpleMediaCollectionMovies_SimpleMediaCollectionsId", |
||||
"SimpleMediaCollectionMovies", |
||||
"SimpleMediaCollectionsId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
"IX_SimpleMediaCollectionSeasons_TelevisionSeasonsId", |
||||
"SimpleMediaCollectionSeasons", |
||||
"TelevisionSeasonsId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
"IX_SimpleMediaCollectionShows_TelevisionShowsId", |
||||
"SimpleMediaCollectionShows", |
||||
"TelevisionShowsId"); |
||||
} |
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder) |
||||
{ |
||||
migrationBuilder.DropTable( |
||||
"SimpleMediaCollectionEpisodes"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"SimpleMediaCollectionMovies"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"SimpleMediaCollectionSeasons"); |
||||
|
||||
migrationBuilder.DropTable( |
||||
"SimpleMediaCollectionShows"); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"MediaItemSimpleMediaCollection", |
||||
table => new |
||||
{ |
||||
ItemsId = table.Column<int>("INTEGER", nullable: false), |
||||
SimpleMediaCollectionsId = table.Column<int>("INTEGER", nullable: false) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey( |
||||
"PK_MediaItemSimpleMediaCollection", |
||||
x => new { x.ItemsId, x.SimpleMediaCollectionsId }); |
||||
table.ForeignKey( |
||||
"FK_MediaItemSimpleMediaCollection_MediaItems_ItemsId", |
||||
x => x.ItemsId, |
||||
"MediaItems", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
table.ForeignKey( |
||||
"FK_MediaItemSimpleMediaCollection_SimpleMediaCollections_SimpleMediaCollectionsId", |
||||
x => x.SimpleMediaCollectionsId, |
||||
"SimpleMediaCollections", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateTable( |
||||
"TelevisionMediaCollections", |
||||
table => new |
||||
{ |
||||
Id = table.Column<int>("INTEGER", nullable: false) |
||||
.Annotation("Sqlite:Autoincrement", true), |
||||
SeasonNumber = table.Column<int>("INTEGER", nullable: true), |
||||
ShowTitle = table.Column<string>("TEXT", nullable: true) |
||||
}, |
||||
constraints: table => |
||||
{ |
||||
table.PrimaryKey("PK_TelevisionMediaCollections", x => x.Id); |
||||
table.ForeignKey( |
||||
"FK_TelevisionMediaCollections_MediaCollections_Id", |
||||
x => x.Id, |
||||
"MediaCollections", |
||||
"Id", |
||||
onDelete: ReferentialAction.Cascade); |
||||
}); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
"IX_MediaItemSimpleMediaCollection_SimpleMediaCollectionsId", |
||||
"MediaItemSimpleMediaCollection", |
||||
"SimpleMediaCollectionsId"); |
||||
|
||||
migrationBuilder.CreateIndex( |
||||
"IX_TelevisionMediaCollections_ShowTitle_SeasonNumber", |
||||
"TelevisionMediaCollections", |
||||
new[] { "ShowTitle", "SeasonNumber" }, |
||||
unique: true); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
@page "/media/collections/{Id:int}" |
||||
@using ErsatzTV.Application.MediaCards |
||||
@using ErsatzTV.Application.MediaCards.Queries |
||||
@inject NavigationManager NavigationManager |
||||
@inject IMediator Mediator |
||||
@inject ILogger<MediaCollectionItems> Logger |
||||
@inject ISnackbar Snackbar |
||||
|
||||
<div class="mb-6" style="display: flex; flex-direction: row;"> |
||||
<MudText GutterBottom="true" Typo="Typo.h2">@_data.Name</MudText> |
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit" |
||||
Link="@($"/media/collections/{Id}/edit")" |
||||
Style="margin-bottom: auto; margin-top: auto;"/> |
||||
</div> |
||||
|
||||
@if (_data.MovieCards.Any()) |
||||
{ |
||||
<MudText GutterBottom="true" Typo="Typo.h4">Movies</MudText> |
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid"> |
||||
@foreach (MovieCardViewModel card in _data.MovieCards) |
||||
{ |
||||
<MediaCard Data="@card" Link="@($"/media/movies/{card.MovieId}")"/> |
||||
} |
||||
</MudContainer> |
||||
} |
||||
|
||||
@if (_data.ShowCards.Any()) |
||||
{ |
||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Shows</MudText> |
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid"> |
||||
@foreach (TelevisionShowCardViewModel card in _data.ShowCards) |
||||
{ |
||||
<MediaCard Data="@card" Link="@($"/media/tv/shows/{card.TelevisionShowId}")"/> |
||||
} |
||||
</MudContainer> |
||||
} |
||||
|
||||
@* <MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid"> *@ |
||||
@* @foreach (var card in _data.Cards) *@ |
||||
@* { *@ |
||||
@* <MediaCard Data="@card" CustomPlaceholder="@SeasonPlaceholder" Link="@($"/media/tv/shows/{ShowId}/seasons/{card.TelevisionSeasonId}")" DataRefreshed="@(() => RefreshData())"/> *@ |
||||
@* } *@ |
||||
@* </MudContainer> *@ |
||||
|
||||
@code { |
||||
|
||||
[Parameter] |
||||
public int Id { get; set; } |
||||
|
||||
private SimpleMediaCollectionCardResultsViewModel _data; |
||||
|
||||
protected override async Task OnParametersSetAsync() => await RefreshData(); |
||||
|
||||
private async Task RefreshData() |
||||
{ |
||||
Either<BaseError, SimpleMediaCollectionCardResultsViewModel> maybeResult = |
||||
await Mediator.Send(new GetSimpleMediaCollectionCards(Id)); |
||||
|
||||
maybeResult.Match( |
||||
result => _data = result, |
||||
error => NavigationManager.NavigateTo("404")); |
||||
} |
||||
|
||||
} |
||||
@ -1,121 +0,0 @@
@@ -1,121 +0,0 @@
|
||||
@page "/media/collections/{Id:int}/items" |
||||
@using ErsatzTV.Application.MediaCollections |
||||
@using ErsatzTV.Application.MediaCollections.Commands |
||||
@using ErsatzTV.Application.MediaCollections.Queries |
||||
@using ErsatzTV.Application.MediaItems |
||||
@using ErsatzTV.Application.MediaItems.Queries |
||||
@using Unit = LanguageExt.Unit |
||||
@inject NavigationManager NavigationManager |
||||
@inject IMediator Mediator |
||||
@inject ILogger<MediaCollectionItemsEditor> Logger |
||||
@inject ISnackbar Snackbar |
||||
|
||||
<MudTable Hover="true" Items="_collectionItems"> |
||||
<ToolBarContent> |
||||
<MudText Typo="Typo.h6">@_mediaCollection.Name Media Items</MudText> |
||||
</ToolBarContent> |
||||
<HeaderContent> |
||||
<MudTh>Source</MudTh> |
||||
<MudTh>Type</MudTh> |
||||
<MudTh>Title</MudTh> |
||||
<MudTh>Duration</MudTh> |
||||
</HeaderContent> |
||||
<RowTemplate> |
||||
<MudTd DataLabel="Source">@context.Source</MudTd> |
||||
<MudTd DataLabel="Type">@context.MediaType</MudTd> |
||||
<MudTd DataLabel="Title">@context.Title</MudTd> |
||||
<MudTd DataLabel="Duration">@context.Duration</MudTd> |
||||
</RowTemplate> |
||||
<PagerContent> |
||||
@if (_collectionItems.Any()) |
||||
{ |
||||
<MudTablePager/> |
||||
} |
||||
</PagerContent> |
||||
</MudTable> |
||||
|
||||
<MudTable @ref="_table" Hover="true" ServerData="@(new Func<TableState, Task<TableData<MediaItemSearchResultViewModel>>>(ServerReload))" Class="mt-8"> |
||||
<ToolBarContent> |
||||
<MudText Typo="Typo.h6">All Media Items</MudText> |
||||
<MudToolBarSpacer/> |
||||
<MudTextField T="string" ValueChanged="@OnSearch" Placeholder="Search" Adornment="Adornment.Start" |
||||
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"> |
||||
</MudTextField> |
||||
</ToolBarContent> |
||||
<HeaderContent> |
||||
<MudTh>Source</MudTh> |
||||
<MudTh>Type</MudTh> |
||||
<MudTh>Title</MudTh> |
||||
<MudTh>Duration</MudTh> |
||||
</HeaderContent> |
||||
<RowTemplate> |
||||
<MudTd DataLabel="Source">@context.Source</MudTd> |
||||
<MudTd DataLabel="Type">@context.MediaType</MudTd> |
||||
<MudTd DataLabel="Title">@context.Title</MudTd> |
||||
<MudTd DataLabel="Duration">@context.Duration</MudTd> |
||||
</RowTemplate> |
||||
<PagerContent> |
||||
<MudTablePager/> |
||||
</PagerContent> |
||||
</MudTable> |
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(AddResultsAsync)" Class="mt-4"> |
||||
Add Results |
||||
</MudButton> |
||||
|
||||
@code { |
||||
|
||||
[Parameter] |
||||
public int Id { get; set; } |
||||
|
||||
private MediaCollectionViewModel _mediaCollection; |
||||
private IEnumerable<MediaItemSearchResultViewModel> _collectionItems; |
||||
|
||||
protected override async Task OnParametersSetAsync() => await LoadMediaCollectionAsync(); |
||||
|
||||
private List<int> _mediaItemIds; |
||||
private IEnumerable<MediaItemSearchResultViewModel> _pagedData; |
||||
private MudTable<MediaItemSearchResultViewModel> _table; |
||||
|
||||
private int _totalItems; |
||||
private string _searchString; |
||||
|
||||
private async Task<TableData<MediaItemSearchResultViewModel>> ServerReload(TableState state) |
||||
{ |
||||
List<MediaItemSearchResultViewModel> data = await Mediator.Send(new SearchAllMediaItems(_searchString)); |
||||
|
||||
_mediaItemIds = data.Map(c => c.Id).ToList(); |
||||
_totalItems = data.Count; |
||||
|
||||
_pagedData = data.OrderBy(c => c.Id).Skip(state.Page * state.PageSize).Take(state.PageSize).ToArray(); |
||||
return new TableData<MediaItemSearchResultViewModel> { TotalItems = _totalItems, Items = _pagedData }; |
||||
} |
||||
|
||||
private async Task OnSearch(string text) |
||||
{ |
||||
_searchString = text; |
||||
await _table.ReloadServerData(); |
||||
} |
||||
|
||||
private async Task AddResultsAsync() |
||||
{ |
||||
Either<BaseError, Unit> result = await Mediator.Send(new AddItemsToSimpleMediaCollection(Id, _mediaItemIds)); |
||||
await result.Match( |
||||
async _ => await LoadMediaCollectionAsync(), |
||||
error => |
||||
{ |
||||
Snackbar.Add(error.Value, Severity.Error); |
||||
Logger.LogError("Error adding items to media collection: {Error}", error.Value); |
||||
return Task.CompletedTask; |
||||
}); |
||||
} |
||||
|
||||
private async Task LoadMediaCollectionAsync() |
||||
{ |
||||
Option<Tuple<MediaCollectionViewModel, List<MediaItemSearchResultViewModel>>> maybeResult = |
||||
await Mediator.Send(new GetSimpleMediaCollectionWithItemsById(Id)); |
||||
maybeResult.Match( |
||||
result => (_mediaCollection, _collectionItems) = result, |
||||
() => NavigationManager.NavigateTo("404")); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
@using ErsatzTV.Application.MediaCollections |
||||
@using ErsatzTV.Application.MediaCollections.Queries |
||||
@inject IMediator Mediator |
||||
|
||||
<MudDialog> |
||||
<DialogContent> |
||||
<MudContainer Class="mb-6"> |
||||
<MudHighlighter Class="mud-primary-text" |
||||
Style="background-color: transparent; font-weight: bold" |
||||
Text="@FormatText()" |
||||
HighlightedText="@EntityName"/> |
||||
</MudContainer> |
||||
<MudSelect Label="Collection" @bind-Value="_selectedCollection" For="@(() => _selectedCollection)" Class="mb-6 mx-4"> |
||||
@foreach (MediaCollectionViewModel collection in _collections) |
||||
{ |
||||
<MudSelectItem Value="@collection">@collection.Name</MudSelectItem> |
||||
} |
||||
</MudSelect> |
||||
</DialogContent> |
||||
<DialogActions> |
||||
<MudButton OnClick="Cancel">Cancel</MudButton> |
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" Disabled="@(_selectedCollection == null)" OnClick="Submit"> |
||||
Add To Collection |
||||
</MudButton> |
||||
</DialogActions> |
||||
</MudDialog> |
||||
|
||||
@code { |
||||
|
||||
[CascadingParameter] |
||||
MudDialogInstance MudDialog { get; set; } |
||||
|
||||
[Parameter] |
||||
public string EntityType { get; set; } |
||||
|
||||
[Parameter] |
||||
public string EntityName { get; set; } |
||||
|
||||
[Parameter] |
||||
public string DetailText { get; set; } |
||||
|
||||
[Parameter] |
||||
public string DetailHighlight { get; set; } |
||||
|
||||
private List<MediaCollectionViewModel> _collections; |
||||
|
||||
private MediaCollectionViewModel _selectedCollection; |
||||
|
||||
protected override async Task OnParametersSetAsync() => |
||||
_collections = await Mediator.Send(new GetAllSimpleMediaCollections()); |
||||
|
||||
private string FormatText() => $"Select the collection to add the {EntityType} {EntityName}"; |
||||
|
||||
private void Submit() => MudDialog.Close(DialogResult.Ok(_selectedCollection)); |
||||
|
||||
private void Cancel() => MudDialog.Cancel(); |
||||
|
||||
} |
||||
Loading…
Reference in new issue