diff --git a/back/.editorconfig b/back/.editorconfig
index 12a6f984..30728be6 100644
--- a/back/.editorconfig
+++ b/back/.editorconfig
@@ -16,6 +16,8 @@ dotnet_diagnostic.IDE0055.severity = none
dotnet_diagnostic.IDE0058.severity = none
dotnet_diagnostic.IDE0130.severity = none
+# Convert to file-scoped namespace
+csharp_style_namespace_declarations = file_scoped:warning
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
csharp_using_directive_placement = outside_namespace:warning
diff --git a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs
index 12bb63fe..8a1516b2 100644
--- a/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/ILibraryManager.cs
@@ -18,64 +18,63 @@
using Kyoo.Abstractions.Models;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// An interface to interact with the database. Every repository is mapped through here.
+///
+public interface ILibraryManager
{
+ IRepository Repository()
+ where T : IResource, IQuery;
+
///
- /// An interface to interact with the database. Every repository is mapped through here.
+ /// The repository that handle libraries items (a wrapper around shows and collections).
///
- public interface ILibraryManager
- {
- IRepository Repository()
- where T : IResource, IQuery;
+ IRepository LibraryItems { get; }
- ///
- /// The repository that handle libraries items (a wrapper around shows and collections).
- ///
- IRepository LibraryItems { get; }
+ ///
+ /// The repository that handle new items.
+ ///
+ IRepository News { get; }
- ///
- /// The repository that handle new items.
- ///
- IRepository News { get; }
+ ///
+ /// The repository that handle watched items.
+ ///
+ IWatchStatusRepository WatchStatus { get; }
- ///
- /// The repository that handle watched items.
- ///
- IWatchStatusRepository WatchStatus { get; }
+ ///
+ /// The repository that handle collections.
+ ///
+ IRepository Collections { get; }
- ///
- /// The repository that handle collections.
- ///
- IRepository Collections { get; }
+ ///
+ /// The repository that handle shows.
+ ///
+ IRepository Movies { get; }
- ///
- /// The repository that handle shows.
- ///
- IRepository Movies { get; }
+ ///
+ /// The repository that handle shows.
+ ///
+ IRepository Shows { get; }
- ///
- /// The repository that handle shows.
- ///
- IRepository Shows { get; }
+ ///
+ /// The repository that handle seasons.
+ ///
+ IRepository Seasons { get; }
- ///
- /// The repository that handle seasons.
- ///
- IRepository Seasons { get; }
+ ///
+ /// The repository that handle episodes.
+ ///
+ IRepository Episodes { get; }
- ///
- /// The repository that handle episodes.
- ///
- IRepository Episodes { get; }
+ ///
+ /// The repository that handle studios.
+ ///
+ IRepository Studios { get; }
- ///
- /// The repository that handle studios.
- ///
- IRepository Studios { get; }
-
- ///
- /// The repository that handle users.
- ///
- IRepository Users { get; }
- }
+ ///
+ /// The repository that handle users.
+ ///
+ IRepository Users { get; }
}
diff --git a/back/src/Kyoo.Abstractions/Controllers/IPermissionValidator.cs b/back/src/Kyoo.Abstractions/Controllers/IPermissionValidator.cs
index 363cecfe..4aa35625 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IPermissionValidator.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IPermissionValidator.cs
@@ -19,29 +19,28 @@
using Kyoo.Abstractions.Models.Permissions;
using Microsoft.AspNetCore.Mvc.Filters;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// A service to validate permissions.
+///
+public interface IPermissionValidator
{
///
- /// A service to validate permissions.
+ /// Create an IAuthorizationFilter that will be used to validate permissions.
+ /// This can registered with any lifetime.
///
- public interface IPermissionValidator
- {
- ///
- /// Create an IAuthorizationFilter that will be used to validate permissions.
- /// This can registered with any lifetime.
- ///
- /// The permission attribute to validate.
- /// An authorization filter used to validate the permission.
- IFilterMetadata Create(PermissionAttribute attribute);
+ /// The permission attribute to validate.
+ /// An authorization filter used to validate the permission.
+ IFilterMetadata Create(PermissionAttribute attribute);
- ///
- /// Create an IAuthorizationFilter that will be used to validate permissions.
- /// This can registered with any lifetime.
- ///
- ///
- /// A partial attribute to validate. See .
- ///
- /// An authorization filter used to validate the permission.
- IFilterMetadata Create(PartialPermissionAttribute attribute);
- }
+ ///
+ /// Create an IAuthorizationFilter that will be used to validate permissions.
+ /// This can registered with any lifetime.
+ ///
+ ///
+ /// A partial attribute to validate. See .
+ ///
+ /// An authorization filter used to validate the permission.
+ IFilterMetadata Create(PartialPermissionAttribute attribute);
}
diff --git a/back/src/Kyoo.Abstractions/Controllers/IPlugin.cs b/back/src/Kyoo.Abstractions/Controllers/IPlugin.cs
index 3363a4ff..a227f6e9 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IPlugin.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IPlugin.cs
@@ -21,46 +21,45 @@ using System.Collections.Generic;
using Autofac;
using Microsoft.Extensions.DependencyInjection;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// A common interface used to discord plugins
+///
+///
+/// You can inject services in the IPlugin constructor.
+/// You should only inject well known services like an ILogger, IConfiguration or IWebHostEnvironment.
+///
+public interface IPlugin
{
///
- /// A common interface used to discord plugins
+ /// The name of the plugin
///
- ///
- /// You can inject services in the IPlugin constructor.
- /// You should only inject well known services like an ILogger, IConfiguration or IWebHostEnvironment.
- ///
- public interface IPlugin
+ string Name { get; }
+
+ ///
+ /// An optional configuration step to allow a plugin to change asp net configurations.
+ ///
+ ///
+ IEnumerable ConfigureSteps => ArraySegment.Empty;
+
+ ///
+ /// A configure method that will be run on plugin's startup.
+ ///
+ /// The autofac service container to register services.
+ void Configure(ContainerBuilder builder)
{
- ///
- /// The name of the plugin
- ///
- string Name { get; }
+ // Skipped
+ }
- ///
- /// An optional configuration step to allow a plugin to change asp net configurations.
- ///
- ///
- IEnumerable ConfigureSteps => ArraySegment.Empty;
-
- ///
- /// A configure method that will be run on plugin's startup.
- ///
- /// The autofac service container to register services.
- void Configure(ContainerBuilder builder)
- {
- // Skipped
- }
-
- ///
- /// A configure method that will be run on plugin's startup.
- /// This is available for libraries that build upon a , for more precise
- /// configuration use .
- ///
- /// A service container to register new services.
- void Configure(IServiceCollection services)
- {
- // Skipped
- }
+ ///
+ /// A configure method that will be run on plugin's startup.
+ /// This is available for libraries that build upon a , for more precise
+ /// configuration use .
+ ///
+ /// A service container to register new services.
+ void Configure(IServiceCollection services)
+ {
+ // Skipped
}
}
diff --git a/back/src/Kyoo.Abstractions/Controllers/IPluginManager.cs b/back/src/Kyoo.Abstractions/Controllers/IPluginManager.cs
index 11aea0b4..4998588c 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IPluginManager.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IPluginManager.cs
@@ -20,51 +20,50 @@ using System;
using System.Collections.Generic;
using Kyoo.Abstractions.Models.Exceptions;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// A manager to load plugins and retrieve information from them.
+///
+public interface IPluginManager
{
///
- /// A manager to load plugins and retrieve information from them.
+ /// Get a single plugin that match the type and name given.
///
- public interface IPluginManager
- {
- ///
- /// Get a single plugin that match the type and name given.
- ///
- /// The name of the plugin
- /// The type of the plugin
- /// If no plugins match the query
- /// A plugin that match the queries
- public T GetPlugin(string name);
+ /// The name of the plugin
+ /// The type of the plugin
+ /// If no plugins match the query
+ /// A plugin that match the queries
+ public T GetPlugin(string name);
- ///
- /// Get all plugins of the given type.
- ///
- /// The type of plugins to get
- /// A list of plugins matching the given type or an empty list of none match.
- public ICollection GetPlugins();
+ ///
+ /// Get all plugins of the given type.
+ ///
+ /// The type of plugins to get
+ /// A list of plugins matching the given type or an empty list of none match.
+ public ICollection GetPlugins();
- ///
- /// Get all plugins currently running on Kyoo. This also includes deleted plugins if the app as not been restarted.
- ///
- /// All plugins currently loaded.
- public ICollection GetAllPlugins();
+ ///
+ /// Get all plugins currently running on Kyoo. This also includes deleted plugins if the app as not been restarted.
+ ///
+ /// All plugins currently loaded.
+ public ICollection GetAllPlugins();
- ///
- /// Load plugins and their dependencies from the plugin directory.
- ///
- ///
- /// An initial plugin list to use.
- /// You should not try to put plugins from the plugins directory here as they will get automatically loaded.
- ///
- public void LoadPlugins(ICollection plugins);
+ ///
+ /// Load plugins and their dependencies from the plugin directory.
+ ///
+ ///
+ /// An initial plugin list to use.
+ /// You should not try to put plugins from the plugins directory here as they will get automatically loaded.
+ ///
+ public void LoadPlugins(ICollection plugins);
- ///
- /// Load plugins and their dependencies from the plugin directory.
- ///
- ///
- /// An initial plugin list to use.
- /// You should not try to put plugins from the plugins directory here as they will get automatically loaded.
- ///
- public void LoadPlugins(params Type[] plugins);
- }
+ ///
+ /// Load plugins and their dependencies from the plugin directory.
+ ///
+ ///
+ /// An initial plugin list to use.
+ /// You should not try to put plugins from the plugins directory here as they will get automatically loaded.
+ ///
+ public void LoadPlugins(params Type[] plugins);
}
diff --git a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs
index 197c43ec..4dbdd953 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IRepository.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IRepository.cs
@@ -23,249 +23,245 @@ using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Abstractions.Models.Utils;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// A common repository for every resources.
+///
+/// The resource's type that this repository manage.
+public interface IRepository : IBaseRepository
+ where T : IResource, IQuery
{
///
- /// A common repository for every resources.
+ /// The event handler type for all events of this repository.
///
- /// The resource's type that this repository manage.
- public interface IRepository : IBaseRepository
- where T : IResource, IQuery
- {
- ///
- /// The event handler type for all events of this repository.
- ///
- /// The resource created/modified/deleted
- /// A representing the asynchronous operation.
- public delegate Task ResourceEventHandler(T resource);
-
- ///
- /// Get a resource from it's ID.
- ///
- /// The id of the resource
- /// The related fields to include.
- /// If the item could not be found.
- /// The resource found
- Task Get(Guid id, Include? include = default);
-
- ///
- /// Get a resource from it's slug.
- ///
- /// The slug of the resource
- /// The related fields to include.
- /// If the item could not be found.
- /// The resource found
- Task Get(string slug, Include? include = default);
-
- ///
- /// Get the first resource that match the predicate.
- ///
- /// A predicate to filter the resource.
- /// The related fields to include.
- /// A custom sort method to handle cases where multiples items match the filters.
- /// Reverse the sort.
- /// Select the first element after this id if it was in a list.
- /// If the item could not be found.
- /// The resource found
- Task Get(
- Filter filter,
- Include? include = default,
- Sort? sortBy = default,
- bool reverse = false,
- Guid? afterId = default
- );
-
- ///
- /// Get a resource from it's ID or null if it is not found.
- ///
- /// The id of the resource
- /// The related fields to include.
- /// The resource found
- Task GetOrDefault(Guid id, Include? include = default);
-
- ///
- /// Get a resource from it's slug or null if it is not found.
- ///
- /// The slug of the resource
- /// The related fields to include.
- /// The resource found
- Task GetOrDefault(string slug, Include? include = default);
-
- ///
- /// Get the first resource that match the predicate or null if it is not found.
- ///
- /// A predicate to filter the resource.
- /// The related fields to include.
- /// A custom sort method to handle cases where multiples items match the filters.
- /// Reverse the sort.
- /// Select the first element after this id if it was in a list.
- /// The resource found
- Task GetOrDefault(
- Filter? filter,
- Include? include = default,
- Sort? sortBy = default,
- bool reverse = false,
- Guid? afterId = default
- );
-
- ///
- /// Search for resources with the database.
- ///
- /// The query string.
- /// The related fields to include.
- /// A list of resources found
- Task> Search(string query, Include? include = default);
-
- ///
- /// Get every resources that match all filters
- ///
- /// A filter predicate
- /// Sort information about the query (sort by, sort order)
- /// The related fields to include.
- /// How pagination should be done (where to start and how many to return)
- /// A list of resources that match every filters
- Task> GetAll(
- Filter? filter = null,
- Sort? sort = default,
- Include? include = default,
- Pagination? limit = default
- );
-
- ///
- /// Get the number of resources that match the filter's predicate.
- ///
- /// A filter predicate
- /// How many resources matched that filter
- Task GetCount(Filter? filter = null);
-
- ///
- /// Map a list of ids to a list of items (keep the order).
- ///
- /// The list of items id.
- /// The related fields to include.
- /// A list of resources mapped from ids.
- Task> FromIds(IList ids, Include? include = default);
-
- ///
- /// Create a new resource.
- ///
- /// The item to register
- /// The resource registers and completed by database's information (related items and so on)
- Task Create(T obj);
-
- ///
- /// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
- ///
- /// The object to create
- /// The newly created item or the existing value if it existed.
- Task CreateIfNotExists(T obj);
-
- ///
- /// Called when a resource has been created.
- ///
- static event ResourceEventHandler OnCreated;
-
- ///
- /// Callback that should be called after a resource has been created.
- ///
- /// The resource newly created.
- /// A representing the asynchronous operation.
- protected static Task OnResourceCreated(T obj) =>
- OnCreated?.Invoke(obj) ?? Task.CompletedTask;
-
- ///
- /// Edit a resource and replace every property
- ///
- /// The resource to edit, it's ID can't change.
- /// If the item is not found
- /// The resource edited and completed by database's information (related items and so on)
- Task Edit(T edited);
-
- ///
- /// Edit only specific properties of a resource
- ///
- /// The id of the resource to edit
- ///
- /// A method that will be called when you need to update every properties that you want to
- /// persist.
- ///
- /// If the item is not found
- /// The resource edited and completed by database's information (related items and so on)
- Task Patch(Guid id, Func patch);
-
- ///
- /// Called when a resource has been edited.
- ///
- static event ResourceEventHandler OnEdited;
-
- ///
- /// Callback that should be called after a resource has been edited.
- ///
- /// The resource newly edited.
- /// A representing the asynchronous operation.
- protected static Task OnResourceEdited(T obj) =>
- OnEdited?.Invoke(obj) ?? Task.CompletedTask;
-
- ///
- /// Delete a resource by it's ID
- ///
- /// The ID of the resource
- /// If the item is not found
- /// A representing the asynchronous operation.
- Task Delete(Guid id);
-
- ///
- /// Delete a resource by it's slug
- ///
- /// The slug of the resource
- /// If the item is not found
- /// A representing the asynchronous operation.
- Task Delete(string slug);
-
- ///
- /// Delete a resource
- ///
- /// The resource to delete
- /// If the item is not found
- /// A representing the asynchronous operation.
- Task Delete(T obj);
-
- ///
- /// Delete all resources that match the predicate.
- ///
- /// A predicate to filter resources to delete. Every resource that match this will be deleted.
- /// A representing the asynchronous operation.
- Task DeleteAll(Filter filter);
-
- ///
- /// Called when a resource has been edited.
- ///
- static event ResourceEventHandler OnDeleted;
-
- ///
- /// Callback that should be called after a resource has been deleted.
- ///
- /// The resource newly deleted.
- /// A representing the asynchronous operation.
- protected static Task OnResourceDeleted(T obj) =>
- OnDeleted?.Invoke(obj) ?? Task.CompletedTask;
- }
+ /// The resource created/modified/deleted
+ /// A representing the asynchronous operation.
+ public delegate Task ResourceEventHandler(T resource);
///
- /// A base class for repositories. Every service implementing this will be handled by the .
+ /// Get a resource from it's ID.
///
- public interface IBaseRepository
- {
- ///
- /// The type for witch this repository is responsible or null if non applicable.
- ///
- Type RepositoryType { get; }
- }
+ /// The id of the resource
+ /// The related fields to include.
+ /// If the item could not be found.
+ /// The resource found
+ Task Get(Guid id, Include? include = default);
- public interface IUserRepository : IRepository
- {
- Task GetByExternalId(string provider, string id);
- Task AddExternalToken(Guid userId, string provider, ExternalToken token);
- Task DeleteExternalToken(Guid userId, string provider);
- }
+ ///
+ /// Get a resource from it's slug.
+ ///
+ /// The slug of the resource
+ /// The related fields to include.
+ /// If the item could not be found.
+ /// The resource found
+ Task Get(string slug, Include? include = default);
+
+ ///
+ /// Get the first resource that match the predicate.
+ ///
+ /// A predicate to filter the resource.
+ /// The related fields to include.
+ /// A custom sort method to handle cases where multiples items match the filters.
+ /// Reverse the sort.
+ /// Select the first element after this id if it was in a list.
+ /// If the item could not be found.
+ /// The resource found
+ Task Get(
+ Filter filter,
+ Include? include = default,
+ Sort? sortBy = default,
+ bool reverse = false,
+ Guid? afterId = default
+ );
+
+ ///
+ /// Get a resource from it's ID or null if it is not found.
+ ///
+ /// The id of the resource
+ /// The related fields to include.
+ /// The resource found
+ Task GetOrDefault(Guid id, Include? include = default);
+
+ ///
+ /// Get a resource from it's slug or null if it is not found.
+ ///
+ /// The slug of the resource
+ /// The related fields to include.
+ /// The resource found
+ Task GetOrDefault(string slug, Include? include = default);
+
+ ///
+ /// Get the first resource that match the predicate or null if it is not found.
+ ///
+ /// A predicate to filter the resource.
+ /// The related fields to include.
+ /// A custom sort method to handle cases where multiples items match the filters.
+ /// Reverse the sort.
+ /// Select the first element after this id if it was in a list.
+ /// The resource found
+ Task GetOrDefault(
+ Filter? filter,
+ Include? include = default,
+ Sort? sortBy = default,
+ bool reverse = false,
+ Guid? afterId = default
+ );
+
+ ///
+ /// Search for resources with the database.
+ ///
+ /// The query string.
+ /// The related fields to include.
+ /// A list of resources found
+ Task> Search(string query, Include? include = default);
+
+ ///
+ /// Get every resources that match all filters
+ ///
+ /// A filter predicate
+ /// Sort information about the query (sort by, sort order)
+ /// The related fields to include.
+ /// How pagination should be done (where to start and how many to return)
+ /// A list of resources that match every filters
+ Task> GetAll(
+ Filter? filter = null,
+ Sort? sort = default,
+ Include? include = default,
+ Pagination? limit = default
+ );
+
+ ///
+ /// Get the number of resources that match the filter's predicate.
+ ///
+ /// A filter predicate
+ /// How many resources matched that filter
+ Task GetCount(Filter? filter = null);
+
+ ///
+ /// Map a list of ids to a list of items (keep the order).
+ ///
+ /// The list of items id.
+ /// The related fields to include.
+ /// A list of resources mapped from ids.
+ Task> FromIds(IList ids, Include? include = default);
+
+ ///
+ /// Create a new resource.
+ ///
+ /// The item to register
+ /// The resource registers and completed by database's information (related items and so on)
+ Task Create(T obj);
+
+ ///
+ /// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
+ ///
+ /// The object to create
+ /// The newly created item or the existing value if it existed.
+ Task CreateIfNotExists(T obj);
+
+ ///
+ /// Called when a resource has been created.
+ ///
+ static event ResourceEventHandler OnCreated;
+
+ ///
+ /// Callback that should be called after a resource has been created.
+ ///
+ /// The resource newly created.
+ /// A representing the asynchronous operation.
+ protected static Task OnResourceCreated(T obj) => OnCreated?.Invoke(obj) ?? Task.CompletedTask;
+
+ ///
+ /// Edit a resource and replace every property
+ ///
+ /// The resource to edit, it's ID can't change.
+ /// If the item is not found
+ /// The resource edited and completed by database's information (related items and so on)
+ Task Edit(T edited);
+
+ ///
+ /// Edit only specific properties of a resource
+ ///
+ /// The id of the resource to edit
+ ///
+ /// A method that will be called when you need to update every properties that you want to
+ /// persist.
+ ///
+ /// If the item is not found
+ /// The resource edited and completed by database's information (related items and so on)
+ Task Patch(Guid id, Func patch);
+
+ ///
+ /// Called when a resource has been edited.
+ ///
+ static event ResourceEventHandler OnEdited;
+
+ ///
+ /// Callback that should be called after a resource has been edited.
+ ///
+ /// The resource newly edited.
+ /// A representing the asynchronous operation.
+ protected static Task OnResourceEdited(T obj) => OnEdited?.Invoke(obj) ?? Task.CompletedTask;
+
+ ///
+ /// Delete a resource by it's ID
+ ///
+ /// The ID of the resource
+ /// If the item is not found
+ /// A representing the asynchronous operation.
+ Task Delete(Guid id);
+
+ ///
+ /// Delete a resource by it's slug
+ ///
+ /// The slug of the resource
+ /// If the item is not found
+ /// A representing the asynchronous operation.
+ Task Delete(string slug);
+
+ ///
+ /// Delete a resource
+ ///
+ /// The resource to delete
+ /// If the item is not found
+ /// A representing the asynchronous operation.
+ Task Delete(T obj);
+
+ ///
+ /// Delete all resources that match the predicate.
+ ///
+ /// A predicate to filter resources to delete. Every resource that match this will be deleted.
+ /// A representing the asynchronous operation.
+ Task DeleteAll(Filter filter);
+
+ ///
+ /// Called when a resource has been edited.
+ ///
+ static event ResourceEventHandler OnDeleted;
+
+ ///
+ /// Callback that should be called after a resource has been deleted.
+ ///
+ /// The resource newly deleted.
+ /// A representing the asynchronous operation.
+ protected static Task OnResourceDeleted(T obj) => OnDeleted?.Invoke(obj) ?? Task.CompletedTask;
+}
+
+///
+/// A base class for repositories. Every service implementing this will be handled by the .
+///
+public interface IBaseRepository
+{
+ ///
+ /// The type for witch this repository is responsible or null if non applicable.
+ ///
+ Type RepositoryType { get; }
+}
+
+public interface IUserRepository : IRepository
+{
+ Task GetByExternalId(string provider, string id);
+ Task AddExternalToken(Guid userId, string provider, ExternalToken token);
+ Task DeleteExternalToken(Guid userId, string provider);
}
diff --git a/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs b/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs
index 1f7fc03c..715fbedc 100644
--- a/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/IThumbnailsManager.cs
@@ -21,59 +21,58 @@ using System.IO;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// Download images and retrieve the path of those images for a resource.
+///
+public interface IThumbnailsManager
{
///
- /// Download images and retrieve the path of those images for a resource.
+ /// Download images of a specified item.
+ /// If no images is available to download, do nothing and silently return.
///
- public interface IThumbnailsManager
- {
- ///
- /// Download images of a specified item.
- /// If no images is available to download, do nothing and silently return.
- ///
- ///
- /// The item to cache images.
- ///
- /// The type of the item
- /// A representing the asynchronous operation.
- Task DownloadImages(T item)
- where T : IThumbnails;
+ ///
+ /// The item to cache images.
+ ///
+ /// The type of the item
+ /// A representing the asynchronous operation.
+ Task DownloadImages(T item)
+ where T : IThumbnails;
- ///
- /// Retrieve the local path of an image of the given item.
- ///
- /// The item to retrieve the poster from.
- /// The ID of the image.
- /// The quality of the image
- /// The type of the item
- /// The path of the image for the given resource or null if it does not exists.
- string GetImagePath(T item, string image, ImageQuality quality)
- where T : IThumbnails;
+ ///
+ /// Retrieve the local path of an image of the given item.
+ ///
+ /// The item to retrieve the poster from.
+ /// The ID of the image.
+ /// The quality of the image
+ /// The type of the item
+ /// The path of the image for the given resource or null if it does not exists.
+ string GetImagePath(T item, string image, ImageQuality quality)
+ where T : IThumbnails;
- ///
- /// Delete images associated with the item.
- ///
- ///
- /// The item with cached images.
- ///
- /// The type of the item
- /// A representing the asynchronous operation.
- Task DeleteImages(T item)
- where T : IThumbnails;
+ ///
+ /// Delete images associated with the item.
+ ///
+ ///
+ /// The item with cached images.
+ ///
+ /// The type of the item
+ /// A representing the asynchronous operation.
+ Task DeleteImages(T item)
+ where T : IThumbnails;
- ///
- /// Set the user's profile picture
- ///
- /// The id of the user.
- /// The byte stream of the image. Null if no image exist.
- Task GetUserImage(Guid userId);
+ ///
+ /// Set the user's profile picture
+ ///
+ /// The id of the user.
+ /// The byte stream of the image. Null if no image exist.
+ Task GetUserImage(Guid userId);
- ///
- /// Set the user's profile picture
- ///
- /// The id of the user.
- /// The byte stream of the image. Null to delete the image.
- Task SetUserImage(Guid userId, Stream? image);
- }
+ ///
+ /// Set the user's profile picture
+ ///
+ /// The id of the user.
+ /// The byte stream of the image. Null to delete the image.
+ Task SetUserImage(Guid userId, Stream? image);
}
diff --git a/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs b/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs
index ea9d9acf..944b899d 100644
--- a/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs
+++ b/back/src/Kyoo.Abstractions/Controllers/StartupAction.cs
@@ -19,256 +19,252 @@
using System;
using Microsoft.Extensions.DependencyInjection;
-namespace Kyoo.Abstractions.Controllers
+namespace Kyoo.Abstractions.Controllers;
+
+///
+/// A list of constant priorities used for 's .
+/// It also contains helper methods for creating new .
+///
+public static class SA
{
///
- /// A list of constant priorities used for 's .
- /// It also contains helper methods for creating new .
+ /// The highest predefined priority existing for .
///
- public static class SA
+ public const int Before = 5000;
+
+ ///
+ /// Items defining routing (see IApplicationBuilder.UseRouting use this priority.
+ ///
+ public const int Routing = 4000;
+
+ ///
+ /// Actions defining new static files router use this priority.
+ ///
+ public const int StaticFiles = 3000;
+
+ ///
+ /// Actions calling IApplicationBuilder.UseAuthentication use this priority.
+ ///
+ public const int Authentication = 2000;
+
+ ///
+ /// Actions calling IApplicationBuilder.UseAuthorization use this priority.
+ ///
+ public const int Authorization = 1000;
+
+ ///
+ /// Action adding endpoint should use this priority (with a negative modificator if there is a catchall).
+ ///
+ public const int Endpoint = 0;
+
+ ///
+ /// The lowest predefined priority existing for .
+ /// It should run after all other actions.
+ ///
+ public const int After = -1000;
+
+ ///
+ /// Create a new .
+ ///
+ /// The action to run
+ /// The priority of the new action
+ /// A new
+ public static StartupAction New(Action action, int priority) => new(action, priority);
+
+ ///
+ /// Create a new .
+ ///
+ /// The action to run
+ /// The priority of the new action
+ /// A dependency that this action will use.
+ /// A new
+ public static StartupAction New(Action action, int priority)
+ where T : notnull => new(action, priority);
+
+ ///
+ /// Create a new .
+ ///
+ /// The action to run
+ /// The priority of the new action
+ /// A dependency that this action will use.
+ /// A second dependency that this action will use.
+ /// A new
+ public static StartupAction New(Action action, int priority)
+ where T : notnull
+ where T2 : notnull => new(action, priority);
+
+ ///
+ /// Create a new .
+ ///
+ /// The action to run
+ /// The priority of the new action
+ /// A dependency that this action will use.
+ /// A second dependency that this action will use.
+ /// A third dependency that this action will use.
+ /// A new
+ public static StartupAction New(Action action, int priority)
+ where T : notnull
+ where T2 : notnull
+ where T3 : notnull => new(action, priority);
+
+ ///
+ /// A with no dependencies.
+ ///
+ public class StartupAction : IStartupAction
{
///
- /// The highest predefined priority existing for .
+ /// The action to execute at startup.
///
- public const int Before = 5000;
+ private readonly Action _action;
- ///
- /// Items defining routing (see IApplicationBuilder.UseRouting use this priority.
- ///
- public const int Routing = 4000;
-
- ///
- /// Actions defining new static files router use this priority.
- ///
- public const int StaticFiles = 3000;
-
- ///
- /// Actions calling IApplicationBuilder.UseAuthentication use this priority.
- ///
- public const int Authentication = 2000;
-
- ///
- /// Actions calling IApplicationBuilder.UseAuthorization use this priority.
- ///
- public const int Authorization = 1000;
-
- ///
- /// Action adding endpoint should use this priority (with a negative modificator if there is a catchall).
- ///
- public const int Endpoint = 0;
-
- ///
- /// The lowest predefined priority existing for .
- /// It should run after all other actions.
- ///
- public const int After = -1000;
+ ///
+ public int Priority { get; }
///
/// Create a new .
///
- /// The action to run
- /// The priority of the new action
- /// A new
- public static StartupAction New(Action action, int priority) => new(action, priority);
-
- ///
- /// Create a new .
- ///
- /// The action to run
- /// The priority of the new action
- /// A dependency that this action will use.
- /// A new
- public static StartupAction New(Action action, int priority)
- where T : notnull => new(action, priority);
-
- ///
- /// Create a new .
- ///
- /// The action to run
- /// The priority of the new action
- /// A dependency that this action will use.
- /// A second dependency that this action will use.
- /// A new
- public static StartupAction New(Action action, int priority)
- where T : notnull
- where T2 : notnull => new(action, priority);
-
- ///
- /// Create a new .
- ///
- /// The action to run
- /// The priority of the new action
- /// A dependency that this action will use.
- /// A second dependency that this action will use.
- /// A third dependency that this action will use.
- /// A new
- public static StartupAction New(
- Action action,
- int priority
- )
- where T : notnull
- where T2 : notnull
- where T3 : notnull => new(action, priority);
-
- ///
- /// A with no dependencies.
- ///
- public class StartupAction : IStartupAction
+ /// The action to execute on startup.
+ /// The priority of this action (see ).
+ public StartupAction(Action action, int priority)
{
- ///
- /// The action to execute at startup.
- ///
- private readonly Action _action;
-
- ///
- public int Priority { get; }
-
- ///
- /// Create a new .
- ///
- /// The action to execute on startup.
- /// The priority of this action (see ).
- public StartupAction(Action action, int priority)
- {
- _action = action;
- Priority = priority;
- }
-
- ///
- public void Run(IServiceProvider provider)
- {
- _action.Invoke();
- }
+ _action = action;
+ Priority = priority;
}
- ///
- /// A with one dependencies.
- ///
- /// The dependency to use.
- public class StartupAction : IStartupAction
- where T : notnull
+ ///
+ public void Run(IServiceProvider provider)
{
- ///
- /// The action to execute at startup.
- ///
- private readonly Action _action;
-
- ///
- public int Priority { get; }
-
- ///
- /// Create a new .
- ///
- /// The action to execute on startup.
- /// The priority of this action (see ).
- public StartupAction(Action action, int priority)
- {
- _action = action;
- Priority = priority;
- }
-
- ///
- public void Run(IServiceProvider provider)
- {
- _action.Invoke(provider.GetRequiredService());
- }
- }
-
- ///
- /// A with two dependencies.
- ///
- /// The dependency to use.
- /// The second dependency to use.
- public class StartupAction : IStartupAction
- where T : notnull
- where T2 : notnull
- {
- ///
- /// The action to execute at startup.
- ///
- private readonly Action _action;
-
- ///
- public int Priority { get; }
-
- ///
- /// Create a new .
- ///
- /// The action to execute on startup.
- /// The priority of this action (see ).
- public StartupAction(Action action, int priority)
- {
- _action = action;
- Priority = priority;
- }
-
- ///
- public void Run(IServiceProvider provider)
- {
- _action.Invoke(provider.GetRequiredService(), provider.GetRequiredService());
- }
- }
-
- ///
- /// A with three dependencies.
- ///
- /// The dependency to use.
- /// The second dependency to use.
- /// The third dependency to use.
- public class StartupAction : IStartupAction
- where T : notnull
- where T2 : notnull
- where T3 : notnull
- {
- ///
- /// The action to execute at startup.
- ///
- private readonly Action _action;
-
- ///
- public int Priority { get; }
-
- ///
- /// Create a new .
- ///
- /// The action to execute on startup.
- /// The priority of this action (see ).
- public StartupAction(Action action, int priority)
- {
- _action = action;
- Priority = priority;
- }
-
- ///
- public void Run(IServiceProvider provider)
- {
- _action.Invoke(
- provider.GetRequiredService(),
- provider.GetRequiredService(),
- provider.GetRequiredService()
- );
- }
+ _action.Invoke();
}
}
///
- /// An action executed on kyoo's startup to initialize the asp-net container.
+ /// A with one dependencies.
///
- ///
- /// This is the base interface, see for a simpler use of this.
- ///
- public interface IStartupAction
+ /// The dependency to use.
+ public class StartupAction : IStartupAction
+ where T : notnull
{
///
- /// The priority of this action. The actions will be executed on descending priority order.
- /// If two actions have the same priority, their order is undefined.
+ /// The action to execute at startup.
///
- int Priority { get; }
+ private readonly Action _action;
+
+ ///
+ public int Priority { get; }
///
- /// Run this action to configure the container, a service provider containing all services can be used.
+ /// Create a new .
///
- /// The service provider containing all services can be used.
- void Run(IServiceProvider provider);
+ /// The action to execute on startup.
+ /// The priority of this action (see ).
+ public StartupAction(Action action, int priority)
+ {
+ _action = action;
+ Priority = priority;
+ }
+
+ ///
+ public void Run(IServiceProvider provider)
+ {
+ _action.Invoke(provider.GetRequiredService());
+ }
+ }
+
+ ///
+ /// A with two dependencies.
+ ///
+ /// The dependency to use.
+ /// The second dependency to use.
+ public class StartupAction : IStartupAction
+ where T : notnull
+ where T2 : notnull
+ {
+ ///
+ /// The action to execute at startup.
+ ///
+ private readonly Action _action;
+
+ ///
+ public int Priority { get; }
+
+ ///
+ /// Create a new .
+ ///
+ /// The action to execute on startup.
+ /// The priority of this action (see ).
+ public StartupAction(Action action, int priority)
+ {
+ _action = action;
+ Priority = priority;
+ }
+
+ ///
+ public void Run(IServiceProvider provider)
+ {
+ _action.Invoke(provider.GetRequiredService(), provider.GetRequiredService());
+ }
+ }
+
+ ///
+ /// A with three dependencies.
+ ///
+ /// The dependency to use.
+ /// The second dependency to use.
+ /// The third dependency to use.
+ public class StartupAction : IStartupAction
+ where T : notnull
+ where T2 : notnull
+ where T3 : notnull
+ {
+ ///
+ /// The action to execute at startup.
+ ///
+ private readonly Action _action;
+
+ ///
+ public int Priority { get; }
+
+ ///
+ /// Create a new .
+ ///
+ /// The action to execute on startup.
+ /// The priority of this action (see ).
+ public StartupAction(Action action, int priority)
+ {
+ _action = action;
+ Priority = priority;
+ }
+
+ ///
+ public void Run(IServiceProvider provider)
+ {
+ _action.Invoke(
+ provider.GetRequiredService(),
+ provider.GetRequiredService(),
+ provider.GetRequiredService()
+ );
+ }
}
}
+
+///
+/// An action executed on kyoo's startup to initialize the asp-net container.
+///
+///
+/// This is the base interface, see for a simpler use of this.
+///
+public interface IStartupAction
+{
+ ///
+ /// The priority of this action. The actions will be executed on descending priority order.
+ /// If two actions have the same priority, their order is undefined.
+ ///
+ int Priority { get; }
+
+ ///
+ /// Run this action to configure the container, a service provider containing all services can be used.
+ ///
+ /// The service provider containing all services can be used.
+ void Run(IServiceProvider provider);
+}
diff --git a/back/src/Kyoo.Abstractions/Extensions.cs b/back/src/Kyoo.Abstractions/Extensions.cs
index 05756ba6..42f4b0af 100644
--- a/back/src/Kyoo.Abstractions/Extensions.cs
+++ b/back/src/Kyoo.Abstractions/Extensions.cs
@@ -23,43 +23,42 @@ using System.Security.Claims;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Authentication.Models;
-namespace Kyoo.Authentication
+namespace Kyoo.Authentication;
+
+///
+/// Extension methods.
+///
+public static class Extensions
{
///
- /// Extension methods.
+ /// Get the permissions of an user.
///
- public static class Extensions
+ /// The user
+ /// The list of permissions
+ public static ICollection GetPermissions(this ClaimsPrincipal user)
{
- ///
- /// Get the permissions of an user.
- ///
- /// The user
- /// The list of permissions
- public static ICollection GetPermissions(this ClaimsPrincipal user)
- {
- return user.Claims.FirstOrDefault(x => x.Type == Claims.Permissions)?.Value.Split(',')
- ?? Array.Empty();
- }
+ return user.Claims.FirstOrDefault(x => x.Type == Claims.Permissions)?.Value.Split(',')
+ ?? Array.Empty();
+ }
- ///
- /// Get the id of the current user or null if unlogged or invalid.
- ///
- /// The user.
- /// The id of the user or null.
- public static Guid? GetId(this ClaimsPrincipal user)
- {
- Claim? value = user.FindFirst(Claims.Id);
- if (Guid.TryParse(value?.Value, out Guid id))
- return id;
- return null;
- }
+ ///
+ /// Get the id of the current user or null if unlogged or invalid.
+ ///
+ /// The user.
+ /// The id of the user or null.
+ public static Guid? GetId(this ClaimsPrincipal user)
+ {
+ Claim? value = user.FindFirst(Claims.Id);
+ if (Guid.TryParse(value?.Value, out Guid id))
+ return id;
+ return null;
+ }
- public static Guid GetIdOrThrow(this ClaimsPrincipal user)
- {
- Guid? ret = user.GetId();
- if (ret == null)
- throw new UnauthorizedException();
- return ret.Value;
- }
+ public static Guid GetIdOrThrow(this ClaimsPrincipal user)
+ {
+ Guid? ret = user.GetId();
+ if (ret == null)
+ throw new UnauthorizedException();
+ return ret.Value;
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs
index 785c8b55..46014a69 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/ApiDefinitionAttribute.cs
@@ -18,35 +18,34 @@
using System;
-namespace Kyoo.Abstractions.Models.Attributes
+namespace Kyoo.Abstractions.Models.Attributes;
+
+///
+/// An attribute to specify on apis to specify it's documentation's name and category.
+/// If this is applied on a method, the specified method will be exploded from the controller's page and be
+/// included on the specified tag page.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+public class ApiDefinitionAttribute : Attribute
{
///
- /// An attribute to specify on apis to specify it's documentation's name and category.
- /// If this is applied on a method, the specified method will be exploded from the controller's page and be
- /// included on the specified tag page.
+ /// The public name of this api.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
- public class ApiDefinitionAttribute : Attribute
+ public string Name { get; }
+
+ ///
+ /// The name of the group in witch this API is. You can also specify a custom sort order using the following
+ /// format: order:name
. Everything before the first : will be removed but kept for
+ /// th alphabetical ordering.
+ ///
+ public string? Group { get; set; }
+
+ ///
+ /// Create a new .
+ ///
+ /// The name of the api that will be used on the documentation page.
+ public ApiDefinitionAttribute(string name)
{
- ///
- /// The public name of this api.
- ///
- public string Name { get; }
-
- ///
- /// The name of the group in witch this API is. You can also specify a custom sort order using the following
- /// format: order:name
. Everything before the first : will be removed but kept for
- /// th alphabetical ordering.
- ///
- public string? Group { get; set; }
-
- ///
- /// Create a new .
- ///
- /// The name of the api that will be used on the documentation page.
- public ApiDefinitionAttribute(string name)
- {
- Name = name;
- }
+ Name = name;
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/ComputedAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/ComputedAttribute.cs
index fd4d37b5..2fde7a84 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/ComputedAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/ComputedAttribute.cs
@@ -18,11 +18,10 @@
using System;
-namespace Kyoo.Abstractions.Models.Attributes
-{
- ///
- /// An attribute to inform that the property is computed automatically and can't be assigned manually.
- ///
- [AttributeUsage(AttributeTargets.Property)]
- public class ComputedAttribute : NotMergeableAttribute { }
-}
+namespace Kyoo.Abstractions.Models.Attributes;
+
+///
+/// An attribute to inform that the property is computed automatically and can't be assigned manually.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class ComputedAttribute : NotMergeableAttribute { }
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs
index bb7c2b05..8b40679e 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/LoadableRelationAttribute.cs
@@ -18,37 +18,36 @@
using System;
-namespace Kyoo.Abstractions.Models.Attributes
+namespace Kyoo.Abstractions.Models.Attributes;
+
+///
+/// The targeted relation can be loaded.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class LoadableRelationAttribute : Attribute
{
///
- /// The targeted relation can be loaded.
+ /// The name of the field containing the related resource's ID.
///
- [AttributeUsage(AttributeTargets.Property)]
- public class LoadableRelationAttribute : Attribute
+ public string? RelationID { get; }
+
+ public string? Sql { get; set; }
+
+ public string? On { get; set; }
+
+ public string? Projected { get; set; }
+
+ ///
+ /// Create a new .
+ ///
+ public LoadableRelationAttribute() { }
+
+ ///
+ /// Create a new with a baking relationID field.
+ ///
+ /// The name of the RelationID field.
+ public LoadableRelationAttribute(string relationID)
{
- ///
- /// The name of the field containing the related resource's ID.
- ///
- public string? RelationID { get; }
-
- public string? Sql { get; set; }
-
- public string? On { get; set; }
-
- public string? Projected { get; set; }
-
- ///
- /// Create a new .
- ///
- public LoadableRelationAttribute() { }
-
- ///
- /// Create a new with a baking relationID field.
- ///
- /// The name of the RelationID field.
- public LoadableRelationAttribute(string relationID)
- {
- RelationID = relationID;
- }
+ RelationID = relationID;
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/NotMergeableAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/NotMergeableAttribute.cs
index c63acb06..138ec9a8 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/NotMergeableAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/NotMergeableAttribute.cs
@@ -18,23 +18,22 @@
using System;
-namespace Kyoo.Abstractions.Models.Attributes
+namespace Kyoo.Abstractions.Models.Attributes;
+
+///
+/// Specify that a property can't be merged.
+///
+[AttributeUsage(AttributeTargets.Property)]
+public class NotMergeableAttribute : Attribute { }
+
+///
+/// An interface with a method called when this object is merged.
+///
+public interface IOnMerge
{
///
- /// Specify that a property can't be merged.
+ /// This function is called after the object has been merged.
///
- [AttributeUsage(AttributeTargets.Property)]
- public class NotMergeableAttribute : Attribute { }
-
- ///
- /// An interface with a method called when this object is merged.
- ///
- public interface IOnMerge
- {
- ///
- /// This function is called after the object has been merged.
- ///
- /// The object that has been merged with this.
- void OnMerge(object merged);
- }
+ /// The object that has been merged with this.
+ void OnMerge(object merged);
}
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs
index 438b9ac8..ea300823 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs
@@ -21,68 +21,67 @@ using Kyoo.Abstractions.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
-namespace Kyoo.Abstractions.Models.Permissions
+namespace Kyoo.Abstractions.Models.Permissions;
+
+///
+/// Specify one part of a permissions needed for the API (the kind or the type).
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+public class PartialPermissionAttribute : Attribute, IFilterFactory
{
///
- /// Specify one part of a permissions needed for the API (the kind or the type).
+ /// The needed permission type.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
- public class PartialPermissionAttribute : Attribute, IFilterFactory
+ public string? Type { get; }
+
+ ///
+ /// The needed permission kind.
+ ///
+ public Kind? Kind { get; }
+
+ ///
+ /// The group of this permission.
+ ///
+ public Group Group { get; set; }
+
+ ///
+ /// Ask a permission to run an action.
+ ///
+ ///
+ /// With this attribute, you can only specify a type or a kind.
+ /// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
+ /// Those attributes can be dispatched at different places (one on the class, one on the method for example).
+ /// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
+ /// lead to unspecified behaviors.
+ ///
+ /// The type of the action
+ public PartialPermissionAttribute(string type)
{
- ///
- /// The needed permission type.
- ///
- public string? Type { get; }
-
- ///
- /// The needed permission kind.
- ///
- public Kind? Kind { get; }
-
- ///
- /// The group of this permission.
- ///
- public Group Group { get; set; }
-
- ///
- /// Ask a permission to run an action.
- ///
- ///
- /// With this attribute, you can only specify a type or a kind.
- /// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
- /// Those attributes can be dispatched at different places (one on the class, one on the method for example).
- /// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
- /// lead to unspecified behaviors.
- ///
- /// The type of the action
- public PartialPermissionAttribute(string type)
- {
- Type = type.ToLower();
- }
-
- ///
- /// Ask a permission to run an action.
- ///
- ///
- /// With this attribute, you can only specify a type or a kind.
- /// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
- /// Those attributes can be dispatched at different places (one on the class, one on the method for example).
- /// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
- /// lead to unspecified behaviors.
- ///
- /// The kind of permission needed.
- public PartialPermissionAttribute(Kind permission)
- {
- Kind = permission;
- }
-
- ///
- public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
- {
- return serviceProvider.GetRequiredService().Create(this);
- }
-
- ///
- public bool IsReusable => true;
+ Type = type.ToLower();
}
+
+ ///
+ /// Ask a permission to run an action.
+ ///
+ ///
+ /// With this attribute, you can only specify a type or a kind.
+ /// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
+ /// Those attributes can be dispatched at different places (one on the class, one on the method for example).
+ /// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
+ /// lead to unspecified behaviors.
+ ///
+ /// The kind of permission needed.
+ public PartialPermissionAttribute(Kind permission)
+ {
+ Kind = permission;
+ }
+
+ ///
+ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
+ {
+ return serviceProvider.GetRequiredService().Create(this);
+ }
+
+ ///
+ public bool IsReusable => true;
}
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PermissionAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PermissionAttribute.cs
index 9e5d27f9..29aad1e4 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PermissionAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/PermissionAttribute.cs
@@ -21,117 +21,116 @@ using Kyoo.Abstractions.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
-namespace Kyoo.Abstractions.Models.Permissions
+namespace Kyoo.Abstractions.Models.Permissions;
+
+///
+/// The kind of permission needed.
+///
+public enum Kind
{
///
+ /// Allow the user to read for this kind of data.
+ ///
+ Read,
+
+ ///
+ /// Allow the user to write for this kind of data.
+ ///
+ Write,
+
+ ///
+ /// Allow the user to create this kind of data.
+ ///
+ Create,
+
+ ///
+ /// Allow the user to delete this kind of data.
+ ///
+ Delete,
+
+ ///
+ /// Allow the user to play this file.
+ ///
+ Play,
+}
+
+///
+/// The group of the permission.
+///
+public enum Group
+{
+ ///
+ /// Default group indicating no value.
+ ///
+ None,
+
+ ///
+ /// Allow all operations on basic items types.
+ ///
+ Overall,
+
+ ///
+ /// Allow operation on sensitive items like libraries path, configurations and so on.
+ ///
+ Admin
+}
+
+///
+/// Specify permissions needed for the API.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+public class PermissionAttribute : Attribute, IFilterFactory
+{
+ ///
+ /// The needed permission as string.
+ ///
+ public string Type { get; }
+
+ ///
+ /// The needed permission kind.
+ ///
+ public Kind Kind { get; }
+
+ ///
+ /// The group of this permission.
+ ///
+ public Group Group { get; }
+
+ ///
+ /// Ask a permission to run an action.
+ ///
+ ///
+ /// The type of the action
+ ///
+ ///
/// The kind of permission needed.
- ///
- public enum Kind
+ ///
+ ///
+ /// The group of this permission (allow grouped permission like overall.read
+ /// for all read permissions of this group).
+ ///
+ public PermissionAttribute(string type, Kind permission, Group group = Group.Overall)
{
- ///
- /// Allow the user to read for this kind of data.
- ///
- Read,
-
- ///
- /// Allow the user to write for this kind of data.
- ///
- Write,
-
- ///
- /// Allow the user to create this kind of data.
- ///
- Create,
-
- ///
- /// Allow the user to delete this kind of data.
- ///
- Delete,
-
- ///
- /// Allow the user to play this file.
- ///
- Play,
+ Type = type.ToLower();
+ Kind = permission;
+ Group = group;
}
- ///
- /// The group of the permission.
- ///
- public enum Group
+ ///
+ public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
- ///
- /// Default group indicating no value.
- ///
- None,
-
- ///
- /// Allow all operations on basic items types.
- ///
- Overall,
-
- ///
- /// Allow operation on sensitive items like libraries path, configurations and so on.
- ///
- Admin
+ return serviceProvider.GetRequiredService().Create(this);
}
+ ///
+ public bool IsReusable => true;
+
///
- /// Specify permissions needed for the API.
+ /// Return this permission attribute as a string.
///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
- public class PermissionAttribute : Attribute, IFilterFactory
+ /// The string representation.
+ public string AsPermissionString()
{
- ///
- /// The needed permission as string.
- ///
- public string Type { get; }
-
- ///
- /// The needed permission kind.
- ///
- public Kind Kind { get; }
-
- ///
- /// The group of this permission.
- ///
- public Group Group { get; }
-
- ///
- /// Ask a permission to run an action.
- ///
- ///
- /// The type of the action
- ///
- ///
- /// The kind of permission needed.
- ///
- ///
- /// The group of this permission (allow grouped permission like overall.read
- /// for all read permissions of this group).
- ///
- public PermissionAttribute(string type, Kind permission, Group group = Group.Overall)
- {
- Type = type.ToLower();
- Kind = permission;
- Group = group;
- }
-
- ///
- public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
- {
- return serviceProvider.GetRequiredService().Create(this);
- }
-
- ///
- public bool IsReusable => true;
-
- ///
- /// Return this permission attribute as a string.
- ///
- /// The string representation.
- public string AsPermissionString()
- {
- return Type;
- }
+ return Type;
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs
index 884187a0..a50424a1 100644
--- a/back/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs
+++ b/back/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs
@@ -18,14 +18,13 @@
using System;
-namespace Kyoo.Abstractions.Models.Permissions
+namespace Kyoo.Abstractions.Models.Permissions;
+
+///
+/// The annotated route can only be accessed by a logged in user.
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+public class UserOnlyAttribute : Attribute
{
- ///
- /// The annotated route can only be accessed by a logged in user.
- ///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
- public class UserOnlyAttribute : Attribute
- {
- // TODO: Implement a Filter Attribute to make this work. For now, this attribute is only useful as documentation.
- }
+ // TODO: Implement a Filter Attribute to make this work. For now, this attribute is only useful as documentation.
}
diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs
index 635d4f71..817b8487 100644
--- a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs
+++ b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs
@@ -19,35 +19,34 @@
using System;
using System.Runtime.Serialization;
-namespace Kyoo.Abstractions.Models.Exceptions
+namespace Kyoo.Abstractions.Models.Exceptions;
+
+///
+/// An exception raised when an item already exists in the database.
+///
+[Serializable]
+public class DuplicatedItemException : Exception
{
///
- /// An exception raised when an item already exists in the database.
+ /// The existing object.
///
- [Serializable]
- public class DuplicatedItemException : Exception
+ public object? Existing { get; }
+
+ ///
+ /// Create a new with the default message.
+ ///
+ /// The existing object.
+ public DuplicatedItemException(object? existing = null)
+ : base("Already exists in the database.")
{
- ///
- /// The existing object.
- ///
- public object? Existing { get; }
-
- ///
- /// Create a new with the default message.
- ///
- /// The existing object.
- public DuplicatedItemException(object? existing = null)
- : base("Already exists in the database.")
- {
- Existing = existing;
- }
-
- ///
- /// The serialization constructor.
- ///
- /// Serialization infos
- /// The serialization context
- protected DuplicatedItemException(SerializationInfo info, StreamingContext context)
- : base(info, context) { }
+ Existing = existing;
}
+
+ ///
+ /// The serialization constructor.
+ ///
+ /// Serialization infos
+ /// The serialization context
+ protected DuplicatedItemException(SerializationInfo info, StreamingContext context)
+ : base(info, context) { }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/ItemNotFoundException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/ItemNotFoundException.cs
index 0fd5825f..eeeffc79 100644
--- a/back/src/Kyoo.Abstractions/Models/Exceptions/ItemNotFoundException.cs
+++ b/back/src/Kyoo.Abstractions/Models/Exceptions/ItemNotFoundException.cs
@@ -19,33 +19,32 @@
using System;
using System.Runtime.Serialization;
-namespace Kyoo.Abstractions.Models.Exceptions
+namespace Kyoo.Abstractions.Models.Exceptions;
+
+///
+/// An exception raised when an item could not be found.
+///
+[Serializable]
+public class ItemNotFoundException : Exception
{
///
- /// An exception raised when an item could not be found.
+ /// Create a default with no message.
///
- [Serializable]
- public class ItemNotFoundException : Exception
- {
- ///
- /// Create a default with no message.
- ///
- public ItemNotFoundException()
- : base("Item not found") { }
+ public ItemNotFoundException()
+ : base("Item not found") { }
- ///
- /// Create a new with a message
- ///
- /// The message of the exception
- public ItemNotFoundException(string message)
- : base(message) { }
+ ///
+ /// Create a new with a message
+ ///
+ /// The message of the exception
+ public ItemNotFoundException(string message)
+ : base(message) { }
- ///
- /// The serialization constructor
- ///
- /// Serialization infos
- /// The serialization context
- protected ItemNotFoundException(SerializationInfo info, StreamingContext context)
- : base(info, context) { }
- }
+ ///
+ /// The serialization constructor
+ ///
+ /// Serialization infos
+ /// The serialization context
+ protected ItemNotFoundException(SerializationInfo info, StreamingContext context)
+ : base(info, context) { }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs
index 27415a97..af0eae6c 100644
--- a/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs
+++ b/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs
@@ -19,18 +19,17 @@
using System;
using System.Runtime.Serialization;
-namespace Kyoo.Abstractions.Models.Exceptions
+namespace Kyoo.Abstractions.Models.Exceptions;
+
+[Serializable]
+public class UnauthorizedException : Exception
{
- [Serializable]
- public class UnauthorizedException : Exception
- {
- public UnauthorizedException()
- : base("User not authenticated or token invalid.") { }
+ public UnauthorizedException()
+ : base("User not authenticated or token invalid.") { }
- public UnauthorizedException(string message)
- : base(message) { }
+ public UnauthorizedException(string message)
+ : base(message) { }
- protected UnauthorizedException(SerializationInfo info, StreamingContext context)
- : base(info, context) { }
- }
+ protected UnauthorizedException(SerializationInfo info, StreamingContext context)
+ : base(info, context) { }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Genre.cs b/back/src/Kyoo.Abstractions/Models/Genre.cs
index 2b7e16f0..fa2af8cb 100644
--- a/back/src/Kyoo.Abstractions/Models/Genre.cs
+++ b/back/src/Kyoo.Abstractions/Models/Genre.cs
@@ -16,30 +16,29 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A genre that allow one to specify categories for shows.
+///
+public enum Genre
{
- ///
- /// A genre that allow one to specify categories for shows.
- ///
- public enum Genre
- {
- Action,
- Adventure,
- Animation,
- Comedy,
- Crime,
- Documentary,
- Drama,
- Family,
- Fantasy,
- History,
- Horror,
- Music,
- Mystery,
- Romance,
- ScienceFiction,
- Thriller,
- War,
- Western,
- }
+ Action,
+ Adventure,
+ Animation,
+ Comedy,
+ Crime,
+ Documentary,
+ Drama,
+ Family,
+ Fantasy,
+ History,
+ Horror,
+ Music,
+ Mystery,
+ Romance,
+ ScienceFiction,
+ Thriller,
+ War,
+ Western,
}
diff --git a/back/src/Kyoo.Abstractions/Models/MetadataID.cs b/back/src/Kyoo.Abstractions/Models/MetadataID.cs
index f16c59ae..37919c10 100644
--- a/back/src/Kyoo.Abstractions/Models/MetadataID.cs
+++ b/back/src/Kyoo.Abstractions/Models/MetadataID.cs
@@ -16,21 +16,20 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// ID and link of an item on an external provider.
+///
+public class MetadataId
{
///
- /// ID and link of an item on an external provider.
+ /// The ID of the resource on the external provider.
///
- public class MetadataId
- {
- ///
- /// The ID of the resource on the external provider.
- ///
- public string DataId { get; set; }
+ public string DataId { get; set; }
- ///
- /// The URL of the resource on the external provider.
- ///
- public string? Link { get; set; }
- }
+ ///
+ /// The URL of the resource on the external provider.
+ ///
+ public string? Link { get; set; }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Page.cs b/back/src/Kyoo.Abstractions/Models/Page.cs
index 8f64d258..75c0f18e 100644
--- a/back/src/Kyoo.Abstractions/Models/Page.cs
+++ b/back/src/Kyoo.Abstractions/Models/Page.cs
@@ -20,93 +20,86 @@ using System.Collections.Generic;
using System.Linq;
using Kyoo.Utils;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A page of resource that contains information about the pagination of resources.
+///
+/// The type of resource contained in this page.
+public class Page
+ where T : IResource
{
///
- /// A page of resource that contains information about the pagination of resources.
+ /// The link of the current page.
///
- /// The type of resource contained in this page.
- public class Page
- where T : IResource
+ public string This { get; }
+
+ ///
+ /// The link of the first page.
+ ///
+ public string First { get; }
+
+ ///
+ /// The link of the previous page.
+ ///
+ public string? Previous { get; }
+
+ ///
+ /// The link of the next page.
+ ///
+ public string? Next { get; }
+
+ ///
+ /// The number of items in the current page.
+ ///
+ public int Count => Items.Count;
+
+ ///
+ /// The list of items in the page.
+ ///
+ public ICollection Items { get; }
+
+ ///
+ /// Create a new .
+ ///
+ /// The list of items in the page.
+ /// The link of the current page.
+ /// The link of the previous page.
+ /// The link of the next page.
+ /// The link of the first page.
+ public Page(ICollection items, string @this, string? previous, string? next, string first)
{
- ///
- /// The link of the current page.
- ///
- public string This { get; }
+ Items = items;
+ This = @this;
+ Previous = previous;
+ Next = next;
+ First = first;
+ }
- ///
- /// The link of the first page.
- ///
- public string First { get; }
-
- ///
- /// The link of the previous page.
- ///
- public string? Previous { get; }
-
- ///
- /// The link of the next page.
- ///
- public string? Next { get; }
-
- ///
- /// The number of items in the current page.
- ///
- public int Count => Items.Count;
-
- ///
- /// The list of items in the page.
- ///
- public ICollection Items { get; }
-
- ///
- /// Create a new .
- ///
- /// The list of items in the page.
- /// The link of the current page.
- /// The link of the previous page.
- /// The link of the next page.
- /// The link of the first page.
- public Page(
- ICollection items,
- string @this,
- string? previous,
- string? next,
- string first
- )
+ ///
+ /// Create a new and compute the urls.
+ ///
+ /// The list of items in the page.
+ /// The base url of the resources available from this page.
+ /// The list of query strings of the current page
+ /// The number of items requested for the current page.
+ public Page(ICollection items, string url, Dictionary query, int limit)
+ {
+ Items = items;
+ This = url + query.ToQueryString();
+ if (items.Count > 0 && query.ContainsKey("afterID"))
{
- Items = items;
- This = @this;
- Previous = previous;
- Next = next;
- First = first;
+ query["afterID"] = items.First().Id.ToString();
+ query["reverse"] = "true";
+ Previous = url + query.ToQueryString();
}
-
- ///
- /// Create a new and compute the urls.
- ///
- /// The list of items in the page.
- /// The base url of the resources available from this page.
- /// The list of query strings of the current page
- /// The number of items requested for the current page.
- public Page(ICollection items, string url, Dictionary query, int limit)
+ query.Remove("reverse");
+ if (items.Count == limit && limit > 0)
{
- Items = items;
- This = url + query.ToQueryString();
- if (items.Count > 0 && query.ContainsKey("afterID"))
- {
- query["afterID"] = items.First().Id.ToString();
- query["reverse"] = "true";
- Previous = url + query.ToQueryString();
- }
- query.Remove("reverse");
- if (items.Count == limit && limit > 0)
- {
- query["afterID"] = items.Last().Id.ToString();
- Next = url + query.ToQueryString();
- }
- query.Remove("afterID");
- First = url + query.ToQueryString();
+ query["afterID"] = items.Last().Id.ToString();
+ Next = url + query.ToQueryString();
}
+ query.Remove("afterID");
+ First = url + query.ToQueryString();
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs
index 23dd12ba..eb1bda94 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Collection.cs
@@ -23,69 +23,68 @@ using System.Text.Json.Serialization;
using Kyoo.Abstractions.Controllers;
using Kyoo.Utils;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A class representing collections of .
+///
+public class Collection : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, ILibraryItem
{
+ public static Sort DefaultSort => new Sort.By(nameof(Collection.Name));
+
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ [MaxLength(256)]
+ public string Slug { get; set; }
+
///
- /// A class representing collections of .
+ /// The name of this collection.
///
- public class Collection : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, ILibraryItem
+ public string Name { get; set; }
+
+ ///
+ /// The description of this collection.
+ ///
+ public string? Overview { get; set; }
+
+ ///
+ public DateTime AddedDate { get; set; }
+
+ ///
+ public Image? Poster { get; set; }
+
+ ///
+ public Image? Thumbnail { get; set; }
+
+ ///
+ public Image? Logo { get; set; }
+
+ ///
+ /// The list of movies contained in this collection.
+ ///
+ [JsonIgnore]
+ public ICollection? Movies { get; set; }
+
+ ///
+ /// The list of shows contained in this collection.
+ ///
+ [JsonIgnore]
+ public ICollection? Shows { get; set; }
+
+ ///
+ public Dictionary ExternalId { get; set; } = new();
+
+ public Collection() { }
+
+ [JsonConstructor]
+ public Collection(string name)
{
- public static Sort DefaultSort => new Sort.By(nameof(Collection.Name));
-
- ///
- public Guid Id { get; set; }
-
- ///
- [MaxLength(256)]
- public string Slug { get; set; }
-
- ///
- /// The name of this collection.
- ///
- public string Name { get; set; }
-
- ///
- /// The description of this collection.
- ///
- public string? Overview { get; set; }
-
- ///
- public DateTime AddedDate { get; set; }
-
- ///
- public Image? Poster { get; set; }
-
- ///
- public Image? Thumbnail { get; set; }
-
- ///
- public Image? Logo { get; set; }
-
- ///
- /// The list of movies contained in this collection.
- ///
- [JsonIgnore]
- public ICollection? Movies { get; set; }
-
- ///
- /// The list of shows contained in this collection.
- ///
- [JsonIgnore]
- public ICollection? Shows { get; set; }
-
- ///
- public Dictionary ExternalId { get; set; } = new();
-
- public Collection() { }
-
- [JsonConstructor]
- public Collection(string name)
+ if (name != null)
{
- if (name != null)
- {
- Slug = Utility.ToSlug(name);
- Name = name;
- }
+ Slug = Utility.ToSlug(name);
+ Name = name;
}
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
index d8325426..75fe4746 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Episode.cs
@@ -26,280 +26,274 @@ using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A class to represent a single show's episode.
+///
+public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews
{
- ///
- /// A class to represent a single show's episode.
- ///
- public class Episode : IQuery, IResource, IMetadata, IThumbnails, IAddedDate, INews
+ // Use absolute numbers by default and fallback to season/episodes if it does not exists.
+ public static Sort DefaultSort =>
+ new Sort.Conglomerate(
+ new Sort.By(x => x.AbsoluteNumber),
+ new Sort.By(x => x.SeasonNumber),
+ new Sort.By(x => x.EpisodeNumber)
+ );
+
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ [Computed]
+ [MaxLength(256)]
+ public string Slug
{
- // Use absolute numbers by default and fallback to season/episodes if it does not exists.
- public static Sort DefaultSort =>
- new Sort.Conglomerate(
- new Sort.By(x => x.AbsoluteNumber),
- new Sort.By(x => x.SeasonNumber),
- new Sort.By(x => x.EpisodeNumber)
- );
-
- ///
- public Guid Id { get; set; }
-
- ///
- [Computed]
- [MaxLength(256)]
- public string Slug
+ get
{
- get
- {
- if (ShowSlug != null || Show?.Slug != null)
- return GetSlug(
- ShowSlug ?? Show!.Slug,
- SeasonNumber,
- EpisodeNumber,
- AbsoluteNumber
- );
- return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
- }
- private set
- {
- Match match = Regex.Match(value, @"(?.+)-s(?\d+)e(?\d+)");
+ if (ShowSlug != null || Show?.Slug != null)
+ return GetSlug(ShowSlug ?? Show!.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber);
+ return GetSlug(ShowId.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber);
+ }
+ private set
+ {
+ Match match = Regex.Match(value, @"(?.+)-s(?\d+)e(?\d+)");
+ if (match.Success)
+ {
+ ShowSlug = match.Groups["show"].Value;
+ SeasonNumber = int.Parse(match.Groups["season"].Value);
+ EpisodeNumber = int.Parse(match.Groups["episode"].Value);
+ }
+ else
+ {
+ match = Regex.Match(value, @"(?.+)-(?\d+)");
if (match.Success)
{
ShowSlug = match.Groups["show"].Value;
- SeasonNumber = int.Parse(match.Groups["season"].Value);
- EpisodeNumber = int.Parse(match.Groups["episode"].Value);
+ AbsoluteNumber = int.Parse(match.Groups["absolute"].Value);
}
else
- {
- match = Regex.Match(value, @"(?.+)-(?\d+)");
- if (match.Success)
- {
- ShowSlug = match.Groups["show"].Value;
- AbsoluteNumber = int.Parse(match.Groups["absolute"].Value);
- }
- else
- ShowSlug = value;
- SeasonNumber = null;
- EpisodeNumber = null;
- }
+ ShowSlug = value;
+ SeasonNumber = null;
+ EpisodeNumber = null;
}
}
+ }
- ///
- /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.
- ///
- [JsonIgnore]
- public string? ShowSlug { private get; set; }
+ ///
+ /// The slug of the Show that contain this episode. If this is not set, this episode is ill-formed.
+ ///
+ [JsonIgnore]
+ public string? ShowSlug { private get; set; }
- ///
- /// The ID of the Show containing this episode.
- ///
- public Guid ShowId { get; set; }
+ ///
+ /// The ID of the Show containing this episode.
+ ///
+ public Guid ShowId { get; set; }
- ///
- /// The show that contains this episode.
- ///
- [LoadableRelation(nameof(ShowId))]
- public Show? Show { get; set; }
+ ///
+ /// The show that contains this episode.
+ ///
+ [LoadableRelation(nameof(ShowId))]
+ public Show? Show { get; set; }
- ///
- /// The ID of the Season containing this episode.
- ///
- public Guid? SeasonId { get; set; }
+ ///
+ /// The ID of the Season containing this episode.
+ ///
+ public Guid? SeasonId { get; set; }
- ///
- /// The season that contains this episode.
- ///
- ///
- /// This can be null if the season is unknown and the episode is only identified
- /// by it's .
- ///
- [LoadableRelation(nameof(SeasonId))]
- public Season? Season { get; set; }
+ ///
+ /// The season that contains this episode.
+ ///
+ ///
+ /// This can be null if the season is unknown and the episode is only identified
+ /// by it's .
+ ///
+ [LoadableRelation(nameof(SeasonId))]
+ public Season? Season { get; set; }
- ///
- /// The season in witch this episode is in.
- ///
- public int? SeasonNumber { get; set; }
+ ///
+ /// The season in witch this episode is in.
+ ///
+ public int? SeasonNumber { get; set; }
- ///
- /// The number of this episode in it's season.
- ///
- public int? EpisodeNumber { get; set; }
+ ///
+ /// The number of this episode in it's season.
+ ///
+ public int? EpisodeNumber { get; set; }
- ///
- /// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
- ///
- public int? AbsoluteNumber { get; set; }
+ ///
+ /// The absolute number of this episode. It's an episode number that is not reset to 1 after a new season.
+ ///
+ public int? AbsoluteNumber { get; set; }
- ///
- /// The path of the video file for this episode.
- ///
- public string Path { get; set; }
+ ///
+ /// The path of the video file for this episode.
+ ///
+ public string Path { get; set; }
- ///
- /// The title of this episode.
- ///
- public string? Name { get; set; }
+ ///
+ /// The title of this episode.
+ ///
+ public string? Name { get; set; }
- ///
- /// The overview of this episode.
- ///
- public string? Overview { get; set; }
+ ///
+ /// The overview of this episode.
+ ///
+ public string? Overview { get; set; }
- ///
- /// How long is this episode? (in minutes)
- ///
- public int? Runtime { get; set; }
+ ///
+ /// How long is this episode? (in minutes)
+ ///
+ public int? Runtime { get; set; }
- ///
- /// The release date of this episode. It can be null if unknown.
- ///
- public DateTime? ReleaseDate { get; set; }
+ ///
+ /// The release date of this episode. It can be null if unknown.
+ ///
+ public DateTime? ReleaseDate { get; set; }
- ///
- public DateTime AddedDate { get; set; }
+ ///
+ public DateTime AddedDate { get; set; }
- ///
- public Image? Poster { get; set; }
+ ///
+ public Image? Poster { get; set; }
- ///
- public Image? Thumbnail { get; set; }
+ ///
+ public Image? Thumbnail { get; set; }
- ///
- public Image? Logo { get; set; }
+ ///
+ public Image? Logo { get; set; }
- ///
- public Dictionary ExternalId { get; set; } = new();
+ ///
+ public Dictionary ExternalId { get; set; } = new();
- ///
- /// The previous episode that should be seen before viewing this one.
- ///
- [Projectable(UseMemberBody = nameof(_PreviousEpisode), OnlyOnInclude = true)]
- [LoadableRelation(
- // language=PostgreSQL
- Sql = """
- select
- pe.* -- Episode as pe
- from
- episodes as "pe"
- where
- pe.show_id = "this".show_id
- and (pe.absolute_number < "this".absolute_number
- or pe.season_number < "this".season_number
- or (pe.season_number = "this".season_number
- and e.episode_number < "this".episode_number))
- order by
- pe.absolute_number desc nulls last,
- pe.season_number desc,
- pe.episode_number desc
- limit 1
- """
- )]
- public Episode? PreviousEpisode { get; set; }
+ ///
+ /// The previous episode that should be seen before viewing this one.
+ ///
+ [Projectable(UseMemberBody = nameof(_PreviousEpisode), OnlyOnInclude = true)]
+ [LoadableRelation(
+ // language=PostgreSQL
+ Sql = """
+ select
+ pe.* -- Episode as pe
+ from
+ episodes as "pe"
+ where
+ pe.show_id = "this".show_id
+ and (pe.absolute_number < "this".absolute_number
+ or pe.season_number < "this".season_number
+ or (pe.season_number = "this".season_number
+ and e.episode_number < "this".episode_number))
+ order by
+ pe.absolute_number desc nulls last,
+ pe.season_number desc,
+ pe.episode_number desc
+ limit 1
+ """
+ )]
+ public Episode? PreviousEpisode { get; set; }
- private Episode? _PreviousEpisode =>
- Show!
- .Episodes!.OrderBy(x => x.AbsoluteNumber == null)
- .ThenByDescending(x => x.AbsoluteNumber)
- .ThenByDescending(x => x.SeasonNumber)
- .ThenByDescending(x => x.EpisodeNumber)
- .FirstOrDefault(x =>
- x.AbsoluteNumber < AbsoluteNumber
- || x.SeasonNumber < SeasonNumber
- || (x.SeasonNumber == SeasonNumber && x.EpisodeNumber < EpisodeNumber)
- );
+ private Episode? _PreviousEpisode =>
+ Show!
+ .Episodes!.OrderBy(x => x.AbsoluteNumber == null)
+ .ThenByDescending(x => x.AbsoluteNumber)
+ .ThenByDescending(x => x.SeasonNumber)
+ .ThenByDescending(x => x.EpisodeNumber)
+ .FirstOrDefault(x =>
+ x.AbsoluteNumber < AbsoluteNumber
+ || x.SeasonNumber < SeasonNumber
+ || (x.SeasonNumber == SeasonNumber && x.EpisodeNumber < EpisodeNumber)
+ );
- ///
- /// The next episode to watch after this one.
- ///
- [Projectable(UseMemberBody = nameof(_NextEpisode), OnlyOnInclude = true)]
- [LoadableRelation(
- // language=PostgreSQL
- Sql = """
- select
- ne.* -- Episode as ne
- from
- episodes as "ne"
- where
- ne.show_id = "this".show_id
- and (ne.absolute_number > "this".absolute_number
- or ne.season_number > "this".season_number
- or (ne.season_number = "this".season_number
- and e.episode_number > "this".episode_number))
- order by
- ne.absolute_number,
- ne.season_number,
- ne.episode_number
- limit 1
- """
- )]
- public Episode? NextEpisode { get; set; }
+ ///
+ /// The next episode to watch after this one.
+ ///
+ [Projectable(UseMemberBody = nameof(_NextEpisode), OnlyOnInclude = true)]
+ [LoadableRelation(
+ // language=PostgreSQL
+ Sql = """
+ select
+ ne.* -- Episode as ne
+ from
+ episodes as "ne"
+ where
+ ne.show_id = "this".show_id
+ and (ne.absolute_number > "this".absolute_number
+ or ne.season_number > "this".season_number
+ or (ne.season_number = "this".season_number
+ and e.episode_number > "this".episode_number))
+ order by
+ ne.absolute_number,
+ ne.season_number,
+ ne.episode_number
+ limit 1
+ """
+ )]
+ public Episode? NextEpisode { get; set; }
- private Episode? _NextEpisode =>
- Show!
- .Episodes!.OrderBy(x => x.AbsoluteNumber)
- .ThenBy(x => x.SeasonNumber)
- .ThenBy(x => x.EpisodeNumber)
- .FirstOrDefault(x =>
- x.AbsoluteNumber > AbsoluteNumber
- || x.SeasonNumber > SeasonNumber
- || (x.SeasonNumber == SeasonNumber && x.EpisodeNumber > EpisodeNumber)
- );
+ private Episode? _NextEpisode =>
+ Show!
+ .Episodes!.OrderBy(x => x.AbsoluteNumber)
+ .ThenBy(x => x.SeasonNumber)
+ .ThenBy(x => x.EpisodeNumber)
+ .FirstOrDefault(x =>
+ x.AbsoluteNumber > AbsoluteNumber
+ || x.SeasonNumber > SeasonNumber
+ || (x.SeasonNumber == SeasonNumber && x.EpisodeNumber > EpisodeNumber)
+ );
- [JsonIgnore]
- public ICollection? Watched { get; set; }
+ [JsonIgnore]
+ public ICollection? Watched { get; set; }
- ///
- /// Metadata of what an user as started/planned to watch.
- ///
- [Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
- [LoadableRelation(
- Sql = "episode_watch_status",
- On = "episode_id = \"this\".id and \"relation\".user_id = [current_user]"
- )]
- public EpisodeWatchStatus? WatchStatus { get; set; }
+ ///
+ /// Metadata of what an user as started/planned to watch.
+ ///
+ [Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
+ [LoadableRelation(
+ Sql = "episode_watch_status",
+ On = "episode_id = \"this\".id and \"relation\".user_id = [current_user]"
+ )]
+ public EpisodeWatchStatus? WatchStatus { get; set; }
- // There is a global query filter to filter by user so we just need to do single.
- private EpisodeWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
+ // There is a global query filter to filter by user so we just need to do single.
+ private EpisodeWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
- ///
- /// Links to watch this episode.
- ///
- public VideoLinks Links =>
- new() { Direct = $"/episode/{Slug}/direct", Hls = $"/episode/{Slug}/master.m3u8", };
+ ///
+ /// Links to watch this episode.
+ ///
+ public VideoLinks Links =>
+ new() { Direct = $"/episode/{Slug}/direct", Hls = $"/episode/{Slug}/master.m3u8", };
- ///
- /// Get the slug of an episode.
- ///
- /// The slug of the show. It can't be null.
- ///
- /// The season in which the episode is.
- /// If this is a movie or if the episode should be referred by it's absolute number, set this to null.
- ///
- ///
- /// The number of the episode in it's season.
- /// If this is a movie or if the episode should be referred by it's absolute number, set this to null.
- ///
- ///
- /// The absolute number of this show.
- /// If you don't know it or this is a movie, use null
- ///
- /// The slug corresponding to the given arguments
- public static string GetSlug(
- string showSlug,
- int? seasonNumber,
- int? episodeNumber,
- int? absoluteNumber = null
- )
+ ///
+ /// Get the slug of an episode.
+ ///
+ /// The slug of the show. It can't be null.
+ ///
+ /// The season in which the episode is.
+ /// If this is a movie or if the episode should be referred by it's absolute number, set this to null.
+ ///
+ ///
+ /// The number of the episode in it's season.
+ /// If this is a movie or if the episode should be referred by it's absolute number, set this to null.
+ ///
+ ///
+ /// The absolute number of this show.
+ /// If you don't know it or this is a movie, use null
+ ///
+ /// The slug corresponding to the given arguments
+ public static string GetSlug(
+ string showSlug,
+ int? seasonNumber,
+ int? episodeNumber,
+ int? absoluteNumber = null
+ )
+ {
+ return seasonNumber switch
{
- return seasonNumber switch
- {
- null when absoluteNumber == null => showSlug,
- null => $"{showSlug}-{absoluteNumber}",
- _ => $"{showSlug}-s{seasonNumber}e{episodeNumber}"
- };
- }
+ null when absoluteNumber == null => showSlug,
+ null => $"{showSlug}-{absoluteNumber}",
+ _ => $"{showSlug}-s{seasonNumber}e{episodeNumber}"
+ };
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IAddedDate.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IAddedDate.cs
index be2af353..8b64b613 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IAddedDate.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IAddedDate.cs
@@ -18,16 +18,15 @@
using System;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// An interface applied to resources.
+///
+public interface IAddedDate
{
///
- /// An interface applied to resources.
+ /// The date at which this resource was added to kyoo.
///
- public interface IAddedDate
- {
- ///
- /// The date at which this resource was added to kyoo.
- ///
- public DateTime AddedDate { get; set; }
- }
+ public DateTime AddedDate { get; set; }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs
index 9cfb2595..db840cae 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IMetadata.cs
@@ -18,16 +18,15 @@
using System.Collections.Generic;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// An interface applied to resources containing external metadata.
+///
+public interface IMetadata
{
///
- /// An interface applied to resources containing external metadata.
+ /// The link to metadata providers that this show has. See for more information.
///
- public interface IMetadata
- {
- ///
- /// The link to metadata providers that this show has. See for more information.
- ///
- public Dictionary ExternalId { get; set; }
- }
+ public Dictionary ExternalId { get; set; }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs
index 2356965e..87796456 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IResource.cs
@@ -20,31 +20,30 @@ using System;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// An interface to represent a resource that can be retrieved from the database.
+///
+public interface IResource : IQuery
{
///
- /// An interface to represent a resource that can be retrieved from the database.
+ /// A unique ID for this type of resource. This can't be changed and duplicates are not allowed.
///
- public interface IResource : IQuery
- {
- ///
- /// A unique ID for this type of resource. This can't be changed and duplicates are not allowed.
- ///
- ///
- /// You don't need to specify an ID manually when creating a new resource,
- /// this field is automatically assigned by the .
- ///
- public Guid Id { get; set; }
+ ///
+ /// You don't need to specify an ID manually when creating a new resource,
+ /// this field is automatically assigned by the .
+ ///
+ public Guid Id { get; set; }
- ///
- /// A human-readable identifier that can be used instead of an ID.
- /// A slug must be unique for a type of resource but it can be changed.
- ///
- ///
- /// There is no setter for a slug since it can be computed from other fields.
- /// For example, a season slug is {ShowSlug}-s{SeasonNumber}.
- ///
- [MaxLength(256)]
- public string Slug { get; }
- }
+ ///
+ /// A human-readable identifier that can be used instead of an ID.
+ /// A slug must be unique for a type of resource but it can be changed.
+ ///
+ ///
+ /// There is no setter for a slug since it can be computed from other fields.
+ /// For example, a season slug is {ShowSlug}-s{SeasonNumber}.
+ ///
+ [MaxLength(256)]
+ public string Slug { get; }
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs
index aed090b1..459f02a0 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/IThumbnails.cs
@@ -23,105 +23,101 @@ using System.Globalization;
using System.Text.Json.Serialization;
using Kyoo.Abstractions.Models.Attributes;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// An interface representing items that contains images (like posters, thumbnails, logo, banners...)
+///
+public interface IThumbnails
{
///
- /// An interface representing items that contains images (like posters, thumbnails, logo, banners...)
+ /// A poster is a 2/3 format image with the cover of the resource.
///
- public interface IThumbnails
- {
- ///
- /// A poster is a 2/3 format image with the cover of the resource.
- ///
- public Image? Poster { get; set; }
-
- ///
- /// 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.
- ///
- public Image? Thumbnail { get; set; }
-
- ///
- /// A logo is a small image representing the resource.
- ///
- public Image? Logo { get; set; }
- }
-
- [TypeConverter(typeof(ImageConvertor))]
- [SqlFirstColumn(nameof(Source))]
- public class Image
- {
- ///
- /// The original image from another server.
- ///
- public string Source { get; set; }
-
- ///
- /// A hash to display as placeholder while the image is loading.
- ///
- [MaxLength(32)]
- public string Blurhash { get; set; }
-
- public Image() { }
-
- [JsonConstructor]
- public Image(string source, string? blurhash = null)
- {
- Source = source;
- Blurhash = blurhash ?? "000000";
- }
-
- public class ImageConvertor : TypeConverter
- {
- ///
- public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
- {
- if (sourceType == typeof(string))
- return true;
- return base.CanConvertFrom(context, sourceType);
- }
-
- ///
- public override object ConvertFrom(
- ITypeDescriptorContext? context,
- CultureInfo? culture,
- object value
- )
- {
- if (value is not string source)
- return base.ConvertFrom(context, culture, value)!;
- return new Image(source);
- }
-
- ///
- public override bool CanConvertTo(
- ITypeDescriptorContext? context,
- Type? destinationType
- )
- {
- return false;
- }
- }
- }
+ public Image? Poster { get; set; }
///
- /// The quality of an image
+ /// 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.
///
- public enum ImageQuality
+ public Image? Thumbnail { get; set; }
+
+ ///
+ /// A logo is a small image representing the resource.
+ ///
+ public Image? Logo { get; set; }
+}
+
+[TypeConverter(typeof(ImageConvertor))]
+[SqlFirstColumn(nameof(Source))]
+public class Image
+{
+ ///
+ /// The original image from another server.
+ ///
+ public string Source { get; set; }
+
+ ///
+ /// A hash to display as placeholder while the image is loading.
+ ///
+ [MaxLength(32)]
+ public string Blurhash { get; set; }
+
+ public Image() { }
+
+ [JsonConstructor]
+ public Image(string source, string? blurhash = null)
{
- ///
- /// Small
- ///
- Low,
+ Source = source;
+ Blurhash = blurhash ?? "000000";
+ }
- ///
- /// Medium
- ///
- Medium,
+ public class ImageConvertor : TypeConverter
+ {
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ return true;
+ return base.CanConvertFrom(context, sourceType);
+ }
- ///
- /// Large
- ///
- High,
+ ///
+ public override object ConvertFrom(
+ ITypeDescriptorContext? context,
+ CultureInfo? culture,
+ object value
+ )
+ {
+ if (value is not string source)
+ return base.ConvertFrom(context, culture, value)!;
+ return new Image(source);
+ }
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
+ {
+ return false;
+ }
}
}
+
+///
+/// The quality of an image
+///
+public enum ImageQuality
+{
+ ///
+ /// Small
+ ///
+ Low,
+
+ ///
+ /// Medium
+ ///
+ Medium,
+
+ ///
+ /// Large
+ ///
+ High,
+}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
index 257ddcb8..3223c5e8 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Movie.cs
@@ -27,163 +27,162 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A series or a movie.
+///
+public class Movie
+ : IQuery,
+ IResource,
+ IMetadata,
+ IThumbnails,
+ IAddedDate,
+ ILibraryItem,
+ INews,
+ IWatchlist
{
+ public static Sort DefaultSort => new Sort.By(x => x.Name);
+
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ [MaxLength(256)]
+ public string Slug { get; set; }
+
///
- /// A series or a movie.
+ /// The title of this show.
///
- public class Movie
- : IQuery,
- IResource,
- IMetadata,
- IThumbnails,
- IAddedDate,
- ILibraryItem,
- INews,
- IWatchlist
+ public string Name { get; set; }
+
+ ///
+ /// A catchphrase for this movie.
+ ///
+ public string? Tagline { get; set; }
+
+ ///
+ /// The list of alternative titles of this show.
+ ///
+ public string[] Aliases { get; set; } = Array.Empty();
+
+ ///
+ /// The path of the movie video file.
+ ///
+ public string Path { get; set; }
+
+ ///
+ /// The summary of this show.
+ ///
+ public string? Overview { get; set; }
+
+ ///
+ /// A list of tags that match this movie.
+ ///
+ public string[] Tags { get; set; } = Array.Empty();
+
+ ///
+ /// The list of genres (themes) this show has.
+ ///
+ public Genre[] Genres { get; set; } = Array.Empty();
+
+ ///
+ /// Is this show airing, not aired yet or finished?
+ ///
+ public Status Status { get; set; }
+
+ ///
+ /// How well this item is rated? (from 0 to 100).
+ ///
+ public int Rating { get; set; }
+
+ ///
+ /// How long is this movie? (in minutes)
+ ///
+ public int? Runtime { get; set; }
+
+ ///
+ /// The date this movie aired.
+ ///
+ public DateTime? AirDate { get; set; }
+
+ ///
+ public DateTime AddedDate { get; set; }
+
+ ///
+ public Image? Poster { get; set; }
+
+ ///
+ public Image? Thumbnail { get; set; }
+
+ ///
+ public Image? Logo { get; set; }
+
+ [JsonIgnore]
+ [Column("air_date")]
+ public DateTime? StartAir => AirDate;
+
+ [JsonIgnore]
+ [Column("air_date")]
+ public DateTime? EndAir => AirDate;
+
+ ///
+ /// A video of a few minutes that tease the content.
+ ///
+ public string? Trailer { get; set; }
+
+ ///
+ public Dictionary ExternalId { get; set; } = new();
+
+ ///
+ /// The ID of the Studio that made this show.
+ ///
+ [JsonIgnore]
+ public Guid? StudioId { get; set; }
+
+ ///
+ /// The Studio that made this show.
+ ///
+ [LoadableRelation(nameof(StudioId))]
+ public Studio? Studio { get; set; }
+
+ ///
+ /// The list of collections that contains this show.
+ ///
+ [JsonIgnore]
+ public ICollection? Collections { get; set; }
+
+ ///
+ /// Links to watch this movie.
+ ///
+ public VideoLinks Links =>
+ new() { Direct = $"/movie/{Slug}/direct", Hls = $"/movie/{Slug}/master.m3u8", };
+
+ [JsonIgnore]
+ public ICollection? Watched { get; set; }
+
+ ///
+ /// Metadata of what an user as started/planned to watch.
+ ///
+ [Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
+ [LoadableRelation(
+ Sql = "movie_watch_status",
+ On = "movie_id = \"this\".id and \"relation\".user_id = [current_user]"
+ )]
+ public MovieWatchStatus? WatchStatus { get; set; }
+
+ // There is a global query filter to filter by user so we just need to do single.
+ private MovieWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
+
+ public Movie() { }
+
+ [JsonConstructor]
+ public Movie(string name)
{
- public static Sort DefaultSort => new Sort.By(x => x.Name);
-
- ///
- public Guid Id { get; set; }
-
- ///
- [MaxLength(256)]
- public string Slug { get; set; }
-
- ///
- /// The title of this show.
- ///
- public string Name { get; set; }
-
- ///
- /// A catchphrase for this movie.
- ///
- public string? Tagline { get; set; }
-
- ///
- /// The list of alternative titles of this show.
- ///
- public string[] Aliases { get; set; } = Array.Empty();
-
- ///
- /// The path of the movie video file.
- ///
- public string Path { get; set; }
-
- ///
- /// The summary of this show.
- ///
- public string? Overview { get; set; }
-
- ///
- /// A list of tags that match this movie.
- ///
- public string[] Tags { get; set; } = Array.Empty();
-
- ///
- /// The list of genres (themes) this show has.
- ///
- public Genre[] Genres { get; set; } = Array.Empty();
-
- ///
- /// Is this show airing, not aired yet or finished?
- ///
- public Status Status { get; set; }
-
- ///
- /// How well this item is rated? (from 0 to 100).
- ///
- public int Rating { get; set; }
-
- ///
- /// How long is this movie? (in minutes)
- ///
- public int? Runtime { get; set; }
-
- ///
- /// The date this movie aired.
- ///
- public DateTime? AirDate { get; set; }
-
- ///
- public DateTime AddedDate { get; set; }
-
- ///
- public Image? Poster { get; set; }
-
- ///
- public Image? Thumbnail { get; set; }
-
- ///
- public Image? Logo { get; set; }
-
- [JsonIgnore]
- [Column("air_date")]
- public DateTime? StartAir => AirDate;
-
- [JsonIgnore]
- [Column("air_date")]
- public DateTime? EndAir => AirDate;
-
- ///
- /// A video of a few minutes that tease the content.
- ///
- public string? Trailer { get; set; }
-
- ///
- public Dictionary ExternalId { get; set; } = new();
-
- ///
- /// The ID of the Studio that made this show.
- ///
- [JsonIgnore]
- public Guid? StudioId { get; set; }
-
- ///
- /// The Studio that made this show.
- ///
- [LoadableRelation(nameof(StudioId))]
- public Studio? Studio { get; set; }
-
- ///
- /// The list of collections that contains this show.
- ///
- [JsonIgnore]
- public ICollection? Collections { get; set; }
-
- ///
- /// Links to watch this movie.
- ///
- public VideoLinks Links =>
- new() { Direct = $"/movie/{Slug}/direct", Hls = $"/movie/{Slug}/master.m3u8", };
-
- [JsonIgnore]
- public ICollection? Watched { get; set; }
-
- ///
- /// Metadata of what an user as started/planned to watch.
- ///
- [Projectable(UseMemberBody = nameof(_WatchStatus), OnlyOnInclude = true)]
- [LoadableRelation(
- Sql = "movie_watch_status",
- On = "movie_id = \"this\".id and \"relation\".user_id = [current_user]"
- )]
- public MovieWatchStatus? WatchStatus { get; set; }
-
- // There is a global query filter to filter by user so we just need to do single.
- private MovieWatchStatus? _WatchStatus => Watched!.FirstOrDefault();
-
- public Movie() { }
-
- [JsonConstructor]
- public Movie(string name)
+ if (name != null)
{
- if (name != null)
- {
- Slug = Utility.ToSlug(name);
- Name = name;
- }
+ Slug = Utility.ToSlug(name);
+ Name = name;
}
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs
index 64c3cfe2..e88680a9 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Season.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Season.cs
@@ -26,124 +26,123 @@ using EntityFrameworkCore.Projectables;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A season of a .
+///
+public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
{
- ///
- /// A season of a .
- ///
- public class Season : IQuery, IResource, IMetadata, IThumbnails, IAddedDate
+ public static Sort DefaultSort => new Sort.By(x => x.SeasonNumber);
+
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ [Computed]
+ [MaxLength(256)]
+ public string Slug
{
- public static Sort DefaultSort => new Sort.By(x => x.SeasonNumber);
-
- ///
- public Guid Id { get; set; }
-
- ///
- [Computed]
- [MaxLength(256)]
- public string Slug
+ get
{
- get
- {
- if (ShowSlug == null && Show == null)
- return $"{ShowId}-s{SeasonNumber}";
- return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}";
- }
- private set
- {
- Match match = Regex.Match(value, @"(?.+)-s(?\d+)");
-
- if (!match.Success)
- throw new ArgumentException(
- "Invalid season slug. Format: {showSlug}-s{seasonNumber}"
- );
- ShowSlug = match.Groups["show"].Value;
- SeasonNumber = int.Parse(match.Groups["season"].Value);
- }
+ if (ShowSlug == null && Show == null)
+ return $"{ShowId}-s{SeasonNumber}";
+ return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}";
}
+ private set
+ {
+ Match match = Regex.Match(value, @"(?.+)-s(?\d+)");
- ///
- /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.
- ///
- [JsonIgnore]
- public string? ShowSlug { private get; set; }
-
- ///
- /// The ID of the Show containing this season.
- ///
- public Guid ShowId { get; set; }
-
- ///
- /// The show that contains this season.
- ///
- [LoadableRelation(nameof(ShowId))]
- public Show? Show { get; set; }
-
- ///
- /// The number of this season. This can be set to 0 to indicate specials.
- ///
- public int SeasonNumber { get; set; }
-
- ///
- /// The title of this season.
- ///
- public string? Name { get; set; }
-
- ///
- /// A quick overview of this season.
- ///
- public string? Overview { get; set; }
-
- ///
- /// The starting air date of this season.
- ///
- public DateTime? StartDate { get; set; }
-
- ///
- public DateTime AddedDate { get; set; }
-
- ///
- /// The ending date of this season.
- ///
- public DateTime? EndDate { get; set; }
-
- ///
- public Image? Poster { get; set; }
-
- ///
- public Image? Thumbnail { get; set; }
-
- ///
- public Image? Logo { get; set; }
-
- ///
- public Dictionary ExternalId { get; set; } = new();
-
- ///
- /// The list of episodes that this season contains.
- ///
- [JsonIgnore]
- public ICollection? Episodes { get; set; }
-
- ///
- /// The number of episodes in this season.
- ///
- [Projectable(UseMemberBody = nameof(_EpisodesCount), OnlyOnInclude = true)]
- [NotMapped]
- [LoadableRelation(
- // language=PostgreSQL
- Projected = """
- (
- select
- count(*)::int
- from
- episodes as e
- where
- e.season_id = id) as episodes_count
- """
- )]
- public int EpisodesCount { get; set; }
-
- private int _EpisodesCount => Episodes!.Count;
+ if (!match.Success)
+ throw new ArgumentException(
+ "Invalid season slug. Format: {showSlug}-s{seasonNumber}"
+ );
+ ShowSlug = match.Groups["show"].Value;
+ SeasonNumber = int.Parse(match.Groups["season"].Value);
+ }
}
+
+ ///
+ /// The slug of the Show that contain this episode. If this is not set, this season is ill-formed.
+ ///
+ [JsonIgnore]
+ public string? ShowSlug { private get; set; }
+
+ ///
+ /// The ID of the Show containing this season.
+ ///
+ public Guid ShowId { get; set; }
+
+ ///
+ /// The show that contains this season.
+ ///
+ [LoadableRelation(nameof(ShowId))]
+ public Show? Show { get; set; }
+
+ ///
+ /// The number of this season. This can be set to 0 to indicate specials.
+ ///
+ public int SeasonNumber { get; set; }
+
+ ///
+ /// The title of this season.
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// A quick overview of this season.
+ ///
+ public string? Overview { get; set; }
+
+ ///
+ /// The starting air date of this season.
+ ///
+ public DateTime? StartDate { get; set; }
+
+ ///
+ public DateTime AddedDate { get; set; }
+
+ ///
+ /// The ending date of this season.
+ ///
+ public DateTime? EndDate { get; set; }
+
+ ///
+ public Image? Poster { get; set; }
+
+ ///
+ public Image? Thumbnail { get; set; }
+
+ ///
+ public Image? Logo { get; set; }
+
+ ///
+ public Dictionary ExternalId { get; set; } = new();
+
+ ///
+ /// The list of episodes that this season contains.
+ ///
+ [JsonIgnore]
+ public ICollection? Episodes { get; set; }
+
+ ///
+ /// The number of episodes in this season.
+ ///
+ [Projectable(UseMemberBody = nameof(_EpisodesCount), OnlyOnInclude = true)]
+ [NotMapped]
+ [LoadableRelation(
+ // language=PostgreSQL
+ Projected = """
+ (
+ select
+ count(*)::int
+ from
+ episodes as e
+ where
+ e.season_id = id) as episodes_count
+ """
+ )]
+ public int EpisodesCount { get; set; }
+
+ private int _EpisodesCount => Episodes!.Count;
}
diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
index a9c8b832..6967a1e7 100644
--- a/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
+++ b/back/src/Kyoo.Abstractions/Models/Resources/Show.cs
@@ -27,254 +27,253 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Utils;
-namespace Kyoo.Abstractions.Models
+namespace Kyoo.Abstractions.Models;
+
+///
+/// A series or a movie.
+///
+public class Show
+ : IQuery,
+ IResource,
+ IMetadata,
+ IOnMerge,
+ IThumbnails,
+ IAddedDate,
+ ILibraryItem,
+ IWatchlist
{
+ public static Sort DefaultSort => new Sort.By(x => x.Name);
+
+ ///
+ public Guid Id { get; set; }
+
+ ///
+ [MaxLength(256)]
+ public string Slug { get; set; }
+
///
- /// A series or a movie.
+ /// The title of this show.
///
- public class Show
- : IQuery,
- IResource,
- IMetadata,
- IOnMerge,
- IThumbnails,
- IAddedDate,
- ILibraryItem,
- IWatchlist
- {
- public static Sort DefaultSort => new Sort.By(x => x.Name);
+ public string Name { get; set; }
- ///
- public Guid Id { get; set; }
+ ///
+ /// A catchphrase for this show.
+ ///
+ public string? Tagline { get; set; }
- ///
- [MaxLength(256)]
- public string Slug { get; set; }
+ ///
+ /// The list of alternative titles of this show.
+ ///
+ public List Aliases { get; set; } = new();
- ///
- /// The title of this show.
- ///
- public string Name { get; set; }
+ ///
+ /// The summary of this show.
+ ///
+ public string? Overview { get; set; }
- ///
- /// A catchphrase for this show.
- ///
- public string? Tagline { get; set; }
+ ///
+ /// A list of tags that match this movie.
+ ///
+ public List Tags { get; set; } = new();
- ///
- /// The list of alternative titles of this show.
- ///
- public List Aliases { get; set; } = new();
+ ///
+ /// The list of genres (themes) this show has.
+ ///
+ public List Genres { get; set; } = new();
- ///
- /// The summary of this show.
- ///
- public string? Overview { get; set; }
+ ///
+ /// Is this show airing, not aired yet or finished?
+ ///
+ public Status Status { get; set; }
- ///
- /// A list of tags that match this movie.
- ///
- public List Tags { get; set; } = new();
+ ///
+ /// How well this item is rated? (from 0 to 100).
+ ///
+ public int Rating { get; set; }
- ///
- /// The list of genres (themes) this show has.
- ///
- public List Genres { get; set; } = new();
+ ///
+ /// The date this show started airing. It can be null if this is unknown.
+ ///
+ public DateTime? StartAir { get; set; }
- ///
- /// Is this show airing, not aired yet or finished?
- ///
- public Status Status { get; set; }
+ ///
+ /// The date this show finished airing.
+ /// It can also be null if this is unknown.
+ ///
+ public DateTime? EndAir { get; set; }
- ///
- /// How well this item is rated? (from 0 to 100).
- ///
- public int Rating { get; set; }
+ ///
+ public DateTime AddedDate { get; set; }
- ///
- /// The date this show started airing. It can be null if this is unknown.
- ///
- public DateTime? StartAir { get; set; }
+ ///
+ public Image? Poster { get; set; }
- ///
- /// The date this show finished airing.
- /// It can also be null if this is unknown.
- ///
- public DateTime? EndAir { get; set; }
+ ///
+ public Image? Thumbnail { get; set; }
- ///
- public DateTime AddedDate { get; set; }
+ ///
+ public Image? Logo { get; set; }
- ///
- public Image? Poster { get; set; }
+ ///
+ /// A video of a few minutes that tease the content.
+ ///
+ public string? Trailer { get; set; }
- ///
- public Image? Thumbnail { get; set; }
+ [JsonIgnore]
+ [Column("start_air")]
+ public DateTime? AirDate => StartAir;
- ///
- public Image? Logo { get; set; }
+ ///
+ public Dictionary ExternalId { get; set; } = new();
- ///
- /// A video of a few minutes that tease the content.
- ///
- public string? Trailer { get; set; }
+ ///
+ /// The ID of the Studio that made this show.
+ ///
+ public Guid? StudioId { get; set; }
- [JsonIgnore]
- [Column("start_air")]
- public DateTime? AirDate => StartAir;
+ ///
+ /// The Studio that made this show.
+ ///
+ [LoadableRelation(nameof(StudioId))]
+ public Studio? Studio { get; set; }
- ///
- public Dictionary ExternalId { get; set; } = new();
+ ///
+ /// The different seasons in this show. If this is a movie, this list is always null or empty.
+ ///
+ [JsonIgnore]
+ public ICollection? Seasons { get; set; }
- ///
- /// The ID of the Studio that made this show.
- ///
- public Guid? StudioId { get; set; }
+ ///
+ /// 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).
+ /// Having an episode is necessary to store metadata and tracks.
+ ///
+ [JsonIgnore]
+ public ICollection? Episodes { get; set; }
- ///