mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Reworking metadata handling and adding it to Collections and Studios
This commit is contained in:
parent
3262343c4a
commit
2812c9cacf
@ -590,10 +590,10 @@ namespace Kyoo.Controllers
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
|
||||
/// <returns>A filtered list of external ids.</returns>
|
||||
Task<ICollection<MetadataID<T>>> GetMetadataID<T>(Expression<Func<MetadataID<T>, bool>> where = null,
|
||||
Sort<MetadataID<T>> sort = default,
|
||||
Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
|
||||
Sort<MetadataID> sort = default,
|
||||
Pagination limit = default)
|
||||
where T : class, IResource;
|
||||
where T : class, IMetadata;
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of external ids that match all filters
|
||||
@ -602,11 +602,11 @@ namespace Kyoo.Controllers
|
||||
/// <param name="sort">A sort by expression</param>
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <returns>A filtered list of external ids.</returns>
|
||||
Task<ICollection<MetadataID<T>>> GetMetadataID<T>([Optional] Expression<Func<MetadataID<T>, bool>> where,
|
||||
Expression<Func<MetadataID<T>, object>> sort,
|
||||
Task<ICollection<MetadataID>> GetMetadataID<T>([Optional] Expression<Func<MetadataID, bool>> where,
|
||||
Expression<Func<MetadataID, object>> sort,
|
||||
Pagination limit = default
|
||||
) where T : class, IResource
|
||||
=> GetMetadataID(where, new Sort<MetadataID<T>>(sort), limit);
|
||||
) where T : class, IMetadata
|
||||
=> GetMetadataID<T>(where, new Sort<MetadataID>(sort), limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -231,7 +231,12 @@ namespace Kyoo.Controllers
|
||||
.Then(x => l.Collections = x),
|
||||
|
||||
|
||||
(Collection c, nameof(Library.Shows)) => ShowRepository
|
||||
(Collection c, nameof(Collection.ExternalIDs)) => SetRelation(c,
|
||||
ProviderRepository.GetMetadataID<Collection>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Collection c, nameof(Collection.Shows)) => ShowRepository
|
||||
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
|
||||
.Then(x => c.Shows = x),
|
||||
|
||||
@ -241,9 +246,9 @@ namespace Kyoo.Controllers
|
||||
|
||||
|
||||
(Show s, nameof(Show.ExternalIDs)) => SetRelation(s,
|
||||
ProviderRepository.GetMetadataID<Show>(x => x.FirstID == obj.ID),
|
||||
ProviderRepository.GetMetadataID<Show>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Show s, nameof(Show.Genres)) => GenreRepository
|
||||
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
|
||||
@ -281,9 +286,9 @@ namespace Kyoo.Controllers
|
||||
|
||||
|
||||
(Season s, nameof(Season.ExternalIDs)) => SetRelation(s,
|
||||
ProviderRepository.GetMetadataID<Season>(x => x.FirstID == obj.ID),
|
||||
ProviderRepository.GetMetadataID<Season>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Season s, nameof(Season.Episodes)) => SetRelation(s,
|
||||
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
|
||||
@ -300,9 +305,9 @@ namespace Kyoo.Controllers
|
||||
|
||||
|
||||
(Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e,
|
||||
ProviderRepository.GetMetadataID<Episode>(x => x.FirstID == obj.ID),
|
||||
ProviderRepository.GetMetadataID<Episode>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Episode e, nameof(Episode.Tracks)) => SetRelation(e,
|
||||
TrackRepository.GetAll(x => x.Episode.ID == obj.ID),
|
||||
@ -344,11 +349,16 @@ namespace Kyoo.Controllers
|
||||
.GetAll(x => x.Studio.ID == obj.ID)
|
||||
.Then(x => s.Shows = x),
|
||||
|
||||
(Studio s, nameof(Studio.ExternalIDs)) => SetRelation(s,
|
||||
ProviderRepository.GetMetadataID<Studio>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
|
||||
(People p, nameof(People.ExternalIDs)) => SetRelation(p,
|
||||
ProviderRepository.GetMetadataID<People>(x => x.FirstID == obj.ID),
|
||||
ProviderRepository.GetMetadataID<People>(x => x.ResourceID == obj.ID),
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(People p, nameof(People.Roles)) => PeopleRepository
|
||||
.GetFromPeople(obj.ID)
|
||||
|
@ -1,15 +1,34 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Kyoo.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// ID and link of an item on an external provider.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class MetadataID<T> : Link<T, Provider>
|
||||
where T : class, IResource
|
||||
public class MetadataID
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the resource which possess the metadata.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int ResourceID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the resource type. This is only used internally to discriminate types.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string ResourceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the provider.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int ProviderID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The provider that can do something with this ID.
|
||||
/// </summary>
|
||||
public Provider Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the resource on the external provider.
|
||||
/// </summary>
|
||||
@ -20,20 +39,14 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
public string Link { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A shortcut to access the provider of this metadata.
|
||||
/// Unlike the <see cref="Link{T, T2}.Second"/> property, this is serializable.
|
||||
/// </summary>
|
||||
public Provider Provider => Second;
|
||||
|
||||
/// <summary>
|
||||
/// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs.
|
||||
/// </summary>
|
||||
public new static Expression<Func<MetadataID<T>, object>> PrimaryKey
|
||||
public static Expression<Func<MetadataID, object>> PrimaryKey
|
||||
{
|
||||
get
|
||||
{
|
||||
return x => new {First = x.FirstID, Second = x.SecondID};
|
||||
return x => new {First = x.ResourceID, Second = x.ProviderID, Type = x.ResourceType};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace Kyoo.Models
|
||||
/// A class representing collections of <see cref="Show"/>.
|
||||
/// A collection can also be stored in a <see cref="Library"/>.
|
||||
/// </summary>
|
||||
public class Collection : IResource
|
||||
public class Collection : IResource, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
@ -43,6 +43,9 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
#if ENABLE_INTERNAL_LINKS
|
||||
|
||||
/// <summary>
|
||||
|
@ -10,7 +10,7 @@ namespace Kyoo.Models
|
||||
/// <summary>
|
||||
/// A class to represent a single show's episode.
|
||||
/// </summary>
|
||||
public class Episode : IResource
|
||||
public class Episode : IResource, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
@ -121,10 +121,8 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The link to metadata providers that this episode has. See <see cref="MetadataID{T}"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Episode>> ExternalIDs { get; set; }
|
||||
/// <inheritdoc />
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of tracks this episode has. This lists video, audio and subtitles available.
|
||||
|
16
Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs
Normal file
16
Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface applied to resources containing external metadata.
|
||||
/// </summary>
|
||||
public interface IMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// The link to metadata providers that this show has. See <see cref="MetadataID"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ namespace Kyoo.Models
|
||||
/// <summary>
|
||||
/// An actor, voice actor, writer, animator, somebody who worked on a <see cref="Show"/>.
|
||||
/// </summary>
|
||||
public class People : IResource
|
||||
public class People : IResource, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
@ -26,10 +26,8 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
[SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The link to metadata providers that this person has. See <see cref="MetadataID{T}"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<People>> ExternalIDs { get; set; }
|
||||
/// <inheritdoc />
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.
|
||||
|
@ -10,7 +10,7 @@ namespace Kyoo.Models
|
||||
/// <summary>
|
||||
/// A season of a <see cref="Show"/>.
|
||||
/// </summary>
|
||||
public class Season : IResource
|
||||
public class Season : IResource, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
@ -81,10 +81,8 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The link to metadata providers that this episode has. See <see cref="MetadataID{T}"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Season>> ExternalIDs { get; set; }
|
||||
/// <inheritdoc />
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of episodes that this season contains.
|
||||
|
@ -11,7 +11,7 @@ namespace Kyoo.Models
|
||||
/// <summary>
|
||||
/// A series or a movie.
|
||||
/// </summary>
|
||||
public class Show : IResource, IOnMerge
|
||||
public class Show : IResource, IMetadata, IOnMerge
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
@ -89,10 +89,8 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
public bool IsMovie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The link to metadata providers that this show has. See <see cref="MetadataID{T}"/> for more information.
|
||||
/// </summary>
|
||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Show>> ExternalIDs { get; set; }
|
||||
/// <inheritdoc />
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the Studio that made this show.
|
||||
@ -157,19 +155,16 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
/// <remarks>This method will never return anything if the <see cref="ExternalIDs"/> are not loaded.</remarks>
|
||||
/// <param name="provider">The slug of the provider</param>
|
||||
/// <returns>The <see cref="MetadataID{T}.DataID"/> field of the asked provider.</returns>
|
||||
/// <returns>The <see cref="MetadataID.DataID"/> field of the asked provider.</returns>
|
||||
[CanBeNull]
|
||||
public string GetID(string provider)
|
||||
{
|
||||
return ExternalIDs?.FirstOrDefault(x => x.Second.Slug == provider)?.DataID;
|
||||
return ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OnMerge(object merged)
|
||||
{
|
||||
if (ExternalIDs != null)
|
||||
foreach (MetadataID<Show> id in ExternalIDs)
|
||||
id.First = this;
|
||||
if (People != null)
|
||||
foreach (PeopleRole link in People)
|
||||
link.Show = this;
|
||||
|
@ -6,7 +6,7 @@ namespace Kyoo.Models
|
||||
/// <summary>
|
||||
/// A studio that make shows.
|
||||
/// </summary>
|
||||
public class Studio : IResource
|
||||
public class Studio : IResource, IMetadata
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
@ -24,6 +24,9 @@ namespace Kyoo.Models
|
||||
/// </summary>
|
||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty, <see cref="Studio"/>.
|
||||
/// </summary>
|
||||
|
@ -61,6 +61,10 @@ namespace Kyoo
|
||||
/// </summary>
|
||||
public DbSet<Provider> Providers { get; set; }
|
||||
/// <summary>
|
||||
/// All metadata ids, not discriminated by type. See <see cref="MetadataID"/>.
|
||||
/// </summary>
|
||||
public DbSet<MetadataID> MetadataIDs { get; set; }
|
||||
/// <summary>
|
||||
/// The list of registered users.
|
||||
/// </summary>
|
||||
public DbSet<User> Users { get; set; }
|
||||
@ -83,17 +87,6 @@ namespace Kyoo
|
||||
/// </remarks>
|
||||
public DbSet<LibraryItem> LibraryItems { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all metadataIDs (ExternalIDs) of a given resource. See <see cref="MetadataID{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The metadata of this type will be returned.</typeparam>
|
||||
/// <returns>A queryable of metadata ids for a type.</returns>
|
||||
public DbSet<MetadataID<T>> MetadataIds<T>()
|
||||
where T : class, IResource
|
||||
{
|
||||
return Set<MetadataID<T>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a generic link between two resource types.
|
||||
/// </summary>
|
||||
@ -132,6 +125,22 @@ namespace Kyoo
|
||||
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the metadata model for the given type.
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">The database model builder</param>
|
||||
/// <typeparam name="T">The type to add metadata to.</typeparam>
|
||||
private void _HasMetadata<T>(ModelBuilder modelBuilder)
|
||||
where T : class, IMetadata
|
||||
{
|
||||
modelBuilder.Entity<T>()
|
||||
.HasMany(x => x.ExternalIDs)
|
||||
.WithOne()
|
||||
.HasForeignKey(x => x.ResourceID)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set database parameters to support every types of Kyoo.
|
||||
/// </summary>
|
||||
@ -234,42 +243,20 @@ namespace Kyoo
|
||||
.WithMany(x => x.ShowLinks),
|
||||
y => y.HasKey(Link<User, Show>.PrimaryKey));
|
||||
|
||||
modelBuilder.Entity<MetadataID<Show>>()
|
||||
.HasKey(MetadataID<Show>.PrimaryKey);
|
||||
modelBuilder.Entity<MetadataID<Show>>()
|
||||
.HasOne(x => x.First)
|
||||
.WithMany(x => x.ExternalIDs)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<Season>>()
|
||||
.HasKey(MetadataID<Season>.PrimaryKey);
|
||||
modelBuilder.Entity<MetadataID<Season>>()
|
||||
.HasOne(x => x.First)
|
||||
.WithMany(x => x.ExternalIDs)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<Episode>>()
|
||||
.HasKey(MetadataID<Episode>.PrimaryKey);
|
||||
modelBuilder.Entity<MetadataID<Episode>>()
|
||||
.HasOne(x => x.First)
|
||||
.WithMany(x => x.ExternalIDs)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<People>>()
|
||||
.HasKey(MetadataID<People>.PrimaryKey);
|
||||
modelBuilder.Entity<MetadataID<People>>()
|
||||
.HasOne(x => x.First)
|
||||
.WithMany(x => x.ExternalIDs)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID>()
|
||||
.HasKey(MetadataID.PrimaryKey);
|
||||
modelBuilder.Entity<MetadataID>()
|
||||
.Property(x => x.ResourceType)
|
||||
.IsRequired();
|
||||
modelBuilder.Entity<MetadataID>()
|
||||
.HasDiscriminator(x => x.ResourceType);
|
||||
|
||||
|
||||
modelBuilder.Entity<MetadataID<Show>>().HasOne(x => x.Second).WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<Season>>().HasOne(x => x.Second).WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<Episode>>().HasOne(x => x.Second).WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<People>>().HasOne(x => x.Second).WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<MetadataID<Show>>().HasOne(x => x.Second).WithMany()
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
_HasMetadata<Collection>(modelBuilder);
|
||||
_HasMetadata<Show>(modelBuilder);
|
||||
_HasMetadata<Season>(modelBuilder);
|
||||
_HasMetadata<Episode>(modelBuilder);
|
||||
_HasMetadata<People>(modelBuilder);
|
||||
_HasMetadata<Studio>(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<WatchedEpisode>()
|
||||
.HasKey(x => new {First = x.FirstID, Second = x.SecondID});
|
||||
|
@ -22,9 +22,9 @@ namespace Kyoo.Tests
|
||||
|
||||
ProviderRepository provider = new(_database);
|
||||
LibraryRepository library = new(_database, provider);
|
||||
CollectionRepository collection = new(_database);
|
||||
CollectionRepository collection = new(_database, provider);
|
||||
GenreRepository genre = new(_database);
|
||||
StudioRepository studio = new(_database);
|
||||
StudioRepository studio = new(_database, provider);
|
||||
PeopleRepository people = new(_database, provider,
|
||||
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
|
||||
ShowRepository show = new(_database, studio, people, genre, provider);
|
||||
|
@ -149,10 +149,9 @@ namespace Kyoo.Tests.Database
|
||||
Show value = await _repository.Get(TestSample.Get<Show>().Slug);
|
||||
value.ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<Show>()
|
||||
new MetadataID
|
||||
{
|
||||
First = value,
|
||||
Second = new Provider("test", "test.png"),
|
||||
Provider = new Provider("test", "test.png"),
|
||||
DataID = "1234"
|
||||
}
|
||||
};
|
||||
@ -160,19 +159,19 @@ namespace Kyoo.Tests.Database
|
||||
|
||||
Assert.Equal(value.Slug, edited.Slug);
|
||||
Assert.Equal(
|
||||
value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}),
|
||||
edited.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}));
|
||||
value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}),
|
||||
edited.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}));
|
||||
|
||||
await using DatabaseContext database = Repositories.Context.New();
|
||||
Show show = await database.Shows
|
||||
.Include(x => x.ExternalIDs)
|
||||
.ThenInclude(x => x.Second)
|
||||
.ThenInclude(x => x.Provider)
|
||||
.FirstAsync();
|
||||
|
||||
Assert.Equal(value.Slug, show.Slug);
|
||||
Assert.Equal(
|
||||
value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}),
|
||||
show.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}));
|
||||
value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}),
|
||||
show.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -209,10 +208,9 @@ namespace Kyoo.Tests.Database
|
||||
expected.Slug = "created-relation-test";
|
||||
expected.ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
First = expected,
|
||||
Second = new Provider("provider", "provider.png"),
|
||||
Provider = new Provider("provider", "provider.png"),
|
||||
DataID = "ID"
|
||||
}
|
||||
};
|
||||
|
@ -48,9 +48,9 @@ namespace Kyoo.TheMovieDb
|
||||
.ToArray(),
|
||||
ExternalIDs = new []
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
|
||||
DataID = movie.Id.ToString()
|
||||
}
|
||||
@ -94,9 +94,9 @@ namespace Kyoo.TheMovieDb
|
||||
.ToArray(),
|
||||
ExternalIDs = new []
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
Link = $"https://www.themoviedb.org/movie/{tv.Id}",
|
||||
DataID = tv.Id.ToString()
|
||||
}
|
||||
@ -145,9 +145,9 @@ namespace Kyoo.TheMovieDb
|
||||
IsMovie = true,
|
||||
ExternalIDs = new []
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
|
||||
DataID = movie.Id.ToString()
|
||||
}
|
||||
@ -178,9 +178,9 @@ namespace Kyoo.TheMovieDb
|
||||
IsMovie = true,
|
||||
ExternalIDs = new []
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
Link = $"https://www.themoviedb.org/movie/{tv.Id}",
|
||||
DataID = tv.Id.ToString()
|
||||
}
|
||||
@ -205,9 +205,9 @@ namespace Kyoo.TheMovieDb
|
||||
Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null,
|
||||
ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<People>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
DataID = cast.Id.ToString(),
|
||||
Link = $"https://www.themoviedb.org/person/{cast.Id}"
|
||||
}
|
||||
@ -235,9 +235,9 @@ namespace Kyoo.TheMovieDb
|
||||
Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null,
|
||||
ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<People>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
DataID = cast.Id.ToString(),
|
||||
Link = $"https://www.themoviedb.org/person/{cast.Id}"
|
||||
}
|
||||
@ -265,9 +265,9 @@ namespace Kyoo.TheMovieDb
|
||||
Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null,
|
||||
ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<People>
|
||||
new MetadataID
|
||||
{
|
||||
Second = provider,
|
||||
Provider = provider,
|
||||
DataID = crew.Id.ToString(),
|
||||
Link = $"https://www.themoviedb.org/person/{crew.Id}"
|
||||
}
|
||||
|
@ -58,11 +58,11 @@ namespace Kyoo.TheTvdb
|
||||
Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null,
|
||||
ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
DataID = result.Id.ToString(),
|
||||
Link = $"https://www.thetvdb.com/series/{result.Slug}",
|
||||
Second = provider
|
||||
Provider = provider
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -89,11 +89,11 @@ namespace Kyoo.TheTvdb
|
||||
Genres = series.Genre.Select(y => new Genre(y)).ToList(),
|
||||
ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<Show>
|
||||
new MetadataID
|
||||
{
|
||||
DataID = series.Id.ToString(),
|
||||
Link = $"https://www.thetvdb.com/series/{series.Slug}",
|
||||
Second = provider
|
||||
Provider = provider
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -116,11 +116,11 @@ namespace Kyoo.TheTvdb
|
||||
Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null,
|
||||
ExternalIDs = new []
|
||||
{
|
||||
new MetadataID<People>()
|
||||
new MetadataID
|
||||
{
|
||||
DataID = actor.Id.ToString(),
|
||||
Link = $"https://www.thetvdb.com/people/{actor.Id}",
|
||||
Second = provider
|
||||
Provider = provider
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -147,11 +147,11 @@ namespace Kyoo.TheTvdb
|
||||
Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null,
|
||||
ExternalIDs = new[]
|
||||
{
|
||||
new MetadataID<Episode>
|
||||
new MetadataID
|
||||
{
|
||||
DataID = episode.Id.ToString(),
|
||||
Link = $"https://www.thetvdb.com/series/{episode.SeriesId}/episodes/{episode.Id}",
|
||||
Second = provider
|
||||
Provider = provider
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -18,6 +18,11 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name;
|
||||
|
||||
@ -25,10 +30,12 @@ namespace Kyoo.Controllers
|
||||
/// Create a new <see cref="CollectionRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle to use</param>
|
||||
public CollectionRepository(DatabaseContext database)
|
||||
/// /// <param name="providers">A provider repository</param>
|
||||
public CollectionRepository(DatabaseContext database, IProviderRepository providers)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -46,10 +53,36 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
||||
await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists).");
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Collection resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
await resource.ExternalIDs.ForEachAsync(async x =>
|
||||
{
|
||||
x.Provider = await _providers.CreateIfNotExists(x.Provider);
|
||||
x.ProviderID = x.Provider.ID;
|
||||
x.ResourceType = nameof(Collection);
|
||||
_database.Entry(x.Provider).State = EntityState.Detached;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld)
|
||||
{
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
|
||||
await base.EditRelations(resource, changed, resetOld);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Collection obj)
|
||||
{
|
||||
|
@ -160,9 +160,10 @@ namespace Kyoo.Controllers
|
||||
await base.Validate(resource);
|
||||
await resource.ExternalIDs.ForEachAsync(async x =>
|
||||
{
|
||||
x.Second = await _providers.CreateIfNotExists(x.Second);
|
||||
x.SecondID = x.Second.ID;
|
||||
_database.Entry(x.Second).State = EntityState.Detached;
|
||||
x.Provider = await _providers.CreateIfNotExists(x.Provider);
|
||||
x.ProviderID = x.Provider.ID;
|
||||
x.ResourceType = nameof(Episode);
|
||||
_database.Entry(x.Provider).State = EntityState.Detached;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -73,9 +73,10 @@ namespace Kyoo.Controllers
|
||||
await base.Validate(resource);
|
||||
await resource.ExternalIDs.ForEachAsync(async id =>
|
||||
{
|
||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
||||
id.SecondID = id.Second.ID;
|
||||
_database.Entry(id.Second).State = EntityState.Detached;
|
||||
id.Provider = await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
id.ResourceType = nameof(People);
|
||||
_database.Entry(id.Provider).State = EntityState.Detached;
|
||||
});
|
||||
await resource.Roles.ForEachAsync(async role =>
|
||||
{
|
||||
|
@ -9,21 +9,18 @@ using Microsoft.EntityFrameworkCore;
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle providers.
|
||||
/// A local repository to handle providers.
|
||||
/// </summary>
|
||||
public class ProviderRepository : LocalRepository<Provider>, IProviderRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderRepository"/>.
|
||||
/// Create a new <see cref="ProviderRepository" />.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
public ProviderRepository(DatabaseContext database)
|
||||
@ -32,6 +29,9 @@ namespace Kyoo.Controllers
|
||||
_database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<Provider>> Search(string query)
|
||||
{
|
||||
@ -47,7 +47,8 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync($"Trying to insert a duplicated provider (slug {obj.Slug} already exists).");
|
||||
await _database.SaveChangesAsync("Trying to insert a duplicated provider " +
|
||||
$"(slug {obj.Slug} already exists).");
|
||||
return obj;
|
||||
}
|
||||
|
||||
@ -62,14 +63,18 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<MetadataID<T>>> GetMetadataID<T>(Expression<Func<MetadataID<T>, bool>> where = null,
|
||||
Sort<MetadataID<T>> sort = default,
|
||||
public Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
|
||||
Sort<MetadataID> sort = default,
|
||||
Pagination limit = default)
|
||||
where T : class, IResource
|
||||
where T : class, IMetadata
|
||||
{
|
||||
return ApplyFilters(_database.MetadataIds<T>().Include(y => y.Second),
|
||||
x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.FirstID == x),
|
||||
x => x.FirstID,
|
||||
string discriminator = typeof(T).Name;
|
||||
return ApplyFilters(_database.MetadataIDs
|
||||
.Include(y => y.Provider)
|
||||
.Where(x => x.ResourceType == discriminator),
|
||||
x => _database.MetadataIDs.FirstOrDefaultAsync(y => y.ResourceID == x
|
||||
&& y.ResourceType == discriminator),
|
||||
x => x.ResourceID,
|
||||
where,
|
||||
sort,
|
||||
limit);
|
||||
|
@ -106,9 +106,10 @@ namespace Kyoo.Controllers
|
||||
await base.Validate(resource);
|
||||
await resource.ExternalIDs.ForEachAsync(async id =>
|
||||
{
|
||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
||||
id.SecondID = id.Second.ID;
|
||||
_database.Entry(id.Second).State = EntityState.Detached;
|
||||
id.Provider = await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
id.ResourceType = nameof(Season);
|
||||
_database.Entry(id.Provider).State = EntityState.Detached;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -101,9 +101,10 @@ namespace Kyoo.Controllers
|
||||
});
|
||||
await resource.ExternalIDs.ForEachAsync(async id =>
|
||||
{
|
||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
||||
id.SecondID = id.Second.ID;
|
||||
_database.Entry(id.Second).State = EntityState.Detached;
|
||||
id.Provider = await _providers.CreateIfNotExists(id.Provider);
|
||||
id.ProviderID = id.Provider.ID;
|
||||
id.ResourceType = nameof(Show);
|
||||
_database.Entry(id.Provider).State = EntityState.Detached;
|
||||
});
|
||||
await resource.People.ForEachAsync(async role =>
|
||||
{
|
||||
|
@ -18,6 +18,11 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name;
|
||||
|
||||
@ -26,10 +31,12 @@ namespace Kyoo.Controllers
|
||||
/// Create a new <see cref="StudioRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
public StudioRepository(DatabaseContext database)
|
||||
/// <param name="providers">A provider repository</param>
|
||||
public StudioRepository(DatabaseContext database, IProviderRepository providers)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -51,6 +58,31 @@ namespace Kyoo.Controllers
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Studio resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
await resource.ExternalIDs.ForEachAsync(async x =>
|
||||
{
|
||||
x.Provider = await _providers.CreateIfNotExists(x.Provider);
|
||||
x.ProviderID = x.Provider.ID;
|
||||
x.ResourceType = nameof(Studio);
|
||||
_database.Entry(x.Provider).State = EntityState.Detached;
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task EditRelations(Studio resource, Studio changed, bool resetOld)
|
||||
{
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
resource.ExternalIDs = changed.ExternalIDs;
|
||||
}
|
||||
|
||||
await base.EditRelations(resource, changed, resetOld);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Studio obj)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user