Switch from int ids to guid

This commit is contained in:
Zoe Roux 2023-11-28 17:50:16 +01:00
parent 22348e1554
commit 070a94d87d
32 changed files with 94 additions and 135 deletions

View File

@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
@ -47,7 +46,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="include">The related fields to include.</param>
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
/// <returns>The resource found</returns>
Task<T> Get(int id, Include<T>? include = default);
Task<T> Get(Guid id, Include<T>? include = default);
/// <summary>
/// Get a resource from it's slug.
@ -73,7 +72,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="id">The id of the resource</param>
/// <param name="include">The related fields to include.</param>
/// <returns>The resource found</returns>
Task<T?> GetOrDefault(int id, Include<T>? include = default);
Task<T?> GetOrDefault(Guid id, Include<T>? include = default);
/// <summary>
/// Get a resource from it's slug or null if it is not found.
@ -128,7 +127,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="ids">The list of items id.</param>
/// <param name="include">The related fields to include.</param>
/// <returns>A list of resources mapped from ids.</returns>
Task<ICollection<T>> FromIds(IList<int> ids, Include<T>? include = default);
Task<ICollection<T>> FromIds(IList<Guid> ids, Include<T>? include = default);
/// <summary>
/// Create a new resource.
@ -175,7 +174,7 @@ namespace Kyoo.Abstractions.Controllers
/// </param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource edited and completed by database's information (related items and so on)</returns>
Task<T> Patch(int id, Func<T, Task<bool>> patch);
Task<T> Patch(Guid id, Func<T, Task<bool>> patch);
/// <summary>
/// Called when a resource has been edited.
@ -196,7 +195,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="id">The ID of the resource</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task Delete(int id);
Task Delete(Guid id);
/// <summary>
/// Delete a resource by it's slug

View File

@ -16,11 +16,13 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Models;
public class PartialResource
{
public int? Id { get; set; }
public Guid? Id { get; set; }
public string? Slug { get; set; }
}

View File

@ -16,6 +16,8 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Models
{
/// <summary>
@ -29,7 +31,7 @@ namespace Kyoo.Abstractions.Models
public class PeopleRole : IResource
{
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
public string Slug => ForPeople ? Show!.Slug : People.Slug;
@ -43,7 +45,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the People playing the role.
/// </summary>
public int PeopleID { get; set; }
public Guid PeopleID { get; set; }
/// <summary>
/// The people that played this role.
@ -53,14 +55,14 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the Show where the People playing in.
/// </summary>
public int? ShowID { get; set; }
public Guid? ShowID { get; set; }
/// <summary>
/// The show where the People played in.
/// </summary>
public Show? Show { get; set; }
public int? MovieID { get; set; }
public Guid? MovieID { get; set; }
public Movie? Movie { get; set; }

View File

@ -34,7 +34,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<Collection>.By(nameof(Collection.Name));
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)] public string Slug { get; set; }

View File

@ -41,7 +41,7 @@ namespace Kyoo.Abstractions.Models
);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; } = Guid.Empty;
/// <inheritdoc />
[Computed]
@ -90,7 +90,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the Show containing this episode.
/// </summary>
public int ShowId { get; set; }
public Guid ShowId { get; set; }
/// <summary>
/// The show that contains this episode.
@ -100,7 +100,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the Season containing this episode.
/// </summary>
public int? SeasonId { get; set; }
public Guid? SeasonId { get; set; }
/// <summary>
/// The season that contains this episode.

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
@ -33,7 +34,7 @@ namespace Kyoo.Abstractions.Models
/// You don't need to specify an ID manually when creating a new resource,
/// this field is automatically assigned by the <see cref="IRepository{T}"/>.
/// </remarks>
public int Id { get; set; }
public Guid Id { get; set; }
/// <summary>
/// A human-readable identifier that can be used instead of an ID.

View File

@ -34,7 +34,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<Movie>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
@ -118,7 +118,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the Studio that made this show.
/// </summary>
[SerializeIgnore] public int? StudioId { get; set; }
[SerializeIgnore] public Guid? StudioId { get; set; }
/// <summary>
/// The Studio that made this show.

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@ -35,7 +36,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<People>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]

View File

@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<Season>.By(x => x.SeasonNumber);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[Computed]
@ -71,7 +71,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the Show containing this season.
/// </summary>
public int ShowId { get; set; }
public Guid ShowId { get; set; }
/// <summary>
/// The show that contains this season.

View File

@ -37,7 +37,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<Show>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]
@ -121,7 +121,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// The ID of the Studio that made this show.
/// </summary>
[SerializeIgnore] public int? StudioId { get; set; }
[SerializeIgnore] public Guid? StudioId { get; set; }
/// <summary>
/// The Studio that made this show.

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Kyoo.Abstractions.Controllers;
@ -33,7 +34,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<Studio>.By(x => x.Name);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]

View File

@ -34,7 +34,7 @@ namespace Kyoo.Abstractions.Models
public static Sort DefaultSort => new Sort<User>.By(x => x.Username);
/// <inheritdoc />
public int Id { get; set; }
public Guid Id { get; set; }
/// <inheritdoc />
[MaxLength(256)]

View File

@ -100,7 +100,7 @@ public abstract record Filter<T> : Filter
/// Internal filter used for keyset paginations to resume random sorts.
/// The pseudo sql is md5(seed || table.id) = md5(seed || 'hardCodedId')
/// </summary>
public record EqRandom(string Seed, int ReferenceId) : Filter<T>;
public record EqRandom(string Seed, Guid ReferenceId) : Filter<T>;
/// <summary>
/// Internal filter used only in EF with hard coded lamdas (used for relations).

View File

@ -37,7 +37,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// <summary>
/// The ID of the resource or null if the slug is specified.
/// </summary>
private readonly int? _id;
private readonly Guid? _id;
/// <summary>
/// The slug of the resource or null if the id is specified.
@ -48,7 +48,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// Create a new <see cref="Identifier"/> for the given id.
/// </summary>
/// <param name="id">The id of the resource.</param>
public Identifier(int id)
public Identifier(Guid id)
{
_id = id;
}
@ -80,7 +80,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// );
/// </code>
/// </example>
public T Match<T>(Func<int, T> idFunc, Func<string, T> slugFunc)
public T Match<T>(Func<Guid, T> idFunc, Func<string, T> slugFunc)
{
return _id.HasValue
? idFunc(_id.Value)
@ -99,7 +99,7 @@ namespace Kyoo.Abstractions.Models.Utils
/// identifier.Matcher&lt;Season&gt;(x => x.ShowID, x => x.Show.Slug)
/// </code>
/// </example>
public Filter<T> Matcher<T>(Expression<Func<T, int>> idGetter,
public Filter<T> Matcher<T>(Expression<Func<T, Guid>> idGetter,
Expression<Func<T, string>> slugGetter)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
@ -111,14 +111,14 @@ namespace Kyoo.Abstractions.Models.Utils
/// <summary>
/// A matcher overload for nullable IDs. See
/// <see cref="Matcher{T}(Expression{Func{T,int}},Expression{Func{T,string}})"/>
/// <see cref="Matcher{T}(Expression{Func{T,Guid}},Expression{Func{T,string}})"/>
/// for more details.
/// </summary>
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
/// <typeparam name="T">The type to match against this identifier.</typeparam>
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
public Filter<T> Matcher<T>(Expression<Func<T, int?>> idGetter,
public Filter<T> Matcher<T>(Expression<Func<T, Guid?>> idGetter,
Expression<Func<T, string>> slugGetter)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
@ -210,11 +210,11 @@ namespace Kyoo.Abstractions.Models.Utils
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is int id)
if (value is Guid id)
return new Identifier(id);
if (value is not string slug)
return base.ConvertFrom(context, culture, value)!;
return int.TryParse(slug, out id)
return Guid.TryParse(slug, out id)
? new Identifier(id)
: new Identifier(slug);
}

View File

@ -16,6 +16,8 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
namespace Kyoo.Abstractions.Controllers
{
/// <summary>
@ -31,7 +33,7 @@ namespace Kyoo.Abstractions.Controllers
/// <summary>
/// Where to start? Using the given sort.
/// </summary>
public int? AfterID { get; set; }
public Guid? AfterID { get; set; }
/// <summary>
/// Should the previous page be returned instead of the next?
@ -54,7 +56,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="count">Set the <see cref="Limit"/> value</param>
/// <param name="afterID">Set the <see cref="AfterID"/> value. If not specified, it will start from the start</param>
/// <param name="reverse">Should the previous page be returned instead of the next?</param>
public Pagination(int count, int? afterID = null, bool reverse = false)
public Pagination(int count, Guid? afterID = null, bool reverse = false)
{
Limit = count;
AfterID = afterID;

View File

@ -49,6 +49,6 @@ namespace Kyoo.Authentication
/// <param name="refreshToken">The refresh token to validate.</param>
/// <exception cref="SecurityTokenException">The given refresh token is not valid.</exception>
/// <returns>The id of the token's user.</returns>
int GetRefreshTokenUserID(string refreshToken);
Guid GetRefreshTokenUserID(string refreshToken);
}
}

View File

@ -18,7 +18,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
@ -61,7 +60,7 @@ namespace Kyoo.Authentication
: string.Empty;
List<Claim> claims = new()
{
new Claim(Claims.Id, user.Id.ToString(CultureInfo.InvariantCulture)),
new Claim(Claims.Id, user.Id.ToString()),
new Claim(Claims.Name, user.Username),
new Claim(Claims.Permissions, permissions),
new Claim(Claims.Type, "access")
@ -85,7 +84,7 @@ namespace Kyoo.Authentication
signingCredentials: credential,
claims: new[]
{
new Claim(Claims.Id, user.Id.ToString(CultureInfo.InvariantCulture)),
new Claim(Claims.Id, user.Id.ToString()),
new Claim(Claims.Guid, Guid.NewGuid().ToString()),
new Claim(Claims.Type, "refresh")
},
@ -96,7 +95,7 @@ namespace Kyoo.Authentication
}
/// <inheritdoc />
public int GetRefreshTokenUserID(string refreshToken)
public Guid GetRefreshTokenUserID(string refreshToken)
{
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Secret));
JwtSecurityTokenHandler tokenHandler = new();
@ -120,7 +119,7 @@ namespace Kyoo.Authentication
if (principal.Claims.First(x => x.Type == Claims.Type).Value != "refresh")
throw new SecurityTokenException("Invalid token type. The token should be a refresh token.");
Claim identifier = principal.Claims.First(x => x.Type == Claims.Id);
if (int.TryParse(identifier.Value, out int id))
if (Guid.TryParse(identifier.Value, out Guid id))
return id;
throw new SecurityTokenException("Token not associated to any user.");
}

View File

@ -17,6 +17,7 @@
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
@ -126,7 +127,7 @@ namespace Kyoo.Authentication.Views
User user = request.ToUser();
user.Permissions = _permissions.NewUser;
// If no users exists, the new one will be an admin. Give it every permissions.
if (await _users.GetOrDefault(1) == null)
if ((await _users.GetAll(limit: new Pagination(1))).Any())
user.Permissions = PermissionOption.Admin;
try
{
@ -161,7 +162,7 @@ namespace Kyoo.Authentication.Views
{
try
{
int userId = _token.GetRefreshTokenUserID(token);
Guid userId = _token.GetRefreshTokenUserID(token);
User user = await _users.Get(userId);
return new JwtToken(
_token.CreateAccessToken(user, out TimeSpan expireIn),
@ -196,7 +197,7 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task<ActionResult<User>> GetMe()
{
if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID))
if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{
@ -225,7 +226,7 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task<ActionResult<User>> EditMe(User user)
{
if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID))
if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{
@ -255,7 +256,7 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task<ActionResult<User>> PatchMe(PartialResource user)
{
if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID))
if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{
@ -285,7 +286,7 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task<ActionResult<User>> DeleteMe()
{
if (!int.TryParse(User.FindFirstValue(Claims.Id), out int userID))
if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{

View File

@ -77,13 +77,13 @@ namespace Kyoo.Core.Controllers
throw new ArgumentException("The collection's name must be set and not empty");
}
public async Task AddMovie(int id, int movieId)
public async Task AddMovie(Guid id, Guid movieId)
{
_database.AddLinks<Collection, Movie>(id, movieId);
await _database.SaveChangesAsync();
}
public async Task AddShow(int id, int showId)
public async Task AddShow(Guid id, Guid showId)
{
_database.AddLinks<Collection, Show>(id, showId);
await _database.SaveChangesAsync();

View File

@ -180,7 +180,7 @@ public static class DapperHelper
FormattableString command,
Dictionary<string, Type> config,
Func<List<object?>, T> mapper,
Func<int, Task<T>> get,
Func<Guid, Task<T>> get,
Include<T>? include,
Filter<T>? filter,
Sort<T>? sort,

View File

@ -46,7 +46,7 @@ public abstract class DapperRepository<T> : IRepository<T>
}
/// <inheritdoc/>
public virtual async Task<T> Get(int id, Include<T>? include = default)
public virtual async Task<T> Get(Guid id, Include<T>? include = default)
{
T? ret = await GetOrDefault(id, include);
if (ret == null)
@ -74,13 +74,13 @@ public abstract class DapperRepository<T> : IRepository<T>
}
/// <inheritdoc />
public Task<ICollection<T>> FromIds(IList<int> ids, Include<T>? include = null)
public Task<ICollection<T>> FromIds(IList<Guid> ids, Include<T>? include = null)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public Task<T?> GetOrDefault(int id, Include<T>? include = null)
public Task<T?> GetOrDefault(Guid id, Include<T>? include = null)
{
return Database.QuerySingle<T>(
Sql,
@ -165,7 +165,7 @@ public abstract class DapperRepository<T> : IRepository<T>
public Task<T> CreateIfNotExists(T obj) => throw new NotImplementedException();
/// <inheritdoc />
public Task Delete(int id) => throw new NotImplementedException();
public Task Delete(Guid id) => throw new NotImplementedException();
/// <inheritdoc />
public Task Delete(string slug) => throw new NotImplementedException();
@ -180,5 +180,5 @@ public abstract class DapperRepository<T> : IRepository<T>
public Task<T> Edit(T edited) => throw new NotImplementedException();
/// <inheritdoc />
public Task<T> Patch(int id, Func<T, Task<bool>> patch) => throw new NotImplementedException();
public Task<T> Patch(Guid id, Func<T, Task<bool>> patch) => throw new NotImplementedException();
}

View File

@ -102,7 +102,7 @@ namespace Kyoo.Core.Controllers
protected override async Task Validate(Episode resource)
{
await base.Validate(resource);
if (resource.ShowId <= 0)
if (resource.ShowId != Guid.Empty)
{
if (resource.Show == null)
{

View File

@ -62,18 +62,12 @@ namespace Kyoo.Core.Controllers
protected override ILibraryItem Mapper(List<object?> items)
{
if (items[0] is Show show && show.Id != 0)
if (items[0] is Show show && show.Id != Guid.Empty)
return show;
if (items[1] is Movie movie && movie.Id != 0)
{
movie.Id = -movie.Id;
if (items[1] is Movie movie && movie.Id != Guid.Empty)
return movie;
}
if (items[2] is Collection collection && collection.Id != 0)
{
collection.Id += 10_000;
if (items[2] is Collection collection && collection.Id != Guid.Empty)
return collection;
}
throw new InvalidDataException();
}
@ -82,7 +76,7 @@ namespace Kyoo.Core.Controllers
{ }
public async Task<ICollection<ILibraryItem>> GetAllOfCollection(
int collectionId,
Guid collectionId,
Filter<ILibraryItem>? filter = default,
Sort<ILibraryItem>? sort = default,
Include<ILibraryItem>? include = default,

View File

@ -119,10 +119,10 @@ namespace Kyoo.Core.Controllers
ParameterExpression x = Expression.Parameter(typeof(T), "x");
Expression EqRandomHandler(string seed, int refId)
Expression EqRandomHandler(string seed, Guid refId)
{
MethodInfo concat = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) })!;
Expression id = Expression.Call(Expression.Property(x, "ID"), nameof(int.ToString), null);
Expression id = Expression.Call(Expression.Property(x, "ID"), nameof(Guid.ToString), null);
Expression xrng = Expression.Call(concat, Expression.Constant(seed), id);
Expression left = Expression.Call(typeof(DatabaseContext), nameof(DatabaseContext.MD5), null, xrng);
Expression right = Expression.Call(typeof(DatabaseContext), nameof(DatabaseContext.MD5), null, Expression.Constant($"{seed}{refId}"));
@ -179,7 +179,7 @@ namespace Kyoo.Core.Controllers
/// <param name="id">The ID of the resource</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The tracked resource with the given ID</returns>
protected virtual async Task<T> GetWithTracking(int id)
protected virtual async Task<T> GetWithTracking(Guid id)
{
T? ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.Id == id);
if (ret == null)
@ -188,7 +188,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual async Task<T> Get(int id, Include<T>? include = default)
public virtual async Task<T> Get(Guid id, Include<T>? include = default)
{
T? ret = await GetOrDefault(id, include);
if (ret == null)
@ -215,7 +215,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
public virtual Task<T?> GetOrDefault(int id, Include<T>? include = default)
public virtual Task<T?> GetOrDefault(Guid id, Include<T>? include = default)
{
return AddIncludes(Database.Set<T>(), include)
.FirstOrDefaultAsync(x => x.Id == id);
@ -247,7 +247,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual async Task<ICollection<T>> FromIds(IList<int> ids, Include<T>? include = default)
public virtual async Task<ICollection<T>> FromIds(IList<Guid> ids, Include<T>? include = default)
{
return (
await AddIncludes(Database.Set<T>(), include)
@ -375,7 +375,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual async Task<T> Patch(int id, Func<T, Task<bool>> patch)
public virtual async Task<T> Patch(Guid id, Func<T, Task<bool>> patch)
{
bool lazyLoading = Database.ChangeTracker.LazyLoadingEnabled;
Database.ChangeTracker.LazyLoadingEnabled = false;
@ -453,7 +453,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual async Task Delete(int id)
public virtual async Task Delete(Guid id)
{
T resource = await Get(id);
await Delete(resource);

View File

@ -53,13 +53,10 @@ namespace Kyoo.Core.Controllers
protected override INews Mapper(List<object?> items)
{
if (items[0] is Episode episode && episode.Id != 0)
if (items[0] is Episode episode && episode.Id != Guid.Empty)
return episode;
if (items[1] is Movie movie && movie.Id != 0)
{
movie.Id = -movie.Id;
if (items[1] is Movie movie && movie.Id != Guid.Empty)
return movie;
}
throw new InvalidDataException();
}

View File

@ -96,7 +96,7 @@ namespace Kyoo.Core.Controllers
protected override async Task Validate(Season resource)
{
await base.Validate(resource);
if (resource.ShowId <= 0)
if (resource.ShowId != Guid.Empty)
{
if (resource.Show == null)
{

View File

@ -166,7 +166,7 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<T>> Edit([FromBody] T resource)
{
if (resource.Id > 0)
if (resource.Id != null)
return await Repository.Edit(resource);
T old = await Repository.Get(resource.Slug);

View File

@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -75,11 +76,11 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult> AddMovie(Identifier identifier, Identifier movie)
{
int collectionId = await identifier.Match(
Guid collectionId = await identifier.Match(
async id => (await _libraryManager.Collections.Get(id)).Id,
async slug => (await _libraryManager.Collections.Get(slug)).Id
);
int movieId = await movie.Match(
Guid movieId = await movie.Match(
async id => (await _libraryManager.Movies.Get(id)).Id,
async slug => (await _libraryManager.Movies.Get(slug)).Id
);
@ -106,11 +107,11 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult> AddShow(Identifier identifier, Identifier show)
{
int collectionId = await identifier.Match(
Guid collectionId = await identifier.Match(
async id => (await _libraryManager.Collections.Get(id)).Id,
async slug => (await _libraryManager.Collections.Get(slug)).Id
);
int showId = await show.Match(
Guid showId = await show.Match(
async id => (await _libraryManager.Shows.Get(id)).Id,
async slug => (await _libraryManager.Shows.Get(slug)).Id
);
@ -144,7 +145,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination,
[FromQuery] Include<ILibraryItem>? fields)
{
int collectionId = await identifier.Match(
Guid collectionId = await identifier.Match(
id => Task.FromResult(id),
async slug => (await _libraryManager.Collections.Get(slug)).Id
);

View File

@ -68,12 +68,12 @@ public class MeiliSync
return _client.Index(index).AddDocumentsAsync(new[] { item });
}
private Task _Delete(string index, int id, string? kind = null)
private Task _Delete(string index, Guid id, string? kind = null)
{
if (kind != null)
{
return _client.Index(index).DeleteOneDocumentAsync($"{kind}/{id}");
}
return _client.Index(index).DeleteOneDocumentAsync(id);
return _client.Index(index).DeleteOneDocumentAsync(id.ToString());
}
}

View File

@ -74,35 +74,13 @@ public class SearchManager : ISearchManager
};
}
public async Task<SearchPage<ILibraryItem>.SearchResult> SearchItems(string? query,
/// <inheritdoc/>
public Task<SearchPage<ILibraryItem>.SearchResult> SearchItems(string? query,
Sort<ILibraryItem> sortBy,
SearchPagination pagination,
Include<ILibraryItem>? include = default)
{
// TODO: add filters and facets
ISearchable<IdResource> res = await _client.Index("items").SearchAsync<IdResource>(query, new SearchQuery()
{
Sort = _GetSortsBy("items", sortBy),
Limit = pagination?.Limit ?? 50,
Offset = pagination?.Skip ?? 0,
});
// Since library items's ID are still ints mapped from real items ids, we must map it here to match the db's value.
// Look at the LibraryItemRepository's Mapper to understand what those magic numbers are.
List<int> ids = res.Hits.Select(x => x.Kind switch
{
nameof(Show) => x.Id,
nameof(Movie) => -x.Id,
nameof(Collection) => x.Id + 10_000,
_ => throw new InvalidOperationException("An unknown item kind was found in meilisearch"),
}).ToList();
return new SearchPage<ILibraryItem>.SearchResult
{
Query = query,
Items = await _libraryManager.LibraryItems
.FromIds(ids, include),
};
return _Search("items", query, null, sortBy, pagination, include);
}
/// <inheritdoc/>
@ -152,7 +130,7 @@ public class SearchManager : ISearchManager
private class IdResource
{
public int Id { get; set; }
public Guid Id { get; set; }
public string? Kind { get; set; }
}

View File

@ -100,7 +100,7 @@ namespace Kyoo.Postgresql
/// <param name="second">The ID of the second resource.</param>
/// <typeparam name="T1">The first resource type of the relation. It is the owner of the second</typeparam>
/// <typeparam name="T2">The second resource type of the relation. It is the contained resource.</typeparam>
public void AddLinks<T1, T2>(int first, int second)
public void AddLinks<T1, T2>(Guid first, Guid second)
where T1 : class, IResource
where T2 : class, IResource
{

View File

@ -72,12 +72,6 @@ namespace Kyoo.Tests.Database
KAssert.DeepEqual(TestSample.Get<T>(), value);
}
[Fact]
public async Task GetByFakeIdTest()
{
await Assert.ThrowsAsync<ItemNotFoundException>(() => _repository.Get(2));
}
[Fact]
public async Task GetByFakeSlugTest()
{
@ -105,18 +99,6 @@ namespace Kyoo.Tests.Database
Assert.Equal(0, await _repository.GetCount());
}
[Fact]
public virtual async Task CreateTest()
{
await Assert.ThrowsAsync<DuplicatedItemException>(() => _repository.Create(TestSample.Get<T>()));
await _repository.Delete(TestSample.Get<T>());
T expected = TestSample.Get<T>();
expected.Id = 0;
await _repository.Create(expected);
KAssert.DeepEqual(expected, await _repository.Get(expected.Slug));
}
[Fact]
public virtual async Task CreateIfNotExistTest()
{
@ -135,7 +117,6 @@ namespace Kyoo.Tests.Database
[Fact]
public async Task GetOrDefaultTest()
{
Assert.Null(await _repository.GetOrDefault(56));
Assert.Null(await _repository.GetOrDefault("non-existing"));
}
}