mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-04 06:04:39 -04:00
Convert news items to dapper implementation
This commit is contained in:
parent
948f8694f2
commit
ee4cc6706e
@ -16,180 +16,16 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using System.Collections.Generic;
|
using Kyoo.Abstractions.Models.Attributes;
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace Kyoo.Abstractions.Models
|
namespace Kyoo.Abstractions.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A show, a movie or a collection.
|
||||||
|
/// </summary>
|
||||||
|
[OneOf(Types = new[] { typeof(Episode), typeof(Movie) })]
|
||||||
|
public interface INews : IResource, IThumbnails, IMetadata, IAddedDate, IQuery
|
||||||
{
|
{
|
||||||
/// <summary>
|
static Sort IQuery.DefaultSort => new Sort<INews>.By(nameof(AddedDate));
|
||||||
/// The type of item, ether a show, a movie or a collection.
|
|
||||||
/// </summary>
|
|
||||||
public enum NewsKind
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="ILibraryItem"/> is an <see cref="Episode"/>.
|
|
||||||
/// </summary>
|
|
||||||
Episode,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="ILibraryItem"/> is a Movie.
|
|
||||||
/// </summary>
|
|
||||||
Movie,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A new item
|
|
||||||
/// </summary>
|
|
||||||
public class News : IResource, IMetadata, IThumbnails, IAddedDate, IQuery
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
[MaxLength(256)]
|
|
||||||
public string Slug { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The title of this show.
|
|
||||||
/// </summary>
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A catchphrase for this movie.
|
|
||||||
/// </summary>
|
|
||||||
public string? Tagline { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The list of alternative titles of this show.
|
|
||||||
/// </summary>
|
|
||||||
public string[] Aliases { get; set; } = Array.Empty<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of the movie video file.
|
|
||||||
/// </summary>
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The summary of this show.
|
|
||||||
/// </summary>
|
|
||||||
public string? Overview { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A list of tags that match this movie.
|
|
||||||
/// </summary>
|
|
||||||
public string[] Tags { get; set; } = Array.Empty<string>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The list of genres (themes) this show has.
|
|
||||||
/// </summary>
|
|
||||||
public Genre[] Genres { get; set; } = Array.Empty<Genre>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is this show airing, not aired yet or finished?
|
|
||||||
/// </summary>
|
|
||||||
public Status? Status { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How well this item is rated? (from 0 to 100).
|
|
||||||
/// </summary>
|
|
||||||
public int? Rating { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// How long is this movie or episode? (in minutes)
|
|
||||||
/// </summary>
|
|
||||||
public int Runtime { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The date this movie aired.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? AirDate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The date this movie aired.
|
|
||||||
/// </summary>
|
|
||||||
public DateTime? ReleaseDate => AirDate;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public DateTime AddedDate { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Image? Poster { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Image? Thumbnail { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Image? Logo { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A video of a few minutes that tease the content.
|
|
||||||
/// </summary>
|
|
||||||
public string? Trailer { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Dictionary<string, MetadataId> ExternalId { get; set; } = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The season in witch this episode is in.
|
|
||||||
/// </summary>
|
|
||||||
public int? SeasonNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The number of this episode in it's season.
|
|
||||||
/// </summary>
|
|
||||||
public int? EpisodeNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
|
|
||||||
/// </summary>
|
|
||||||
public int? AbsoluteNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A simple summary of informations about the show of this episode
|
|
||||||
/// (this is specially useful since news can't have includes).
|
|
||||||
/// </summary>
|
|
||||||
public ShowInfo? Show { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is the item a a movie or an episode?
|
|
||||||
/// </summary>
|
|
||||||
public NewsKind Kind { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Links to watch this movie.
|
|
||||||
/// </summary>
|
|
||||||
public VideoLinks Links => new()
|
|
||||||
{
|
|
||||||
Direct = $"/video/{Kind.ToString().ToLower()}/{Slug}/direct",
|
|
||||||
Hls = $"/video/{Kind.ToString().ToLower()}/{Slug}/master.m3u8",
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A simple summary of informations about the show of this episode
|
|
||||||
/// (this is specially useful since news can't have includes).
|
|
||||||
/// </summary>
|
|
||||||
public class ShowInfo : IResource, IThumbnails
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public int Id { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public string Slug { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The title of this show.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Image? Poster { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Image? Thumbnail { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Image? Logo { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class to represent a single show's episode.
|
/// A class to represent a single show's episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
|
public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews
|
||||||
{
|
{
|
||||||
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
|
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
|
||||||
public static Sort DefaultSort => new Sort<Episode>.Conglomerate(
|
public static Sort DefaultSort => new Sort<Episode>.Conglomerate(
|
||||||
|
@ -29,7 +29,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A series or a movie.
|
/// A series or a movie.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem
|
public class Movie : IQuery, IResource, IMetadata, IOnMerge, IThumbnails, IAddedDate, ILibraryItem, INews
|
||||||
{
|
{
|
||||||
public static Sort DefaultSort => new Sort<Movie>.By(x => x.Name);
|
public static Sort DefaultSort => new Sort<Movie>.By(x => x.Name);
|
||||||
|
|
||||||
|
@ -39,12 +39,12 @@ public static class DapperHelper
|
|||||||
{
|
{
|
||||||
private static string _Property(string key, Dictionary<string, Type> config)
|
private static string _Property(string key, Dictionary<string, Type> config)
|
||||||
{
|
{
|
||||||
if (config.Count == 1)
|
string[] keys = config
|
||||||
return $"{config.First()}.{key.ToSnakeCase()}";
|
|
||||||
|
|
||||||
IEnumerable<string> keys = config
|
|
||||||
.Where(x => key == "id" || x.Value.GetProperty(key) != null)
|
.Where(x => key == "id" || x.Value.GetProperty(key) != null)
|
||||||
.Select(x => $"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}");
|
.Select(x => $"{x.Key}.{x.Value.GetProperty(key)?.GetCustomAttribute<ColumnAttribute>()?.Name ?? key.ToSnakeCase()}")
|
||||||
|
.ToArray();
|
||||||
|
if (keys.Length == 1)
|
||||||
|
return keys.First();
|
||||||
return $"coalesce({string.Join(", ", keys)})";
|
return $"coalesce({string.Join(", ", keys)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,14 +66,16 @@ public static class DapperHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static (
|
public static (
|
||||||
Dictionary<string, Type> config,
|
string projection,
|
||||||
string join,
|
string join,
|
||||||
|
List<Type> types,
|
||||||
Func<T, IEnumerable<object?>, T> map
|
Func<T, IEnumerable<object?>, T> map
|
||||||
) ProcessInclude<T>(Include<T> include, Dictionary<string, Type> config)
|
) ProcessInclude<T>(Include<T> include, Dictionary<string, Type> config)
|
||||||
where T : class
|
where T : class
|
||||||
{
|
{
|
||||||
int relation = 0;
|
int relation = 0;
|
||||||
Dictionary<string, Type> retConfig = new();
|
List<Type> types = new();
|
||||||
|
StringBuilder projection = new();
|
||||||
StringBuilder join = new();
|
StringBuilder join = new();
|
||||||
|
|
||||||
foreach (Include.Metadata metadata in include.Metadatas)
|
foreach (Include.Metadata metadata in include.Metadatas)
|
||||||
@ -83,7 +85,8 @@ public static class DapperHelper
|
|||||||
{
|
{
|
||||||
case Include.SingleRelation(var name, var type, var rid):
|
case Include.SingleRelation(var name, var type, var rid):
|
||||||
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
string tableName = type.GetCustomAttribute<TableAttribute>()?.Name ?? $"{type.Name.ToSnakeCase()}s";
|
||||||
retConfig.Add($"r{relation}", type);
|
types.Add(type);
|
||||||
|
projection.AppendLine($", r{relation}.* -- {type.Name} as r{relation}");
|
||||||
join.Append($"\nleft join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}");
|
join.Append($"\nleft join {tableName} as r{relation} on r{relation}.id = {_Property(rid, config)}");
|
||||||
break;
|
break;
|
||||||
case Include.CustomRelation(var name, var type, var sql, var on, var declaring):
|
case Include.CustomRelation(var name, var type, var sql, var on, var declaring):
|
||||||
@ -91,7 +94,8 @@ public static class DapperHelper
|
|||||||
string lateral = sql.Contains("\"this\"") ? " lateral" : string.Empty;
|
string lateral = sql.Contains("\"this\"") ? " lateral" : string.Empty;
|
||||||
sql = sql.Replace("\"this\"", owner);
|
sql = sql.Replace("\"this\"", owner);
|
||||||
on = on?.Replace("\"this\"", owner);
|
on = on?.Replace("\"this\"", owner);
|
||||||
retConfig.Add($"r{relation}", type);
|
types.Add(type);
|
||||||
|
projection.AppendLine($", r{relation}.*");
|
||||||
join.Append($"\nleft join{lateral} ({sql}) as r{relation} on r{relation}.{on}");
|
join.Append($"\nleft join{lateral} ({sql}) as r{relation} on r{relation}.{on}");
|
||||||
break;
|
break;
|
||||||
case Include.ProjectedRelation:
|
case Include.ProjectedRelation:
|
||||||
@ -114,7 +118,7 @@ public static class DapperHelper
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (retConfig, join.ToString(), Map);
|
return (projection.ToString(), join.ToString(), types, Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FormattableString ProcessFilter<T>(Filter<T> filter, Dictionary<string, Type> config)
|
public static FormattableString ProcessFilter<T>(Filter<T> filter, Dictionary<string, Type> config)
|
||||||
@ -187,9 +191,8 @@ public static class DapperHelper
|
|||||||
|
|
||||||
// Include handling
|
// Include handling
|
||||||
include ??= new();
|
include ??= new();
|
||||||
var (includeConfig, includeJoin, mapIncludes) = ProcessInclude(include, config);
|
var (includeProjection, includeJoin, includeTypes, mapIncludes) = ProcessInclude(include, config);
|
||||||
query.AppendLiteral(includeJoin);
|
query.AppendLiteral(includeJoin);
|
||||||
string includeProjection = string.Join(string.Empty, includeConfig.Select(x => $", {x.Key}.*"));
|
|
||||||
query.Replace("/* includes */", $"{includeProjection:raw}", out bool replaced);
|
query.Replace("/* includes */", $"{includeProjection:raw}", out bool replaced);
|
||||||
if (!replaced)
|
if (!replaced)
|
||||||
throw new ArgumentException("Missing '/* includes */' placeholder in top level sql select to support includes.");
|
throw new ArgumentException("Missing '/* includes */' placeholder in top level sql select to support includes.");
|
||||||
@ -210,9 +213,7 @@ public static class DapperHelper
|
|||||||
// Build query and prepare to do the query/projections
|
// Build query and prepare to do the query/projections
|
||||||
IDapperSqlCommand cmd = query.Build();
|
IDapperSqlCommand cmd = query.Build();
|
||||||
string sql = cmd.Sql;
|
string sql = cmd.Sql;
|
||||||
List<Type> types = config.Select(x => x.Value)
|
List<Type> types = config.Select(x => x.Value).Concat(includeTypes).ToList();
|
||||||
.Concat(includeConfig.Select(x => x.Value))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Expand projections on every types received.
|
// Expand projections on every types received.
|
||||||
sql = Regex.Replace(sql, @"(,?) -- (\w+)( as (\w+))?", (match) =>
|
sql = Regex.Replace(sql, @"(,?) -- (\w+)( as (\w+))?", (match) =>
|
||||||
@ -296,6 +297,7 @@ public static class DapperHelper
|
|||||||
query += ProcessFilter(filter, config);
|
query += ProcessFilter(filter, config);
|
||||||
|
|
||||||
IDapperSqlCommand cmd = query.Build();
|
IDapperSqlCommand cmd = query.Build();
|
||||||
|
|
||||||
// language=postgreSQL
|
// language=postgreSQL
|
||||||
string sql = $"select count(*) from ({cmd.Sql}) as query";
|
string sql = $"select count(*) from ({cmd.Sql}) as query";
|
||||||
|
|
||||||
|
@ -18,53 +18,53 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Data.Common;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using System.IO;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
|
||||||
using Kyoo.Postgresql;
|
|
||||||
|
|
||||||
namespace Kyoo.Core.Controllers
|
namespace Kyoo.Core.Controllers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A local repository to handle shows
|
/// A local repository to handle shows
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class NewsRepository : LocalRepository<News>
|
public class NewsRepository : DapperRepository<INews>
|
||||||
{
|
{
|
||||||
public NewsRepository(DatabaseContext database, IThumbnailsManager thumbs)
|
// language=PostgreSQL
|
||||||
: base(database, thumbs)
|
protected override FormattableString Sql => $"""
|
||||||
|
select
|
||||||
|
e.*, -- Episode as e
|
||||||
|
m.*
|
||||||
|
/* includes */
|
||||||
|
from
|
||||||
|
episodes as e
|
||||||
|
full outer join (
|
||||||
|
select
|
||||||
|
* -- Movie
|
||||||
|
from
|
||||||
|
movies
|
||||||
|
) as m on false
|
||||||
|
""";
|
||||||
|
|
||||||
|
protected override Dictionary<string, Type> Config => new()
|
||||||
|
{
|
||||||
|
{ "e", typeof(Episode) },
|
||||||
|
{ "m", typeof(Movie) },
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override INews Mapper(List<object?> items)
|
||||||
|
{
|
||||||
|
if (items[0] is Episode episode && episode.Id != 0)
|
||||||
|
return episode;
|
||||||
|
if (items[1] is Movie movie && movie.Id != 0)
|
||||||
|
{
|
||||||
|
movie.Id = -movie.Id;
|
||||||
|
return movie;
|
||||||
|
}
|
||||||
|
throw new InvalidDataException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NewsRepository(DbConnection database)
|
||||||
|
: base(database)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<ICollection<News>> Search(string query, Include<News>? include = default)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<News> Create(News obj)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<News> CreateIfNotExists(News obj)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<News> Edit(News edited)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task<News> Patch(int id, Func<News, Task<bool>> patch)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task Delete(int id)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task Delete(string slug)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override Task Delete(News obj)
|
|
||||||
=> throw new InvalidOperationException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,9 @@ namespace Kyoo.Core.Api
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
[PartialPermission("LibraryItem")]
|
[PartialPermission("LibraryItem")]
|
||||||
[ApiDefinition("News", Group = ResourcesGroup)]
|
[ApiDefinition("News", Group = ResourcesGroup)]
|
||||||
public class NewsApi : CrudThumbsApi<News>
|
public class NewsApi : CrudThumbsApi<INews>
|
||||||
{
|
{
|
||||||
public NewsApi(IRepository<News> news, IThumbnailsManager thumbs)
|
public NewsApi(IRepository<INews> news, IThumbnailsManager thumbs)
|
||||||
: base(news, thumbs)
|
: base(news, thumbs)
|
||||||
{ }
|
{ }
|
||||||
}
|
}
|
||||||
|
@ -92,14 +92,6 @@ namespace Kyoo.Postgresql
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<PeopleRole> PeopleRoles { get; set; }
|
public DbSet<PeopleRole> PeopleRoles { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The list of new items (episodes and movies).
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This set is ready only, on most database this will be a view.
|
|
||||||
/// </remarks>
|
|
||||||
public DbSet<News> News { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a many to many link between two resources.
|
/// Add a many to many link between two resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -284,7 +276,6 @@ namespace Kyoo.Postgresql
|
|||||||
.WithMany("Users")
|
.WithMany("Users")
|
||||||
.UsingEntity(x => x.ToTable(LinkName<User, Show>()));
|
.UsingEntity(x => x.ToTable(LinkName<User, Show>()));
|
||||||
|
|
||||||
_HasMetadata<News>(modelBuilder);
|
|
||||||
_HasMetadata<Collection>(modelBuilder);
|
_HasMetadata<Collection>(modelBuilder);
|
||||||
_HasMetadata<Movie>(modelBuilder);
|
_HasMetadata<Movie>(modelBuilder);
|
||||||
_HasMetadata<Show>(modelBuilder);
|
_HasMetadata<Show>(modelBuilder);
|
||||||
@ -293,7 +284,6 @@ namespace Kyoo.Postgresql
|
|||||||
_HasMetadata<People>(modelBuilder);
|
_HasMetadata<People>(modelBuilder);
|
||||||
_HasMetadata<Studio>(modelBuilder);
|
_HasMetadata<Studio>(modelBuilder);
|
||||||
|
|
||||||
_HasImages<News>(modelBuilder);
|
|
||||||
_HasImages<Collection>(modelBuilder);
|
_HasImages<Collection>(modelBuilder);
|
||||||
_HasImages<Movie>(modelBuilder);
|
_HasImages<Movie>(modelBuilder);
|
||||||
_HasImages<Show>(modelBuilder);
|
_HasImages<Show>(modelBuilder);
|
||||||
@ -301,7 +291,6 @@ namespace Kyoo.Postgresql
|
|||||||
_HasImages<Episode>(modelBuilder);
|
_HasImages<Episode>(modelBuilder);
|
||||||
_HasImages<People>(modelBuilder);
|
_HasImages<People>(modelBuilder);
|
||||||
|
|
||||||
_HasAddedDate<News>(modelBuilder);
|
|
||||||
_HasAddedDate<Collection>(modelBuilder);
|
_HasAddedDate<Collection>(modelBuilder);
|
||||||
_HasAddedDate<Movie>(modelBuilder);
|
_HasAddedDate<Movie>(modelBuilder);
|
||||||
_HasAddedDate<Show>(modelBuilder);
|
_HasAddedDate<Show>(modelBuilder);
|
||||||
@ -347,14 +336,6 @@ namespace Kyoo.Postgresql
|
|||||||
|
|
||||||
modelBuilder.Entity<Movie>()
|
modelBuilder.Entity<Movie>()
|
||||||
.Ignore(x => x.Links);
|
.Ignore(x => x.Links);
|
||||||
modelBuilder.Entity<News>()
|
|
||||||
.Ignore(x => x.Links);
|
|
||||||
|
|
||||||
var builder = modelBuilder.Entity<News>()
|
|
||||||
.OwnsOne(x => x.Show);
|
|
||||||
builder.OwnsOne(x => x.Poster);
|
|
||||||
builder.OwnsOne(x => x.Thumbnail);
|
|
||||||
builder.OwnsOne(x => x.Logo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -370,10 +370,6 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.HasColumnType("genre[]")
|
.HasColumnType("genre[]")
|
||||||
.HasColumnName("genres");
|
.HasColumnName("genres");
|
||||||
|
|
||||||
b.Property<NewsKind>("Kind")
|
|
||||||
.HasColumnType("news_kind")
|
|
||||||
.HasColumnName("kind");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("name");
|
.HasColumnName("name");
|
||||||
|
@ -390,10 +390,6 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.HasColumnType("genre[]")
|
.HasColumnType("genre[]")
|
||||||
.HasColumnName("genres");
|
.HasColumnName("genres");
|
||||||
|
|
||||||
b.Property<NewsKind>("Kind")
|
|
||||||
.HasColumnType("news_kind")
|
|
||||||
.HasColumnName("kind");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("name");
|
.HasColumnName("name");
|
||||||
|
@ -387,10 +387,6 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.HasColumnType("genre[]")
|
.HasColumnType("genre[]")
|
||||||
.HasColumnName("genres");
|
.HasColumnName("genres");
|
||||||
|
|
||||||
b.Property<NewsKind>("Kind")
|
|
||||||
.HasColumnType("news_kind")
|
|
||||||
.HasColumnName("kind");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.HasColumnType("text")
|
.HasColumnType("text")
|
||||||
.HasColumnName("name");
|
.HasColumnName("name");
|
||||||
|
@ -18,11 +18,8 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using System.Reflection;
|
|
||||||
using EFCore.NamingConventions.Internal;
|
using EFCore.NamingConventions.Internal;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
using Kyoo.Utils;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
|
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
@ -50,7 +47,6 @@ namespace Kyoo.Postgresql
|
|||||||
{
|
{
|
||||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
|
NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
|
||||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<NewsKind>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -103,7 +99,6 @@ namespace Kyoo.Postgresql
|
|||||||
{
|
{
|
||||||
modelBuilder.HasPostgresEnum<Status>();
|
modelBuilder.HasPostgresEnum<Status>();
|
||||||
modelBuilder.HasPostgresEnum<Genre>();
|
modelBuilder.HasPostgresEnum<Genre>();
|
||||||
modelBuilder.HasPostgresEnum<NewsKind>();
|
|
||||||
|
|
||||||
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
|
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
|
||||||
.HasTranslation(args =>
|
.HasTranslation(args =>
|
||||||
|
@ -37,7 +37,10 @@ export const EpisodeP = BaseEpisodeP.and(
|
|||||||
|
|
||||||
show: ShowP.optional(),
|
show: ShowP.optional(),
|
||||||
}),
|
}),
|
||||||
);
|
).transform((x) => {
|
||||||
|
if (x.show && !x.thumbnail && x.show.thumbnail) x.thumbnail = x.show.thumbnail;
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to represent a single show's episode.
|
* A class to represent a single show's episode.
|
||||||
|
@ -20,9 +20,7 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { MovieP } from "./movie";
|
import { MovieP } from "./movie";
|
||||||
import { BaseEpisodeP } from "./episode.base";
|
import { EpisodeP } from "./episode";
|
||||||
import { ResourceP } from "../traits/resource";
|
|
||||||
import { withImages } from "../traits/images";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of item, ether a a movie or an episode.
|
* The type of item, ether a a movie or an episode.
|
||||||
@ -36,29 +34,7 @@ export const NewsP = z.union([
|
|||||||
/*
|
/*
|
||||||
* Either an episode
|
* Either an episode
|
||||||
*/
|
*/
|
||||||
BaseEpisodeP.and(
|
EpisodeP.and(z.object({ kind: z.literal(NewsKind.Episode) })),
|
||||||
z.object({
|
|
||||||
kind: z.literal(NewsKind.Episode),
|
|
||||||
show: withImages(
|
|
||||||
ResourceP.extend({
|
|
||||||
name: z.string(),
|
|
||||||
}),
|
|
||||||
"shows",
|
|
||||||
).transform((x) => {
|
|
||||||
if (!x.thumbnail && x.poster) {
|
|
||||||
x.thumbnail = { ...x.poster };
|
|
||||||
if (x.thumbnail) {
|
|
||||||
x.thumbnail.low = x.thumbnail.high;
|
|
||||||
x.thumbnail.medium = x.thumbnail.high;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return x;
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
).transform((x) => {
|
|
||||||
if (!x.thumbnail && x.show.thumbnail) x.thumbnail = x.show.thumbnail;
|
|
||||||
return x;
|
|
||||||
}),
|
|
||||||
/*
|
/*
|
||||||
* Or a Movie
|
* Or a Movie
|
||||||
*/
|
*/
|
||||||
|
@ -92,5 +92,6 @@ NewsList.query = (): QueryIdentifier<News> => ({
|
|||||||
params: {
|
params: {
|
||||||
// Limit the inital numbers of items
|
// Limit the inital numbers of items
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
fields: ["show"],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -149,13 +149,15 @@ export const ItemDetails = ({
|
|||||||
minHeight: px(50),
|
minHeight: px(50),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ScrollView horizontal {...css({ alignItems: "center" })}>
|
{(isLoading || genres) && (
|
||||||
{(genres || [...Array(3)])?.map((x, i) => (
|
<ScrollView horizontal {...css({ alignItems: "center" })}>
|
||||||
<Chip key={x ?? i} size="small" {...css({ mX: ts(0.5) })}>
|
{(genres || [...Array(3)])?.map((x, i) => (
|
||||||
{x ?? <Skeleton {...css({ width: rem(3), height: rem(0.8) })} />}
|
<Chip key={x ?? i} size="small" {...css({ mX: ts(0.5) })}>
|
||||||
</Chip>
|
{x ?? <Skeleton {...css({ width: rem(3), height: rem(0.8) })} />}
|
||||||
))}
|
</Chip>
|
||||||
</ScrollView>
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
)}
|
||||||
{playHref !== null && (
|
{playHref !== null && (
|
||||||
<IconFab
|
<IconFab
|
||||||
icon={PlayArrow}
|
icon={PlayArrow}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user