add movie and show genres (#61)
* load movie genres from sidecar metadata * search movie and tv show genres * rebuild all playouts (needed after time zone fix) * code cleanup * fix duplicate tv show search results
This commit is contained in:
@@ -18,7 +18,17 @@ namespace ErsatzTV.Application.MediaCards.Queries
|
|||||||
public Task<Either<BaseError, SearchCardResultsViewModel>> Handle(
|
public Task<Either<BaseError, SearchCardResultsViewModel>> Handle(
|
||||||
GetSearchCards request,
|
GetSearchCards request,
|
||||||
CancellationToken cancellationToken) =>
|
CancellationToken cancellationToken) =>
|
||||||
Try(_searchRepository.SearchMediaItems(request.Query)).Sequence()
|
request.Query.StartsWith("genre:")
|
||||||
|
? GenreSearch(request.Query.Replace("genre:", string.Empty))
|
||||||
|
: TitleSearch(request.Query);
|
||||||
|
|
||||||
|
private Task<Either<BaseError, SearchCardResultsViewModel>> TitleSearch(string query) =>
|
||||||
|
Try(_searchRepository.SearchMediaItemsByTitle(query)).Sequence()
|
||||||
|
.Map(ProjectToSearchResults)
|
||||||
|
.Map(t => t.ToEither(ex => BaseError.New($"Failed to search: {ex.Message}")));
|
||||||
|
|
||||||
|
private Task<Either<BaseError, SearchCardResultsViewModel>> GenreSearch(string query) =>
|
||||||
|
Try(_searchRepository.SearchMediaItemsByGenre(query)).Sequence()
|
||||||
.Map(ProjectToSearchResults)
|
.Map(ProjectToSearchResults)
|
||||||
.Map(t => t.ToEither(ex => BaseError.New($"Failed to search: {ex.Message}")));
|
.Map(t => t.ToEither(ex => BaseError.New($"Failed to search: {ex.Message}")));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ namespace ErsatzTV.Application.Movies
|
|||||||
metadata.Year?.ToString(),
|
metadata.Year?.ToString(),
|
||||||
metadata.Plot,
|
metadata.Plot,
|
||||||
Artwork(metadata, ArtworkKind.Poster),
|
Artwork(metadata, ArtworkKind.Poster),
|
||||||
Artwork(metadata, ArtworkKind.FanArt));
|
Artwork(metadata, ArtworkKind.FanArt),
|
||||||
|
metadata.Genres.Map(g => g.Name).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Artwork(Metadata metadata, ArtworkKind artworkKind) =>
|
private static string Artwork(Metadata metadata, ArtworkKind artworkKind) =>
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
namespace ErsatzTV.Application.Movies
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Movies
|
||||||
{
|
{
|
||||||
public record MovieViewModel(string Title, string Year, string Plot, string Poster, string FanArt);
|
public record MovieViewModel(
|
||||||
|
string Title,
|
||||||
|
string Year,
|
||||||
|
string Plot,
|
||||||
|
string Poster,
|
||||||
|
string FanArt,
|
||||||
|
List<string> Genres);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
using static LanguageExt.Prelude;
|
using static LanguageExt.Prelude;
|
||||||
|
|
||||||
@@ -13,7 +14,8 @@ namespace ErsatzTV.Application.Television
|
|||||||
show.ShowMetadata.HeadOrNone().Map(m => m.Year?.ToString() ?? string.Empty).IfNone(string.Empty),
|
show.ShowMetadata.HeadOrNone().Map(m => m.Year?.ToString() ?? string.Empty).IfNone(string.Empty),
|
||||||
show.ShowMetadata.HeadOrNone().Map(m => m.Plot ?? string.Empty).IfNone(string.Empty),
|
show.ShowMetadata.HeadOrNone().Map(m => m.Plot ?? string.Empty).IfNone(string.Empty),
|
||||||
show.ShowMetadata.HeadOrNone().Map(GetPoster).IfNone(string.Empty),
|
show.ShowMetadata.HeadOrNone().Map(GetPoster).IfNone(string.Empty),
|
||||||
show.ShowMetadata.HeadOrNone().Map(GetFanArt).IfNone(string.Empty));
|
show.ShowMetadata.HeadOrNone().Map(GetFanArt).IfNone(string.Empty),
|
||||||
|
show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone(new List<string>()));
|
||||||
|
|
||||||
internal static TelevisionSeasonViewModel ProjectToViewModel(Season season) =>
|
internal static TelevisionSeasonViewModel ProjectToViewModel(Season season) =>
|
||||||
new(
|
new(
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
namespace ErsatzTV.Application.Television
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Television
|
||||||
{
|
{
|
||||||
public record TelevisionShowViewModel(int Id, string Title, string Year, string Plot, string Poster, string FanArt);
|
public record TelevisionShowViewModel(
|
||||||
|
int Id,
|
||||||
|
string Title,
|
||||||
|
string Year,
|
||||||
|
string Plot,
|
||||||
|
string Poster,
|
||||||
|
string FanArt,
|
||||||
|
List<string> Genres);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace ErsatzTV.Core.Domain
|
||||||
|
{
|
||||||
|
public class Genre
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,5 +15,6 @@ namespace ErsatzTV.Core.Domain
|
|||||||
public DateTime DateAdded { get; set; }
|
public DateTime DateAdded { get; set; }
|
||||||
public DateTime DateUpdated { get; set; }
|
public DateTime DateUpdated { get; set; }
|
||||||
public List<Artwork> Artwork { get; set; }
|
public List<Artwork> Artwork { get; set; }
|
||||||
|
public List<Genre> Genres { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace ErsatzTV.Core.Interfaces.Repositories
|
|||||||
{
|
{
|
||||||
public interface ISearchRepository
|
public interface ISearchRepository
|
||||||
{
|
{
|
||||||
public Task<List<MediaItem>> SearchMediaItems(string query);
|
public Task<List<MediaItem>> SearchMediaItemsByTitle(string query);
|
||||||
|
public Task<List<MediaItem>> SearchMediaItemsByGenre(string genre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Serialization;
|
using System.Xml.Serialization;
|
||||||
using ErsatzTV.Core.Domain;
|
using ErsatzTV.Core.Domain;
|
||||||
@@ -133,6 +134,18 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle)
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle)
|
||||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||||
: metadata.SortTitle;
|
: metadata.SortTitle;
|
||||||
|
|
||||||
|
foreach (Genre genre in existing.Genres.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existing.Genres.Remove(genre);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existing.Genres.Add(genre);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
@@ -163,6 +176,18 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle)
|
existing.SortTitle = string.IsNullOrWhiteSpace(metadata.SortTitle)
|
||||||
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
? _fallbackMetadataProvider.GetSortTitle(metadata.Title)
|
||||||
: metadata.SortTitle;
|
: metadata.SortTitle;
|
||||||
|
|
||||||
|
foreach (Genre genre in existing.Genres.Filter(g => metadata.Genres.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existing.Genres.Remove(genre);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Genre genre in metadata.Genres.Filter(g => existing.Genres.All(g2 => g2.Name != g.Name))
|
||||||
|
.ToList())
|
||||||
|
{
|
||||||
|
existing.Genres.Add(genre);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
@@ -224,7 +249,8 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
Outline = nfo.Outline,
|
Outline = nfo.Outline,
|
||||||
Tagline = nfo.Tagline,
|
Tagline = nfo.Tagline,
|
||||||
Year = nfo.Year,
|
Year = nfo.Year,
|
||||||
ReleaseDate = GetAired(nfo.Premiered) ?? new DateTime(nfo.Year, 1, 1)
|
ReleaseDate = GetAired(nfo.Premiered) ?? new DateTime(nfo.Year, 1, 1),
|
||||||
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList()
|
||||||
},
|
},
|
||||||
None);
|
None);
|
||||||
}
|
}
|
||||||
@@ -279,7 +305,8 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
ReleaseDate = nfo.Premiered,
|
ReleaseDate = nfo.Premiered,
|
||||||
Plot = nfo.Plot,
|
Plot = nfo.Plot,
|
||||||
Outline = nfo.Outline,
|
Outline = nfo.Outline,
|
||||||
Tagline = nfo.Tagline
|
Tagline = nfo.Tagline,
|
||||||
|
Genres = nfo.Genres.Map(g => new Genre { Name = g }).ToList()
|
||||||
},
|
},
|
||||||
None);
|
None);
|
||||||
}
|
}
|
||||||
@@ -328,6 +355,9 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
|
|
||||||
[XmlElement("tagline")]
|
[XmlElement("tagline")]
|
||||||
public string Tagline { get; set; }
|
public string Tagline { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("genre")]
|
||||||
|
public List<string> Genres { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[XmlRoot("tvshow")]
|
[XmlRoot("tvshow")]
|
||||||
@@ -350,6 +380,9 @@ namespace ErsatzTV.Core.Metadata
|
|||||||
|
|
||||||
[XmlElement("premiered")]
|
[XmlElement("premiered")]
|
||||||
public string Premiered { get; set; }
|
public string Premiered { get; set; }
|
||||||
|
|
||||||
|
[XmlElement("genre")]
|
||||||
|
public List<string> Genres { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[XmlRoot("episodedetails")]
|
[XmlRoot("episodedetails")]
|
||||||
|
|||||||
@@ -297,7 +297,8 @@ namespace ErsatzTV.Core.Scheduling
|
|||||||
{
|
{
|
||||||
NextScheduleItem = schedule,
|
NextScheduleItem = schedule,
|
||||||
NextScheduleItemId = schedule.Id,
|
NextScheduleItemId = schedule.Id,
|
||||||
NextStart = (start - start.TimeOfDay).UtcDateTime + schedule.StartTime.GetValueOrDefault()
|
NextStart = (start - start.TimeOfDay).UtcDateTime +
|
||||||
|
schedule.StartTime.GetValueOrDefault()
|
||||||
};
|
};
|
||||||
case StartType.Dynamic:
|
case StartType.Dynamic:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
|||||||
builder.HasMany(mm => mm.Artwork)
|
builder.HasMany(mm => mm.Artwork)
|
||||||
.WithOne()
|
.WithOne()
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(mm => mm.Genres)
|
||||||
|
.WithOne()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ namespace ErsatzTV.Infrastructure.Data.Configurations
|
|||||||
builder.HasMany(sm => sm.Artwork)
|
builder.HasMany(sm => sm.Artwork)
|
||||||
.WithOne()
|
.WithOne()
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasMany(mm => mm.Genres)
|
||||||
|
.WithOne()
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
return await dbContext.Movies
|
return await dbContext.Movies
|
||||||
.Include(m => m.MovieMetadata)
|
.Include(m => m.MovieMetadata)
|
||||||
.ThenInclude(m => m.Artwork)
|
.ThenInclude(m => m.Artwork)
|
||||||
|
.Include(m => m.MovieMetadata)
|
||||||
|
.ThenInclude(m => m.Genres)
|
||||||
.OrderBy(m => m.Id)
|
.OrderBy(m => m.Id)
|
||||||
.SingleOrDefaultAsync(m => m.Id == movieId)
|
.SingleOrDefaultAsync(m => m.Id == movieId)
|
||||||
.Map(Optional);
|
.Map(Optional);
|
||||||
@@ -45,6 +47,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
Option<Movie> maybeExisting = await _dbContext.Movies
|
Option<Movie> maybeExisting = await _dbContext.Movies
|
||||||
.Include(i => i.MovieMetadata)
|
.Include(i => i.MovieMetadata)
|
||||||
.ThenInclude(mm => mm.Artwork)
|
.ThenInclude(mm => mm.Artwork)
|
||||||
|
.Include(i => i.MovieMetadata)
|
||||||
|
.ThenInclude(mm => mm.Genres)
|
||||||
.Include(i => i.LibraryPath)
|
.Include(i => i.LibraryPath)
|
||||||
.Include(i => i.MediaVersions)
|
.Include(i => i.MediaVersions)
|
||||||
.ThenInclude(mv => mv.MediaFiles)
|
.ThenInclude(mv => mv.MediaFiles)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
_dbConnection = dbConnection;
|
_dbConnection = dbConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<MediaItem>> SearchMediaItems(string query)
|
public async Task<List<MediaItem>> SearchMediaItemsByTitle(string query)
|
||||||
{
|
{
|
||||||
List<int> ids = await _dbConnection.QueryAsync<int>(
|
List<int> ids = await _dbConnection.QueryAsync<int>(
|
||||||
@"SELECT M.Id FROM Movie M
|
@"SELECT M.Id FROM Movie M
|
||||||
@@ -30,7 +30,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
UNION
|
UNION
|
||||||
SELECT S.Id FROM Show S
|
SELECT S.Id FROM Show S
|
||||||
INNER JOIN ShowMetadata SM on S.Id = SM.ShowId
|
INNER JOIN ShowMetadata SM on S.Id = SM.ShowId
|
||||||
WHERE SM.Title LIKE @Query",
|
WHERE SM.Title LIKE @Query
|
||||||
|
GROUP BY SM.Title, SM.Year",
|
||||||
new { Query = $"%{query}%" })
|
new { Query = $"%{query}%" })
|
||||||
.Map(results => results.ToList());
|
.Map(results => results.ToList());
|
||||||
|
|
||||||
@@ -44,5 +45,32 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
.OfType<MediaItem>()
|
.OfType<MediaItem>()
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<MediaItem>> SearchMediaItemsByGenre(string genre)
|
||||||
|
{
|
||||||
|
List<int> ids = await _dbConnection.QueryAsync<int>(
|
||||||
|
@"SELECT M.Id FROM Movie M
|
||||||
|
INNER JOIN MovieMetadata MM on M.Id = MM.MovieId
|
||||||
|
INNER JOIN Genre G on MM.Id = G.MovieMetadataId
|
||||||
|
WHERE G.Name LIKE @Query
|
||||||
|
UNION
|
||||||
|
SELECT S.Id FROM Show S
|
||||||
|
INNER JOIN ShowMetadata SM on S.Id = SM.ShowId
|
||||||
|
INNER JOIN Genre G2 on SM.Id = G2.ShowMetadataId
|
||||||
|
WHERE G2.Name LIKE @Query
|
||||||
|
GROUP BY SM.Title, SM.Year",
|
||||||
|
new { Query = genre })
|
||||||
|
.Map(results => results.ToList());
|
||||||
|
|
||||||
|
await using TvContext context = _dbContextFactory.CreateDbContext();
|
||||||
|
return await context.MediaItems
|
||||||
|
.Filter(m => ids.Contains(m.Id))
|
||||||
|
.Include(m => (m as Movie).MovieMetadata)
|
||||||
|
.ThenInclude(mm => mm.Artwork)
|
||||||
|
.Include(m => (m as Show).ShowMetadata)
|
||||||
|
.ThenInclude(mm => mm.Artwork)
|
||||||
|
.OfType<MediaItem>()
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
.Filter(s => s.Id == showId)
|
.Filter(s => s.Id == showId)
|
||||||
.Include(s => s.ShowMetadata)
|
.Include(s => s.ShowMetadata)
|
||||||
.ThenInclude(sm => sm.Artwork)
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.Include(s => s.ShowMetadata)
|
||||||
|
.ThenInclude(sm => sm.Genres)
|
||||||
.OrderBy(s => s.Id)
|
.OrderBy(s => s.Id)
|
||||||
.SingleOrDefaultAsync()
|
.SingleOrDefaultAsync()
|
||||||
.Map(Optional);
|
.Map(Optional);
|
||||||
@@ -171,6 +173,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
return _dbContext.Shows
|
return _dbContext.Shows
|
||||||
.Include(s => s.ShowMetadata)
|
.Include(s => s.ShowMetadata)
|
||||||
.ThenInclude(sm => sm.Artwork)
|
.ThenInclude(sm => sm.Artwork)
|
||||||
|
.Include(s => s.ShowMetadata)
|
||||||
|
.ThenInclude(sm => sm.Genres)
|
||||||
.OrderBy(s => s.Id)
|
.OrderBy(s => s.Id)
|
||||||
.SingleOrDefaultAsync(s => s.Id == id)
|
.SingleOrDefaultAsync(s => s.Id == id)
|
||||||
.Map(Optional);
|
.Map(Optional);
|
||||||
@@ -183,6 +187,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
metadata.DateAdded = DateTime.UtcNow;
|
metadata.DateAdded = DateTime.UtcNow;
|
||||||
|
metadata.Genres ??= new List<Genre>();
|
||||||
var show = new Show
|
var show = new Show
|
||||||
{
|
{
|
||||||
LibraryPathId = libraryPathId,
|
LibraryPathId = libraryPathId,
|
||||||
|
|||||||
+1560
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class Add_MetadataGenres : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
"Genre",
|
||||||
|
table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>("INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Name = table.Column<string>("TEXT", nullable: true),
|
||||||
|
EpisodeMetadataId = table.Column<int>("INTEGER", nullable: true),
|
||||||
|
MovieMetadataId = table.Column<int>("INTEGER", nullable: true),
|
||||||
|
SeasonMetadataId = table.Column<int>("INTEGER", nullable: true),
|
||||||
|
ShowMetadataId = table.Column<int>("INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Genre", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
"FK_Genre_EpisodeMetadata_EpisodeMetadataId",
|
||||||
|
x => x.EpisodeMetadataId,
|
||||||
|
"EpisodeMetadata",
|
||||||
|
"Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
"FK_Genre_MovieMetadata_MovieMetadataId",
|
||||||
|
x => x.MovieMetadataId,
|
||||||
|
"MovieMetadata",
|
||||||
|
"Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
"FK_Genre_SeasonMetadata_SeasonMetadataId",
|
||||||
|
x => x.SeasonMetadataId,
|
||||||
|
"SeasonMetadata",
|
||||||
|
"Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
table.ForeignKey(
|
||||||
|
"FK_Genre_ShowMetadata_ShowMetadataId",
|
||||||
|
x => x.ShowMetadataId,
|
||||||
|
"ShowMetadata",
|
||||||
|
"Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
"IX_Genre_EpisodeMetadataId",
|
||||||
|
"Genre",
|
||||||
|
"EpisodeMetadataId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
"IX_Genre_MovieMetadataId",
|
||||||
|
"Genre",
|
||||||
|
"MovieMetadataId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
"IX_Genre_SeasonMetadataId",
|
||||||
|
"Genre",
|
||||||
|
"SeasonMetadataId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
"IX_Genre_ShowMetadataId",
|
||||||
|
"Genre",
|
||||||
|
"ShowMetadataId");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) =>
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
"Genre");
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1560
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class Reset_MetadataDateUpdated_Genres : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"UPDATE MovieMetadata SET DateUpdated = '0001-01-01 00:00:00'");
|
||||||
|
migrationBuilder.Sql(@"UPDATE ShowMetadata SET DateUpdated = '0001-01-01 00:00:00'");
|
||||||
|
migrationBuilder.Sql(@"UPDATE Library SET LastScan = '0001-01-01 00:00:00'");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1560
File diff suppressed because it is too large
Load Diff
+18
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
public partial class RebuildAllPlayouts_TimeZonesAgain : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql(@"DELETE FROM PlayoutItem");
|
||||||
|
migrationBuilder.Sql(@"DELETE FROM PlayoutProgramScheduleAnchor");
|
||||||
|
migrationBuilder.Sql(@"UPDATE Playout SET Anchor_NextStart = null, Anchor_NextScheduleItemId = null");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -274,6 +274,42 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b.ToTable("FFmpegProfile");
|
b.ToTable("FFmpegProfile");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity(
|
||||||
|
"ErsatzTV.Core.Domain.Genre",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("EpisodeMetadataId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MovieMetadataId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("SeasonMetadataId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("ShowMetadataId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("EpisodeMetadataId");
|
||||||
|
|
||||||
|
b.HasIndex("MovieMetadataId");
|
||||||
|
|
||||||
|
b.HasIndex("SeasonMetadataId");
|
||||||
|
|
||||||
|
b.HasIndex("ShowMetadataId");
|
||||||
|
|
||||||
|
b.ToTable("Genre");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity(
|
modelBuilder.Entity(
|
||||||
"ErsatzTV.Core.Domain.Library",
|
"ErsatzTV.Core.Domain.Library",
|
||||||
b =>
|
b =>
|
||||||
@@ -1068,6 +1104,29 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b.Navigation("Resolution");
|
b.Navigation("Resolution");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity(
|
||||||
|
"ErsatzTV.Core.Domain.Genre",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.EpisodeMetadata", null)
|
||||||
|
.WithMany("Genres")
|
||||||
|
.HasForeignKey("EpisodeMetadataId");
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.MovieMetadata", null)
|
||||||
|
.WithMany("Genres")
|
||||||
|
.HasForeignKey("MovieMetadataId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.SeasonMetadata", null)
|
||||||
|
.WithMany("Genres")
|
||||||
|
.HasForeignKey("SeasonMetadataId");
|
||||||
|
|
||||||
|
b.HasOne("ErsatzTV.Core.Domain.ShowMetadata", null)
|
||||||
|
.WithMany("Genres")
|
||||||
|
.HasForeignKey("ShowMetadataId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity(
|
modelBuilder.Entity(
|
||||||
"ErsatzTV.Core.Domain.Library",
|
"ErsatzTV.Core.Domain.Library",
|
||||||
b =>
|
b =>
|
||||||
@@ -1543,7 +1602,14 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => { b.Navigation("CollectionItems"); });
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Collection", b => { b.Navigation("CollectionItems"); });
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.EpisodeMetadata", b => { b.Navigation("Artwork"); });
|
modelBuilder.Entity(
|
||||||
|
"ErsatzTV.Core.Domain.EpisodeMetadata",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Artwork");
|
||||||
|
|
||||||
|
b.Navigation("Genres");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => { b.Navigation("Paths"); });
|
modelBuilder.Entity("ErsatzTV.Core.Domain.Library", b => { b.Navigation("Paths"); });
|
||||||
|
|
||||||
@@ -1555,7 +1621,14 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => { b.Navigation("MediaFiles"); });
|
modelBuilder.Entity("ErsatzTV.Core.Domain.MediaVersion", b => { b.Navigation("MediaFiles"); });
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.MovieMetadata", b => { b.Navigation("Artwork"); });
|
modelBuilder.Entity(
|
||||||
|
"ErsatzTV.Core.Domain.MovieMetadata",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Artwork");
|
||||||
|
|
||||||
|
b.Navigation("Genres");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity(
|
modelBuilder.Entity(
|
||||||
"ErsatzTV.Core.Domain.Playout",
|
"ErsatzTV.Core.Domain.Playout",
|
||||||
@@ -1575,9 +1648,23 @@ namespace ErsatzTV.Infrastructure.Migrations
|
|||||||
b.Navigation("Playouts");
|
b.Navigation("Playouts");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.SeasonMetadata", b => { b.Navigation("Artwork"); });
|
modelBuilder.Entity(
|
||||||
|
"ErsatzTV.Core.Domain.SeasonMetadata",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Artwork");
|
||||||
|
|
||||||
modelBuilder.Entity("ErsatzTV.Core.Domain.ShowMetadata", b => { b.Navigation("Artwork"); });
|
b.Navigation("Genres");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity(
|
||||||
|
"ErsatzTV.Core.Domain.ShowMetadata",
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Artwork");
|
||||||
|
|
||||||
|
b.Navigation("Genres");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity(
|
modelBuilder.Entity(
|
||||||
"ErsatzTV.Core.Domain.Episode",
|
"ErsatzTV.Core.Domain.Episode",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px">
|
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px">
|
||||||
<div style="display: flex; flex-direction: row;">
|
<div style="display: flex; flex-direction: row;" class="mb-6">
|
||||||
@if (!string.IsNullOrWhiteSpace(_movie.Poster))
|
@if (!string.IsNullOrWhiteSpace(_movie.Poster))
|
||||||
{
|
{
|
||||||
<img class="mud-elevation-2 mr-6"
|
<img class="mud-elevation-2 mr-6"
|
||||||
@@ -43,6 +43,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_movie.Genres.Any())
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
@foreach (string genre in _movie.Genres.OrderBy(g => g))
|
||||||
|
{
|
||||||
|
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2" Link="@($"/search?query=genre%3a{genre}")"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<MudText GutterBottom="true" Typo="Typo.h4">Movies</MudText>
|
<MudText GutterBottom="true" Typo="Typo.h4">Movies</MudText>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||||
@foreach (MovieCardViewModel card in _data.MovieCards)
|
@foreach (MovieCardViewModel card in _data.MovieCards.OrderBy(m => m.SortTitle))
|
||||||
{
|
{
|
||||||
<MediaCard Data="@card"
|
<MediaCard Data="@card"
|
||||||
Link="@($"/media/movies/{card.MovieId}")"
|
Link="@($"/media/movies/{card.MovieId}")"
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<MudText GutterBottom="true" Typo="Typo.h4">Television Shows</MudText>
|
<MudText GutterBottom="true" Typo="Typo.h4">Television Shows</MudText>
|
||||||
|
|
||||||
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
|
||||||
@foreach (TelevisionShowCardViewModel card in _data.ShowCards)
|
@foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(s => s.SortTitle))
|
||||||
{
|
{
|
||||||
<MediaCard Data="@card"
|
<MediaCard Data="@card"
|
||||||
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px">
|
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px">
|
||||||
<div style="display: flex; flex-direction: row;">
|
<div style="display: flex; flex-direction: row;" class="mb-6">
|
||||||
@if (!string.IsNullOrWhiteSpace(_show.Poster))
|
@if (!string.IsNullOrWhiteSpace(_show.Poster))
|
||||||
{
|
{
|
||||||
<img class="mud-elevation-2 mr-6"
|
<img class="mud-elevation-2 mr-6"
|
||||||
@@ -58,6 +58,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (_show.Genres.Any())
|
||||||
|
{
|
||||||
|
<div>
|
||||||
|
@foreach (string genre in _show.Genres.OrderBy(g => g))
|
||||||
|
{
|
||||||
|
<MudFab Color="Color.Info" Size="Size.Small" Label="@genre" Class="mr-2" Link="@($"/search?query=genre%3a{genre}")"/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</MudContainer>
|
</MudContainer>
|
||||||
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid mt-8">
|
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid mt-8">
|
||||||
@foreach (TelevisionSeasonCardViewModel card in _data.Cards)
|
@foreach (TelevisionSeasonCardViewModel card in _data.Cards)
|
||||||
|
|||||||
Reference in New Issue
Block a user