Split movies and shows, enable nullable handling (wip)

This commit is contained in:
Zoe Roux 2023-08-05 14:13:49 +09:00
parent 386c6bf268
commit 19ae15f53f
No known key found for this signature in database
78 changed files with 1976 additions and 7118 deletions

View File

@ -33,10 +33,11 @@
<Rule Id="SA1513" Action="None"/> <!-- ClosingBraceMustBeFollowedByBlankLine --> <Rule Id="SA1513" Action="None"/> <!-- ClosingBraceMustBeFollowedByBlankLine -->
</Rules> </Rules>
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.DocumentationRules"> <Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.CSharp.DocumentationRules">
<Rule Id="SA1600" Action="None" /> <!-- Elements Shuld be Documented -->
<Rule Id="SA1602" Action="None" /> <!-- Enums should be documented -->
<Rule Id="SA1642" Action="None" /> <!-- ConstructorSummaryDocumentationMustBeginWithStandardText --> <Rule Id="SA1642" Action="None" /> <!-- ConstructorSummaryDocumentationMustBeginWithStandardText -->
<Rule Id="SA1643" Action="None" /> <!-- DestructorSummaryDocumentationMustBeginWithStandardText --> <Rule Id="SA1643" Action="None" /> <!-- DestructorSummaryDocumentationMustBeginWithStandardText -->
<Rule Id="SA1623" Action="None" /> <!-- PropertySummaryDocumentationMustMatchAccessors --> <Rule Id="SA1623" Action="None" /> <!-- PropertySummaryDocumentationMustMatchAccessors -->
<Rule Id="SA1629" Action="None" /> <!-- DocumentationTextMustEndWithAPeriod --> <Rule Id="SA1629" Action="None" /> <!-- DocumentationTextMustEndWithAPeriod -->
<Rule Id="SA1600" Action="None" /> <!-- Elements Shuld be Documented -->
</Rules> </Rules>
</RuleSet> </RuleSet>

View File

@ -20,7 +20,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
@ -40,11 +39,6 @@ namespace Kyoo.Abstractions.Controllers
IRepository<T> GetRepository<T>() IRepository<T> GetRepository<T>()
where T : class, IResource; where T : class, IResource;
/// <summary>
/// The repository that handle libraries.
/// </summary>
ILibraryRepository LibraryRepository { get; }
/// <summary> /// <summary>
/// The repository that handle libraries items (a wrapper around shows and collections). /// The repository that handle libraries items (a wrapper around shows and collections).
/// </summary> /// </summary>
@ -55,6 +49,11 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
ICollectionRepository CollectionRepository { get; } ICollectionRepository CollectionRepository { get; }
/// <summary>
/// The repository that handle shows.
/// </summary>
IMovieRepository MovieRepository { get; }
/// <summary> /// <summary>
/// The repository that handle shows. /// The repository that handle shows.
/// </summary> /// </summary>
@ -80,11 +79,6 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
IStudioRepository StudioRepository { get; } IStudioRepository StudioRepository { get; }
/// <summary>
/// The repository that handle genres.
/// </summary>
IGenreRepository GenreRepository { get; }
/// <summary> /// <summary>
/// The repository that handle users. /// The repository that handle users.
/// </summary> /// </summary>
@ -97,7 +91,6 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the resource</typeparam> /// <typeparam name="T">The type of the resource</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get<T>(int id) Task<T> Get<T>(int id)
where T : class, IResource; where T : class, IResource;
@ -108,7 +101,6 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the resource</typeparam> /// <typeparam name="T">The type of the resource</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get<T>(string slug) Task<T> Get<T>(string slug)
where T : class, IResource; where T : class, IResource;
@ -119,7 +111,6 @@ namespace Kyoo.Abstractions.Controllers
/// <typeparam name="T">The type of the resource</typeparam> /// <typeparam name="T">The type of the resource</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The first resource found that match the where function</returns> /// <returns>The first resource found that match the where function</returns>
[ItemNotNull]
Task<T> Get<T>(Expression<Func<T, bool>> where) Task<T> Get<T>(Expression<Func<T, bool>> where)
where T : class, IResource; where T : class, IResource;
@ -130,7 +121,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="seasonNumber">The season's number</param> /// <param name="seasonNumber">The season's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The season found</returns> /// <returns>The season found</returns>
[ItemNotNull]
Task<Season> Get(int showID, int seasonNumber); Task<Season> Get(int showID, int seasonNumber);
/// <summary> /// <summary>
@ -140,7 +130,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="seasonNumber">The season's number</param> /// <param name="seasonNumber">The season's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The season found</returns> /// <returns>The season found</returns>
[ItemNotNull]
Task<Season> Get(string showSlug, int seasonNumber); Task<Season> Get(string showSlug, int seasonNumber);
/// <summary> /// <summary>
@ -151,7 +140,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="episodeNumber">The episode's number</param> /// <param name="episodeNumber">The episode's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The episode found</returns> /// <returns>The episode found</returns>
[ItemNotNull]
Task<Episode> Get(int showID, int seasonNumber, int episodeNumber); Task<Episode> Get(int showID, int seasonNumber, int episodeNumber);
/// <summary> /// <summary>
@ -162,7 +150,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="episodeNumber">The episode's number</param> /// <param name="episodeNumber">The episode's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The episode found</returns> /// <returns>The episode found</returns>
[ItemNotNull]
Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber); Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber);
/// <summary> /// <summary>
@ -171,8 +158,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="id">The id of the resource</param> /// <param name="id">The id of the resource</param>
/// <typeparam name="T">The type of the resource</typeparam> /// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemCanBeNull] Task<T?> GetOrDefault<T>(int id)
Task<T> GetOrDefault<T>(int id)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -181,8 +167,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="slug">The slug of the resource</param> /// <param name="slug">The slug of the resource</param>
/// <typeparam name="T">The type of the resource</typeparam> /// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemCanBeNull] Task<T?> GetOrDefault<T>(string slug)
Task<T> GetOrDefault<T>(string slug)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -192,8 +177,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param> /// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
/// <typeparam name="T">The type of the resource</typeparam> /// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The first resource found that match the where function</returns> /// <returns>The first resource found that match the where function</returns>
[ItemCanBeNull] Task<T?> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T>? sortBy = default)
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -202,8 +186,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="showID">The id of the show</param> /// <param name="showID">The id of the show</param>
/// <param name="seasonNumber">The season's number</param> /// <param name="seasonNumber">The season's number</param>
/// <returns>The season found</returns> /// <returns>The season found</returns>
[ItemCanBeNull] Task<Season?> GetOrDefault(int showID, int seasonNumber);
Task<Season> GetOrDefault(int showID, int seasonNumber);
/// <summary> /// <summary>
/// Get a season from it's show slug and it's seasonNumber or null if it is not found. /// Get a season from it's show slug and it's seasonNumber or null if it is not found.
@ -211,8 +194,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="showSlug">The slug of the show</param> /// <param name="showSlug">The slug of the show</param>
/// <param name="seasonNumber">The season's number</param> /// <param name="seasonNumber">The season's number</param>
/// <returns>The season found</returns> /// <returns>The season found</returns>
[ItemCanBeNull] Task<Season?> GetOrDefault(string showSlug, int seasonNumber);
Task<Season> GetOrDefault(string showSlug, int seasonNumber);
/// <summary> /// <summary>
/// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found. /// Get a episode from it's showID, it's seasonNumber and it's episode number or null if it is not found.
@ -221,8 +203,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="seasonNumber">The season's number</param> /// <param name="seasonNumber">The season's number</param>
/// <param name="episodeNumber">The episode's number</param> /// <param name="episodeNumber">The episode's number</param>
/// <returns>The episode found</returns> /// <returns>The episode found</returns>
[ItemCanBeNull] Task<Episode?> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
/// <summary> /// <summary>
/// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found. /// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found.
@ -231,8 +212,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="seasonNumber">The season's number</param> /// <param name="seasonNumber">The season's number</param>
/// <param name="episodeNumber">The episode's number</param> /// <param name="episodeNumber">The episode's number</param>
/// <returns>The episode found</returns> /// <returns>The episode found</returns>
[ItemCanBeNull] Task<Episode?> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
/// <summary> /// <summary>
/// Load a related resource /// Load a related resource
@ -248,7 +228,7 @@ namespace Kyoo.Abstractions.Controllers
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
/// <seealso cref="Load{T}(T, string, bool)"/> /// <seealso cref="Load{T}(T, string, bool)"/>
/// <seealso cref="Load(IResource, string, bool)"/> /// <seealso cref="Load(IResource, string, bool)"/>
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member, bool force = false) Task<T> Load<T, T2>(T obj, Expression<Func<T, T2>> member, bool force = false)
where T : class, IResource where T : class, IResource
where T2 : class, IResource; where T2 : class, IResource;
@ -266,7 +246,7 @@ namespace Kyoo.Abstractions.Controllers
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T}(T, string, bool)"/> /// <seealso cref="Load{T}(T, string, bool)"/>
/// <seealso cref="Load(IResource, string, bool)"/> /// <seealso cref="Load(IResource, string, bool)"/>
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false) Task<T> Load<T, T2>(T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false)
where T : class, IResource where T : class, IResource
where T2 : class; where T2 : class;
@ -283,7 +263,7 @@ namespace Kyoo.Abstractions.Controllers
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,T2}}, bool)"/>
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
/// <seealso cref="Load(IResource, string, bool)"/> /// <seealso cref="Load(IResource, string, bool)"/>
Task<T> Load<T>([NotNull] T obj, string memberName, bool force = false) Task<T> Load<T>(T obj, string memberName, bool force = false)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -298,35 +278,7 @@ namespace Kyoo.Abstractions.Controllers
/// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/> /// <seealso cref="Load{T,T2}(T, Expression{Func{T,ICollection{T2}}}, bool)"/>
/// <seealso cref="Load{T}(T, string, bool)"/> /// <seealso cref="Load{T}(T, string, bool)"/>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Load([NotNull] IResource obj, string memberName, bool force = false); Task Load(IResource obj, string memberName, bool force = false);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="id">The ID of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="slug">The slug of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default);
/// <summary> /// <summary>
/// Get people's roles from a show. /// Get people's roles from a show.
@ -338,9 +290,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception> /// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID, Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary> /// <summary>
/// Get people's roles from a show. /// Get people's roles from a show.
@ -352,9 +304,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception> /// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug, Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
@ -366,9 +318,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception> /// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetRolesFromPeople(int id, Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
@ -380,27 +332,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception> /// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug, Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary>
/// Setup relations between a show, a library and a collection
/// </summary>
/// <param name="showID">The show's ID to setup relations with</param>
/// <param name="libraryID">The library's ID to setup relations with (optional)</param>
/// <param name="collectionID">The collection's ID to setup relations with (optional)</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task AddShowLink(int showID, int? libraryID, int? collectionID);
/// <summary>
/// Setup relations between a show, a library and a collection
/// </summary>
/// <param name="show">The show to setup relations with</param>
/// <param name="library">The library to setup relations with (optional)</param>
/// <param name="collection">The collection to setup relations with (optional)</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task AddShowLink([NotNull] Show show, Library library, Collection collection);
/// <summary> /// <summary>
/// Get all resources with filters /// Get all resources with filters
@ -410,9 +344,9 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="limit">How many items to return and where to start</param> /// <param name="limit">How many items to return and where to start</param>
/// <typeparam name="T">The type of resources to load</typeparam> /// <typeparam name="T">The type of resources to load</typeparam>
/// <returns>A list of resources that match every filters</returns> /// <returns>A list of resources that match every filters</returns>
Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>> where = null, Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>>? where = null,
Sort<T> sort = default, Sort<T>? sort = default,
Pagination limit = default) Pagination? limit = default)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -421,7 +355,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="where">A filter function</param> /// <param name="where">A filter function</param>
/// <typeparam name="T">The type of resources to load</typeparam> /// <typeparam name="T">The type of resources to load</typeparam>
/// <returns>A list of resources that match every filters</returns> /// <returns>A list of resources that match every filters</returns>
Task<int> GetCount<T>(Expression<Func<T, bool>> where = null) Task<int> GetCount<T>(Expression<Func<T, bool>>? where = null)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -439,7 +373,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="item">The item to register</param> /// <param name="item">The item to register</param>
/// <typeparam name="T">The type of resource</typeparam> /// <typeparam name="T">The type of resource</typeparam>
/// <returns>The resource registers and completed by database's information (related items and so on)</returns> /// <returns>The resource registers and completed by database's information (related items and so on)</returns>
Task<T> Create<T>([NotNull] T item) Task<T> Create<T>(T item)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>
@ -448,7 +382,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="item">The item to register</param> /// <param name="item">The item to register</param>
/// <typeparam name="T">The type of resource</typeparam> /// <typeparam name="T">The type of resource</typeparam>
/// <returns>The newly created item or the existing value if it existed.</returns> /// <returns>The newly created item or the existing value if it existed.</returns>
Task<T> CreateIfNotExists<T>([NotNull] T item) Task<T> CreateIfNotExists<T>(T item)
where T : class, IResource; where T : class, IResource;
/// <summary> /// <summary>

View File

@ -20,7 +20,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Exceptions;
@ -45,7 +44,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="id">The id of the resource</param> /// <param name="id">The id of the resource</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception> /// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get(int id); Task<T> Get(int id);
/// <summary> /// <summary>
@ -54,7 +52,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="slug">The slug of the resource</param> /// <param name="slug">The slug of the resource</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception> /// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get(string slug); Task<T> Get(string slug);
/// <summary> /// <summary>
@ -63,7 +60,6 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="where">A predicate to filter the resource.</param> /// <param name="where">A predicate to filter the resource.</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception> /// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get(Expression<Func<T, bool>> where); Task<T> Get(Expression<Func<T, bool>> where);
/// <summary> /// <summary>
@ -71,16 +67,14 @@ namespace Kyoo.Abstractions.Controllers
/// </summary> /// </summary>
/// <param name="id">The id of the resource</param> /// <param name="id">The id of the resource</param>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemCanBeNull] Task<T?> GetOrDefault(int id);
Task<T> GetOrDefault(int id);
/// <summary> /// <summary>
/// Get a resource from it's slug or null if it is not found. /// Get a resource from it's slug or null if it is not found.
/// </summary> /// </summary>
/// <param name="slug">The slug of the resource</param> /// <param name="slug">The slug of the resource</param>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemCanBeNull] Task<T?> GetOrDefault(string slug);
Task<T> GetOrDefault(string slug);
/// <summary> /// <summary>
/// Get the first resource that match the predicate or null if it is not found. /// Get the first resource that match the predicate or null if it is not found.
@ -88,15 +82,13 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="where">A predicate to filter the resource.</param> /// <param name="where">A predicate to filter the resource.</param>
/// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param> /// <param name="sortBy">A custom sort method to handle cases where multiples items match the filters.</param>
/// <returns>The resource found</returns> /// <returns>The resource found</returns>
[ItemCanBeNull] Task<T?> GetOrDefault(Expression<Func<T, bool>> where, Sort<T>? sortBy = default);
Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default);
/// <summary> /// <summary>
/// Search for resources. /// Search for resources.
/// </summary> /// </summary>
/// <param name="query">The query string.</param> /// <param name="query">The query string.</param>
/// <returns>A list of resources found</returns> /// <returns>A list of resources found</returns>
[ItemNotNull]
Task<ICollection<T>> Search(string query); Task<ICollection<T>> Search(string query);
/// <summary> /// <summary>
@ -106,33 +98,30 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="sort">Sort information about the query (sort by, sort order)</param> /// <param name="sort">Sort information about the query (sort by, sort order)</param>
/// <param name="limit">How pagination should be done (where to start and how many to return)</param> /// <param name="limit">How pagination should be done (where to start and how many to return)</param>
/// <returns>A list of resources that match every filters</returns> /// <returns>A list of resources that match every filters</returns>
[ItemNotNull] Task<ICollection<T>> GetAll(Expression<Func<T, bool>>? where = null,
Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null, Sort<T>? sort = default,
Sort<T> sort = default, Pagination? limit = default);
Pagination limit = default);
/// <summary> /// <summary>
/// Get the number of resources that match the filter's predicate. /// Get the number of resources that match the filter's predicate.
/// </summary> /// </summary>
/// <param name="where">A filter predicate</param> /// <param name="where">A filter predicate</param>
/// <returns>How many resources matched that filter</returns> /// <returns>How many resources matched that filter</returns>
Task<int> GetCount(Expression<Func<T, bool>> where = null); Task<int> GetCount(Expression<Func<T, bool>>? where = null);
/// <summary> /// <summary>
/// Create a new resource. /// Create a new resource.
/// </summary> /// </summary>
/// <param name="obj">The item to register</param> /// <param name="obj">The item to register</param>
/// <returns>The resource registers and completed by database's information (related items and so on)</returns> /// <returns>The resource registers and completed by database's information (related items and so on)</returns>
[ItemNotNull] Task<T> Create(T obj);
Task<T> Create([NotNull] T obj);
/// <summary> /// <summary>
/// Create a new resource if it does not exist already. If it does, the existing value is returned instead. /// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
/// </summary> /// </summary>
/// <param name="obj">The object to create</param> /// <param name="obj">The object to create</param>
/// <returns>The newly created item or the existing value if it existed.</returns> /// <returns>The newly created item or the existing value if it existed.</returns>
[ItemNotNull] Task<T> CreateIfNotExists(T obj);
Task<T> CreateIfNotExists([NotNull] T obj);
/// <summary> /// <summary>
/// Called when a resource has been created. /// Called when a resource has been created.
@ -146,8 +135,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param> /// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns> /// <returns>The resource edited and completed by database's information (related items and so on)</returns>
[ItemNotNull] Task<T> Edit(T edited, bool resetOld);
Task<T> Edit([NotNull] T edited, bool resetOld);
/// <summary> /// <summary>
/// Called when a resource has been edited. /// Called when a resource has been edited.
@ -176,14 +164,14 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="obj">The resource to delete</param> /// <param name="obj">The resource to delete</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception> /// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Delete([NotNull] T obj); Task Delete(T obj);
/// <summary> /// <summary>
/// Delete all resources that match the predicate. /// Delete all resources that match the predicate.
/// </summary> /// </summary>
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param> /// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task DeleteAll([NotNull] Expression<Func<T, bool>> where); Task DeleteAll(Expression<Func<T, bool>> where);
/// <summary> /// <summary>
/// Called when a resource has been edited. /// Called when a resource has been edited.
@ -202,21 +190,16 @@ namespace Kyoo.Abstractions.Controllers
Type RepositoryType { get; } Type RepositoryType { get; }
} }
/// <summary>
/// A repository to handle shows.
/// </summary>
public interface IMovieRepository : IRepository<Movie> { }
/// <summary> /// <summary>
/// A repository to handle shows. /// A repository to handle shows.
/// </summary> /// </summary>
public interface IShowRepository : IRepository<Show> public interface IShowRepository : IRepository<Show>
{ {
/// <summary>
/// Link a show to a collection and/or a library. The given show is now part of those containers.
/// If both a library and a collection are given, the collection is added to the library too.
/// </summary>
/// <param name="showID">The ID of the show</param>
/// <param name="libraryID">The ID of the library (optional)</param>
/// <param name="collectionID">The ID of the collection (optional)</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task AddShowLink(int showID, int? libraryID, int? collectionID);
/// <summary> /// <summary>
/// Get a show's slug from it's ID. /// Get a show's slug from it's ID.
/// </summary> /// </summary>
@ -330,55 +313,16 @@ namespace Kyoo.Abstractions.Controllers
Task<Episode> GetAbsolute(string showSlug, int absoluteNumber); Task<Episode> GetAbsolute(string showSlug, int absoluteNumber);
} }
/// <summary>
/// A repository to handle libraries.
/// </summary>
public interface ILibraryRepository : IRepository<Library> { }
/// <summary> /// <summary>
/// A repository to handle library items (A wrapper around shows and collections). /// A repository to handle library items (A wrapper around shows and collections).
/// </summary> /// </summary>
public interface ILibraryItemRepository : IRepository<LibraryItem> public interface ILibraryItemRepository : IRepository<ILibraryItem> { }
{
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="id">The ID of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default);
/// <summary>
/// Get items (A wrapper around shows or collections) from a library.
/// </summary>
/// <param name="slug">The slug of the library</param>
/// <param name="where">A filter function</param>
/// <param name="sort">Sort information (sort order and sort by)</param>
/// <param name="limit">How many items to return and where to start</param>
/// <exception cref="ItemNotFoundException">No library exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns>
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default);
}
/// <summary> /// <summary>
/// A repository for collections /// A repository for collections
/// </summary> /// </summary>
public interface ICollectionRepository : IRepository<Collection> { } public interface ICollectionRepository : IRepository<Collection> { }
/// <summary>
/// A repository for genres.
/// </summary>
public interface IGenreRepository : IRepository<Genre> { }
/// <summary> /// <summary>
/// A repository for studios. /// A repository for studios.
/// </summary> /// </summary>
@ -399,9 +343,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception> /// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(int showID, Task<ICollection<PeopleRole>> GetFromShow(int showID,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary> /// <summary>
/// Get people's roles from a show. /// Get people's roles from a show.
@ -413,9 +357,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception> /// <exception cref="ItemNotFoundException">No <see cref="Show"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromShow(string showSlug, Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
@ -427,9 +371,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception> /// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given ID.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(int id, Task<ICollection<PeopleRole>> GetFromPeople(int id,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
/// <summary> /// <summary>
/// Get people's roles from a person. /// Get people's roles from a person.
@ -441,9 +385,9 @@ namespace Kyoo.Abstractions.Controllers
/// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception> /// <exception cref="ItemNotFoundException">No <see cref="People"/> exist with the given slug.</exception>
/// <returns>A list of items that match every filters</returns> /// <returns>A list of items that match every filters</returns>
Task<ICollection<PeopleRole>> GetFromPeople(string slug, Task<ICollection<PeopleRole>> GetFromPeople(string slug,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole> sort = default, Sort<PeopleRole>? sort = default,
Pagination limit = default); Pagination? limit = default);
} }
/// <summary> /// <summary>

View File

@ -3,6 +3,7 @@
<Title>Kyoo.Abstractions</Title> <Title>Kyoo.Abstractions</Title>
<Description>Base package to create plugins for Kyoo.</Description> <Description>Base package to create plugins for Kyoo.</Description>
<RootNamespace>Kyoo.Abstractions</RootNamespace> <RootNamespace>Kyoo.Abstractions</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -32,7 +32,7 @@ namespace Kyoo.Abstractions.Models.Attributes
/// <summary> /// <summary>
/// The public name of this api. /// The public name of this api.
/// </summary> /// </summary>
[NotNull] public string Name { get; } public string Name { get; }
/// <summary> /// <summary>
/// The name of the group in witch this API is. You can also specify a custom sort order using the following /// The name of the group in witch this API is. You can also specify a custom sort order using the following
@ -45,7 +45,7 @@ namespace Kyoo.Abstractions.Models.Attributes
/// Create a new <see cref="ApiDefinitionAttribute"/>. /// Create a new <see cref="ApiDefinitionAttribute"/>.
/// </summary> /// </summary>
/// <param name="name">The name of the api that will be used on the documentation page.</param> /// <param name="name">The name of the api that will be used on the documentation page.</param>
public ApiDefinitionAttribute([NotNull] string name) public ApiDefinitionAttribute(string name)
{ {
if (name == null) if (name == null)
throw new ArgumentNullException(nameof(name)); throw new ArgumentNullException(nameof(name));

View File

@ -60,7 +60,7 @@ namespace Kyoo.Abstractions.Models
/// </param> /// </param>
/// <param name="type">The type of the object</param> /// <param name="type">The type of the object</param>
/// <returns>The list of configuration reference a type has.</returns> /// <returns>The list of configuration reference a type has.</returns>
public static IEnumerable<ConfigurationReference> CreateReference(string path, [NotNull] Type type) public static IEnumerable<ConfigurationReference> CreateReference(string path, Type type)
{ {
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));

View File

@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models.Exceptions
/// Create a new <see cref="DuplicatedItemException"/> with the default message. /// Create a new <see cref="DuplicatedItemException"/> with the default message.
/// </summary> /// </summary>
/// <param name="existing">The existing object.</param> /// <param name="existing">The existing object.</param>
public DuplicatedItemException(object existing = null) public DuplicatedItemException(object? existing = null)
: base("Already exists in the database.") : base("Already exists in the database.")
{ {
Existing = existing; Existing = existing;

View File

@ -19,13 +19,27 @@
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
/// <summary> /// <summary>
/// An interface to represent resources that should have a link field in their return values (like videos). /// A genre that allow one to specify categories for shows.
/// </summary> /// </summary>
public interface ILink public enum Genre
{ {
/// <summary> Action,
/// The link to return, in most cases this should be a string. Adventure,
/// </summary> Animation,
public object Link { get; } Comedy,
Crime,
Documentary,
Drama,
Family,
Fantasy,
History,
Horror,
Music,
Mystery,
Romance,
ScienceFiction,
Thriller,
War,
Western,
} }
} }

View File

@ -17,16 +17,13 @@
// 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 System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
/// <summary> /// <summary>
/// The type of item, ether a show, a movie or a collection. /// The type of item, ether a show, a movie or a collection.
/// </summary> /// </summary>
public enum ItemType public enum ItemKind
{ {
/// <summary> /// <summary>
/// The <see cref="LibraryItem"/> is a <see cref="Show"/>. /// The <see cref="LibraryItem"/> is a <see cref="Show"/>.
@ -49,138 +46,67 @@ namespace Kyoo.Abstractions.Models
/// A type union between <see cref="Show"/> and <see cref="Collection"/>. /// A type union between <see cref="Show"/> and <see cref="Collection"/>.
/// This is used to list content put inside a library. /// This is used to list content put inside a library.
/// </summary> /// </summary>
public class LibraryItem : CustomTypeDescriptor, IResource, IThumbnails public interface ILibraryItem : IResource
{ {
/// <inheritdoc /> /// <summary>
/// Is the item a collection, a movie or a show?
/// </summary>
public ItemKind Kind { get; }
public string Name { get; }
public DateTime? AirDate { get; }
public Image Poster { get; }
}
public class BagItem : ILibraryItem
{
public ItemKind Kind { get; }
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc />
public string Slug { get; set; } public string Slug { get; set; }
/// <summary> public string Name { get; set; }
/// The title of the show or collection.
/// </summary>
public string Title { get; set; }
/// <summary> public DateTime? AirDate { get; set; }
/// The summary of the show or collection.
/// </summary>
public string Overview { get; set; }
/// <summary>
/// Is this show airing, not aired yet or finished? This is only applicable for shows.
/// </summary>
public Status? Status { get; set; }
/// <summary>
/// The date this show or collection started airing. It can be null if this is unknown.
/// </summary>
public DateTime? StartAir { get; set; }
/// <summary>
/// The date this show or collection finished airing.
/// It must be after the <see cref="StartAir"/> but can be the same (example: for movies).
/// It can also be null if this is unknown.
/// </summary>
public DateTime? EndAir { get; set; }
/// <inheritdoc />
public Image Poster { get; set; } public Image Poster { get; set; }
/// <inheritdoc /> public object Rest { get; set; }
public Image Thumbnail { get; set; }
/// <inheritdoc /> public ILibraryItem ToItem()
public Image Logo { get; set; }
/// <summary>
/// The type of this item (ether a collection, a show or a movie).
/// </summary>
public ItemType Type { get; set; }
/// <summary>
/// Create a new, empty <see cref="LibraryItem"/>.
/// </summary>
public LibraryItem() { }
/// <summary>
/// Create a <see cref="LibraryItem"/> from a show.
/// </summary>
/// <param name="show">The show that this library item should represent.</param>
public LibraryItem(Show show)
{ {
ID = show.ID; return Kind switch
Slug = show.Slug;
Title = show.Title;
Overview = show.Overview;
Status = show.Status;
StartAir = show.StartAir;
EndAir = show.EndAir;
Poster = show.Poster;
Thumbnail = show.Thumbnail;
Logo = show.Logo;
Type = show.IsMovie ? ItemType.Movie : ItemType.Show;
}
/// <summary>
/// Create a <see cref="LibraryItem"/> from a collection
/// </summary>
/// <param name="collection">The collection that this library item should represent.</param>
public LibraryItem(Collection collection)
{ {
ID = -collection.ID; ItemKind.Movie => Rest as MovieItem,
Slug = collection.Slug; ItemKind.Show => Rest as ShowItem,
Title = collection.Name; ItemKind.Collection => Rest as CollectionItem,
Overview = collection.Overview;
Status = Models.Status.Unknown;
StartAir = null;
EndAir = null;
Poster = collection.Poster;
Thumbnail = collection.Thumbnail;
Logo = collection.Logo;
Type = ItemType.Collection;
}
/// <summary>
/// An expression to create a <see cref="LibraryItem"/> representing a show.
/// </summary>
public static Expression<Func<Show, LibraryItem>> FromShow => x => new LibraryItem
{
ID = x.ID,
Slug = x.Slug,
Title = x.Title,
Overview = x.Overview,
Status = x.Status,
StartAir = x.StartAir,
EndAir = x.EndAir,
Poster = x.Poster,
Thumbnail = x.Thumbnail,
Logo = x.Logo,
Type = x.IsMovie ? ItemType.Movie : ItemType.Show
}; };
}
}
/// <summary> public sealed class ShowItem : Show, ILibraryItem
/// An expression to create a <see cref="LibraryItem"/> representing a collection.
/// </summary>
public static Expression<Func<Collection, LibraryItem>> FromCollection => x => new LibraryItem
{ {
ID = -x.ID,
Slug = x.Slug,
Title = x.Name,
Overview = x.Overview,
Status = Models.Status.Unknown,
StartAir = null,
EndAir = null,
Poster = x.Poster,
Thumbnail = x.Thumbnail,
Logo = x.Logo,
Type = ItemType.Collection
};
/// <inheritdoc/> /// <inheritdoc/>
public override string GetClassName() public ItemKind Kind => ItemKind.Show;
public DateTime? AirDate => StartAir;
}
public sealed class MovieItem : Movie, ILibraryItem
{ {
return Type.ToString(); /// <inheritdoc/>
public ItemKind Kind => ItemKind.Movie;
} }
public sealed class CollectionItem : Collection, ILibraryItem
{
/// <inheritdoc/>
public ItemKind Kind => ItemKind.Collection;
public DateTime? AirDate => null;
} }
} }

View File

@ -53,12 +53,16 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The ID of the Show where the People playing in. /// The ID of the Show where the People playing in.
/// </summary> /// </summary>
public int ShowID { get; set; } public int? ShowID { get; set; }
/// <summary> /// <summary>
/// The show where the People played in. /// The show where the People played in.
/// </summary> /// </summary>
public Show Show { get; set; } public Show? Show { get; set; }
public int? MovieID { get; set; }
public Movie? Movie { get; set; }
/// <summary> /// <summary>
/// The type of work the person has done for the show. /// The type of work the person has done for the show.

View File

@ -17,7 +17,10 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
@ -31,7 +34,7 @@ namespace Kyoo.Abstractions.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public string Slug { get; set; } [MaxLength(256)] public string Slug { get; set; }
/// <summary> /// <summary>
/// The name of this collection. /// The name of this collection.
@ -39,30 +42,39 @@ namespace Kyoo.Abstractions.Models
public string Name { get; set; } public string Name { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Poster { get; set; } public Image? Poster { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Thumbnail { get; set; } public Image? Thumbnail { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Logo { get; set; } public Image? Logo { get; set; }
/// <summary> /// <summary>
/// The description of this collection. /// The description of this collection.
/// </summary> /// </summary>
public string Overview { get; set; } public string? Overview { get; set; }
/// <summary>
/// The list of movies contained in this collection.
/// </summary>
[LoadableRelation] public ICollection<Movie>? Movies { get; set; }
/// <summary> /// <summary>
/// The list of shows contained in this collection. /// The list of shows contained in this collection.
/// </summary> /// </summary>
[LoadableRelation] public ICollection<Show> Shows { get; set; } [LoadableRelation] public ICollection<Show>? Shows { get; set; }
/// <summary>
/// The list of libraries that contains this collection.
/// </summary>
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<string, MetadataID> ExternalId { get; set; } public Dictionary<string, MetadataID> ExternalId { get; set; } = new();
public Collection() { }
[JsonConstructor]
public Collection(string name)
{
Slug = Utility.ToSlug(name);
Name = name;
}
} }
} }

View File

@ -18,6 +18,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
@ -35,24 +36,19 @@ namespace Kyoo.Abstractions.Models
/// <inheritdoc /> /// <inheritdoc />
[Computed] [Computed]
[MaxLength(256)]
public string Slug public string Slug
{ {
get get
{ {
if (ShowSlug != null || Show?.Slug != null) if (ShowSlug != null || Show?.Slug != null)
return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
return ShowID != 0 return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
? GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber)
: null;
} }
[UsedImplicitly] [UsedImplicitly]
[NotNull]
private set private set
{ {
if (value == null)
throw new ArgumentNullException(nameof(value));
Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)e(?<episode>\d+)"); Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)e(?<episode>\d+)");
if (match.Success) if (match.Success)
@ -80,7 +76,7 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed. /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.
/// </summary> /// </summary>
[SerializeIgnore] public string ShowSlug { private get; set; } [SerializeIgnore] public string? ShowSlug { private get; set; }
/// <summary> /// <summary>
/// The ID of the Show containing this episode. /// The ID of the Show containing this episode.
@ -90,7 +86,7 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The show that contains this episode. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>. /// The show that contains this episode. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary> /// </summary>
[LoadableRelation(nameof(ShowID))] public Show Show { get; set; } [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; }
/// <summary> /// <summary>
/// The ID of the Season containing this episode. /// The ID of the Season containing this episode.
@ -105,7 +101,7 @@ namespace Kyoo.Abstractions.Models
/// This can be null if the season is unknown and the episode is only identified /// This can be null if the season is unknown and the episode is only identified
/// by it's <see cref="AbsoluteNumber"/>. /// by it's <see cref="AbsoluteNumber"/>.
/// </remarks> /// </remarks>
[LoadableRelation(nameof(SeasonID))] public Season Season { get; set; } [LoadableRelation(nameof(SeasonID))] public Season? Season { get; set; }
/// <summary> /// <summary>
/// The season in witch this episode is in. /// The season in witch this episode is in.
@ -130,12 +126,12 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The title of this episode. /// The title of this episode.
/// </summary> /// </summary>
public string Title { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// The overview of this episode. /// The overview of this episode.
/// </summary> /// </summary>
public string Overview { get; set; } public string? Overview { get; set; }
/// <summary> /// <summary>
/// The release date of this episode. It can be null if unknown. /// The release date of this episode. It can be null if unknown.
@ -143,16 +139,16 @@ namespace Kyoo.Abstractions.Models
public DateTime? ReleaseDate { get; set; } public DateTime? ReleaseDate { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Poster { get; set; } public Image? Poster { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Thumbnail { get; set; } public Image? Thumbnail { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Logo { get; set; } public Image? Logo { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<string, MetadataID> ExternalId { get; set; } public Dictionary<string, MetadataID> ExternalId { get; set; } = new();
/// <summary> /// <summary>
/// Get the slug of an episode. /// Get the slug of an episode.
@ -172,7 +168,7 @@ namespace Kyoo.Abstractions.Models
/// </param> /// </param>
/// <returns>The slug corresponding to the given arguments</returns> /// <returns>The slug corresponding to the given arguments</returns>
/// <exception cref="ArgumentNullException">The given show slug was null.</exception> /// <exception cref="ArgumentNullException">The given show slug was null.</exception>
public static string GetSlug([NotNull] string showSlug, public static string GetSlug(string showSlug,
int? seasonNumber, int? seasonNumber,
int? episodeNumber, int? episodeNumber,
int? absoluteNumber = null) int? absoluteNumber = null)

View File

@ -1,62 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
namespace Kyoo.Abstractions.Models
{
/// <summary>
/// A genre that allow one to specify categories for shows.
/// </summary>
public class Genre : IResource
{
/// <inheritdoc />
public int ID { get; set; }
/// <inheritdoc />
public string Slug { get; set; }
/// <summary>
/// The name of this genre.
/// </summary>
public string Name { get; set; }
/// <summary>
/// The list of shows that have this genre.
/// </summary>
[LoadableRelation] public ICollection<Show> Shows { get; set; }
/// <summary>
/// Create a new, empty <see cref="Genre"/>.
/// </summary>
public Genre() { }
/// <summary>
/// Create a new <see cref="Genre"/> and specify it's <see cref="Name"/>.
/// The <see cref="Slug"/> is automatically calculated from it's name.
/// </summary>
/// <param name="name">The name of this genre.</param>
public Genre(string name)
{
Slug = Utility.ToSlug(name);
Name = name;
}
}
}

View File

@ -16,6 +16,7 @@
// 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.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
@ -42,6 +43,7 @@ namespace Kyoo.Abstractions.Models
/// There is no setter for a slug since it can be computed from other fields. /// There is no setter for a slug since it can be computed from other fields.
/// For example, a season slug is {ShowSlug}-s{SeasonNumber}. /// For example, a season slug is {ShowSlug}-s{SeasonNumber}.
/// </remarks> /// </remarks>
[MaxLength(256)]
public string Slug { get; } public string Slug { get; }
} }
} }

View File

@ -28,18 +28,18 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// A poster is a 9/16 format image with the cover of the resource. /// A poster is a 9/16 format image with the cover of the resource.
/// </summary> /// </summary>
public Image Poster { get; set; } public Image? Poster { get; set; }
/// <summary> /// <summary>
/// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually /// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually
/// is not an official image. /// is not an official image.
/// </summary> /// </summary>
public Image Thumbnail { get; set; } public Image? Thumbnail { get; set; }
/// <summary> /// <summary>
/// A logo is a small image representing the resource. /// A logo is a small image representing the resource.
/// </summary> /// </summary>
public Image Logo { get; set; } public Image? Logo { get; set; }
} }
public class Image public class Image

View File

@ -0,0 +1,140 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models
{
/// <summary>
/// A series or a movie.
/// </summary>
public class Movie : IResource, IMetadata, IOnMerge, IThumbnails
{
/// <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>
/// The date this movie aired.
/// </summary>
public DateTime? AirDate { 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 ID of the Studio that made this show.
/// </summary>
[SerializeIgnore] public int? StudioID { get; set; }
/// <summary>
/// The Studio that made this show.
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary>
[LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; }
/// <summary>
/// The list of people that made this show.
/// </summary>
[LoadableRelation][EditableRelation] public ICollection<PeopleRole>? People { get; set; }
/// <summary>
/// The list of collections that contains this show.
/// </summary>
[LoadableRelation] public ICollection<Collection>? Collections { get; set; }
/// <inheritdoc />
public void OnMerge(object merged)
{
foreach (PeopleRole link in People)
link.Movie = this;
}
public Movie() { }
[JsonConstructor]
public Movie(string name)
{
Slug = Utility.ToSlug(name);
Name = name;
}
}
}

View File

@ -17,7 +17,10 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
@ -30,6 +33,7 @@ namespace Kyoo.Abstractions.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; } public string Slug { get; set; }
/// <summary> /// <summary>
@ -38,20 +42,29 @@ namespace Kyoo.Abstractions.Models
public string Name { get; set; } public string Name { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Poster { get; set; } public Image? Poster { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Thumbnail { get; set; } public Image? Thumbnail { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Logo { get; set; } public Image? Logo { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<string, MetadataID> ExternalId { get; set; } public Dictionary<string, MetadataID> ExternalId { get; set; } = new();
/// <summary> /// <summary>
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information. /// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.
/// </summary> /// </summary>
[EditableRelation][LoadableRelation] public ICollection<PeopleRole> Roles { get; set; } [EditableRelation][LoadableRelation] public ICollection<PeopleRole>? Roles { get; set; }
public People() { }
[JsonConstructor]
public People(string name)
{
Slug = Utility.ToSlug(name);
Name = name;
}
} }
} }

View File

@ -18,6 +18,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
@ -35,6 +36,7 @@ namespace Kyoo.Abstractions.Models
/// <inheritdoc /> /// <inheritdoc />
[Computed] [Computed]
[MaxLength(256)]
public string Slug public string Slug
{ {
get get
@ -48,7 +50,7 @@ namespace Kyoo.Abstractions.Models
[NotNull] [NotNull]
private set private set
{ {
Match match = Regex.Match(value ?? string.Empty, @"(?<show>.+)-s(?<season>\d+)"); Match match = Regex.Match(value, @"(?<show>.+)-s(?<season>\d+)");
if (!match.Success) if (!match.Success)
throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}"); throw new ArgumentException("Invalid season slug. Format: {showSlug}-s{seasonNumber}");
@ -60,7 +62,7 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The slug of the Show that contain this episode. If this is not set, this season is ill-formed. /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.
/// </summary> /// </summary>
[SerializeIgnore] public string ShowSlug { private get; set; } [SerializeIgnore] public string? ShowSlug { private get; set; }
/// <summary> /// <summary>
/// The ID of the Show containing this season. /// The ID of the Show containing this season.
@ -71,7 +73,7 @@ namespace Kyoo.Abstractions.Models
/// The show that contains this season. /// The show that contains this season.
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>. /// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary> /// </summary>
[LoadableRelation(nameof(ShowID))] public Show Show { get; set; } [LoadableRelation(nameof(ShowID))] public Show? Show { get; set; }
/// <summary> /// <summary>
/// The number of this season. This can be set to 0 to indicate specials. /// The number of this season. This can be set to 0 to indicate specials.
@ -81,12 +83,12 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The title of this season. /// The title of this season.
/// </summary> /// </summary>
public string Title { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// A quick overview of this season. /// A quick overview of this season.
/// </summary> /// </summary>
public string Overview { get; set; } public string? Overview { get; set; }
/// <summary> /// <summary>
/// The starting air date of this season. /// The starting air date of this season.
@ -99,20 +101,20 @@ namespace Kyoo.Abstractions.Models
public DateTime? EndDate { get; set; } public DateTime? EndDate { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Poster { get; set; } public Image? Poster { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Thumbnail { get; set; } public Image? Thumbnail { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Logo { get; set; } public Image? Logo { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<string, MetadataID> ExternalId { get; set; } public Dictionary<string, MetadataID> ExternalId { get; set; } = new();
/// <summary> /// <summary>
/// The list of episodes that this season contains. /// The list of episodes that this season contains.
/// </summary> /// </summary>
[LoadableRelation] public ICollection<Episode> Episodes { get; set; } [LoadableRelation] public ICollection<Episode>? Episodes { get; set; }
} }
} }

View File

@ -18,8 +18,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
@ -32,27 +35,38 @@ namespace Kyoo.Abstractions.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; } public string Slug { get; set; }
/// <summary> /// <summary>
/// The title of this show. /// The title of this show.
/// </summary> /// </summary>
public string Title { get; set; } public string Name { get; set; }
/// <summary>
/// A catchphrase for this show.
/// </summary>
public string? Tagline { get; set; }
/// <summary> /// <summary>
/// The list of alternative titles of this show. /// The list of alternative titles of this show.
/// </summary> /// </summary>
[EditableRelation] public string[] Aliases { get; set; } public string[] Aliases { get; set; } = Array.Empty<string>();
/// <summary>
/// The path of the root directory of this show.
/// </summary>
[SerializeIgnore] public string Path { get; set; }
/// <summary> /// <summary>
/// The summary of this show. /// The summary of this show.
/// </summary> /// </summary>
public string Overview { get; set; } 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> /// <summary>
/// Is this show airing, not aired yet or finished? /// Is this show airing, not aired yet or finished?
@ -71,27 +85,22 @@ namespace Kyoo.Abstractions.Models
/// </summary> /// </summary>
public DateTime? EndAir { get; set; } public DateTime? EndAir { get; set; }
/// <summary> /// <inheritdoc />
/// True if this show represent a movie, false otherwise. public Image? Poster { get; set; }
/// </summary>
public bool IsMovie { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Poster { get; set; } public Image? Thumbnail { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Image Thumbnail { get; set; } public Image? Logo { get; set; }
/// <inheritdoc />
public Image Logo { get; set; }
/// <summary> /// <summary>
/// A video of a few minutes that tease the content. /// A video of a few minutes that tease the content.
/// </summary> /// </summary>
public string Trailer { get; set; } public string? Trailer { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<string, MetadataID> ExternalId { get; set; } public Dictionary<string, MetadataID> ExternalId { get; set; } = new();
/// <summary> /// <summary>
/// The ID of the Studio that made this show. /// The ID of the Studio that made this show.
@ -102,39 +111,29 @@ namespace Kyoo.Abstractions.Models
/// The Studio that made this show. /// The Studio that made this show.
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>. /// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary> /// </summary>
[LoadableRelation(nameof(StudioID))][EditableRelation] public Studio Studio { get; set; } [LoadableRelation(nameof(StudioID))][EditableRelation] public Studio? Studio { get; set; }
/// <summary>
/// The list of genres (themes) this show has.
/// </summary>
[LoadableRelation][EditableRelation] public ICollection<Genre> Genres { get; set; }
/// <summary> /// <summary>
/// The list of people that made this show. /// The list of people that made this show.
/// </summary> /// </summary>
[LoadableRelation][EditableRelation] public ICollection<PeopleRole> People { get; set; } [LoadableRelation][EditableRelation] public ICollection<PeopleRole>? People { get; set; }
/// <summary> /// <summary>
/// The different seasons in this show. If this is a movie, this list is always null or empty. /// The different seasons in this show. If this is a movie, this list is always null or empty.
/// </summary> /// </summary>
[LoadableRelation] public ICollection<Season> Seasons { get; set; } [LoadableRelation] public ICollection<Season>? Seasons { get; set; }
/// <summary> /// <summary>
/// The list of episodes in this show. /// The list of episodes in this show.
/// If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to null). /// If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to null).
/// Having an episode is necessary to store metadata and tracks. /// Having an episode is necessary to store metadata and tracks.
/// </summary> /// </summary>
[LoadableRelation] public ICollection<Episode> Episodes { get; set; } [LoadableRelation] public ICollection<Episode>? Episodes { get; set; }
/// <summary>
/// The list of libraries that contains this show.
/// </summary>
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
/// <summary> /// <summary>
/// The list of collections that contains this show. /// The list of collections that contains this show.
/// </summary> /// </summary>
[LoadableRelation] public ICollection<Collection> Collections { get; set; } [LoadableRelation] public ICollection<Collection>? Collections { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public void OnMerge(object merged) public void OnMerge(object merged)
@ -157,6 +156,15 @@ namespace Kyoo.Abstractions.Models
episode.Show = this; episode.Show = this;
} }
} }
public Show() { }
[JsonConstructor]
public Show(string name)
{
Slug = Utility.ToSlug(name);
Name = name;
}
} }
/// <summary> /// <summary>

View File

@ -17,8 +17,10 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils; using Kyoo.Utils;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
@ -31,6 +33,7 @@ namespace Kyoo.Abstractions.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; } public string Slug { get; set; }
/// <summary> /// <summary>
@ -41,10 +44,15 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The list of shows that are made by this studio. /// The list of shows that are made by this studio.
/// </summary> /// </summary>
[LoadableRelation] public ICollection<Show> Shows { get; set; } [LoadableRelation] public ICollection<Show>? Shows { get; set; }
/// <summary>
/// The list of movies that are made by this studio.
/// </summary>
[LoadableRelation] public ICollection<Movie>? Movies { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public Dictionary<string, MetadataID> ExternalId { get; set; } public Dictionary<string, MetadataID> ExternalId { get; set; } = new();
/// <summary> /// <summary>
/// Create a new, empty, <see cref="Studio"/>. /// Create a new, empty, <see cref="Studio"/>.
@ -55,6 +63,7 @@ namespace Kyoo.Abstractions.Models
/// Create a new <see cref="Studio"/> with a specific name, the slug is calculated automatically. /// Create a new <see cref="Studio"/> with a specific name, the slug is calculated automatically.
/// </summary> /// </summary>
/// <param name="name">The name of the studio.</param> /// <param name="name">The name of the studio.</param>
[JsonConstructor]
public Studio(string name) public Studio(string name)
{ {
Slug = Utility.ToSlug(name); Slug = Utility.ToSlug(name);

View File

@ -17,7 +17,10 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>. // along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models namespace Kyoo.Abstractions.Models
{ {
@ -30,6 +33,7 @@ namespace Kyoo.Abstractions.Models
public int ID { get; set; } public int ID { get; set; }
/// <inheritdoc /> /// <inheritdoc />
[MaxLength(256)]
public string Slug { get; set; } public string Slug { get; set; }
/// <summary> /// <summary>
@ -53,27 +57,30 @@ namespace Kyoo.Abstractions.Models
/// </summary> /// </summary>
public string[] Permissions { get; set; } public string[] Permissions { get; set; }
/// <summary>
/// Arbitrary extra data that can be used by specific authentication implementations.
/// </summary>
[SerializeIgnore]
public Dictionary<string, string> ExtraData { get; set; }
/// <summary> /// <summary>
/// A logo is a small image representing the resource. /// A logo is a small image representing the resource.
/// </summary> /// </summary>
public Image Logo { get; set; } public Image? Logo { get; set; }
/// <summary> /// <summary>
/// The list of shows the user has finished. /// The list of shows the user has finished.
/// </summary> /// </summary>
[SerializeIgnore] [SerializeIgnore]
public ICollection<Show> Watched { get; set; } public ICollection<Show>? Watched { get; set; }
/// <summary> /// <summary>
/// The list of episodes the user is watching (stopped in progress or the next episode of the show) /// The list of episodes the user is watching (stopped in progress or the next episode of the show)
/// </summary> /// </summary>
[SerializeIgnore] [SerializeIgnore]
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; } public ICollection<WatchedEpisode>? CurrentlyWatching { get; set; }
public User() { }
[JsonConstructor]
public User(string username)
{
Slug = Utility.ToSlug(username);
Username = username;
}
} }
} }

View File

@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models
/// <summary> /// <summary>
/// The <see cref="Episode"/> started. /// The <see cref="Episode"/> started.
/// </summary> /// </summary>
public Episode Episode { get; set; } public Episode? Episode { get; set; }
/// <summary> /// <summary>
/// Where the player has stopped watching the episode (between 0 and 100). /// Where the player has stopped watching the episode (between 0 and 100).

View File

@ -58,7 +58,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// Create a new <see cref="Identifier"/> for the given slug. /// Create a new <see cref="Identifier"/> for the given slug.
/// </summary> /// </summary>
/// <param name="slug">The slug of the resource.</param> /// <param name="slug">The slug of the resource.</param>
public Identifier([NotNull] string slug) public Identifier(string slug)
{ {
if (slug == null) if (slug == null)
throw new ArgumentNullException(nameof(slug)); throw new ArgumentNullException(nameof(slug));

View File

@ -31,13 +31,13 @@ namespace Kyoo.Abstractions.Models.Utils
/// The list of errors that where made in the request. /// The list of errors that where made in the request.
/// </summary> /// </summary>
/// <example><c>["InvalidFilter: no field 'startYear' on a collection"]</c></example> /// <example><c>["InvalidFilter: no field 'startYear' on a collection"]</c></example>
[NotNull] public string[] Errors { get; set; } public string[] Errors { get; set; }
/// <summary> /// <summary>
/// Create a new <see cref="RequestError"/> with one error. /// Create a new <see cref="RequestError"/> with one error.
/// </summary> /// </summary>
/// <param name="error">The error to specify in the response.</param> /// <param name="error">The error to specify in the response.</param>
public RequestError([NotNull] string error) public RequestError(string error)
{ {
if (error == null) if (error == null)
throw new ArgumentNullException(nameof(error)); throw new ArgumentNullException(nameof(error));
@ -48,7 +48,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// Create a new <see cref="RequestError"/> with multiple errors. /// Create a new <see cref="RequestError"/> with multiple errors.
/// </summary> /// </summary>
/// <param name="errors">The errors to specify in the response.</param> /// <param name="errors">The errors to specify in the response.</param>
public RequestError([NotNull] string[] errors) public RequestError(string[] errors)
{ {
if (errors == null || !errors.Any()) if (errors == null || !errors.Any())
throw new ArgumentException("Errors must be non null and not empty", nameof(errors)); throw new ArgumentException("Errors must be non null and not empty", nameof(errors));

View File

@ -1,194 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Newtonsoft.Json;
namespace Kyoo.Abstractions.Models
{
/// <summary>
/// A watch item give information useful for playback.
/// Information about tracks and display information that could be used by the player.
/// This contains mostly data from an <see cref="Episode"/> with another form.
/// </summary>
public class WatchItem : CustomTypeDescriptor, IThumbnails, ILink
{
/// <summary>
/// The ID of the episode associated with this item.
/// </summary>
public int EpisodeID { get; set; }
/// <summary>
/// The slug of this episode.
/// </summary>
public string Slug { get; set; }
/// <summary>
/// The title of the show containing this episode.
/// </summary>
public string ShowTitle { get; set; }
/// <summary>
/// The slug of the show containing this episode
/// </summary>
public string ShowSlug { get; set; }
/// <summary>
/// The season in witch this episode is in.
/// </summary>
public int? SeasonNumber { get; set; }
/// <summary>
/// The number of this episode is 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>
/// The title of this episode.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The summary of this episode.
/// </summary>
public string Overview { get; set; }
/// <summary>
/// The release date of this episode. It can be null if unknown.
/// </summary>
public DateTime? ReleaseDate { get; set; }
/// <summary>
/// The episode that come before this one if you follow usual watch orders.
/// If this is the first episode or this is a movie, it will be null.
/// </summary>
public Episode PreviousEpisode { get; set; }
/// <summary>
/// The episode that come after this one if you follow usual watch orders.
/// If this is the last aired episode or this is a movie, it will be null.
/// </summary>
public Episode NextEpisode { get; set; }
/// <summary>
/// <c>true</c> if this is a movie, <c>false</c> otherwise.
/// </summary>
public bool IsMovie { get; set; }
/// <inheritdoc />
public Image Poster { get; set; }
/// <inheritdoc />
public Image Thumbnail { get; set; }
/// <inheritdoc />
public Image Logo { get; set; }
/// <summary>
/// The transcoder's info for this item. This include subtitles, fonts, chapters...
/// </summary>
public object Info { get; set; }
[SerializeIgnore]
private string _Type => IsMovie ? "movie" : "episode";
/// <inheritdoc/>
public object Link => new
{
Direct = $"/video/{_Type}/{Slug}/direct",
Hls = $"/video/{_Type}/{Slug}/master.m3u8",
};
/// <summary>
/// Create a <see cref="WatchItem"/> from an <see cref="Episode"/>.
/// </summary>
/// <param name="ep">The episode to transform.</param>
/// <param name="library">
/// A library manager to retrieve the next and previous episode and load the show and tracks of the episode.
/// </param>
/// <param name="client">A http client to reach the transcoder.</param>
/// <returns>A new WatchItem representing the given episode.</returns>
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library, HttpClient client)
{
await library.Load(ep, x => x.Show);
return new WatchItem
{
EpisodeID = ep.ID,
Slug = ep.Slug,
ShowSlug = ep.Show.Slug,
ShowTitle = ep.Show.Title,
SeasonNumber = ep.SeasonNumber,
EpisodeNumber = ep.EpisodeNumber,
AbsoluteNumber = ep.AbsoluteNumber,
Title = ep.Title,
Overview = ep.Overview,
ReleaseDate = ep.ReleaseDate,
Poster = ep.Poster,
Thumbnail = ep.Thumbnail,
Logo = ep.Logo,
PreviousEpisode = ep.Show.IsMovie
? null
: (await library.GetAll<Episode>(
where: x => x.ShowID == ep.ShowID,
limit: new Pagination(1, ep.ID, true)
)).FirstOrDefault(),
NextEpisode = ep.Show.IsMovie
? null
: (await library.GetAll<Episode>(
where: x => x.ShowID == ep.ShowID,
limit: new Pagination(1, ep.ID)
)).FirstOrDefault(),
IsMovie = ep.Show.IsMovie,
Info = await _GetInfo(ep, client),
};
}
private static async Task<object> _GetInfo(Episode ep, HttpClient client)
{
HttpResponseMessage ret = await client.GetAsync($"http://transcoder:7666/{(ep.Show.IsMovie ? "movie" : "episode")}/{ep.Slug}/info");
ret.EnsureSuccessStatusCode();
string content = await ret.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<object>(content);
}
/// <inheritdoc />
public override string GetClassName()
{
return nameof(Show);
}
/// <inheritdoc />
public override string GetComponentName()
{
return ShowSlug;
}
}
}

View File

@ -39,8 +39,8 @@ namespace Kyoo.Utils
/// <returns>The list mapped.</returns> /// <returns>The list mapped.</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception> /// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel] [LinqTunnel]
public static IEnumerable<T2> Map<T, T2>([NotNull] this IEnumerable<T> self, public static IEnumerable<T2> Map<T, T2>(this IEnumerable<T> self,
[NotNull] Func<T, int, T2> mapper) Func<T, int, T2> mapper)
{ {
if (self == null) if (self == null)
throw new ArgumentNullException(nameof(self)); throw new ArgumentNullException(nameof(self));
@ -72,8 +72,8 @@ namespace Kyoo.Utils
/// <returns>The list mapped as an AsyncEnumerable.</returns> /// <returns>The list mapped as an AsyncEnumerable.</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null.</exception> /// <exception cref="ArgumentNullException">The list or the mapper can't be null.</exception>
[LinqTunnel] [LinqTunnel]
public static IAsyncEnumerable<T2> MapAsync<T, T2>([NotNull] this IEnumerable<T> self, public static IAsyncEnumerable<T2> MapAsync<T, T2>(this IEnumerable<T> self,
[NotNull] Func<T, int, Task<T2>> mapper) Func<T, int, Task<T2>> mapper)
{ {
if (self == null) if (self == null)
throw new ArgumentNullException(nameof(self)); throw new ArgumentNullException(nameof(self));
@ -105,8 +105,8 @@ namespace Kyoo.Utils
/// <returns>The list mapped as an AsyncEnumerable</returns> /// <returns>The list mapped as an AsyncEnumerable</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception> /// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel] [LinqTunnel]
public static IAsyncEnumerable<T2> SelectAsync<T, T2>([NotNull] this IEnumerable<T> self, public static IAsyncEnumerable<T2> SelectAsync<T, T2>(this IEnumerable<T> self,
[NotNull] Func<T, Task<T2>> mapper) Func<T, Task<T2>> mapper)
{ {
if (self == null) if (self == null)
throw new ArgumentNullException(nameof(self)); throw new ArgumentNullException(nameof(self));
@ -132,7 +132,7 @@ namespace Kyoo.Utils
/// <returns>A task that will return a simple list</returns> /// <returns>A task that will return a simple list</returns>
/// <exception cref="ArgumentNullException">The list can't be null</exception> /// <exception cref="ArgumentNullException">The list can't be null</exception>
[LinqTunnel] [LinqTunnel]
public static Task<List<T>> ToListAsync<T>([NotNull] this IAsyncEnumerable<T> self) public static Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> self)
{ {
if (self == null) if (self == null)
throw new ArgumentNullException(nameof(self)); throw new ArgumentNullException(nameof(self));
@ -157,7 +157,7 @@ namespace Kyoo.Utils
/// <exception cref="ArgumentNullException">The iterable and the action can't be null.</exception> /// <exception cref="ArgumentNullException">The iterable and the action can't be null.</exception>
/// <returns>The iterator proxied, there is no dual iterations.</returns> /// <returns>The iterator proxied, there is no dual iterations.</returns>
[LinqTunnel] [LinqTunnel]
public static IEnumerable<T> IfEmpty<T>([NotNull] this IEnumerable<T> self, [NotNull] Action action) public static IEnumerable<T> IfEmpty<T>(this IEnumerable<T> self, Action action)
{ {
if (self == null) if (self == null)
throw new ArgumentNullException(nameof(self)); throw new ArgumentNullException(nameof(self));
@ -190,7 +190,7 @@ namespace Kyoo.Utils
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param> /// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The action to execute for each arguments</param> /// <param name="action">The action to execute for each arguments</param>
/// <typeparam name="T">The type of items in the list</typeparam> /// <typeparam name="T">The type of items in the list</typeparam>
public static void ForEach<T>([CanBeNull] this IEnumerable<T> self, Action<T> action) public static void ForEach<T>(this IEnumerable<T>? self, Action<T> action)
{ {
if (self == null) if (self == null)
return; return;
@ -203,7 +203,7 @@ namespace Kyoo.Utils
/// </summary> /// </summary>
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param> /// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The action to execute for each arguments</param> /// <param name="action">The action to execute for each arguments</param>
public static void ForEach([CanBeNull] this IEnumerable self, Action<object> action) public static void ForEach(this IEnumerable? self, Action<object> action)
{ {
if (self == null) if (self == null)
return; return;
@ -217,7 +217,7 @@ namespace Kyoo.Utils
/// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param> /// <param name="self">The list to enumerate. If this is null, the function result in a no-op</param>
/// <param name="action">The action to execute for each arguments</param> /// <param name="action">The action to execute for each arguments</param>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task ForEachAsync([CanBeNull] this IEnumerable self, Func<object, Task> action) public static async Task ForEachAsync(this IEnumerable? self, Func<object, Task> action)
{ {
if (self == null) if (self == null)
return; return;
@ -232,7 +232,7 @@ namespace Kyoo.Utils
/// <param name="action">The asynchronous action to execute for each arguments</param> /// <param name="action">The asynchronous action to execute for each arguments</param>
/// <typeparam name="T">The type of items in the list.</typeparam> /// <typeparam name="T">The type of items in the list.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task ForEachAsync<T>([CanBeNull] this IEnumerable<T> self, Func<T, Task> action) public static async Task ForEachAsync<T>(this IEnumerable<T>? self, Func<T, Task> action)
{ {
if (self == null) if (self == null)
return; return;
@ -247,7 +247,7 @@ namespace Kyoo.Utils
/// <param name="action">The action to execute for each arguments</param> /// <param name="action">The action to execute for each arguments</param>
/// <typeparam name="T">The type of items in the list.</typeparam> /// <typeparam name="T">The type of items in the list.</typeparam>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static async Task ForEachAsync<T>([CanBeNull] this IAsyncEnumerable<T> self, Action<T> action) public static async Task ForEachAsync<T>(this IAsyncEnumerable<T>? self, Action<T> action)
{ {
if (self == null) if (self == null)
return; return;

View File

@ -42,9 +42,9 @@ namespace Kyoo.Utils
/// <typeparam name="T">The type of items in the lists to merge.</typeparam> /// <typeparam name="T">The type of items in the lists to merge.</typeparam>
/// <returns>The two list merged as an array</returns> /// <returns>The two list merged as an array</returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static T[] MergeLists<T>([CanBeNull] IEnumerable<T> first, public static T[] MergeLists<T>(IEnumerable<T>? first,
[CanBeNull] IEnumerable<T> second, IEnumerable<T>? second,
[CanBeNull] Func<T, T, bool> isEqual = null) Func<T, T, bool>? isEqual = null)
{ {
if (first == null) if (first == null)
return second?.ToArray(); return second?.ToArray();
@ -66,8 +66,8 @@ namespace Kyoo.Utils
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns> /// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
/// <seealso cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2},out bool)"/> /// <seealso cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2},out bool)"/>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static IDictionary<T, T2> MergeDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first, public static IDictionary<T, T2> MergeDictionaries<T, T2>(IDictionary<T, T2>? first,
[CanBeNull] IDictionary<T, T2> second) IDictionary<T, T2>? second)
{ {
return MergeDictionaries(first, second, out bool _); return MergeDictionaries(first, second, out bool _);
} }
@ -84,8 +84,8 @@ namespace Kyoo.Utils
/// <typeparam name="T2">The type of values in the dictionaries</typeparam> /// <typeparam name="T2">The type of values in the dictionaries</typeparam>
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns> /// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static IDictionary<T, T2> MergeDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first, public static IDictionary<T, T2> MergeDictionaries<T, T2>(IDictionary<T, T2>? first,
[CanBeNull] IDictionary<T, T2> second, IDictionary<T, T2>? second,
out bool hasChanged) out bool hasChanged)
{ {
if (first == null) if (first == null)
@ -138,8 +138,8 @@ namespace Kyoo.Utils
/// set to those of <paramref name="first"/>. /// set to those of <paramref name="first"/>.
/// </returns> /// </returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static IDictionary<T, T2> CompleteDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first, public static IDictionary<T, T2> CompleteDictionaries<T, T2>(IDictionary<T, T2>? first,
[CanBeNull] IDictionary<T, T2> second, IDictionary<T, T2>? second,
out bool hasChanged) out bool hasChanged)
{ {
if (first == null) if (first == null)
@ -157,32 +157,6 @@ namespace Kyoo.Utils
return second; return second;
} }
/// <summary>
/// Set every fields of first to those of second. Ignore fields marked with the <see cref="NotMergeableAttribute"/> attribute
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
/// </summary>
/// <param name="first">The object to assign</param>
/// <param name="second">The object containing new values</param>
/// <typeparam name="T">Fields of T will be used</typeparam>
/// <returns><paramref name="first"/></returns>
public static T Assign<T>(T first, T second)
{
Type type = typeof(T);
IEnumerable<PropertyInfo> properties = type.GetProperties()
.Where(x => x.CanRead && x.CanWrite
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
foreach (PropertyInfo property in properties)
{
object value = property.GetValue(second);
property.SetValue(first, value);
}
if (first is IOnMerge merge)
merge.OnMerge(second);
return first;
}
/// <summary> /// <summary>
/// Set every non-default values of seconds to the corresponding property of second. /// Set every non-default values of seconds to the corresponding property of second.
/// Dictionaries are handled like anonymous objects with a property per key/pair value /// Dictionaries are handled like anonymous objects with a property per key/pair value
@ -210,8 +184,8 @@ namespace Kyoo.Utils
/// <returns><paramref name="first"/></returns> /// <returns><paramref name="first"/></returns>
/// <exception cref="ArgumentNullException">If first is null</exception> /// <exception cref="ArgumentNullException">If first is null</exception>
public static T Complete<T>([NotNull] T first, public static T Complete<T>([NotNull] T first,
[CanBeNull] T second, T? second,
[InstantHandle] Func<PropertyInfo, bool> where = null) [InstantHandle] Func<PropertyInfo, bool>? where = null)
{ {
if (first == null) if (first == null)
throw new ArgumentNullException(nameof(first)); throw new ArgumentNullException(nameof(first));
@ -281,9 +255,9 @@ namespace Kyoo.Utils
/// <typeparam name="T">Fields of T will be merged</typeparam> /// <typeparam name="T">Fields of T will be merged</typeparam>
/// <returns><paramref name="first"/></returns> /// <returns><paramref name="first"/></returns>
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)] [ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
public static T Merge<T>([CanBeNull] T first, public static T Merge<T>(T? first,
[CanBeNull] T second, T? second,
[InstantHandle] Func<PropertyInfo, bool> where = null) [InstantHandle] Func<PropertyInfo, bool>? where = null)
{ {
if (first == null) if (first == null)
return second; return second;

View File

@ -77,8 +77,7 @@ namespace Kyoo.Utils
/// <param name="value">The initial task</param> /// <param name="value">The initial task</param>
/// <typeparam name="T">The type that the task will return</typeparam> /// <typeparam name="T">The type that the task will return</typeparam>
/// <returns>A non-null task.</returns> /// <returns>A non-null task.</returns>
[NotNull] public static Task<T> DefaultIfNull<T>(Task<T>? value)
public static Task<T> DefaultIfNull<T>([CanBeNull] Task<T> value)
{ {
return value ?? Task.FromResult<T>(default); return value ?? Task.FromResult<T>(default);
} }

View File

@ -63,34 +63,12 @@ namespace Kyoo.Utils
return member!.Member.Name; return member!.Member.Name;
} }
/// <summary>
/// Get the value of a member (property or field)
/// </summary>
/// <param name="member">The member value</param>
/// <param name="obj">The owner of this member</param>
/// <returns>The value boxed as an object</returns>
/// <exception cref="ArgumentNullException">if <paramref name="member"/> or <paramref name="obj"/> is null.</exception>
/// <exception cref="ArgumentException">The member is not a field or a property.</exception>
public static object GetValue([NotNull] this MemberInfo member, [NotNull] object obj)
{
if (member == null)
throw new ArgumentNullException(nameof(member));
if (obj == null)
throw new ArgumentNullException(nameof(obj));
return member switch
{
PropertyInfo property => property.GetValue(obj),
FieldInfo field => field.GetValue(obj),
_ => throw new ArgumentException($"Can't get value of a non property/field (member: {member}).")
};
}
/// <summary> /// <summary>
/// Slugify a string (Replace spaces by -, Uniformize accents) /// Slugify a string (Replace spaces by -, Uniformize accents)
/// </summary> /// </summary>
/// <param name="str">The string to slugify</param> /// <param name="str">The string to slugify</param>
/// <returns>The slug version of the given string</returns> /// <returns>The slug version of the given string</returns>
public static string ToSlug([CanBeNull] string str) public static string ToSlug(string? str)
{ {
if (str == null) if (str == null)
return null; return null;
@ -132,7 +110,7 @@ namespace Kyoo.Utils
/// <param name="type">The starting type</param> /// <param name="type">The starting type</param>
/// <returns>A list of types</returns> /// <returns>A list of types</returns>
/// <exception cref="ArgumentNullException"><paramref name="type"/> can't be null</exception> /// <exception cref="ArgumentNullException"><paramref name="type"/> can't be null</exception>
public static IEnumerable<Type> GetInheritanceTree([NotNull] this Type type) public static IEnumerable<Type> GetInheritanceTree(this Type type)
{ {
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
@ -140,20 +118,6 @@ namespace Kyoo.Utils
yield return type; yield return type;
} }
/// <summary>
/// Check if <paramref name="obj"/> inherit from a generic type <paramref name="genericType"/>.
/// </summary>
/// <param name="obj">Does this object's type is a <paramref name="genericType"/></param>
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param>
/// <returns>True if obj inherit from genericType. False otherwise</returns>
/// <exception cref="ArgumentNullException">obj and genericType can't be null</exception>
public static bool IsOfGenericType([NotNull] object obj, [NotNull] Type genericType)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
return IsOfGenericType(obj.GetType(), genericType);
}
/// <summary> /// <summary>
/// Check if <paramref name="type"/> inherit from a generic type <paramref name="genericType"/>. /// Check if <paramref name="type"/> inherit from a generic type <paramref name="genericType"/>.
/// </summary> /// </summary>
@ -161,7 +125,7 @@ namespace Kyoo.Utils
/// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param> /// <param name="genericType">The generic type to check against (Only generic types are supported like typeof(IEnumerable&lt;&gt;).</param>
/// <returns>True if obj inherit from genericType. False otherwise</returns> /// <returns>True if obj inherit from genericType. False otherwise</returns>
/// <exception cref="ArgumentNullException">obj and genericType can't be null</exception> /// <exception cref="ArgumentNullException">obj and genericType can't be null</exception>
public static bool IsOfGenericType([NotNull] Type type, [NotNull] Type genericType) public static bool IsOfGenericType(Type type, Type genericType)
{ {
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
@ -186,7 +150,7 @@ namespace Kyoo.Utils
/// <returns>The generic definition of genericType that type inherit or null if type does not implement the generic type.</returns> /// <returns>The generic definition of genericType that type inherit or null if type does not implement the generic type.</returns>
/// <exception cref="ArgumentNullException"><paramref name="type"/> and <paramref name="genericType"/> can't be null</exception> /// <exception cref="ArgumentNullException"><paramref name="type"/> and <paramref name="genericType"/> can't be null</exception>
/// <exception cref="ArgumentException"><paramref name="genericType"/> must be a generic type</exception> /// <exception cref="ArgumentException"><paramref name="genericType"/> must be a generic type</exception>
public static Type GetGenericDefinition([NotNull] Type type, [NotNull] Type genericType) public static Type GetGenericDefinition(Type type, Type genericType)
{ {
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
@ -224,12 +188,11 @@ namespace Kyoo.Utils
/// <exception cref="ArgumentException">No method match the given constraints.</exception> /// <exception cref="ArgumentException">No method match the given constraints.</exception>
/// <returns>The method handle of the matching method.</returns> /// <returns>The method handle of the matching method.</returns>
[PublicAPI] [PublicAPI]
[NotNull] public static MethodInfo GetMethod(Type type,
public static MethodInfo GetMethod([NotNull] Type type,
BindingFlags flag, BindingFlags flag,
string name, string name,
[NotNull] Type[] generics, Type[] generics,
[NotNull] object[] args) object[] args)
{ {
if (type == null) if (type == null)
throw new ArgumentNullException(nameof(type)); throw new ArgumentNullException(nameof(type));
@ -297,9 +260,9 @@ namespace Kyoo.Utils
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type,object[])"/> /// <seealso cref="RunGenericMethod{T}(object,string,System.Type,object[])"/>
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type[],object[])"/> /// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type[],object[])"/>
public static T RunGenericMethod<T>( public static T RunGenericMethod<T>(
[NotNull] Type owner, Type owner,
[NotNull] string methodName, string methodName,
[NotNull] Type type, Type type,
params object[] args) params object[] args)
{ {
return RunGenericMethod<T>(owner, methodName, new[] { type }, args); return RunGenericMethod<T>(owner, methodName, new[] { type }, args);
@ -334,9 +297,9 @@ namespace Kyoo.Utils
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/> /// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/>
[PublicAPI] [PublicAPI]
public static T RunGenericMethod<T>( public static T RunGenericMethod<T>(
[NotNull] Type owner, Type owner,
[NotNull] string methodName, string methodName,
[NotNull] Type[] types, Type[] types,
params object[] args) params object[] args)
{ {
if (owner == null) if (owner == null)
@ -351,83 +314,6 @@ namespace Kyoo.Utils
return (T)method.MakeGenericMethod(types).Invoke(null, args); return (T)method.MakeGenericMethod(types).Invoke(null, args);
} }
/// <summary>
/// Run a generic method for a runtime <see cref="Type"/>.
/// </summary>
/// <example>
/// To run <see cref="Merger.MergeLists{T}"/> for a List where you don't know the type at compile type,
/// you could do:
/// <code>
/// Utility.RunGenericMethod&lt;object&gt;(
/// typeof(Utility),
/// nameof(MergeLists),
/// enumerableType,
/// oldValue, newValue, equalityComparer)
/// </code>
/// </example>
/// <param name="instance">The <c>this</c> of the method to run.</param>
/// <param name="methodName">The name of the method. You should use the <c>nameof</c> keyword.</param>
/// <param name="type">The generic type to run the method with.</param>
/// <param name="args">The list of arguments of the method</param>
/// <typeparam name="T">
/// The return type of the method. You can put <see cref="object"/> for an unknown one.
/// </typeparam>
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
/// <returns>The return of the method you wanted to run.</returns>
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type,object[])"/>
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type[],object[])"/>
public static T RunGenericMethod<T>(
[NotNull] object instance,
[NotNull] string methodName,
[NotNull] Type type,
params object[] args)
{
return RunGenericMethod<T>(instance, methodName, new[] { type }, args);
}
/// <summary>
/// Run a generic method for a multiple runtime <see cref="Type"/>.
/// If your generic method only needs one type, see
/// <see cref="RunGenericMethod{T}(object,string,System.Type,object[])"/>
/// </summary>
/// <example>
/// To run <see cref="Merger.MergeLists{T}"/> for a List where you don't know the type at compile type,
/// you could do:
/// <code>
/// Utility.RunGenericMethod&lt;object&gt;(
/// typeof(Utility),
/// nameof(MergeLists),
/// enumerableType,
/// oldValue, newValue, equalityComparer)
/// </code>
/// </example>
/// <param name="instance">The <c>this</c> of the method to run.</param>
/// <param name="methodName">The name of the method. You should use the <c>nameof</c> keyword.</param>
/// <param name="types">The list of generic types to run the method with.</param>
/// <param name="args">The list of arguments of the method</param>
/// <typeparam name="T">
/// The return type of the method. You can put <see cref="object"/> for an unknown one.
/// </typeparam>
/// <exception cref="ArgumentException">No method match the given constraints.</exception>
/// <returns>The return of the method you wanted to run.</returns>
/// <seealso cref="RunGenericMethod{T}(object,string,System.Type[],object[])"/>
/// <seealso cref="RunGenericMethod{T}(System.Type,string,System.Type,object[])"/>
public static T RunGenericMethod<T>(
[NotNull] object instance,
[NotNull] string methodName,
[NotNull] Type[] types,
params object[] args)
{
if (instance == null)
throw new ArgumentNullException(nameof(instance));
if (methodName == null)
throw new ArgumentNullException(nameof(methodName));
if (types == null || types.Length == 0)
throw new ArgumentNullException(nameof(types));
MethodInfo method = GetMethod(instance.GetType(), BindingFlags.Instance, methodName, types, args);
return (T)method.MakeGenericMethod(types).Invoke(instance, args.ToArray());
}
/// <summary> /// <summary>
/// Convert a dictionary to a query string. /// Convert a dictionary to a query string.
/// </summary> /// </summary>
@ -446,25 +332,11 @@ namespace Kyoo.Utils
/// </summary> /// </summary>
/// <param name="ex">The exception to rethrow.</param> /// <param name="ex">The exception to rethrow.</param>
[System.Diagnostics.CodeAnalysis.DoesNotReturn] [System.Diagnostics.CodeAnalysis.DoesNotReturn]
public static void ReThrow([NotNull] this Exception ex) public static void ReThrow(this Exception ex)
{ {
if (ex == null) if (ex == null)
throw new ArgumentNullException(nameof(ex)); throw new ArgumentNullException(nameof(ex));
ExceptionDispatchInfo.Capture(ex).Throw(); ExceptionDispatchInfo.Capture(ex).Throw();
} }
/// <summary>
/// Get a friendly type name (supporting generics)
/// For example a list of string will be displayed as List&lt;string&gt; and not as List`1.
/// </summary>
/// <param name="type">The type to use</param>
/// <returns>The friendly name of the type</returns>
public static string FriendlyName(this Type type)
{
if (!type.IsGenericType)
return type.Name;
string generics = string.Join(", ", type.GetGenericArguments().Select(x => x.FriendlyName()));
return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>";
}
} }
} }

View File

@ -72,7 +72,6 @@ namespace Kyoo.Authentication.Models.DTO
Username = Username, Username = Username,
Password = BCryptNet.HashPassword(Password), Password = BCryptNet.HashPassword(Password),
Email = Email, Email = Email,
ExtraData = new Dictionary<string, string>()
}; };
} }
} }

View File

@ -39,15 +39,15 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
private readonly IBaseRepository[] _repositories; private readonly IBaseRepository[] _repositories;
/// <inheritdoc />
public ILibraryRepository LibraryRepository { get; }
/// <inheritdoc /> /// <inheritdoc />
public ILibraryItemRepository LibraryItemRepository { get; } public ILibraryItemRepository LibraryItemRepository { get; }
/// <inheritdoc /> /// <inheritdoc />
public ICollectionRepository CollectionRepository { get; } public ICollectionRepository CollectionRepository { get; }
/// <inheritdoc />
public IMovieRepository MovieRepository { get; }
/// <inheritdoc /> /// <inheritdoc />
public IShowRepository ShowRepository { get; } public IShowRepository ShowRepository { get; }
@ -63,9 +63,6 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
public IStudioRepository StudioRepository { get; } public IStudioRepository StudioRepository { get; }
/// <inheritdoc />
public IGenreRepository GenreRepository { get; }
/// <inheritdoc /> /// <inheritdoc />
public IUserRepository UserRepository { get; } public IUserRepository UserRepository { get; }
@ -77,15 +74,14 @@ namespace Kyoo.Core.Controllers
public LibraryManager(IEnumerable<IBaseRepository> repositories) public LibraryManager(IEnumerable<IBaseRepository> repositories)
{ {
_repositories = repositories.ToArray(); _repositories = repositories.ToArray();
LibraryRepository = GetRepository<Library>() as ILibraryRepository; LibraryItemRepository = GetRepository<ILibraryItem>() as ILibraryItemRepository;
LibraryItemRepository = GetRepository<LibraryItem>() as ILibraryItemRepository;
CollectionRepository = GetRepository<Collection>() as ICollectionRepository; CollectionRepository = GetRepository<Collection>() as ICollectionRepository;
MovieRepository = GetRepository<Movie>() as IMovieRepository;
ShowRepository = GetRepository<Show>() as IShowRepository; ShowRepository = GetRepository<Show>() as IShowRepository;
SeasonRepository = GetRepository<Season>() as ISeasonRepository; SeasonRepository = GetRepository<Season>() as ISeasonRepository;
EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository; EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository;
PeopleRepository = GetRepository<People>() as IPeopleRepository; PeopleRepository = GetRepository<People>() as IPeopleRepository;
StudioRepository = GetRepository<Studio>() as IStudioRepository; StudioRepository = GetRepository<Studio>() as IStudioRepository;
GenreRepository = GetRepository<Genre>() as IGenreRepository;
UserRepository = GetRepository<User>() as IUserRepository; UserRepository = GetRepository<User>() as IUserRepository;
} }
@ -255,28 +251,10 @@ namespace Kyoo.Core.Controllers
return (obj, member: memberName) switch return (obj, member: memberName) switch
{ {
(Library l, nameof(Library.Shows)) => ShowRepository
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
.Then(x => l.Shows = x),
(Library l, nameof(Library.Collections)) => CollectionRepository
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
.Then(x => l.Collections = x),
(Collection c, nameof(Collection.Shows)) => ShowRepository (Collection c, nameof(Collection.Shows)) => ShowRepository
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID)) .GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
.Then(x => c.Shows = x), .Then(x => c.Shows = x),
(Collection c, nameof(Collection.Libraries)) => LibraryRepository
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
.Then(x => c.Libraries = x),
(Show s, nameof(Show.Genres)) => GenreRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Genres = x),
(Show s, nameof(Show.People)) => PeopleRepository (Show s, nameof(Show.People)) => PeopleRepository
.GetFromShow(obj.ID) .GetFromShow(obj.ID)
.Then(x => s.People = x), .Then(x => s.People = x),
@ -291,10 +269,6 @@ namespace Kyoo.Core.Controllers
(x, y) => x.Episodes = y, (x, y) => x.Episodes = y,
(x, y) => { x.Show = y; x.ShowID = y.ID; }), (x, y) => { x.Show = y; x.ShowID = y.ID; }),
(Show s, nameof(Show.Libraries)) => LibraryRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Libraries = x),
(Show s, nameof(Show.Collections)) => CollectionRepository (Show s, nameof(Show.Collections)) => CollectionRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID)) .GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Collections = x), .Then(x => s.Collections = x),
@ -339,11 +313,6 @@ namespace Kyoo.Core.Controllers
}), }),
(Genre g, nameof(Genre.Shows)) => ShowRepository
.GetAll(x => x.Genres.Any(y => y.ID == obj.ID))
.Then(x => g.Shows = x),
(Studio s, nameof(Studio.Shows)) => ShowRepository (Studio s, nameof(Studio.Shows)) => ShowRepository
.GetAll(x => x.Studio.ID == obj.ID) .GetAll(x => x.Studio.ID == obj.ID)
.Then(x => s.Shows = x), .Then(x => s.Shows = x),
@ -356,24 +325,6 @@ namespace Kyoo.Core.Controllers
}; };
} }
/// <inheritdoc />
public Task<ICollection<LibraryItem>> GetItemsFromLibrary(int id,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default)
{
return LibraryItemRepository.GetFromLibrary(id, where, sort, limit);
}
/// <inheritdoc />
public Task<ICollection<LibraryItem>> GetItemsFromLibrary(string slug,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default)
{
return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit);
}
/// <inheritdoc /> /// <inheritdoc />
public Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID, public Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
Expression<Func<PeopleRole, bool>> where = null, Expression<Func<PeopleRole, bool>> where = null,
@ -410,20 +361,6 @@ namespace Kyoo.Core.Controllers
return PeopleRepository.GetFromPeople(slug, where, sort, limit); return PeopleRepository.GetFromPeople(slug, where, sort, limit);
} }
/// <inheritdoc />
public Task AddShowLink(int showID, int? libraryID, int? collectionID)
{
return ShowRepository.AddShowLink(showID, libraryID, collectionID);
}
/// <inheritdoc />
public Task AddShowLink(Show show, Library library, Collection collection)
{
if (show == null)
throw new ArgumentNullException(nameof(show));
return ShowRepository.AddShowLink(show.ID, library?.ID, collection?.ID);
}
/// <inheritdoc /> /// <inheritdoc />
public Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>> where = null, public Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>> where = null,
Sort<T> sort = default, Sort<T> sort = default,

View File

@ -129,7 +129,7 @@ namespace Kyoo.Core.Controllers
_database.Episodes _database.Episodes
.Include(x => x.Show) .Include(x => x.Show)
.Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null)
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%")) .Where(_database.Like<Episode>(x => x.Name, $"%{query}%"))
) )
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();

View File

@ -1,93 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers
{
/// <summary>
/// A local repository for genres.
/// </summary>
public class GenreRepository : LocalRepository<Genre>, IGenreRepository
{
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Sort<Genre> DefaultSort => new Sort<Genre>.By(x => x.Slug);
/// <summary>
/// Create a new <see cref="GenreRepository"/>.
/// </summary>
/// <param name="database">The database handle</param>
public GenreRepository(DatabaseContext database)
: base(database)
{
_database = database;
}
/// <inheritdoc />
public override async Task<ICollection<Genre>> Search(string query)
{
return await Sort(
_database.Genres
.Where(_database.Like<Genre>(x => x.Name, $"%{query}%"))
)
.Take(20)
.ToListAsync();
}
/// <inheritdoc />
protected override async Task Validate(Genre resource)
{
resource.Slug ??= Utility.ToSlug(resource.Name);
await base.Validate(resource);
}
/// <inheritdoc />
public override async Task<Genre> Create(Genre obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
OnResourceCreated(obj);
return obj;
}
/// <inheritdoc />
public override async Task Delete(Genre obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
_database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync();
await base.Delete(obj);
}
}
}

View File

@ -23,7 +23,6 @@ using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Postgresql; using Kyoo.Postgresql;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -32,84 +31,80 @@ namespace Kyoo.Core.Controllers
/// <summary> /// <summary>
/// A local repository to handle library items. /// A local repository to handle library items.
/// </summary> /// </summary>
public class LibraryItemRepository : LocalRepository<LibraryItem>, ILibraryItemRepository public class LibraryItemRepository : LocalRepository<ILibraryItem>, ILibraryItemRepository
{ {
/// <summary> /// <summary>
/// The database handle /// The database handle
/// </summary> /// </summary>
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
/// <summary>
/// A lazy loaded library repository to validate queries (check if a library does exist)
/// </summary>
private readonly Lazy<ILibraryRepository> _libraries;
/// <inheritdoc /> /// <inheritdoc />
protected override Sort<LibraryItem> DefaultSort => new Sort<LibraryItem>.By(x => x.Title); protected override Sort<ILibraryItem> DefaultSort => new Sort<ILibraryItem>.By(x => x.Name);
/// <summary> /// <summary>
/// Create a new <see cref="LibraryItemRepository"/>. /// Create a new <see cref="ILibraryItemRepository"/>.
/// </summary> /// </summary>
/// <param name="database">The database instance</param> /// <param name="database">The database instance</param>
/// <param name="libraries">A lazy loaded library repository</param> public LibraryItemRepository(DatabaseContext database)
public LibraryItemRepository(DatabaseContext database,
Lazy<ILibraryRepository> libraries)
: base(database) : base(database)
{ {
_database = database; _database = database;
_libraries = libraries;
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<LibraryItem> GetOrDefault(int id) public override async Task<ILibraryItem> GetOrDefault(int id)
{ {
return _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id); return (await _database.LibraryItems.FirstOrDefaultAsync(x => x.ID == id)).ToItem();
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<LibraryItem> GetOrDefault(string slug) public override async Task<ILibraryItem> GetOrDefault(string slug)
{ {
return _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug); return (await _database.LibraryItems.SingleOrDefaultAsync(x => x.Slug == slug)).ToItem();
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<ICollection<LibraryItem>> GetAll(Expression<Func<LibraryItem, bool>> where = null, public override async Task<ICollection<ILibraryItem>> GetAll(Expression<Func<ILibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default, Sort<ILibraryItem> sort = default,
Pagination limit = default) Pagination limit = default)
{ {
return ApplyFilters(_database.LibraryItems, where, sort, limit); return (await ApplyFilters(_database.LibraryItems, where, sort, limit))
.Select(x => (x as BagItem)!.ToItem())
.ToList();
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<int> GetCount(Expression<Func<LibraryItem, bool>> where = null) public override Task<int> GetCount(Expression<Func<ILibraryItem, bool>> where = null)
{ {
IQueryable<LibraryItem> query = _database.LibraryItems; IQueryable<ILibraryItem> query = _database.LibraryItems;
if (where != null) if (where != null)
query = query.Where(where); query = query.Where(where);
return query.CountAsync(); return query.CountAsync();
} }
/// <inheritdoc /> /// <inheritdoc />
public override async Task<ICollection<LibraryItem>> Search(string query) public override async Task<ICollection<ILibraryItem>> Search(string query)
{ {
return await Sort( return (await Sort(
_database.LibraryItems _database.LibraryItems
.Where(_database.Like<LibraryItem>(x => x.Title, $"%{query}%")) .Where(_database.Like<ILibraryItem>(x => x.Name, $"%{query}%"))
) )
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync())
.Select(x => (x as BagItem)!.ToItem())
.ToList();
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<LibraryItem> Create(LibraryItem obj) public override Task<ILibraryItem> Create(ILibraryItem obj)
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <inheritdoc /> /// <inheritdoc />
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj) public override Task<ILibraryItem> CreateIfNotExists(ILibraryItem obj)
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <inheritdoc /> /// <inheritdoc />
public override Task<LibraryItem> Edit(LibraryItem obj, bool resetOld) public override Task<ILibraryItem> Edit(ILibraryItem obj, bool resetOld)
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <inheritdoc /> /// <inheritdoc />
@ -121,58 +116,7 @@ namespace Kyoo.Core.Controllers
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <inheritdoc /> /// <inheritdoc />
public override Task Delete(LibraryItem obj) public override Task Delete(ILibraryItem obj)
=> throw new InvalidOperationException(); => throw new InvalidOperationException();
/// <summary>
/// Get a basic queryable for a library with the right mapping from shows and collections.
/// Shows contained in a collection are excluded.
/// </summary>
/// <param name="selector">Only items that are part of a library that match this predicate will be returned.</param>
/// <returns>A queryable containing items that are part of a library matching the selector.</returns>
private IQueryable<LibraryItem> _LibraryRelatedQuery(Expression<Func<Library, bool>> selector)
=> _database.Libraries
.Where(selector)
.SelectMany(x => x.Shows)
.Where(x => !x.Collections.Any())
.Select(LibraryItem.FromShow)
.Concat(_database.Libraries
.Where(selector)
.SelectMany(x => x.Collections)
.Select(LibraryItem.FromCollection));
/// <inheritdoc />
public async Task<ICollection<LibraryItem>> GetFromLibrary(int id,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default)
{
ICollection<LibraryItem> items = await ApplyFilters(
_LibraryRelatedQuery(x => x.ID == id),
where,
sort,
limit
);
if (!items.Any() && await _libraries.Value.GetOrDefault(id) == null)
throw new ItemNotFoundException();
return items;
}
/// <inheritdoc />
public async Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
Expression<Func<LibraryItem, bool>> where = null,
Sort<LibraryItem> sort = default,
Pagination limit = default)
{
ICollection<LibraryItem> items = await ApplyFilters(
_LibraryRelatedQuery(x => x.Slug == slug),
where,
sort,
limit
);
if (!items.Any() && await _libraries.Value.GetOrDefault(slug) == null)
throw new ItemNotFoundException();
return items;
}
} }
} }

View File

@ -1,99 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers
{
/// <summary>
/// A local repository to handle libraries.
/// </summary>
public class LibraryRepository : LocalRepository<Library>, ILibraryRepository
{
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Sort<Library> DefaultSort => new Sort<Library>.By(x => x.ID);
/// <summary>
/// Create a new <see cref="LibraryRepository"/> instance.
/// </summary>
/// <param name="database">The database handle</param>
public LibraryRepository(DatabaseContext database)
: base(database)
{
_database = database;
}
/// <inheritdoc />
public override async Task<ICollection<Library>> Search(string query)
{
return await Sort(
_database.Libraries
.Where(_database.Like<Library>(x => x.Name + " " + x.Slug, $"%{query}%"))
)
.Take(20)
.ToListAsync();
}
/// <inheritdoc />
public override async Task<Library> Create(Library obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
OnResourceCreated(obj);
return obj;
}
/// <inheritdoc />
protected override async Task Validate(Library resource)
{
await base.Validate(resource);
if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("The library's slug must be set and not empty");
if (string.IsNullOrEmpty(resource.Name))
throw new ArgumentException("The library's name must be set and not empty");
if (resource.Paths == null || !resource.Paths.Any())
throw new ArgumentException("The library should have a least one path.");
}
/// <inheritdoc />
public override async Task Delete(Library obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
_database.Entry(obj).State = EntityState.Deleted;
await _database.SaveChangesAsync();
await base.Delete(obj);
}
}
}

View File

@ -0,0 +1,139 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
namespace Kyoo.Core.Controllers
{
/// <summary>
/// A local repository to handle shows
/// </summary>
public class MovieRepository : LocalRepository<Movie>, IMovieRepository
{
/// <summary>
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// A studio repository to handle creation/validation of related studios.
/// </summary>
private readonly IStudioRepository _studios;
/// <summary>
/// A people repository to handle creation/validation of related people.
/// </summary>
private readonly IPeopleRepository _people;
/// <inheritdoc />
protected override Sort<Movie> DefaultSort => new Sort<Movie>.By(x => x.Name);
/// <summary>
/// Create a new <see cref="MovieRepository"/>.
/// </summary>
/// <param name="database">The database handle to use</param>
/// <param name="studios">A studio repository</param>
/// <param name="people">A people repository</param>
public MovieRepository(DatabaseContext database,
IStudioRepository studios,
IPeopleRepository people)
: base(database)
{
_database = database;
_studios = studios;
_people = people;
}
/// <inheritdoc />
public override async Task<ICollection<Movie>> Search(string query)
{
query = $"%{query}%";
return await Sort(
_database.Movies
.Where(_database.Like<Movie>(x => x.Name + " " + x.Slug, query))
)
.Take(20)
.ToListAsync();
}
/// <inheritdoc />
public override async Task<Movie> Create(Movie obj)
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
OnResourceCreated(obj);
return obj;
}
/// <inheritdoc />
protected override async Task Validate(Movie resource)
{
await base.Validate(resource);
if (resource.Studio != null)
{
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
resource.StudioID = resource.Studio.ID;
}
if (resource.People != null)
{
foreach (PeopleRole role in resource.People)
{
role.People = _database.LocalEntity<People>(role.People.Slug)
?? await _people.CreateIfNotExists(role.People);
role.PeopleID = role.People.ID;
_database.Entry(role).State = EntityState.Added;
}
}
}
/// <inheritdoc />
protected override async Task EditRelations(Movie resource, Movie changed, bool resetOld)
{
await Validate(changed);
if (changed.Studio != null || resetOld)
{
await Database.Entry(resource).Reference(x => x.Studio).LoadAsync();
resource.Studio = changed.Studio;
}
if (changed.People != null || resetOld)
{
await Database.Entry(resource).Collection(x => x.People).LoadAsync();
resource.People = changed.People;
}
}
/// <inheritdoc />
public override async Task Delete(Movie obj)
{
_database.Remove(obj);
await _database.SaveChangesAsync();
await base.Delete(obj);
}
}
}

View File

@ -102,7 +102,7 @@ namespace Kyoo.Core.Controllers
{ {
return await Sort( return await Sort(
_database.Seasons _database.Seasons
.Where(_database.Like<Season>(x => x.Title, $"%{query}%")) .Where(_database.Like<Season>(x => x.Name, $"%{query}%"))
) )
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();

View File

@ -47,13 +47,8 @@ namespace Kyoo.Core.Controllers
/// </summary> /// </summary>
private readonly IPeopleRepository _people; private readonly IPeopleRepository _people;
/// <summary>
/// A genres repository to handle creation/validation of related genres.
/// </summary>
private readonly IGenreRepository _genres;
/// <inheritdoc /> /// <inheritdoc />
protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Title); protected override Sort<Show> DefaultSort => new Sort<Show>.By(x => x.Name);
/// <summary> /// <summary>
/// Create a new <see cref="ShowRepository"/>. /// Create a new <see cref="ShowRepository"/>.
@ -61,17 +56,14 @@ namespace Kyoo.Core.Controllers
/// <param name="database">The database handle to use</param> /// <param name="database">The database handle to use</param>
/// <param name="studios">A studio repository</param> /// <param name="studios">A studio repository</param>
/// <param name="people">A people repository</param> /// <param name="people">A people repository</param>
/// <param name="genres">A genres repository</param>
public ShowRepository(DatabaseContext database, public ShowRepository(DatabaseContext database,
IStudioRepository studios, IStudioRepository studios,
IPeopleRepository people, IPeopleRepository people)
IGenreRepository genres)
: base(database) : base(database)
{ {
_database = database; _database = database;
_studios = studios; _studios = studios;
_people = people; _people = people;
_genres = genres;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -80,7 +72,7 @@ namespace Kyoo.Core.Controllers
query = $"%{query}%"; query = $"%{query}%";
return await Sort( return await Sort(
_database.Shows _database.Shows
.Where(_database.Like<Show>(x => x.Title + " " + x.Slug, query)) .Where(_database.Like<Show>(x => x.Name + " " + x.Slug, query))
) )
.Take(20) .Take(20)
.ToListAsync(); .ToListAsync();
@ -99,7 +91,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc /> /// <inheritdoc />
protected override async Task Validate(Show resource) protected override async Task Validate(Show resource)
{ {
resource.Slug ??= Utility.ToSlug(resource.Title); resource.Slug ??= Utility.ToSlug(resource.Name);
await base.Validate(resource); await base.Validate(resource);
if (resource.Studio != null) if (resource.Studio != null)
@ -108,14 +100,6 @@ namespace Kyoo.Core.Controllers
resource.StudioID = resource.Studio.ID; resource.StudioID = resource.Studio.ID;
} }
if (resource.Genres != null)
{
resource.Genres = await resource.Genres
.SelectAsync(x => _genres.CreateIfNotExists(x))
.ToListAsync();
_database.AttachRange(resource.Genres);
}
if (resource.People != null) if (resource.People != null)
{ {
foreach (PeopleRole role in resource.People) foreach (PeopleRole role in resource.People)
@ -133,21 +117,12 @@ namespace Kyoo.Core.Controllers
{ {
await Validate(changed); await Validate(changed);
if (changed.Aliases != null || resetOld)
resource.Aliases = changed.Aliases;
if (changed.Studio != null || resetOld) if (changed.Studio != null || resetOld)
{ {
await Database.Entry(resource).Reference(x => x.Studio).LoadAsync(); await Database.Entry(resource).Reference(x => x.Studio).LoadAsync();
resource.Studio = changed.Studio; resource.Studio = changed.Studio;
} }
if (changed.Genres != null || resetOld)
{
await Database.Entry(resource).Collection(x => x.Genres).LoadAsync();
resource.Genres = changed.Genres;
}
if (changed.People != null || resetOld) if (changed.People != null || resetOld)
{ {
await Database.Entry(resource).Collection(x => x.People).LoadAsync(); await Database.Entry(resource).Collection(x => x.People).LoadAsync();
@ -155,27 +130,6 @@ namespace Kyoo.Core.Controllers
} }
} }
/// <inheritdoc />
public async Task AddShowLink(int showID, int? libraryID, int? collectionID)
{
if (collectionID != null)
{
await _database.AddLinks<Collection, Show>(collectionID.Value, showID);
await _database.SaveIfNoDuplicates();
if (libraryID != null)
{
await _database.AddLinks<Library, Collection>(libraryID.Value, collectionID.Value);
await _database.SaveIfNoDuplicates();
}
}
if (libraryID != null)
{
await _database.AddLinks<Library, Show>(libraryID.Value, showID);
await _database.SaveIfNoDuplicates();
}
}
/// <inheritdoc /> /// <inheritdoc />
public Task<string> GetSlug(int showID) public Task<string> GetSlug(int showID)
{ {

View File

@ -98,9 +98,9 @@ namespace Kyoo.Core.Controllers
throw new ArgumentNullException(nameof(item)); throw new ArgumentNullException(nameof(item));
string name = item is IResource res ? res.Slug : "???"; string name = item is IResource res ? res.Slug : "???";
await _DownloadImage(item.Poster.Source, _GetBaseImagePath(item, "poster"), $"The poster of {name}"); await _DownloadImage(item.Poster?.Source, _GetBaseImagePath(item, "poster"), $"The poster of {name}");
await _DownloadImage(item.Thumbnail.Source, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}"); await _DownloadImage(item.Thumbnail?.Source, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}");
await _DownloadImage(item.Logo.Source, _GetBaseImagePath(item, "logo"), $"The poster of {name}"); await _DownloadImage(item.Logo?.Source, _GetBaseImagePath(item, "logo"), $"The poster of {name}");
} }
private static string _GetBaseImagePath<T>(T item, string image) private static string _GetBaseImagePath<T>(T item, string image)

View File

@ -30,6 +30,7 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using JsonOptions = Kyoo.Core.Api.JsonOptions; using JsonOptions = Kyoo.Core.Api.JsonOptions;
namespace Kyoo.Core namespace Kyoo.Core
@ -48,15 +49,14 @@ namespace Kyoo.Core
builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().InstancePerLifetimeScope(); builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().InstancePerLifetimeScope();
builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope(); builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope();
builder.RegisterRepository<ILibraryRepository, LibraryRepository>();
builder.RegisterRepository<ILibraryItemRepository, LibraryItemRepository>(); builder.RegisterRepository<ILibraryItemRepository, LibraryItemRepository>();
builder.RegisterRepository<ICollectionRepository, CollectionRepository>(); builder.RegisterRepository<ICollectionRepository, CollectionRepository>();
builder.RegisterRepository<IMovieRepository, MovieRepository>();
builder.RegisterRepository<IShowRepository, ShowRepository>(); builder.RegisterRepository<IShowRepository, ShowRepository>();
builder.RegisterRepository<ISeasonRepository, SeasonRepository>(); builder.RegisterRepository<ISeasonRepository, SeasonRepository>();
builder.RegisterRepository<IEpisodeRepository, EpisodeRepository>(); builder.RegisterRepository<IEpisodeRepository, EpisodeRepository>();
builder.RegisterRepository<IPeopleRepository, PeopleRepository>(); builder.RegisterRepository<IPeopleRepository, PeopleRepository>();
builder.RegisterRepository<IStudioRepository, StudioRepository>(); builder.RegisterRepository<IStudioRepository, StudioRepository>();
builder.RegisterRepository<IGenreRepository, GenreRepository>();
builder.RegisterRepository<IUserRepository, UserRepository>(); builder.RegisterRepository<IUserRepository, UserRepository>();
} }
@ -73,6 +73,7 @@ namespace Kyoo.Core
.AddNewtonsoftJson(x => .AddNewtonsoftJson(x =>
{ {
x.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; x.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
x.SerializerSettings.Converters.Add(new StringEnumConverter());
}) })
.AddDataAnnotations() .AddDataAnnotations()
.AddControllersAsServices() .AddControllersAsServices()

View File

@ -16,40 +16,31 @@
// 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 System.Collections.Generic; using System.Collections.Generic;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Kyoo.Abstractions.Models namespace Kyoo.Core.Api
{ {
/// <summary> public class ImageConverter : JsonConverter<Image>
/// A library containing <see cref="Show"/> and <see cref="Collection"/>.
/// </summary>
public class Library : IResource
{ {
/// <inheritdoc /> /// <inheritdoc />
public int ID { get; set; } public override void WriteJson(JsonWriter writer, Image value, JsonSerializer serializer)
{
JObject obj = JObject.FromObject(value, serializer);
obj.WriteTo(writer);
}
/// <inheritdoc /> /// <inheritdoc />
public string Slug { get; set; } public override Image ReadJson(JsonReader reader,
Type objectType,
/// <summary> Image existingValue,
/// The name of this library. bool hasExistingValue,
/// </summary> JsonSerializer serializer)
public string Name { get; set; } {
throw new NotImplementedException();
/// <summary> }
/// The list of paths that this library is responsible for. This is mainly used by the Scan task.
/// </summary>
public string[] Paths { get; set; }
/// <summary>
/// The list of shows in this library.
/// </summary>
[LoadableRelation] public ICollection<Show> Shows { get; set; }
/// <summary>
/// The list of collections in this library.
/// </summary>
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
} }
} }

View File

@ -18,7 +18,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection; using System.Reflection;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Attributes;

View File

@ -1,95 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api
{
/// <summary>
/// Information about one or multiple <see cref="Genre"/>.
/// </summary>
[Route("genres")]
[Route("genre", Order = AlternativeRoute)]
[ApiController]
[PartialPermission(nameof(Genre))]
[ApiDefinition("Genres", Group = MetadataGroup)]
public class GenreApi : CrudApi<Genre>
{
/// <summary>
/// The library manager used to modify or retrieve information about the data store.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Create a new <see cref="GenreApi"/>.
/// </summary>
/// <param name="libraryManager">
/// The library manager used to modify or retrieve information about the data store.
/// </param>
public GenreApi(ILibraryManager libraryManager)
: base(libraryManager.GenreRepository)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Get shows with genre
/// </summary>
/// <remarks>
/// Lists the shows that have the selected genre.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Genre"/>.</param>
/// <param name="sortBy">A key to sort shows by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of shows to return and where to start.</param>
/// <returns>A page of shows.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No genre with the given ID could be found.</response>
[HttpGet("{identifier:id}/shows")]
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Genre>(x => x.Genres)),
Sort<Show>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Genre>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
}
}

View File

@ -93,40 +93,5 @@ namespace Kyoo.Core.Api
return NotFound(); return NotFound();
return Page(resources, pagination.Limit); return Page(resources, pagination.Limit);
} }
/// <summary>
/// Get libraries containing this collection
/// </summary>
/// <remarks>
/// Lists the libraries that contain the collection with the given id or slug.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Collection"/>.</param>
/// <param name="sortBy">A key to sort libraries by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of libraries to return.</param>
/// <returns>A page of libraries.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No collection with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/libraries")]
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Library> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Collection>(x => x.Collections)),
Sort<Library>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Collection>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
} }
} }

View File

@ -1,171 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api
{
/// <summary>
/// Information about one or multiple <see cref="Library"/>.
/// </summary>
[Route("libraries")]
[Route("library", Order = AlternativeRoute)]
[ApiController]
[ResourceView]
[PartialPermission(nameof(Library), Group = Group.Admin)]
[ApiDefinition("Library", Group = ResourcesGroup)]
public class LibraryApi : CrudApi<Library>
{
/// <summary>
/// The library manager used to modify or retrieve information in the data store.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Create a new <see cref="EpisodeApi"/>.
/// </summary>
/// <param name="libraryManager">
/// The library manager used to modify or retrieve information in the data store.
/// </param>
public LibraryApi(ILibraryManager libraryManager)
: base(libraryManager.LibraryRepository)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Get shows
/// </summary>
/// <remarks>
/// List the shows that are part of this library.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
/// <param name="sortBy">A key to sort shows by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of shows to return.</param>
/// <returns>A page of shows.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No library with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/shows")]
[HttpGet("{identifier:id}/show", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Show>>> GetShows(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Library>(x => x.Libraries)),
Sort<Show>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
/// <summary>
/// Get collections
/// </summary>
/// <remarks>
/// List the collections that are part of this library.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
/// <param name="sortBy">A key to sort collections by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of collections to return.</param>
/// <returns>A page of collections.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No library with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/collections")]
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Library>(x => x.Libraries)),
Sort<Collection>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Library>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
/// <summary>
/// Get items
/// </summary>
/// <remarks>
/// List all items of this library.
/// An item can ether represent a collection or a show.
/// This endpoint allow one to retrieve all collections and shows that are not contained in a collection.
/// This is what is displayed on the /browse/library page of the webapp.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Library"/>.</param>
/// <param name="sortBy">A key to sort items by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of items to return.</param>
/// <returns>A page of items.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No library with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/items")]
[HttpGet("{identifier:id}/item", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<LibraryItem>>> GetItems(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
Expression<Func<LibraryItem, bool>> whereQuery = ApiHelper.ParseWhere<LibraryItem>(where);
Sort<LibraryItem> sort = Sort<LibraryItem>.From(sortBy);
ICollection<LibraryItem> resources = await identifier.Match(
id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination),
slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination)
);
return Page(resources, pagination.Limit);
}
}
}

View File

@ -16,7 +16,6 @@
// 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 System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
@ -38,7 +37,7 @@ namespace Kyoo.Core.Api
[Route("item", Order = AlternativeRoute)] [Route("item", Order = AlternativeRoute)]
[ApiController] [ApiController]
[ResourceView] [ResourceView]
[PartialPermission(nameof(LibraryItem))] [PartialPermission("LibraryItem")]
[ApiDefinition("Items", Group = ResourcesGroup)] [ApiDefinition("Items", Group = ResourcesGroup)]
public class LibraryItemApi : BaseApi public class LibraryItemApi : BaseApi
{ {
@ -78,14 +77,14 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<LibraryItem>>> GetAll( public async Task<ActionResult<Page<ILibraryItem>>> GetAll(
[FromQuery] string sortBy, [FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where, [FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination) [FromQuery] Pagination pagination)
{ {
ICollection<LibraryItem> resources = await _libraryItems.GetAll( ICollection<ILibraryItem> resources = await _libraryItems.GetAll(
ApiHelper.ParseWhere<LibraryItem>(where), ApiHelper.ParseWhere<ILibraryItem>(where),
Sort<LibraryItem>.From(sortBy), Sort<ILibraryItem>.From(sortBy),
pagination pagination
); );

View File

@ -0,0 +1,149 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api
{
/// <summary>
/// Information about one or multiple <see cref="Movie"/>.
/// </summary>
[Route("movies")]
[Route("movie", Order = AlternativeRoute)]
[ApiController]
[PartialPermission(nameof(Show))]
[ApiDefinition("Shows", Group = ResourcesGroup)]
public class MovieApi : CrudThumbsApi<Movie>
{
/// <summary>
/// The library manager used to modify or retrieve information in the data store.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Create a new <see cref="ShowApi"/>.
/// </summary>
/// <param name="libraryManager">
/// The library manager used to modify or retrieve information about the data store.
/// </param>
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
public MovieApi(ILibraryManager libraryManager,
IThumbnailsManager thumbs)
: base(libraryManager.MovieRepository, thumbs)
{
_libraryManager = libraryManager;
}
// /// <summary>
// /// Get staff
// /// </summary>
// /// <remarks>
// /// List staff members that made this show.
// /// </remarks>
// /// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
// /// <param name="sortBy">A key to sort staff members by.</param>
// /// <param name="where">An optional list of filters.</param>
// /// <param name="pagination">The number of people to return.</param>
// /// <returns>A page of people.</returns>
// /// <response code="400">The filters or the sort parameters are invalid.</response>
// /// <response code="404">No show with the given ID or slug could be found.</response>
// [HttpGet("{identifier:id}/staff")]
// [HttpGet("{identifier:id}/people", Order = AlternativeRoute)]
// [PartialPermission(Kind.Read)]
// [ProducesResponseType(StatusCodes.Status200OK)]
// [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
// [ProducesResponseType(StatusCodes.Status404NotFound)]
// public async Task<ActionResult<Page<PeopleRole>>> GetPeople(Identifier identifier,
// [FromQuery] string sortBy,
// [FromQuery] Dictionary<string, string> where,
// [FromQuery] Pagination pagination)
// {
// Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
// Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
//
// ICollection<PeopleRole> resources = await identifier.Match(
// id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination),
// slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination)
// );
// return Page(resources, pagination.Limit);
// }
/// <summary>
/// Get studio that made the show
/// </summary>
/// <remarks>
/// Get the studio that made the show.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <returns>The studio that made the show.</returns>
/// <response code="404">No show with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/studio")]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Studio>> GetStudio(Identifier identifier)
{
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Movie>(x => x.Movies));
}
/// <summary>
/// Get collections containing this show
/// </summary>
/// <remarks>
/// List the collections that contain this show.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort collections by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of collections to return.</param>
/// <returns>A page of collections.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No show with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/collections")]
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Collection>>> GetCollections(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Movie>(x => x.Movies)),
Sort<Collection>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Movie>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
}
}

View File

@ -79,7 +79,6 @@ namespace Kyoo.Core.Api
Shows = await _libraryManager.Search<Show>(query), Shows = await _libraryManager.Search<Show>(query),
Episodes = await _libraryManager.Search<Episode>(query), Episodes = await _libraryManager.Search<Episode>(query),
People = await _libraryManager.Search<People>(query), People = await _libraryManager.Search<People>(query),
Genres = await _libraryManager.Search<Genre>(query),
Studios = await _libraryManager.Search<Studio>(query) Studios = await _libraryManager.Search<Studio>(query)
}; };
} }
@ -133,9 +132,9 @@ namespace Kyoo.Core.Api
[Permission(nameof(Show), Kind.Read)] [Permission(nameof(Show), Kind.Read)]
[ApiDefinition("Items")] [ApiDefinition("Items")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<LibraryItem>> SearchItems(string query) public Task<ICollection<ILibraryItem>> SearchItems(string query)
{ {
return _libraryManager.Search<LibraryItem>(query); return _libraryManager.Search<ILibraryItem>(query);
} }
/// <summary> /// <summary>
@ -175,24 +174,6 @@ namespace Kyoo.Core.Api
return _libraryManager.Search<People>(query); return _libraryManager.Search<People>(query);
} }
/// <summary>
/// Search genres
/// </summary>
/// <remarks>
/// Search for genres
/// </remarks>
/// <param name="query">The query to search for.</param>
/// <returns>A list of genres found for the specified query.</returns>
[HttpGet("genres")]
[HttpGet("genre", Order = AlternativeRoute)]
[Permission(nameof(Genre), Kind.Read)]
[ApiDefinition("Genres")]
[ProducesResponseType(StatusCodes.Status200OK)]
public Task<ICollection<Genre>> SearchGenres(string query)
{
return _libraryManager.Search<Genre>(query);
}
/// <summary> /// <summary>
/// Search studios /// Search studios
/// </summary> /// </summary>

View File

@ -37,8 +37,6 @@ namespace Kyoo.Core.Api
/// </summary> /// </summary>
[Route("shows")] [Route("shows")]
[Route("show", Order = AlternativeRoute)] [Route("show", Order = AlternativeRoute)]
[Route("movie", Order = AlternativeRoute)]
[Route("movies", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(Show))] [PartialPermission(nameof(Show))]
[ApiDefinition("Shows", Group = ResourcesGroup)] [ApiDefinition("Shows", Group = ResourcesGroup)]
@ -63,24 +61,6 @@ namespace Kyoo.Core.Api
_libraryManager = libraryManager; _libraryManager = libraryManager;
} }
/// <inheritdoc/>
public override async Task<ActionResult<Show>> Create([FromBody] Show resource)
{
ActionResult<Show> ret = await base.Create(resource);
if (ret.Value.IsMovie)
{
Episode episode = new()
{
Show = ret.Value,
Title = ret.Value.Title,
Path = ret.Value.Path
};
await _libraryManager.Create(episode);
}
return ret;
}
/// <summary> /// <summary>
/// Get seasons of this show /// Get seasons of this show
/// </summary> /// </summary>
@ -185,41 +165,6 @@ namespace Kyoo.Core.Api
return Page(resources, pagination.Limit); return Page(resources, pagination.Limit);
} }
/// <summary>
/// Get genres of this show
/// </summary>
/// <remarks>
/// List the genres that represent this show.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort genres by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of genres to return.</param>
/// <returns>A page of genres.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No show with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/genres")]
[HttpGet("{identifier:id}/genre", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Genre>>> GetGenres(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Genre> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Genre, Show>(x => x.Shows)),
Sort<Genre>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
/// <summary> /// <summary>
/// Get studio that made the show /// Get studio that made the show
/// </summary> /// </summary>
@ -238,42 +183,6 @@ namespace Kyoo.Core.Api
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Show>(x => x.Shows)); return await _libraryManager.Get(identifier.IsContainedIn<Studio, Show>(x => x.Shows));
} }
/// <summary>
/// Get libraries containing this show
/// </summary>
/// <remarks>
/// List the libraries that contain this show. If this show is contained in a collection that is contained in
/// a library, this library will be returned too.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Show"/>.</param>
/// <param name="sortBy">A key to sort libraries by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="pagination">The number of libraries to return.</param>
/// <returns>A page of libraries.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No show with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/libraries")]
[HttpGet("{identifier:id}/library", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Library>>> GetLibraries(Identifier identifier,
[FromQuery] string sortBy,
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
ICollection<Library> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Library, Show>(x => x.Shows)),
Sort<Library>.From(sortBy),
pagination
);
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Show>()) == null)
return NotFound();
return Page(resources, pagination.Limit);
}
/// <summary> /// <summary>
/// Get collections containing this show /// Get collections containing this show
/// </summary> /// </summary>

View File

@ -1,91 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Net.Http;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api
{
/// <summary>
/// Retrieve information of an <see cref="Episode"/> as a <see cref="WatchItem"/>.
/// A watch item is another representation of an episode in a form easier to read and display for playback.
/// It contains streams (video, audio, subtitles) information, chapters, next and previous episodes and a bit of
/// information of the show.
/// </summary>
[Route("watch")]
[Route("watchitem", Order = AlternativeRoute)]
[ApiController]
[ApiDefinition("Watch Items", Group = WatchGroup)]
public class WatchApi : ControllerBase
{
/// <summary>
/// The library manager used to modify or retrieve information in the data store.
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The http client to reach transcoder.
/// </summary>
private readonly HttpClient _client;
/// <summary>
/// Create a new <see cref="WatchApi"/>.
/// </summary>
/// <param name="libraryManager">
/// The library manager used to modify or retrieve information in the data store.
/// </param>
/// <param name="client">The http client to reach transcoder.</param>
public WatchApi(ILibraryManager libraryManager, HttpClient client)
{
_libraryManager = libraryManager;
_client = client;
}
/// <summary>
/// Get a watch item
/// </summary>
/// <remarks>
/// Retrieve a watch item of an episode.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
/// <returns>A page of items.</returns>
/// <response code="404">No episode with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}")]
[Permission("watch", Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<WatchItem>> GetWatchItem(Identifier identifier)
{
Episode item = await identifier.Match(
id => _libraryManager.GetOrDefault<Episode>(id),
slug => _libraryManager.GetOrDefault<Episode>(slug)
);
if (item == null)
return NotFound();
return await WatchItem.FromEpisode(item, _libraryManager, _client);
}
}
}

View File

@ -41,16 +41,16 @@ namespace Kyoo.Postgresql
/// </remarks> /// </remarks>
public abstract class DatabaseContext : DbContext public abstract class DatabaseContext : DbContext
{ {
/// <summary>
/// All libraries of Kyoo. See <see cref="Library"/>.
/// </summary>
public DbSet<Library> Libraries { get; set; }
/// <summary> /// <summary>
/// All collections of Kyoo. See <see cref="Collection"/>. /// All collections of Kyoo. See <see cref="Collection"/>.
/// </summary> /// </summary>
public DbSet<Collection> Collections { get; set; } public DbSet<Collection> Collections { get; set; }
/// <summary>
/// All movies of Kyoo. See <see cref="Movie"/>.
/// </summary>
public DbSet<Movie> Movies { get; set; }
/// <summary> /// <summary>
/// All shows of Kyoo. See <see cref="Show"/>. /// All shows of Kyoo. See <see cref="Show"/>.
/// </summary> /// </summary>
@ -66,11 +66,6 @@ namespace Kyoo.Postgresql
/// </summary> /// </summary>
public DbSet<Episode> Episodes { get; set; } public DbSet<Episode> Episodes { get; set; }
/// <summary>
/// All genres of Kyoo. See <see cref="Genres"/>.
/// </summary>
public DbSet<Genre> Genres { get; set; }
/// <summary> /// <summary>
/// All people of Kyoo. See <see cref="People"/>. /// All people of Kyoo. See <see cref="People"/>.
/// </summary> /// </summary>
@ -91,18 +86,38 @@ namespace Kyoo.Postgresql
/// </summary> /// </summary>
public DbSet<PeopleRole> PeopleRoles { get; set; } public DbSet<PeopleRole> PeopleRoles { get; set; }
/// <summary>
/// Episodes with a watch percentage. See <see cref="WatchedEpisode"/>.
/// </summary>
public DbSet<WatchedEpisode> WatchedEpisodes { get; set; }
/// <summary> /// <summary>
/// The list of library items (shows and collections that are part of a library - or the global one). /// The list of library items (shows and collections that are part of a library - or the global one).
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This set is ready only, on most database this will be a view. /// This set is ready only, on most database this will be a view.
/// </remarks> /// </remarks>
public DbSet<LibraryItem> LibraryItems { get; set; } public IQueryable<BagItem> LibraryItems =>
Shows.Select(x => new BagItem
{
ID = x.ID,
Slug = x.Slug,
Name = x.Name,
AirDate = x.StartAir,
Poster = x.Poster,
Rest = x
}).Union(Movies.Select(x => new BagItem
{
ID = x.ID,
Slug = x.Slug,
Name = x.Name,
AirDate = x.AirDate,
Poster = x.Poster,
Rest = x
})).Union(Collections.Select(x => new BagItem
{
ID = x.ID,
Slug = x.Slug,
Name = x.Name,
AirDate = null,
Poster = x.Poster,
Rest = x
}));
/// <summary> /// <summary>
/// Add a many to many link between two resources. /// Add a many to many link between two resources.
@ -138,14 +153,6 @@ namespace Kyoo.Postgresql
: base(options) : base(options)
{ } { }
/// <summary>
/// Get the name of the metadata table of the given type.
/// </summary>
/// <typeparam name="T">The type related to the metadata</typeparam>
/// <returns>The name of the table containing the metadata.</returns>
protected abstract string MetadataName<T>()
where T : IMetadata;
/// <summary> /// <summary>
/// Get the name of the link table of the two given types. /// Get the name of the link table of the two given types.
/// </summary> /// </summary>
@ -265,15 +272,16 @@ namespace Kyoo.Postgresql
.WithOne(x => x.Season) .WithOne(x => x.Season)
.OnDelete(DeleteBehavior.Cascade); .OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Movie>()
.HasOne(x => x.Studio)
.WithMany(x => x.Movies)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<Show>() modelBuilder.Entity<Show>()
.HasOne(x => x.Studio) .HasOne(x => x.Studio)
.WithMany(x => x.Shows) .WithMany(x => x.Shows)
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
_HasManyToMany<Library, Collection>(modelBuilder, x => x.Collections, x => x.Libraries);
_HasManyToMany<Library, Show>(modelBuilder, x => x.Shows, x => x.Libraries);
_HasManyToMany<Collection, Show>(modelBuilder, x => x.Shows, x => x.Collections); _HasManyToMany<Collection, Show>(modelBuilder, x => x.Shows, x => x.Collections);
_HasManyToMany<Show, Genre>(modelBuilder, x => x.Genres, x => x.Shows);
modelBuilder.Entity<User>() modelBuilder.Entity<User>()
.HasMany(x => x.Watched) .HasMany(x => x.Watched)
@ -281,14 +289,15 @@ namespace Kyoo.Postgresql
.UsingEntity(x => x.ToTable(LinkName<User, Show>())); .UsingEntity(x => x.ToTable(LinkName<User, Show>()));
_HasMetadata<Collection>(modelBuilder); _HasMetadata<Collection>(modelBuilder);
_HasMetadata<Movie>(modelBuilder);
_HasMetadata<Show>(modelBuilder); _HasMetadata<Show>(modelBuilder);
_HasMetadata<Season>(modelBuilder); _HasMetadata<Season>(modelBuilder);
_HasMetadata<Episode>(modelBuilder); _HasMetadata<Episode>(modelBuilder);
_HasMetadata<People>(modelBuilder); _HasMetadata<People>(modelBuilder);
_HasMetadata<Studio>(modelBuilder); _HasMetadata<Studio>(modelBuilder);
_HasImages<LibraryItem>(modelBuilder);
_HasImages<Collection>(modelBuilder); _HasImages<Collection>(modelBuilder);
_HasImages<Movie>(modelBuilder);
_HasImages<Show>(modelBuilder); _HasImages<Show>(modelBuilder);
_HasImages<Season>(modelBuilder); _HasImages<Season>(modelBuilder);
_HasImages<Episode>(modelBuilder); _HasImages<Episode>(modelBuilder);
@ -299,28 +308,15 @@ namespace Kyoo.Postgresql
modelBuilder.Entity<WatchedEpisode>() modelBuilder.Entity<WatchedEpisode>()
.HasKey(x => new { User = x.UserID, Episode = x.EpisodeID }); .HasKey(x => new { User = x.UserID, Episode = x.EpisodeID });
modelBuilder.Entity<Collection>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Genre>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Library>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<People>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Show>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Season>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Episode>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Studio>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<User>().Property(x => x.Slug).IsRequired();
modelBuilder.Entity<Collection>() modelBuilder.Entity<Collection>()
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)
.IsUnique(); .IsUnique();
modelBuilder.Entity<Genre>()
.HasIndex(x => x.Slug)
.IsUnique();
modelBuilder.Entity<Library>()
.HasIndex(x => x.Slug)
.IsUnique();
modelBuilder.Entity<People>() modelBuilder.Entity<People>()
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)
.IsUnique(); .IsUnique();
modelBuilder.Entity<Movie>()
.HasIndex(x => x.Slug)
.IsUnique();
modelBuilder.Entity<Show>() modelBuilder.Entity<Show>()
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)
.IsUnique(); .IsUnique();
@ -342,9 +338,6 @@ namespace Kyoo.Postgresql
modelBuilder.Entity<User>() modelBuilder.Entity<User>()
.HasIndex(x => x.Slug) .HasIndex(x => x.Slug)
.IsUnique(); .IsUnique();
modelBuilder.Entity<LibraryItem>()
.ToView("library_items");
} }
/// <summary> /// <summary>

View File

@ -18,4 +18,8 @@
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" /> <ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" /> <FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Migrations\" />
</ItemGroup>
</Project> </Project>

View File

@ -1,872 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using Kyoo.Abstractions.Models;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
namespace Kyoo.Postgresql.Migrations
{
/// <summary>
/// The initial migration that build most of the database.
/// </summary>
[DbContext(typeof(PostgresContext))]
[Migration("20210801171613_Initial")]
public partial class Initial : Migration
{
/// <inheritdoc/>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:item_type", "show,movie,collection")
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
.Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle,attachment");
migrationBuilder.CreateTable(
name: "collections",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
overview = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_collections", x => x.id);
});
migrationBuilder.CreateTable(
name: "genres",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_genres", x => x.id);
});
migrationBuilder.CreateTable(
name: "libraries",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true),
paths = table.Column<string[]>(type: "text[]", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_libraries", x => x.id);
});
migrationBuilder.CreateTable(
name: "people",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_people", x => x.id);
});
migrationBuilder.CreateTable(
name: "providers",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_providers", x => x.id);
});
migrationBuilder.CreateTable(
name: "studios",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_studios", x => x.id);
});
migrationBuilder.CreateTable(
name: "users",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
username = table.Column<string>(type: "text", nullable: true),
email = table.Column<string>(type: "text", nullable: true),
password = table.Column<string>(type: "text", nullable: true),
permissions = table.Column<string[]>(type: "text[]", nullable: true),
extra_data = table.Column<Dictionary<string, string>>(type: "jsonb", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_users", x => x.id);
});
migrationBuilder.CreateTable(
name: "link_library_collection",
columns: table => new
{
collection_id = table.Column<int>(type: "integer", nullable: false),
library_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id });
table.ForeignKey(
name: "fk_link_library_collection_collections_collection_id",
column: x => x.collection_id,
principalTable: "collections",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_library_collection_libraries_library_id",
column: x => x.library_id,
principalTable: "libraries",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "collection_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_collection_metadata_id_collections_collection_id",
column: x => x.resource_id,
principalTable: "collections",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_collection_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_library_provider",
columns: table => new
{
library_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id });
table.ForeignKey(
name: "fk_link_library_provider_libraries_library_id",
column: x => x.library_id,
principalTable: "libraries",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_library_provider_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "people_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_people_metadata_id_people_people_id",
column: x => x.resource_id,
principalTable: "people",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_people_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "shows",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: false),
title = table.Column<string>(type: "text", nullable: true),
aliases = table.Column<string[]>(type: "text[]", nullable: true),
path = table.Column<string>(type: "text", nullable: true),
overview = table.Column<string>(type: "text", nullable: true),
status = table.Column<Status>(type: "status", nullable: false),
start_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
end_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
is_movie = table.Column<bool>(type: "boolean", nullable: false),
studio_id = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_shows", x => x.id);
table.ForeignKey(
name: "fk_shows_studios_studio_id",
column: x => x.studio_id,
principalTable: "studios",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "studio_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_studio_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_studio_metadata_id_studios_studio_id",
column: x => x.resource_id,
principalTable: "studios",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_collection_show",
columns: table => new
{
collection_id = table.Column<int>(type: "integer", nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id });
table.ForeignKey(
name: "fk_link_collection_show_collections_collection_id",
column: x => x.collection_id,
principalTable: "collections",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_collection_show_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_library_show",
columns: table => new
{
library_id = table.Column<int>(type: "integer", nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id });
table.ForeignKey(
name: "fk_link_library_show_libraries_library_id",
column: x => x.library_id,
principalTable: "libraries",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_library_show_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_show_genre",
columns: table => new
{
genre_id = table.Column<int>(type: "integer", nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id });
table.ForeignKey(
name: "fk_link_show_genre_genres_genre_id",
column: x => x.genre_id,
principalTable: "genres",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_show_genre_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_user_show",
columns: table => new
{
users_id = table.Column<int>(type: "integer", nullable: false),
watched_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id });
table.ForeignKey(
name: "fk_link_user_show_shows_watched_id",
column: x => x.watched_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_user_show_users_users_id",
column: x => x.users_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "people_roles",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
people_id = table.Column<int>(type: "integer", nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false),
type = table.Column<string>(type: "text", nullable: true),
role = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_people_roles", x => x.id);
table.ForeignKey(
name: "fk_people_roles_people_people_id",
column: x => x.people_id,
principalTable: "people",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_people_roles_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "seasons",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: true),
show_id = table.Column<int>(type: "integer", nullable: false),
season_number = table.Column<int>(type: "integer", nullable: false),
title = table.Column<string>(type: "text", nullable: true),
overview = table.Column<string>(type: "text", nullable: true),
start_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
end_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_seasons", x => x.id);
table.ForeignKey(
name: "fk_seasons_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "show_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_show_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_show_metadata_id_shows_show_id",
column: x => x.resource_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "episodes",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: true),
show_id = table.Column<int>(type: "integer", nullable: false),
season_id = table.Column<int>(type: "integer", nullable: true),
season_number = table.Column<int>(type: "integer", nullable: true),
episode_number = table.Column<int>(type: "integer", nullable: true),
absolute_number = table.Column<int>(type: "integer", nullable: true),
path = table.Column<string>(type: "text", nullable: true),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
title = table.Column<string>(type: "text", nullable: true),
overview = table.Column<string>(type: "text", nullable: true),
release_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_episodes", x => x.id);
table.ForeignKey(
name: "fk_episodes_seasons_season_id",
column: x => x.season_id,
principalTable: "seasons",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_episodes_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "season_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_season_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_season_metadata_id_seasons_season_id",
column: x => x.resource_id,
principalTable: "seasons",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "episode_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_episode_metadata_id_episodes_episode_id",
column: x => x.resource_id,
principalTable: "episodes",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_episode_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "tracks",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "text", nullable: true),
title = table.Column<string>(type: "text", nullable: true),
language = table.Column<string>(type: "text", nullable: true),
codec = table.Column<string>(type: "text", nullable: true),
is_default = table.Column<bool>(type: "boolean", nullable: false),
is_forced = table.Column<bool>(type: "boolean", nullable: false),
is_external = table.Column<bool>(type: "boolean", nullable: false),
path = table.Column<string>(type: "text", nullable: true),
type = table.Column<object>(type: "stream_type", nullable: false),
episode_id = table.Column<int>(type: "integer", nullable: false),
track_index = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_tracks", x => x.id);
table.ForeignKey(
name: "fk_tracks_episodes_episode_id",
column: x => x.episode_id,
principalTable: "episodes",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "watched_episodes",
columns: table => new
{
user_id = table.Column<int>(type: "integer", nullable: false),
episode_id = table.Column<int>(type: "integer", nullable: false),
watched_percentage = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id });
table.ForeignKey(
name: "fk_watched_episodes_episodes_episode_id",
column: x => x.episode_id,
principalTable: "episodes",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_watched_episodes_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_collection_metadata_id_provider_id",
table: "collection_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_collections_slug",
table: "collections",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_episode_metadata_id_provider_id",
table: "episode_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_episodes_season_id",
table: "episodes",
column: "season_id");
migrationBuilder.CreateIndex(
name: "ix_episodes_show_id_season_number_episode_number_absolute_numb",
table: "episodes",
columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_episodes_slug",
table: "episodes",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_genres_slug",
table: "genres",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_libraries_slug",
table: "libraries",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_link_collection_show_show_id",
table: "link_collection_show",
column: "show_id");
migrationBuilder.CreateIndex(
name: "ix_link_library_collection_library_id",
table: "link_library_collection",
column: "library_id");
migrationBuilder.CreateIndex(
name: "ix_link_library_provider_provider_id",
table: "link_library_provider",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_link_library_show_show_id",
table: "link_library_show",
column: "show_id");
migrationBuilder.CreateIndex(
name: "ix_link_show_genre_show_id",
table: "link_show_genre",
column: "show_id");
migrationBuilder.CreateIndex(
name: "ix_link_user_show_watched_id",
table: "link_user_show",
column: "watched_id");
migrationBuilder.CreateIndex(
name: "ix_people_slug",
table: "people",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_people_metadata_id_provider_id",
table: "people_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_people_roles_people_id",
table: "people_roles",
column: "people_id");
migrationBuilder.CreateIndex(
name: "ix_people_roles_show_id",
table: "people_roles",
column: "show_id");
migrationBuilder.CreateIndex(
name: "ix_providers_slug",
table: "providers",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_season_metadata_id_provider_id",
table: "season_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_seasons_show_id_season_number",
table: "seasons",
columns: new[] { "show_id", "season_number" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_seasons_slug",
table: "seasons",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_show_metadata_id_provider_id",
table: "show_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_shows_slug",
table: "shows",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_shows_studio_id",
table: "shows",
column: "studio_id");
migrationBuilder.CreateIndex(
name: "ix_studio_metadata_id_provider_id",
table: "studio_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_studios_slug",
table: "studios",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tracks_episode_id_type_language_track_index_is_forced",
table: "tracks",
columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tracks_slug",
table: "tracks",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_users_slug",
table: "users",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_watched_episodes_episode_id",
table: "watched_episodes",
column: "episode_id");
}
/// <inheritdoc/>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "collection_metadata_id");
migrationBuilder.DropTable(
name: "episode_metadata_id");
migrationBuilder.DropTable(
name: "link_collection_show");
migrationBuilder.DropTable(
name: "link_library_collection");
migrationBuilder.DropTable(
name: "link_library_provider");
migrationBuilder.DropTable(
name: "link_library_show");
migrationBuilder.DropTable(
name: "link_show_genre");
migrationBuilder.DropTable(
name: "link_user_show");
migrationBuilder.DropTable(
name: "people_metadata_id");
migrationBuilder.DropTable(
name: "people_roles");
migrationBuilder.DropTable(
name: "season_metadata_id");
migrationBuilder.DropTable(
name: "show_metadata_id");
migrationBuilder.DropTable(
name: "studio_metadata_id");
migrationBuilder.DropTable(
name: "tracks");
migrationBuilder.DropTable(
name: "watched_episodes");
migrationBuilder.DropTable(
name: "collections");
migrationBuilder.DropTable(
name: "libraries");
migrationBuilder.DropTable(
name: "genres");
migrationBuilder.DropTable(
name: "people");
migrationBuilder.DropTable(
name: "providers");
migrationBuilder.DropTable(
name: "episodes");
migrationBuilder.DropTable(
name: "users");
migrationBuilder.DropTable(
name: "seasons");
migrationBuilder.DropTable(
name: "shows");
migrationBuilder.DropTable(
name: "studios");
}
}
}

View File

@ -1,192 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Kyoo.Postgresql.Migrations
{
/// <summary>
/// A migration that adds postgres triggers to update slugs.
/// </summary>
[DbContext(typeof(PostgresContext))]
[Migration("20210801171641_Triggers")]
public partial class Triggers : Migration
{
/// <inheritdoc/>
protected override void Up(MigrationBuilder migrationBuilder)
{
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE FUNCTION season_slug_update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
NEW.slug := CONCAT(
(SELECT slug FROM shows WHERE id = NEW.show_id),
'-s',
NEW.season_number
);
RETURN NEW;
END
$$;");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE TRIGGER season_slug_trigger BEFORE INSERT OR UPDATE OF season_number, show_id ON seasons
FOR EACH ROW EXECUTE PROCEDURE season_slug_update();");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE FUNCTION episode_slug_update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
NEW.slug := CONCAT(
(SELECT slug FROM shows WHERE id = NEW.show_id),
CASE
WHEN NEW.season_number IS NULL AND NEW.episode_number IS NULL THEN NULL
WHEN NEW.season_number IS NULL THEN CONCAT('-', NEW.absolute_number)
ELSE CONCAT('-s', NEW.season_number, 'e', NEW.episode_number)
END
);
RETURN NEW;
END
$$;");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE TRIGGER episode_slug_trigger
BEFORE INSERT OR UPDATE OF absolute_number, episode_number, season_number, show_id ON episodes
FOR EACH ROW EXECUTE PROCEDURE episode_slug_update();");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE FUNCTION show_slug_update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
UPDATE seasons SET slug = CONCAT(NEW.slug, '-s', season_number) WHERE show_id = NEW.id;
UPDATE episodes SET slug = CASE
WHEN season_number IS NULL AND episode_number IS NULL THEN NEW.slug
WHEN season_number IS NULL THEN CONCAT(NEW.slug, '-', absolute_number)
ELSE CONCAT(NEW.slug, '-s', season_number, 'e', episode_number)
END WHERE show_id = NEW.id;
RETURN NEW;
END
$$;");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE TRIGGER show_slug_trigger AFTER UPDATE OF slug ON shows
FOR EACH ROW EXECUTE PROCEDURE show_slug_update();");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE FUNCTION episode_update_tracks_slug()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
UPDATE tracks SET slug = CONCAT(
NEW.slug,
'.', language,
CASE (track_index)
WHEN 0 THEN ''
ELSE CONCAT('-', track_index)
END,
CASE (is_forced)
WHEN false THEN ''
ELSE '.forced'
END,
'.', type
) WHERE episode_id = NEW.id;
RETURN NEW;
END;
$$;");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE TRIGGER episode_track_slug_trigger AFTER UPDATE OF slug ON episodes
FOR EACH ROW EXECUTE PROCEDURE episode_update_tracks_slug();");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE FUNCTION track_slug_update()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS $$
BEGIN
IF NEW.track_index = 0 THEN
NEW.track_index := (SELECT COUNT(*) FROM tracks
WHERE episode_id = NEW.episode_id AND type = NEW.type
AND language = NEW.language AND is_forced = NEW.is_forced);
END IF;
NEW.slug := CONCAT(
(SELECT slug FROM episodes WHERE id = NEW.episode_id),
'.', COALESCE(NEW.language, 'und'),
CASE (NEW.track_index)
WHEN 0 THEN ''
ELSE CONCAT('-', NEW.track_index)
END,
CASE (NEW.is_forced)
WHEN false THEN ''
ELSE '.forced'
END,
'.', NEW.type
);
RETURN NEW;
END
$$;");
// language=PostgreSQL
migrationBuilder.Sql(@"
CREATE TRIGGER track_slug_trigger
BEFORE INSERT OR UPDATE OF episode_id, is_forced, language, track_index, type ON tracks
FOR EACH ROW EXECUTE PROCEDURE track_slug_update();");
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
}
/// <inheritdoc/>
protected override void Down(MigrationBuilder migrationBuilder)
{
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION show_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP TRIGGER season_slug_trigger ON seasons;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION season_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;");
MigrationHelper.DropLibraryItemsView(migrationBuilder);
}
}
}

View File

@ -1,114 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Kyoo.Postgresql.Migrations
{
/// <summary>
/// Remove triggers
/// </summary>
[DbContext(typeof(PostgresContext))]
[Migration("20230724144449_RemoveTriggers")]
public partial class RemoveTriggers : Migration
{
/// <inheritdoc/>
protected override void Up(MigrationBuilder migrationBuilder)
{
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER show_slug_trigger ON shows;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION show_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP TRIGGER season_slug_trigger ON seasons;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION season_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER episode_slug_trigger ON episodes;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION episode_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER track_slug_trigger ON tracks;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION track_slug_update;");
// language=PostgreSQL
migrationBuilder.Sql("DROP TRIGGER episode_track_slug_trigger ON episodes;");
// language=PostgreSQL
migrationBuilder.Sql(@"DROP FUNCTION episode_update_tracks_slug;");
migrationBuilder.AlterColumn<string>(
name: "slug",
table: "tracks",
type: "text",
nullable: false,
defaultValue: string.Empty,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "slug",
table: "seasons",
type: "text",
nullable: false,
defaultValue: string.Empty,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "slug",
table: "episodes",
type: "text",
nullable: false,
defaultValue: string.Empty,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
}
/// <inheritdoc/>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "slug",
table: "tracks",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "text");
migrationBuilder.AlterColumn<string>(
name: "slug",
table: "seasons",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "text");
migrationBuilder.AlterColumn<string>(
name: "slug",
table: "episodes",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "text");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,138 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Kyoo.Postgresql.Migrations
{
/// <inheritdoc />
[DbContext(typeof(PostgresContext))]
[Migration("20230726100747_Timestamp")]
public partial class Timestamp : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
MigrationHelper.DropLibraryItemsView(migrationBuilder);
migrationBuilder.AlterColumn<DateTime>(
name: "start_air",
table: "shows",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "end_air",
table: "shows",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "start_date",
table: "seasons",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "end_date",
table: "seasons",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "release_date",
table: "episodes",
type: "timestamp with time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp without time zone",
oldNullable: true);
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
MigrationHelper.DropLibraryItemsView(migrationBuilder);
migrationBuilder.AlterColumn<DateTime>(
name: "start_air",
table: "shows",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "end_air",
table: "shows",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "start_date",
table: "seasons",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "end_date",
table: "seasons",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
migrationBuilder.AlterColumn<DateTime>(
name: "release_date",
table: "episodes",
type: "timestamp without time zone",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "timestamp with time zone",
oldNullable: true);
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,95 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Kyoo.Postgresql.Migrations
{
/// <inheritdoc />
public partial class RemoveTracks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "tracks");
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:item_type", "show,movie,collection")
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
.OldAnnotation("Npgsql:Enum:item_type", "show,movie,collection")
.OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
.OldAnnotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:item_type", "show,movie,collection")
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned")
.Annotation("Npgsql:Enum:stream_type", "unknown,video,audio,subtitle")
.OldAnnotation("Npgsql:Enum:item_type", "show,movie,collection")
.OldAnnotation("Npgsql:Enum:status", "unknown,finished,airing,planned");
migrationBuilder.CreateTable(
name: "tracks",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
episode_id = table.Column<int>(type: "integer", nullable: false),
codec = table.Column<string>(type: "text", nullable: true),
is_default = table.Column<bool>(type: "boolean", nullable: false),
is_external = table.Column<bool>(type: "boolean", nullable: false),
is_forced = table.Column<bool>(type: "boolean", nullable: false),
language = table.Column<string>(type: "text", nullable: true),
path = table.Column<string>(type: "text", nullable: true),
slug = table.Column<string>(type: "text", nullable: false),
title = table.Column<string>(type: "text", nullable: true),
track_index = table.Column<int>(type: "integer", nullable: false),
type = table.Column<string>(type: "stream_type", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_tracks", x => x.id);
table.ForeignKey(
name: "fk_tracks_episodes_episode_id",
column: x => x.episode_id,
principalTable: "episodes",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_tracks_episode_id_type_language_track_index_is_forced",
table: "tracks",
columns: new[] { "episode_id", "type", "language", "track_index", "is_forced" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_tracks_slug",
table: "tracks",
column: "slug",
unique: true);
}
}
}

View File

@ -1,772 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Kyoo.Postgresql.Migrations
{
/// <inheritdoc />
public partial class AddBlurhash : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
MigrationHelper.DropLibraryItemsView(migrationBuilder);
migrationBuilder.DropTable(
name: "collection_metadata_id");
migrationBuilder.DropTable(
name: "episode_metadata_id");
migrationBuilder.DropTable(
name: "link_library_provider");
migrationBuilder.DropTable(
name: "people_metadata_id");
migrationBuilder.DropTable(
name: "season_metadata_id");
migrationBuilder.DropTable(
name: "show_metadata_id");
migrationBuilder.DropTable(
name: "studio_metadata_id");
migrationBuilder.DropTable(
name: "providers");
migrationBuilder.DropColumn(
name: "images",
table: "users");
migrationBuilder.DropColumn(
name: "images",
table: "shows");
migrationBuilder.DropColumn(
name: "images",
table: "seasons");
migrationBuilder.DropColumn(
name: "images",
table: "people");
migrationBuilder.DropColumn(
name: "images",
table: "episodes");
migrationBuilder.DropColumn(
name: "images",
table: "collections");
migrationBuilder.AddColumn<string>(
name: "logo_blurhash",
table: "users",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_source",
table: "users",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "external_id",
table: "studios",
type: "json",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "external_id",
table: "shows",
type: "json",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_blurhash",
table: "shows",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_source",
table: "shows",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_blurhash",
table: "shows",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_source",
table: "shows",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_blurhash",
table: "shows",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_source",
table: "shows",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "trailer",
table: "shows",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "external_id",
table: "seasons",
type: "json",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_blurhash",
table: "seasons",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_source",
table: "seasons",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_blurhash",
table: "seasons",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_source",
table: "seasons",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_blurhash",
table: "seasons",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_source",
table: "seasons",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "external_id",
table: "people",
type: "json",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_blurhash",
table: "people",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_source",
table: "people",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_blurhash",
table: "people",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_source",
table: "people",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_blurhash",
table: "people",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_source",
table: "people",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "external_id",
table: "episodes",
type: "json",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_blurhash",
table: "episodes",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_source",
table: "episodes",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_blurhash",
table: "episodes",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_source",
table: "episodes",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_blurhash",
table: "episodes",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_source",
table: "episodes",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "external_id",
table: "collections",
type: "json",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_blurhash",
table: "collections",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "logo_source",
table: "collections",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_blurhash",
table: "collections",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "poster_source",
table: "collections",
type: "text",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_blurhash",
table: "collections",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "thumbnail_source",
table: "collections",
type: "text",
nullable: true);
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
MigrationHelper.DropLibraryItemsView(migrationBuilder);
migrationBuilder.DropColumn(
name: "logo_blurhash",
table: "users");
migrationBuilder.DropColumn(
name: "logo_source",
table: "users");
migrationBuilder.DropColumn(
name: "external_id",
table: "studios");
migrationBuilder.DropColumn(
name: "external_id",
table: "shows");
migrationBuilder.DropColumn(
name: "logo_blurhash",
table: "shows");
migrationBuilder.DropColumn(
name: "logo_source",
table: "shows");
migrationBuilder.DropColumn(
name: "poster_blurhash",
table: "shows");
migrationBuilder.DropColumn(
name: "poster_source",
table: "shows");
migrationBuilder.DropColumn(
name: "thumbnail_blurhash",
table: "shows");
migrationBuilder.DropColumn(
name: "thumbnail_source",
table: "shows");
migrationBuilder.DropColumn(
name: "trailer",
table: "shows");
migrationBuilder.DropColumn(
name: "external_id",
table: "seasons");
migrationBuilder.DropColumn(
name: "logo_blurhash",
table: "seasons");
migrationBuilder.DropColumn(
name: "logo_source",
table: "seasons");
migrationBuilder.DropColumn(
name: "poster_blurhash",
table: "seasons");
migrationBuilder.DropColumn(
name: "poster_source",
table: "seasons");
migrationBuilder.DropColumn(
name: "thumbnail_blurhash",
table: "seasons");
migrationBuilder.DropColumn(
name: "thumbnail_source",
table: "seasons");
migrationBuilder.DropColumn(
name: "external_id",
table: "people");
migrationBuilder.DropColumn(
name: "logo_blurhash",
table: "people");
migrationBuilder.DropColumn(
name: "logo_source",
table: "people");
migrationBuilder.DropColumn(
name: "poster_blurhash",
table: "people");
migrationBuilder.DropColumn(
name: "poster_source",
table: "people");
migrationBuilder.DropColumn(
name: "thumbnail_blurhash",
table: "people");
migrationBuilder.DropColumn(
name: "thumbnail_source",
table: "people");
migrationBuilder.DropColumn(
name: "external_id",
table: "episodes");
migrationBuilder.DropColumn(
name: "logo_blurhash",
table: "episodes");
migrationBuilder.DropColumn(
name: "logo_source",
table: "episodes");
migrationBuilder.DropColumn(
name: "poster_blurhash",
table: "episodes");
migrationBuilder.DropColumn(
name: "poster_source",
table: "episodes");
migrationBuilder.DropColumn(
name: "thumbnail_blurhash",
table: "episodes");
migrationBuilder.DropColumn(
name: "thumbnail_source",
table: "episodes");
migrationBuilder.DropColumn(
name: "external_id",
table: "collections");
migrationBuilder.DropColumn(
name: "logo_blurhash",
table: "collections");
migrationBuilder.DropColumn(
name: "logo_source",
table: "collections");
migrationBuilder.DropColumn(
name: "poster_blurhash",
table: "collections");
migrationBuilder.DropColumn(
name: "poster_source",
table: "collections");
migrationBuilder.DropColumn(
name: "thumbnail_blurhash",
table: "collections");
migrationBuilder.DropColumn(
name: "thumbnail_source",
table: "collections");
migrationBuilder.AddColumn<Dictionary<int, string>>(
name: "images",
table: "users",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Dictionary<int, string>>(
name: "images",
table: "shows",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Dictionary<int, string>>(
name: "images",
table: "seasons",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Dictionary<int, string>>(
name: "images",
table: "people",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Dictionary<int, string>>(
name: "images",
table: "episodes",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Dictionary<int, string>>(
name: "images",
table: "collections",
type: "jsonb",
nullable: true);
migrationBuilder.CreateTable(
name: "providers",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
name = table.Column<string>(type: "text", nullable: true),
slug = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_providers", x => x.id);
});
migrationBuilder.CreateTable(
name: "collection_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_collection_metadata_id_collections_collection_id",
column: x => x.resource_id,
principalTable: "collections",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_collection_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "episode_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_episode_metadata_id_episodes_episode_id",
column: x => x.resource_id,
principalTable: "episodes",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_episode_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_library_provider",
columns: table => new
{
library_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id });
table.ForeignKey(
name: "fk_link_library_provider_libraries_library_id",
column: x => x.library_id,
principalTable: "libraries",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_library_provider_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "people_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_people_metadata_id_people_people_id",
column: x => x.resource_id,
principalTable: "people",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_people_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "season_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_season_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_season_metadata_id_seasons_season_id",
column: x => x.resource_id,
principalTable: "seasons",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "show_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_show_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_show_metadata_id_shows_show_id",
column: x => x.resource_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "studio_metadata_id",
columns: table => new
{
resource_id = table.Column<int>(type: "integer", nullable: false),
provider_id = table.Column<int>(type: "integer", nullable: false),
data_id = table.Column<string>(type: "text", nullable: true),
link = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id });
table.ForeignKey(
name: "fk_studio_metadata_id_providers_provider_id",
column: x => x.provider_id,
principalTable: "providers",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_studio_metadata_id_studios_studio_id",
column: x => x.resource_id,
principalTable: "studios",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_collection_metadata_id_provider_id",
table: "collection_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_episode_metadata_id_provider_id",
table: "episode_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_link_library_provider_provider_id",
table: "link_library_provider",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_people_metadata_id_provider_id",
table: "people_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_providers_slug",
table: "providers",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_season_metadata_id_provider_id",
table: "season_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_show_metadata_id_provider_id",
table: "show_metadata_id",
column: "provider_id");
migrationBuilder.CreateIndex(
name: "ix_studio_metadata_id_provider_id",
table: "studio_metadata_id",
column: "provider_id");
MigrationHelper.CreateLibraryItemsView(migrationBuilder);
}
}
}

View File

@ -0,0 +1,528 @@
using System;
using Kyoo.Abstractions.Models;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Kyoo.Postgresql.Migrations
{
/// <inheritdoc />
public partial class initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:genre", "action,adventure,animation,comedy,crime,documentary,drama,family,fantasy,history,horror,music,mystery,romance,science_fiction,thriller,war,western")
.Annotation("Npgsql:Enum:status", "unknown,finished,airing,planned");
migrationBuilder.CreateTable(
name: "collections",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
name = table.Column<string>(type: "text", nullable: false),
poster_source = table.Column<string>(type: "text", nullable: true),
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
thumbnail_source = table.Column<string>(type: "text", nullable: true),
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
overview = table.Column<string>(type: "text", nullable: true),
external_id = table.Column<string>(type: "json", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_collections", x => x.id);
});
migrationBuilder.CreateTable(
name: "people",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
name = table.Column<string>(type: "text", nullable: false),
poster_source = table.Column<string>(type: "text", nullable: true),
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
thumbnail_source = table.Column<string>(type: "text", nullable: true),
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
external_id = table.Column<string>(type: "json", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_people", x => x.id);
});
migrationBuilder.CreateTable(
name: "studios",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
name = table.Column<string>(type: "text", nullable: false),
external_id = table.Column<string>(type: "json", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_studios", x => x.id);
});
migrationBuilder.CreateTable(
name: "users",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
username = table.Column<string>(type: "text", nullable: false),
email = table.Column<string>(type: "text", nullable: false),
password = table.Column<string>(type: "text", nullable: false),
permissions = table.Column<string[]>(type: "text[]", nullable: false),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_users", x => x.id);
});
migrationBuilder.CreateTable(
name: "movies",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
name = table.Column<string>(type: "text", nullable: false),
tagline = table.Column<string>(type: "text", nullable: true),
aliases = table.Column<string[]>(type: "text[]", nullable: false),
path = table.Column<string>(type: "text", nullable: false),
overview = table.Column<string>(type: "text", nullable: true),
tags = table.Column<string[]>(type: "text[]", nullable: false),
genres = table.Column<Genre[]>(type: "genre[]", nullable: false),
status = table.Column<Status>(type: "status", nullable: false),
air_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
poster_source = table.Column<string>(type: "text", nullable: true),
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
thumbnail_source = table.Column<string>(type: "text", nullable: true),
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
trailer = table.Column<string>(type: "text", nullable: true),
external_id = table.Column<string>(type: "json", nullable: false),
studio_id = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_movies", x => x.id);
table.ForeignKey(
name: "fk_movies_studios_studio_id",
column: x => x.studio_id,
principalTable: "studios",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "shows",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
name = table.Column<string>(type: "text", nullable: false),
tagline = table.Column<string>(type: "text", nullable: true),
aliases = table.Column<string[]>(type: "text[]", nullable: false),
overview = table.Column<string>(type: "text", nullable: true),
tags = table.Column<string[]>(type: "text[]", nullable: false),
genres = table.Column<Genre[]>(type: "genre[]", nullable: false),
status = table.Column<Status>(type: "status", nullable: false),
start_air = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
end_air = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
poster_source = table.Column<string>(type: "text", nullable: true),
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
thumbnail_source = table.Column<string>(type: "text", nullable: true),
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
trailer = table.Column<string>(type: "text", nullable: true),
external_id = table.Column<string>(type: "json", nullable: false),
studio_id = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("pk_shows", x => x.id);
table.ForeignKey(
name: "fk_shows_studios_studio_id",
column: x => x.studio_id,
principalTable: "studios",
principalColumn: "id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateTable(
name: "collection_movie",
columns: table => new
{
collections_id = table.Column<int>(type: "integer", nullable: false),
movies_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_collection_movie", x => new { x.collections_id, x.movies_id });
table.ForeignKey(
name: "fk_collection_movie_collections_collections_id",
column: x => x.collections_id,
principalTable: "collections",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_collection_movie_movies_movies_id",
column: x => x.movies_id,
principalTable: "movies",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_collection_show",
columns: table => new
{
collection_id = table.Column<int>(type: "integer", nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id });
table.ForeignKey(
name: "fk_link_collection_show_collections_collection_id",
column: x => x.collection_id,
principalTable: "collections",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_collection_show_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "link_user_show",
columns: table => new
{
users_id = table.Column<int>(type: "integer", nullable: false),
watched_id = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id });
table.ForeignKey(
name: "fk_link_user_show_shows_watched_id",
column: x => x.watched_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_link_user_show_users_users_id",
column: x => x.users_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "people_roles",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
people_id = table.Column<int>(type: "integer", nullable: false),
show_id = table.Column<int>(type: "integer", nullable: true),
movie_id = table.Column<int>(type: "integer", nullable: true),
type = table.Column<string>(type: "text", nullable: false),
role = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_people_roles", x => x.id);
table.ForeignKey(
name: "fk_people_roles_movies_movie_id",
column: x => x.movie_id,
principalTable: "movies",
principalColumn: "id");
table.ForeignKey(
name: "fk_people_roles_people_people_id",
column: x => x.people_id,
principalTable: "people",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_people_roles_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id");
});
migrationBuilder.CreateTable(
name: "seasons",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false),
season_number = table.Column<int>(type: "integer", nullable: false),
name = table.Column<string>(type: "text", nullable: true),
overview = table.Column<string>(type: "text", nullable: true),
start_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
end_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
poster_source = table.Column<string>(type: "text", nullable: true),
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
thumbnail_source = table.Column<string>(type: "text", nullable: true),
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
external_id = table.Column<string>(type: "json", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_seasons", x => x.id);
table.ForeignKey(
name: "fk_seasons_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "episodes",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
slug = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
show_id = table.Column<int>(type: "integer", nullable: false),
season_id = table.Column<int>(type: "integer", nullable: true),
season_number = table.Column<int>(type: "integer", nullable: true),
episode_number = table.Column<int>(type: "integer", nullable: true),
absolute_number = table.Column<int>(type: "integer", nullable: true),
path = table.Column<string>(type: "text", nullable: false),
name = table.Column<string>(type: "text", nullable: true),
overview = table.Column<string>(type: "text", nullable: true),
release_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
poster_source = table.Column<string>(type: "text", nullable: true),
poster_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
thumbnail_source = table.Column<string>(type: "text", nullable: true),
thumbnail_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
logo_source = table.Column<string>(type: "text", nullable: true),
logo_blurhash = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
external_id = table.Column<string>(type: "json", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_episodes", x => x.id);
table.ForeignKey(
name: "fk_episodes_seasons_season_id",
column: x => x.season_id,
principalTable: "seasons",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_episodes_shows_show_id",
column: x => x.show_id,
principalTable: "shows",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "watched_episode",
columns: table => new
{
user_id = table.Column<int>(type: "integer", nullable: false),
episode_id = table.Column<int>(type: "integer", nullable: false),
watched_percentage = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("pk_watched_episode", x => new { x.user_id, x.episode_id });
table.ForeignKey(
name: "fk_watched_episode_episodes_episode_id",
column: x => x.episode_id,
principalTable: "episodes",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "fk_watched_episode_users_user_id",
column: x => x.user_id,
principalTable: "users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_collection_movie_movies_id",
table: "collection_movie",
column: "movies_id");
migrationBuilder.CreateIndex(
name: "ix_collections_slug",
table: "collections",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_episodes_season_id",
table: "episodes",
column: "season_id");
migrationBuilder.CreateIndex(
name: "ix_episodes_show_id_season_number_episode_number_absolute_numb",
table: "episodes",
columns: new[] { "show_id", "season_number", "episode_number", "absolute_number" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_episodes_slug",
table: "episodes",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_link_collection_show_show_id",
table: "link_collection_show",
column: "show_id");
migrationBuilder.CreateIndex(
name: "ix_link_user_show_watched_id",
table: "link_user_show",
column: "watched_id");
migrationBuilder.CreateIndex(
name: "ix_movies_slug",
table: "movies",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_movies_studio_id",
table: "movies",
column: "studio_id");
migrationBuilder.CreateIndex(
name: "ix_people_slug",
table: "people",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_people_roles_movie_id",
table: "people_roles",
column: "movie_id");
migrationBuilder.CreateIndex(
name: "ix_people_roles_people_id",
table: "people_roles",
column: "people_id");
migrationBuilder.CreateIndex(
name: "ix_people_roles_show_id",
table: "people_roles",
column: "show_id");
migrationBuilder.CreateIndex(
name: "ix_seasons_show_id_season_number",
table: "seasons",
columns: new[] { "show_id", "season_number" },
unique: true);
migrationBuilder.CreateIndex(
name: "ix_seasons_slug",
table: "seasons",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_shows_slug",
table: "shows",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_shows_studio_id",
table: "shows",
column: "studio_id");
migrationBuilder.CreateIndex(
name: "ix_studios_slug",
table: "studios",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_users_slug",
table: "users",
column: "slug",
unique: true);
migrationBuilder.CreateIndex(
name: "ix_watched_episode_episode_id",
table: "watched_episode",
column: "episode_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "collection_movie");
migrationBuilder.DropTable(
name: "link_collection_show");
migrationBuilder.DropTable(
name: "link_user_show");
migrationBuilder.DropTable(
name: "people_roles");
migrationBuilder.DropTable(
name: "watched_episode");
migrationBuilder.DropTable(
name: "collections");
migrationBuilder.DropTable(
name: "movies");
migrationBuilder.DropTable(
name: "people");
migrationBuilder.DropTable(
name: "episodes");
migrationBuilder.DropTable(
name: "users");
migrationBuilder.DropTable(
name: "seasons");
migrationBuilder.DropTable(
name: "shows");
migrationBuilder.DropTable(
name: "studios");
}
}
}

View File

@ -1,6 +1,5 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using System.Collections.Generic;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Postgresql; using Kyoo.Postgresql;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -22,10 +21,29 @@ namespace Kyoo.Postgresql.Migrations
.HasAnnotation("ProductVersion", "7.0.9") .HasAnnotation("ProductVersion", "7.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "item_type", new[] { "show", "movie", "collection" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "genre", new[] { "action", "adventure", "animation", "comedy", "crime", "documentary", "drama", "family", "fantasy", "history", "horror", "music", "mystery", "romance", "science_fiction", "thriller", "war", "western" });
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" }); NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "status", new[] { "unknown", "finished", "airing", "planned" });
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("CollectionMovie", b =>
{
b.Property<int>("CollectionsID")
.HasColumnType("integer")
.HasColumnName("collections_id");
b.Property<int>("MoviesID")
.HasColumnType("integer")
.HasColumnName("movies_id");
b.HasKey("CollectionsID", "MoviesID")
.HasName("pk_collection_movie");
b.HasIndex("MoviesID")
.HasDatabaseName("ix_collection_movie_movies_id");
b.ToTable("collection_movie", (string)null);
});
modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b =>
{ {
b.Property<int>("ID") b.Property<int>("ID")
@ -35,11 +53,13 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string>("ExternalIDs") b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json") .HasColumnType("json")
.HasColumnName("external_i_ds"); .HasColumnName("external_id");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("name"); .HasColumnName("name");
@ -49,7 +69,8 @@ namespace Kyoo.Postgresql.Migrations
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.HasKey("ID") b.HasKey("ID")
@ -79,15 +100,21 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("episode_number"); .HasColumnName("episode_number");
b.Property<string>("ExternalIDs") b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json") .HasColumnType("json")
.HasColumnName("external_i_ds"); .HasColumnName("external_id");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Overview") b.Property<string>("Overview")
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("overview"); .HasColumnName("overview");
b.Property<string>("Path") b.Property<string>("Path")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("path"); .HasColumnName("path");
@ -109,13 +136,10 @@ namespace Kyoo.Postgresql.Migrations
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.Property<string>("Title")
.HasColumnType("text")
.HasColumnName("title");
b.HasKey("ID") b.HasKey("ID")
.HasName("pk_episodes"); .HasName("pk_episodes");
@ -133,7 +157,7 @@ namespace Kyoo.Postgresql.Migrations
b.ToTable("episodes", (string)null); b.ToTable("episodes", (string)null);
}); });
modelBuilder.Entity("Kyoo.Abstractions.Models.Genre", b => modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b =>
{ {
b.Property<int>("ID") b.Property<int>("ID")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -142,97 +166,77 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string>("Name") b.Property<DateTime?>("AirDate")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text")
.HasColumnName("slug");
b.HasKey("ID")
.HasName("pk_genres");
b.HasIndex("Slug")
.IsUnique()
.HasDatabaseName("ix_genres_slug");
b.ToTable("genres", (string)null);
});
modelBuilder.Entity("Kyoo.Abstractions.Models.Library", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string[]>("Paths")
.HasColumnType("text[]")
.HasColumnName("paths");
b.Property<string>("Slug")
.IsRequired()
.HasColumnType("text")
.HasColumnName("slug");
b.HasKey("ID")
.HasName("pk_libraries");
b.HasIndex("Slug")
.IsUnique()
.HasDatabaseName("ix_libraries_slug");
b.ToTable("libraries", (string)null);
});
modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b =>
{
b.Property<int>("ID")
.HasColumnType("integer")
.HasColumnName("id");
b.Property<DateTime?>("EndAir")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("end_air"); .HasColumnName("air_date");
b.Property<string[]>("Aliases")
.IsRequired()
.HasColumnType("text[]")
.HasColumnName("aliases");
b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json")
.HasColumnName("external_id");
b.Property<Genre[]>("Genres")
.IsRequired()
.HasColumnType("genre[]")
.HasColumnName("genres");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Overview") b.Property<string>("Overview")
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("overview"); .HasColumnName("overview");
b.Property<string>("Slug") b.Property<string>("Path")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("path");
b.Property<string>("Slug")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.Property<DateTime?>("StartAir") b.Property<Status>("Status")
.HasColumnType("timestamp with time zone")
.HasColumnName("start_air");
b.Property<Status?>("Status")
.HasColumnType("status") .HasColumnType("status")
.HasColumnName("status"); .HasColumnName("status");
b.Property<string>("Title") b.Property<int?>("StudioID")
.HasColumnType("text") .HasColumnType("integer")
.HasColumnName("title"); .HasColumnName("studio_id");
b.Property<ItemType>("Type") b.Property<string>("Tagline")
.HasColumnType("item_type") .HasColumnType("text")
.HasColumnName("type"); .HasColumnName("tagline");
b.Property<string[]>("Tags")
.IsRequired()
.HasColumnType("text[]")
.HasColumnName("tags");
b.Property<string>("Trailer")
.HasColumnType("text")
.HasColumnName("trailer");
b.HasKey("ID") b.HasKey("ID")
.HasName("pk_library_items"); .HasName("pk_movies");
b.ToTable((string)null); b.HasIndex("Slug")
.IsUnique()
.HasDatabaseName("ix_movies_slug");
b.ToView("library_items", (string)null); b.HasIndex("StudioID")
.HasDatabaseName("ix_movies_studio_id");
b.ToTable("movies", (string)null);
}); });
modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => modelBuilder.Entity("Kyoo.Abstractions.Models.People", b =>
@ -244,17 +248,20 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string>("ExternalIDs") b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json") .HasColumnType("json")
.HasColumnName("external_i_ds"); .HasColumnName("external_id");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("name"); .HasColumnName("name");
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.HasKey("ID") b.HasKey("ID")
@ -276,25 +283,34 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<int?>("MovieID")
.HasColumnType("integer")
.HasColumnName("movie_id");
b.Property<int>("PeopleID") b.Property<int>("PeopleID")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("people_id"); .HasColumnName("people_id");
b.Property<string>("Role") b.Property<string>("Role")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("role"); .HasColumnName("role");
b.Property<int>("ShowID") b.Property<int?>("ShowID")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("show_id"); .HasColumnName("show_id");
b.Property<string>("Type") b.Property<string>("Type")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("type"); .HasColumnName("type");
b.HasKey("ID") b.HasKey("ID")
.HasName("pk_people_roles"); .HasName("pk_people_roles");
b.HasIndex("MovieID")
.HasDatabaseName("ix_people_roles_movie_id");
b.HasIndex("PeopleID") b.HasIndex("PeopleID")
.HasDatabaseName("ix_people_roles_people_id"); .HasDatabaseName("ix_people_roles_people_id");
@ -317,9 +333,14 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("end_date"); .HasColumnName("end_date");
b.Property<string>("ExternalIDs") b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json") .HasColumnType("json")
.HasColumnName("external_i_ds"); .HasColumnName("external_id");
b.Property<string>("Name")
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Overview") b.Property<string>("Overview")
.HasColumnType("text") .HasColumnType("text")
@ -335,17 +356,14 @@ namespace Kyoo.Postgresql.Migrations
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.Property<DateTime?>("StartDate") b.Property<DateTime?>("StartDate")
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("start_date"); .HasColumnName("start_date");
b.Property<string>("Title")
.HasColumnType("text")
.HasColumnName("title");
b.HasKey("ID") b.HasKey("ID")
.HasName("pk_seasons"); .HasName("pk_seasons");
@ -370,6 +388,7 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string[]>("Aliases") b.Property<string[]>("Aliases")
.IsRequired()
.HasColumnType("text[]") .HasColumnType("text[]")
.HasColumnName("aliases"); .HasColumnName("aliases");
@ -377,25 +396,29 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("timestamp with time zone") .HasColumnType("timestamp with time zone")
.HasColumnName("end_air"); .HasColumnName("end_air");
b.Property<string>("ExternalIDs") b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json") .HasColumnType("json")
.HasColumnName("external_i_ds"); .HasColumnName("external_id");
b.Property<bool>("IsMovie") b.Property<Genre[]>("Genres")
.HasColumnType("boolean") .IsRequired()
.HasColumnName("is_movie"); .HasColumnType("genre[]")
.HasColumnName("genres");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.Property<string>("Overview") b.Property<string>("Overview")
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("overview"); .HasColumnName("overview");
b.Property<string>("Path")
.HasColumnType("text")
.HasColumnName("path");
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.Property<DateTime?>("StartAir") b.Property<DateTime?>("StartAir")
@ -410,9 +433,14 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("studio_id"); .HasColumnName("studio_id");
b.Property<string>("Title") b.Property<string>("Tagline")
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("title"); .HasColumnName("tagline");
b.Property<string[]>("Tags")
.IsRequired()
.HasColumnType("text[]")
.HasColumnName("tags");
b.Property<string>("Trailer") b.Property<string>("Trailer")
.HasColumnType("text") .HasColumnType("text")
@ -440,17 +468,20 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string>("ExternalIDs") b.Property<string>("ExternalId")
.IsRequired()
.HasColumnType("json") .HasColumnType("json")
.HasColumnName("external_i_ds"); .HasColumnName("external_id");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("name"); .HasColumnName("name");
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.HasKey("ID") b.HasKey("ID")
@ -473,27 +504,28 @@ namespace Kyoo.Postgresql.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ID"));
b.Property<string>("Email") b.Property<string>("Email")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("email"); .HasColumnName("email");
b.Property<Dictionary<string, string>>("ExtraData")
.HasColumnType("jsonb")
.HasColumnName("extra_data");
b.Property<string>("Password") b.Property<string>("Password")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("password"); .HasColumnName("password");
b.Property<string[]>("Permissions") b.Property<string[]>("Permissions")
.IsRequired()
.HasColumnType("text[]") .HasColumnType("text[]")
.HasColumnName("permissions"); .HasColumnName("permissions");
b.Property<string>("Slug") b.Property<string>("Slug")
.IsRequired() .IsRequired()
.HasColumnType("text") .HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("slug"); .HasColumnName("slug");
b.Property<string>("Username") b.Property<string>("Username")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("username"); .HasColumnName("username");
@ -522,12 +554,12 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("watched_percentage"); .HasColumnName("watched_percentage");
b.HasKey("UserID", "EpisodeID") b.HasKey("UserID", "EpisodeID")
.HasName("pk_watched_episodes"); .HasName("pk_watched_episode");
b.HasIndex("EpisodeID") b.HasIndex("EpisodeID")
.HasDatabaseName("ix_watched_episodes_episode_id"); .HasDatabaseName("ix_watched_episode_episode_id");
b.ToTable("watched_episodes", (string)null); b.ToTable("watched_episode", (string)null);
}); });
modelBuilder.Entity("ShowUser", b => modelBuilder.Entity("ShowUser", b =>
@ -568,61 +600,21 @@ namespace Kyoo.Postgresql.Migrations
b.ToTable("link_collection_show", (string)null); b.ToTable("link_collection_show", (string)null);
}); });
modelBuilder.Entity("link_library_collection", b => modelBuilder.Entity("CollectionMovie", b =>
{ {
b.Property<int>("collection_id") b.HasOne("Kyoo.Abstractions.Models.Collection", null)
.HasColumnType("integer") .WithMany()
.HasColumnName("collection_id"); .HasForeignKey("CollectionsID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_collection_movie_collections_collections_id");
b.Property<int>("library_id") b.HasOne("Kyoo.Abstractions.Models.Movie", null)
.HasColumnType("integer") .WithMany()
.HasColumnName("library_id"); .HasForeignKey("MoviesID")
.OnDelete(DeleteBehavior.Cascade)
b.HasKey("collection_id", "library_id") .IsRequired()
.HasName("pk_link_library_collection"); .HasConstraintName("fk_collection_movie_movies_movies_id");
b.HasIndex("library_id")
.HasDatabaseName("ix_link_library_collection_library_id");
b.ToTable("link_library_collection", (string)null);
});
modelBuilder.Entity("link_library_show", b =>
{
b.Property<int>("library_id")
.HasColumnType("integer")
.HasColumnName("library_id");
b.Property<int>("show_id")
.HasColumnType("integer")
.HasColumnName("show_id");
b.HasKey("library_id", "show_id")
.HasName("pk_link_library_show");
b.HasIndex("show_id")
.HasDatabaseName("ix_link_library_show_show_id");
b.ToTable("link_library_show", (string)null);
});
modelBuilder.Entity("link_show_genre", b =>
{
b.Property<int>("genre_id")
.HasColumnType("integer")
.HasColumnName("genre_id");
b.Property<int>("show_id")
.HasColumnType("integer")
.HasColumnName("show_id");
b.HasKey("genre_id", "show_id")
.HasName("pk_link_show_genre");
b.HasIndex("show_id")
.HasDatabaseName("ix_link_show_genre_show_id");
b.ToTable("link_show_genre", (string)null);
}); });
modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b => modelBuilder.Entity("Kyoo.Abstractions.Models.Collection", b =>
@ -634,11 +626,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("logo_blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("logo_source"); .HasColumnName("logo_source");
@ -658,11 +652,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("poster_blurhash"); .HasColumnName("poster_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("poster_source"); .HasColumnName("poster_source");
@ -682,11 +678,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("thumbnail_blurhash"); .HasColumnName("thumbnail_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("thumbnail_source"); .HasColumnName("thumbnail_source");
@ -728,11 +726,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("logo_blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("logo_source"); .HasColumnName("logo_source");
@ -752,11 +752,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("poster_blurhash"); .HasColumnName("poster_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("poster_source"); .HasColumnName("poster_source");
@ -776,11 +778,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("thumbnail_blurhash"); .HasColumnName("thumbnail_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("thumbnail_source"); .HasColumnName("thumbnail_source");
@ -804,87 +808,98 @@ namespace Kyoo.Postgresql.Migrations
b.Navigation("Thumbnail"); b.Navigation("Thumbnail");
}); });
modelBuilder.Entity("Kyoo.Abstractions.Models.LibraryItem", b => modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b =>
{ {
b.HasOne("Kyoo.Abstractions.Models.Studio", "Studio")
.WithMany("Movies")
.HasForeignKey("StudioID")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("fk_movies_studios_studio_id");
b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 => b.OwnsOne("Kyoo.Abstractions.Models.Image", "Logo", b1 =>
{ {
b1.Property<int>("LibraryItemID") b1.Property<int>("MovieID")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("library_item_id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("source"); .HasColumnName("logo_source");
b1.HasKey("LibraryItemID"); b1.HasKey("MovieID");
b1.ToTable((string)null); b1.ToTable("movies");
b1.ToView("library_items");
b1.WithOwner() b1.WithOwner()
.HasForeignKey("LibraryItemID"); .HasForeignKey("MovieID")
.HasConstraintName("fk_movies_movies_id");
}); });
b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 => b.OwnsOne("Kyoo.Abstractions.Models.Image", "Poster", b1 =>
{ {
b1.Property<int>("LibraryItemID") b1.Property<int>("MovieID")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("library_item_id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("blurhash"); .HasColumnName("poster_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("source"); .HasColumnName("poster_source");
b1.HasKey("LibraryItemID"); b1.HasKey("MovieID");
b1.ToTable((string)null); b1.ToTable("movies");
b1.ToView("library_items");
b1.WithOwner() b1.WithOwner()
.HasForeignKey("LibraryItemID"); .HasForeignKey("MovieID")
.HasConstraintName("fk_movies_movies_id");
}); });
b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 => b.OwnsOne("Kyoo.Abstractions.Models.Image", "Thumbnail", b1 =>
{ {
b1.Property<int>("LibraryItemID") b1.Property<int>("MovieID")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("library_item_id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("blurhash"); .HasColumnName("thumbnail_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("source"); .HasColumnName("thumbnail_source");
b1.HasKey("LibraryItemID"); b1.HasKey("MovieID");
b1.ToTable((string)null); b1.ToTable("movies");
b1.ToView("library_items");
b1.WithOwner() b1.WithOwner()
.HasForeignKey("LibraryItemID"); .HasForeignKey("MovieID")
.HasConstraintName("fk_movies_movies_id");
}); });
b.Navigation("Logo"); b.Navigation("Logo");
b.Navigation("Poster"); b.Navigation("Poster");
b.Navigation("Studio");
b.Navigation("Thumbnail"); b.Navigation("Thumbnail");
}); });
@ -897,11 +912,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("logo_blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("logo_source"); .HasColumnName("logo_source");
@ -921,11 +938,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("poster_blurhash"); .HasColumnName("poster_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("poster_source"); .HasColumnName("poster_source");
@ -945,11 +964,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("thumbnail_blurhash"); .HasColumnName("thumbnail_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("thumbnail_source"); .HasColumnName("thumbnail_source");
@ -971,6 +992,11 @@ namespace Kyoo.Postgresql.Migrations
modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b => modelBuilder.Entity("Kyoo.Abstractions.Models.PeopleRole", b =>
{ {
b.HasOne("Kyoo.Abstractions.Models.Movie", "Movie")
.WithMany("People")
.HasForeignKey("MovieID")
.HasConstraintName("fk_people_roles_movies_movie_id");
b.HasOne("Kyoo.Abstractions.Models.People", "People") b.HasOne("Kyoo.Abstractions.Models.People", "People")
.WithMany("Roles") .WithMany("Roles")
.HasForeignKey("PeopleID") .HasForeignKey("PeopleID")
@ -981,10 +1007,10 @@ namespace Kyoo.Postgresql.Migrations
b.HasOne("Kyoo.Abstractions.Models.Show", "Show") b.HasOne("Kyoo.Abstractions.Models.Show", "Show")
.WithMany("People") .WithMany("People")
.HasForeignKey("ShowID") .HasForeignKey("ShowID")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_people_roles_shows_show_id"); .HasConstraintName("fk_people_roles_shows_show_id");
b.Navigation("Movie");
b.Navigation("People"); b.Navigation("People");
b.Navigation("Show"); b.Navigation("Show");
@ -1006,11 +1032,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("logo_blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("logo_source"); .HasColumnName("logo_source");
@ -1030,11 +1058,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("poster_blurhash"); .HasColumnName("poster_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("poster_source"); .HasColumnName("poster_source");
@ -1054,11 +1084,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("thumbnail_blurhash"); .HasColumnName("thumbnail_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("thumbnail_source"); .HasColumnName("thumbnail_source");
@ -1095,11 +1127,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("logo_blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("logo_source"); .HasColumnName("logo_source");
@ -1119,11 +1153,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("poster_blurhash"); .HasColumnName("poster_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("poster_source"); .HasColumnName("poster_source");
@ -1143,11 +1179,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("thumbnail_blurhash"); .HasColumnName("thumbnail_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("thumbnail_source"); .HasColumnName("thumbnail_source");
@ -1178,11 +1216,13 @@ namespace Kyoo.Postgresql.Migrations
.HasColumnName("id"); .HasColumnName("id");
b1.Property<string>("Blurhash") b1.Property<string>("Blurhash")
.IsRequired()
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("logo_blurhash"); .HasColumnName("logo_blurhash");
b1.Property<string>("Source") b1.Property<string>("Source")
.IsRequired()
.HasColumnType("text") .HasColumnType("text")
.HasColumnName("logo_source"); .HasColumnName("logo_source");
@ -1205,14 +1245,14 @@ namespace Kyoo.Postgresql.Migrations
.HasForeignKey("EpisodeID") .HasForeignKey("EpisodeID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired() .IsRequired()
.HasConstraintName("fk_watched_episodes_episodes_episode_id"); .HasConstraintName("fk_watched_episode_episodes_episode_id");
b.HasOne("Kyoo.Abstractions.Models.User", null) b.HasOne("Kyoo.Abstractions.Models.User", null)
.WithMany("CurrentlyWatching") .WithMany("CurrentlyWatching")
.HasForeignKey("UserID") .HasForeignKey("UserID")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired() .IsRequired()
.HasConstraintName("fk_watched_episodes_users_user_id"); .HasConstraintName("fk_watched_episode_users_user_id");
b.Navigation("Episode"); b.Navigation("Episode");
}); });
@ -1251,55 +1291,9 @@ namespace Kyoo.Postgresql.Migrations
.HasConstraintName("fk_link_collection_show_shows_show_id"); .HasConstraintName("fk_link_collection_show_shows_show_id");
}); });
modelBuilder.Entity("link_library_collection", b => modelBuilder.Entity("Kyoo.Abstractions.Models.Movie", b =>
{ {
b.HasOne("Kyoo.Abstractions.Models.Collection", null) b.Navigation("People");
.WithMany()
.HasForeignKey("collection_id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_link_library_collection_collections_collection_id");
b.HasOne("Kyoo.Abstractions.Models.Library", null)
.WithMany()
.HasForeignKey("library_id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_link_library_collection_libraries_library_id");
});
modelBuilder.Entity("link_library_show", b =>
{
b.HasOne("Kyoo.Abstractions.Models.Library", null)
.WithMany()
.HasForeignKey("library_id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_link_library_show_libraries_library_id");
b.HasOne("Kyoo.Abstractions.Models.Show", null)
.WithMany()
.HasForeignKey("show_id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_link_library_show_shows_show_id");
});
modelBuilder.Entity("link_show_genre", b =>
{
b.HasOne("Kyoo.Abstractions.Models.Genre", null)
.WithMany()
.HasForeignKey("genre_id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_link_show_genre_genres_genre_id");
b.HasOne("Kyoo.Abstractions.Models.Show", null)
.WithMany()
.HasForeignKey("show_id")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_link_show_genre_shows_show_id");
}); });
modelBuilder.Entity("Kyoo.Abstractions.Models.People", b => modelBuilder.Entity("Kyoo.Abstractions.Models.People", b =>
@ -1323,6 +1317,8 @@ namespace Kyoo.Postgresql.Migrations
modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b => modelBuilder.Entity("Kyoo.Abstractions.Models.Studio", b =>
{ {
b.Navigation("Movies");
b.Navigation("Shows"); b.Navigation("Shows");
}); });

View File

@ -43,12 +43,12 @@ namespace Kyoo.Postgresql
/// </summary> /// </summary>
private readonly bool _skipConfigure; private readonly bool _skipConfigure;
// TOOD: This needs ot be updated but ef-core still does not offer a way to use this. // TODO: This needs ot be updated but ef-core still does not offer a way to use this.
[Obsolete] [Obsolete]
static PostgresContext() static PostgresContext()
{ {
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>(); NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>(); NpgsqlConnection.GlobalTypeMapper.MapEnum<Genre>();
} }
/// <summary> /// <summary>
@ -100,26 +100,11 @@ namespace Kyoo.Postgresql
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.HasPostgresEnum<Status>(); modelBuilder.HasPostgresEnum<Status>();
modelBuilder.HasPostgresEnum<ItemType>(); modelBuilder.HasPostgresEnum<Genre>();
modelBuilder.Entity<LibraryItem>()
.ToView("library_items")
.HasKey(x => x.ID);
modelBuilder.Entity<User>()
.Property(x => x.ExtraData)
.HasColumnType("jsonb");
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
} }
/// <inheritdoc />
protected override string MetadataName<T>()
{
SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture);
return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID));
}
/// <inheritdoc /> /// <inheritdoc />
protected override string LinkName<T, T2>() protected override string LinkName<T, T2>()
{ {

View File

@ -37,21 +37,17 @@ namespace Kyoo.Tests.Database
{ {
Context = new PostgresTestContext(postgres, output); Context = new PostgresTestContext(postgres, output);
LibraryRepository library = new(_NewContext());
CollectionRepository collection = new(_NewContext()); CollectionRepository collection = new(_NewContext());
GenreRepository genre = new(_NewContext());
StudioRepository studio = new(_NewContext()); StudioRepository studio = new(_NewContext());
PeopleRepository people = new(_NewContext(), PeopleRepository people = new(_NewContext(),
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository)); new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
ShowRepository show = new(_NewContext(), studio, people, genre); ShowRepository show = new(_NewContext(), studio, people);
SeasonRepository season = new(_NewContext(), show); SeasonRepository season = new(_NewContext(), show);
LibraryItemRepository libraryItem = new(_NewContext(), LibraryItemRepository libraryItem = new(_NewContext());
new Lazy<ILibraryRepository>(() => LibraryManager.LibraryRepository));
EpisodeRepository episode = new(_NewContext(), show); EpisodeRepository episode = new(_NewContext(), show);
UserRepository user = new(_NewContext()); UserRepository user = new(_NewContext());
LibraryManager = new LibraryManager(new IBaseRepository[] { LibraryManager = new LibraryManager(new IBaseRepository[] {
library,
libraryItem, libraryItem,
collection, collection,
show, show,
@ -59,7 +55,6 @@ namespace Kyoo.Tests.Database
episode, episode,
people, people,
studio, studio,
genre,
user user
}); });
} }

View File

@ -234,7 +234,7 @@ namespace Kyoo.Tests.Database
public async Task EditTest() public async Task EditTest()
{ {
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug); Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
value.Title = "New Title"; value.Name = "New Title";
value.Images = new Dictionary<int, string> value.Images = new Dictionary<int, string>
{ {
[Images.Poster] = "new-poster" [Images.Poster] = "new-poster"
@ -325,7 +325,7 @@ namespace Kyoo.Tests.Database
{ {
Episode value = new() Episode value = new()
{ {
Title = "This is a test super title", Name = "This is a test super title",
ShowID = 1, ShowID = 1,
AbsoluteNumber = 2 AbsoluteNumber = 2
}; };

View File

@ -121,7 +121,7 @@ namespace Kyoo.Tests.Database
public async Task EditTest() public async Task EditTest()
{ {
Season value = await _repository.Get(TestSample.Get<Season>().Slug); Season value = await _repository.Get(TestSample.Get<Season>().Slug);
value.Title = "New Title"; value.Name = "New Title";
value.Images = new Dictionary<int, string> value.Images = new Dictionary<int, string>
{ {
[Images.Poster] = "new-poster" [Images.Poster] = "new-poster"
@ -212,7 +212,7 @@ namespace Kyoo.Tests.Database
{ {
Season value = new() Season value = new()
{ {
Title = "This is a test super title", Name = "This is a test super title",
ShowID = 1 ShowID = 1
}; };
await _repository.Create(value); await _repository.Create(value);

View File

@ -56,7 +56,7 @@ namespace Kyoo.Tests.Database
{ {
Show value = await _repository.Get(TestSample.Get<Show>().Slug); Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.Path = "/super"; value.Path = "/super";
value.Title = "New Title"; value.Name = "New Title";
Show edited = await _repository.Edit(value, false); Show edited = await _repository.Edit(value, false);
KAssert.DeepEqual(value, edited); KAssert.DeepEqual(value, edited);
@ -215,7 +215,7 @@ namespace Kyoo.Tests.Database
{ {
ID = value.ID, ID = value.ID,
Slug = "reset", Slug = "reset",
Title = "Reset" Name = "Reset"
}; };
Show edited = await _repository.Edit(newValue, true); Show edited = await _repository.Edit(newValue, true);
@ -223,7 +223,7 @@ namespace Kyoo.Tests.Database
Assert.Equal(value.ID, edited.ID); Assert.Equal(value.ID, edited.ID);
Assert.Null(edited.Overview); Assert.Null(edited.Overview);
Assert.Equal("reset", edited.Slug); Assert.Equal("reset", edited.Slug);
Assert.Equal("Reset", edited.Title); Assert.Equal("Reset", edited.Name);
Assert.Null(edited.Aliases); Assert.Null(edited.Aliases);
Assert.Null(edited.ExternalId); Assert.Null(edited.ExternalId);
Assert.Null(edited.People); Assert.Null(edited.People);
@ -348,7 +348,7 @@ namespace Kyoo.Tests.Database
Show value = new() Show value = new()
{ {
Slug = "super-test", Slug = "super-test",
Title = "This is a test title?" Name = "This is a test title?"
}; };
await _repository.Create(value); await _repository.Create(value);
ICollection<Show> ret = await _repository.Search(query); ICollection<Show> ret = await _repository.Search(query);

View File

@ -27,16 +27,6 @@ namespace Kyoo.Tests
{ {
private static readonly Dictionary<Type, Func<object>> NewSamples = new() private static readonly Dictionary<Type, Func<object>> NewSamples = new()
{ {
{
typeof(Library),
() => new Library
{
ID = 2,
Slug = "new-library",
Name = "New Library",
Paths = new[] { "/a/random/path" }
}
},
{ {
typeof(Collection), typeof(Collection),
() => new Collection () => new Collection
@ -57,7 +47,7 @@ namespace Kyoo.Tests
{ {
ID = 2, ID = 2,
Slug = "new-show", Slug = "new-show",
Title = "New Show", Name = "New Show",
Overview = "overview", Overview = "overview",
Status = Status.Planned, Status = Status.Planned,
StartAir = new DateTime(2011, 1, 1).ToUniversalTime(), StartAir = new DateTime(2011, 1, 1).ToUniversalTime(),
@ -79,7 +69,7 @@ namespace Kyoo.Tests
ID = 2, ID = 2,
ShowID = 1, ShowID = 1,
ShowSlug = Get<Show>().Slug, ShowSlug = Get<Show>().Slug,
Title = "New season", Name = "New season",
Overview = "New overview", Overview = "New overview",
EndDate = new DateTime(2000, 10, 10).ToUniversalTime(), EndDate = new DateTime(2000, 10, 10).ToUniversalTime(),
SeasonNumber = 2, SeasonNumber = 2,
@ -102,7 +92,7 @@ namespace Kyoo.Tests
EpisodeNumber = 3, EpisodeNumber = 3,
AbsoluteNumber = 4, AbsoluteNumber = 4,
Path = "/episode-path", Path = "/episode-path",
Title = "New Episode Title", Name = "New Episode Title",
ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(), ReleaseDate = new DateTime(2000, 10, 10).ToUniversalTime(),
Overview = "new episode overview", Overview = "new episode overview",
Images = new Dictionary<int, string> Images = new Dictionary<int, string>
@ -172,7 +162,7 @@ namespace Kyoo.Tests
{ {
ID = 1, ID = 1,
Slug = "anohana", Slug = "anohana",
Title = "Anohana: The Flower We Saw That Day", Name = "Anohana: The Flower We Saw That Day",
Aliases = new[] Aliases = new[]
{ {
"Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.", "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.",
@ -204,7 +194,7 @@ namespace Kyoo.Tests
ShowSlug = "anohana", ShowSlug = "anohana",
ShowID = 1, ShowID = 1,
SeasonNumber = 1, SeasonNumber = 1,
Title = "Season 1", Name = "Season 1",
Overview = "The first season", Overview = "The first season",
StartDate = new DateTime(2020, 06, 05).ToUniversalTime(), StartDate = new DateTime(2020, 06, 05).ToUniversalTime(),
EndDate = new DateTime(2020, 07, 05).ToUniversalTime(), EndDate = new DateTime(2020, 07, 05).ToUniversalTime(),
@ -234,7 +224,7 @@ namespace Kyoo.Tests
[Images.Logo] = "Logo", [Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail" [Images.Thumbnail] = "Thumbnail"
}, },
Title = "Episode 1", Name = "Episode 1",
Overview = "Summary of the first episode", Overview = "Summary of the first episode",
ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime()
} }
@ -379,7 +369,7 @@ namespace Kyoo.Tests
[Images.Logo] = "Logo", [Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail" [Images.Thumbnail] = "Thumbnail"
}, },
Title = "Episode 3", Name = "Episode 3",
Overview = "Summary of the third absolute episode", Overview = "Summary of the third absolute episode",
ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime() ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime()
}; };
@ -399,7 +389,7 @@ namespace Kyoo.Tests
[Images.Logo] = "Logo", [Images.Logo] = "Logo",
[Images.Thumbnail] = "Thumbnail" [Images.Thumbnail] = "Thumbnail"
}, },
Title = "John wick", Name = "John wick",
Overview = "A movie episode test", Overview = "A movie episode test",
ReleaseDate = new DateTime(1595, 05, 12).ToUniversalTime() ReleaseDate = new DateTime(1595, 05, 12).ToUniversalTime()
}; };

View File

@ -123,7 +123,7 @@ class TheMovieDatabase(Provider):
ret = Movie( ret = Movie(
original_language=movie["original_language"], original_language=movie["original_language"],
aliases=[x["title"] for x in movie["alternative_titles"]["titles"]], aliases=[x["title"] for x in movie["alternative_titles"]["titles"]],
release_date=datetime.strptime(movie["release_date"], "%Y-%m-%d").date() air_date=datetime.strptime(movie["release_date"], "%Y-%m-%d").date()
if movie["release_date"] if movie["release_date"]
else None, else None,
status=MovieStatus.FINISHED status=MovieStatus.FINISHED
@ -148,8 +148,8 @@ class TheMovieDatabase(Provider):
) )
translation = MovieTranslation( translation = MovieTranslation(
name=movie["title"], name=movie["title"],
tagline=movie["tagline"], tagline=movie["tagline"] if movie["tagline"] else None,
keywords=list(map(lambda x: x["name"], movie["keywords"]["keywords"])), tags=list(map(lambda x: x["name"], movie["keywords"]["keywords"])),
overview=movie["overview"], overview=movie["overview"],
posters=self.get_image(movie["images"]["posters"]), posters=self.get_image(movie["images"]["posters"]),
logos=self.get_image(movie["images"]["logos"]), logos=self.get_image(movie["images"]["logos"]),
@ -224,8 +224,8 @@ class TheMovieDatabase(Provider):
) )
translation = ShowTranslation( translation = ShowTranslation(
name=show["name"], name=show["name"],
tagline=show["tagline"], tagline=show["tagline"] if show["tagline"] else None,
keywords=list(map(lambda x: x["name"], show["keywords"]["results"])), tags=list(map(lambda x: x["name"], show["keywords"]["results"])),
overview=show["overview"], overview=show["overview"],
posters=self.get_image(show["images"]["posters"]), posters=self.get_image(show["images"]["posters"]),
logos=self.get_image(show["images"]["logos"]), logos=self.get_image(show["images"]["logos"]),

View File

@ -40,9 +40,9 @@ class Episode:
return { return {
**asdict(self), **asdict(self),
**asdict(self.translations[default_language]), **asdict(self.translations[default_language]),
"title": self.translations[default_language].name, # "poster": next(iter(self.translations[default_language].posters), None),
"images": { # "thumbnail": next(iter(self.translations[default_language].thumbnails), None),
"1": self.thumbnail, # "logo": next(iter(self.translations[default_language].logos), None),
}, "thumbnail": None,
"show": None, "show": None,
} }

View File

@ -22,4 +22,4 @@ class Genre(str, Enum):
WESTERN = "Western" WESTERN = "Western"
def to_kyoo(self): def to_kyoo(self):
return {"name": self.value} return self.value

View File

@ -20,7 +20,7 @@ class Status(str, Enum):
class MovieTranslation: class MovieTranslation:
name: str name: str
tagline: Optional[str] = None tagline: Optional[str] = None
keywords: list[str] = field(default_factory=list) tags: list[str] = field(default_factory=list)
overview: Optional[str] = None overview: Optional[str] = None
posters: list[str] = field(default_factory=list) posters: list[str] = field(default_factory=list)
@ -33,7 +33,7 @@ class MovieTranslation:
class Movie: class Movie:
original_language: Optional[str] = None original_language: Optional[str] = None
aliases: list[str] = field(default_factory=list) aliases: list[str] = field(default_factory=list)
release_date: Optional[date | int] = None air_date: Optional[date | int] = None
status: Status = Status.UNKNOWN status: Status = Status.UNKNOWN
path: Optional[str] = None path: Optional[str] = None
studios: list[Studio] = field(default_factory=list) studios: list[Studio] = field(default_factory=list)
@ -50,18 +50,10 @@ class Movie:
return { return {
**asdict(self), **asdict(self),
**asdict(self.translations[default_language]), **asdict(self.translations[default_language]),
"images": { # "poster": next(iter(self.translations[default_language].posters), None),
"0": next(iter(self.translations[default_language].posters), None), # "thumbnail": next(iter(self.translations[default_language].thumbnails), None),
"1": next(iter(self.translations[default_language].thumbnails), None), # "logo": next(iter(self.translations[default_language].logos), None),
"2": next(iter(self.translations[default_language].logos), None), "trailer": next(iter(self.translations[default_language].trailers), None),
"3": next(iter(self.translations[default_language].trailers), None),
},
"studio": next((x.to_kyoo() for x in self.studios), None), "studio": next((x.to_kyoo() for x in self.studios), None),
"release_date": None,
"startAir": format_date(self.release_date),
"title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres], "genres": [x.to_kyoo() for x in self.genres],
"isMovie": True,
# TODO: The back has bad external id support, we disable it for now
"external_ids": None,
} }

View File

@ -30,11 +30,7 @@ class Season:
return { return {
**asdict(self), **asdict(self),
**asdict(self.translations[default_language]), **asdict(self.translations[default_language]),
"images": { # "poster": next(iter(self.translations[default_language].posters), None),
"0": next(iter(self.translations[default_language].posters), None), # "thumbnail": next(iter(self.translations[default_language].thumbnails), None),
"1": next(iter(self.translations[default_language].thumbnails), None), # "logo": next(iter(self.translations[default_language].logos), None),
},
"title": self.translations[default_language].name,
# TODO: The back has bad external id support, we disable it for now
"external_ids": None,
} }

View File

@ -21,7 +21,7 @@ class Status(str, Enum):
class ShowTranslation: class ShowTranslation:
name: str name: str
tagline: Optional[str] tagline: Optional[str]
keywords: list[str] tags: list[str]
overview: Optional[str] overview: Optional[str]
posters: list[str] posters: list[str]
@ -52,16 +52,11 @@ class Show:
return { return {
**asdict(self), **asdict(self),
**asdict(self.translations[default_language]), **asdict(self.translations[default_language]),
"images": {
"0": next(iter(self.translations[default_language].posters), None),
"1": next(iter(self.translations[default_language].thumbnails), None),
"2": next(iter(self.translations[default_language].logos), None),
"3": next(iter(self.translations[default_language].trailers), None),
},
"studio": next((x.to_kyoo() for x in self.studios), None), "studio": next((x.to_kyoo() for x in self.studios), None),
"title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres],
"seasons": None, "seasons": None,
# TODO: The back has bad external id support, we disable it for now # "poster": next(iter(self.translations[default_language].posters), None),
"external_ids": None, # "thumbnail": next(iter(self.translations[default_language].thumbnails), None),
# "logo": next(iter(self.translations[default_language].logos), None),
"trailer": next(iter(self.translations[default_language].trailers), None),
"genres": [x.to_kyoo() for x in self.genres],
} }

View File

@ -12,9 +12,5 @@ class Studio:
def to_kyoo(self): def to_kyoo(self):
return { return {
**asdict(self), **asdict(self),
"images": { # "logo": next(iter(self.logos), None),
"2": next(iter(self.logos), None),
},
# TODO: The back has bad external id support, we disable it for now
"external_ids": None,
} }

View File

@ -48,7 +48,7 @@ class Scanner:
await asyncio.gather(*map(self.identify, group)) await asyncio.gather(*map(self.identify, group))
async def get_registered_paths(self) -> List[str]: async def get_registered_paths(self) -> List[str]:
# TODO: Once movies are separated from the api, a new endpoint should be created to check for paths. paths = None
async with self._client.get( async with self._client.get(
f"{self._url}/episodes", f"{self._url}/episodes",
params={"limit": 0}, params={"limit": 0},
@ -56,7 +56,17 @@ class Scanner:
) as r: ) as r:
r.raise_for_status() r.raise_for_status()
ret = await r.json() ret = await r.json()
return list(x["path"] for x in ret["items"]) paths = list(x["path"] for x in ret["items"])
async with self._client.get(
f"{self._url}/movies",
params={"limit": 0},
headers={"X-API-Key": self._api_key},
) as r:
r.raise_for_status()
ret = await r.json()
paths += list(x["path"] for x in ret["items"])
return paths;
@log_errors @log_errors
async def identify(self, path: str): async def identify(self, path: str):
@ -157,7 +167,13 @@ class Scanner:
async def delete(self, path: str): async def delete(self, path: str):
logging.info("Deleting %s", path) logging.info("Deleting %s", path)
# TODO: Adapt this for movies as well when they are split async with self._client.delete(
f"{self._url}/movies?path={path}", headers={"X-API-Key": self._api_key}
) as r:
if not r.ok:
logging.error(f"Request error: {await r.text()}")
r.raise_for_status()
async with self._client.delete( async with self._client.delete(
f"{self._url}/episodes?path={path}", headers={"X-API-Key": self._api_key} f"{self._url}/episodes?path={path}", headers={"X-API-Key": self._api_key}
) as r: ) as r:

View File

@ -24,6 +24,7 @@ in
openssl openssl
mediainfo mediainfo
ffmpeg ffmpeg
postgresql
]; ];
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";