Use null safety

This commit is contained in:
Zoe Roux 2023-10-25 01:39:15 +02:00
parent e13f9c6aa8
commit 8b102b083f
29 changed files with 198 additions and 222 deletions

View File

@ -249,7 +249,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="showID">The id of the show</param>
/// <param name="seasonNumber">The season's number</param>
/// <returns>The season found</returns>
Task<Season> GetOrDefault(int showID, int seasonNumber);
Task<Season?> GetOrDefault(int showID, int seasonNumber);
/// <summary>
/// Get a season from it's show slug and it's seasonNumber or null if it is not found.
@ -257,7 +257,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="showSlug">The slug of the show</param>
/// <param name="seasonNumber">The season's number</param>
/// <returns>The season found</returns>
Task<Season> GetOrDefault(string showSlug, int seasonNumber);
Task<Season?> GetOrDefault(string showSlug, int seasonNumber);
}
/// <summary>
@ -294,7 +294,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="seasonNumber">The season's number</param>
/// <param name="episodeNumber">The episode's number</param>
/// <returns>The episode found</returns>
Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
Task<Episode?> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
/// <summary>
/// Get a episode from it's show slug, it's seasonNumber and it's episode number or null if it is not found.
@ -303,7 +303,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="seasonNumber">The season's number</param>
/// <param name="episodeNumber">The episode's number</param>
/// <returns>The episode found</returns>
Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
Task<Episode?> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
/// <summary>
/// Get a episode from it's showID and it's absolute number.

View File

@ -46,7 +46,7 @@ namespace Kyoo.Abstractions.Controllers
/// <param name="desendant">
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
/// </param>
public By(Expression<Func<T, object>> key, bool desendant = false)
public By(Expression<Func<T, object?>> key, bool desendant = false)
: this(Utility.GetPropertyName(key), desendant) { }
}

View File

@ -28,8 +28,8 @@ namespace Kyoo.Core.Controllers
public class IdentifierRouteConstraint : IRouteConstraint
{
/// <inheritdoc />
public bool Match(HttpContext httpContext,
IRouter route,
public bool Match(HttpContext? httpContext,
IRouter? route,
string routeKey,
RouteValueDictionary values,
RouteDirection routeDirection)

View File

@ -74,15 +74,15 @@ namespace Kyoo.Core.Controllers
public LibraryManager(IEnumerable<IBaseRepository> repositories)
{
_repositories = repositories.ToArray();
LibraryItemRepository = GetRepository<LibraryItem>() as ILibraryItemRepository;
CollectionRepository = GetRepository<Collection>() as ICollectionRepository;
MovieRepository = GetRepository<Movie>() as IMovieRepository;
ShowRepository = GetRepository<Show>() as IShowRepository;
SeasonRepository = GetRepository<Season>() as ISeasonRepository;
EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository;
PeopleRepository = GetRepository<People>() as IPeopleRepository;
StudioRepository = GetRepository<Studio>() as IStudioRepository;
UserRepository = GetRepository<User>() as IUserRepository;
LibraryItemRepository = (ILibraryItemRepository)GetRepository<LibraryItem>();
CollectionRepository = (ICollectionRepository)GetRepository<Collection>();
MovieRepository = (IMovieRepository)GetRepository<Movie>();
ShowRepository = (IShowRepository)GetRepository<Show>();
SeasonRepository = (ISeasonRepository)GetRepository<Season>();
EpisodeRepository = (IEpisodeRepository)GetRepository<Episode>();
PeopleRepository = (IPeopleRepository)GetRepository<People>();
StudioRepository = (IStudioRepository)GetRepository<Studio>();
UserRepository = (IUserRepository)GetRepository<User>();
}
/// <inheritdoc />
@ -140,46 +140,46 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
public async Task<T> GetOrDefault<T>(int id)
public async Task<T?> GetOrDefault<T>(int id)
where T : class, IResource
{
return await GetRepository<T>().GetOrDefault(id);
}
/// <inheritdoc />
public async Task<T> GetOrDefault<T>(string slug)
public async Task<T?> GetOrDefault<T>(string slug)
where T : class, IResource
{
return await GetRepository<T>().GetOrDefault(slug);
}
/// <inheritdoc />
public async Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T> sortBy)
public async Task<T?> GetOrDefault<T>(Expression<Func<T, bool>> where, Sort<T>? sortBy)
where T : class, IResource
{
return await GetRepository<T>().GetOrDefault(where, sortBy);
}
/// <inheritdoc />
public async Task<Season> GetOrDefault(int showID, int seasonNumber)
public async Task<Season?> GetOrDefault(int showID, int seasonNumber)
{
return await SeasonRepository.GetOrDefault(showID, seasonNumber);
}
/// <inheritdoc />
public async Task<Season> GetOrDefault(string showSlug, int seasonNumber)
public async Task<Season?> GetOrDefault(string showSlug, int seasonNumber)
{
return await SeasonRepository.GetOrDefault(showSlug, seasonNumber);
}
/// <inheritdoc />
public async Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
public async Task<Episode?> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
{
return await EpisodeRepository.GetOrDefault(showID, seasonNumber, episodeNumber);
}
/// <inheritdoc />
public async Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
public async Task<Episode?> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
{
return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber);
}
@ -238,7 +238,7 @@ namespace Kyoo.Core.Controllers
if (obj == null)
throw new ArgumentNullException(nameof(obj));
object existingValue = obj.GetType()
object? existingValue = obj.GetType()
.GetProperties()
.FirstOrDefault(x => string.Equals(x.Name, memberName, StringComparison.InvariantCultureIgnoreCase))
?.GetValue(obj);
@ -248,11 +248,11 @@ namespace Kyoo.Core.Controllers
return (obj, member: memberName) switch
{
(Collection c, nameof(Collection.Shows)) => ShowRepository
.GetAll(x => x.Collections.Any(y => y.Id == obj.Id))
.GetAll(x => x.Collections!.Any(y => y.Id == obj.Id))
.Then(x => c.Shows = x),
(Collection c, nameof(Collection.Movies)) => MovieRepository
.GetAll(x => x.Collections.Any(y => y.Id == obj.Id))
.GetAll(x => x.Collections!.Any(y => y.Id == obj.Id))
.Then(x => c.Movies = x),
@ -261,11 +261,11 @@ namespace Kyoo.Core.Controllers
.Then(x => m.People = x),
(Movie m, nameof(Movie.Collections)) => CollectionRepository
.GetAll(x => x.Movies.Any(y => y.Id == obj.Id))
.GetAll(x => x.Movies!.Any(y => y.Id == obj.Id))
.Then(x => m.Collections = x),
(Movie m, nameof(Movie.Studio)) => StudioRepository
.GetOrDefault(x => x.Movies.Any(y => y.Id == obj.Id))
.GetOrDefault(x => x.Movies!.Any(y => y.Id == obj.Id))
.Then(x =>
{
m.Studio = x;
@ -278,21 +278,21 @@ namespace Kyoo.Core.Controllers
.Then(x => s.People = x),
(Show s, nameof(Show.Seasons)) => _SetRelation(s,
SeasonRepository.GetAll(x => x.Show.Id == obj.Id),
SeasonRepository.GetAll(x => x.Show!.Id == obj.Id),
(x, y) => x.Seasons = y,
(x, y) => { x.Show = y; x.ShowId = y.Id; }),
(Show s, nameof(Show.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Show.Id == obj.Id),
EpisodeRepository.GetAll(x => x.Show!.Id == obj.Id),
(x, y) => x.Episodes = y,
(x, y) => { x.Show = y; x.ShowId = y.Id; }),
(Show s, nameof(Show.Collections)) => CollectionRepository
.GetAll(x => x.Shows.Any(y => y.Id == obj.Id))
.GetAll(x => x.Shows!.Any(y => y.Id == obj.Id))
.Then(x => s.Collections = x),
(Show s, nameof(Show.Studio)) => StudioRepository
.GetOrDefault(x => x.Shows.Any(y => y.Id == obj.Id))
.GetOrDefault(x => x.Shows!.Any(y => y.Id == obj.Id))
.Then(x =>
{
s.Studio = x;
@ -301,12 +301,12 @@ namespace Kyoo.Core.Controllers
(Season s, nameof(Season.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Season.Id == obj.Id),
EpisodeRepository.GetAll(x => x.Season!.Id == obj.Id),
(x, y) => x.Episodes = y,
(x, y) => { x.Season = y; x.SeasonId = y.Id; }),
(Season s, nameof(Season.Show)) => ShowRepository
.GetOrDefault(x => x.Seasons.Any(y => y.Id == obj.Id))
.GetOrDefault(x => x.Seasons!.Any(y => y.Id == obj.Id))
.Then(x =>
{
s.Show = x;
@ -315,7 +315,7 @@ namespace Kyoo.Core.Controllers
(Episode e, nameof(Episode.Show)) => ShowRepository
.GetOrDefault(x => x.Episodes.Any(y => y.Id == obj.Id))
.GetOrDefault(x => x.Episodes!.Any(y => y.Id == obj.Id))
.Then(x =>
{
e.Show = x;
@ -323,7 +323,7 @@ namespace Kyoo.Core.Controllers
}),
(Episode e, nameof(Episode.Season)) => SeasonRepository
.GetOrDefault(x => x.Episodes.Any(y => y.Id == e.Id))
.GetOrDefault(x => x.Episodes!.Any(y => y.Id == e.Id))
.Then(x =>
{
e.Season = x;
@ -344,11 +344,11 @@ namespace Kyoo.Core.Controllers
(Studio s, nameof(Studio.Shows)) => ShowRepository
.GetAll(x => x.Studio.Id == obj.Id)
.GetAll(x => x.Studio!.Id == obj.Id)
.Then(x => s.Shows = x),
(Studio s, nameof(Studio.Movies)) => MovieRepository
.GetAll(x => x.Studio.Id == obj.Id)
.GetAll(x => x.Studio!.Id == obj.Id)
.Then(x => s.Movies = x),
@ -362,51 +362,51 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetPeopleFromShow(int showID,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return PeopleRepository.GetFromShow(showID, where, sort, limit);
}
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetPeopleFromShow(string showSlug,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return PeopleRepository.GetFromShow(showSlug, where, sort, limit);
}
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetRolesFromPeople(int id,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return PeopleRepository.GetFromPeople(id, where, sort, limit);
}
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetRolesFromPeople(string slug,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return PeopleRepository.GetFromPeople(slug, where, sort, limit);
}
/// <inheritdoc />
public Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>> where = null,
Sort<T> sort = default,
Pagination limit = default)
public Task<ICollection<T>> GetAll<T>(Expression<Func<T, bool>>? where = null,
Sort<T>? sort = default,
Pagination? limit = default)
where T : class, IResource
{
return GetRepository<T>().GetAll(where, sort, limit);
}
/// <inheritdoc />
public Task<int> GetCount<T>(Expression<Func<T, bool>> where = null)
public Task<int> GetCount<T>(Expression<Func<T, bool>>? where = null)
where T : class, IResource
{
return GetRepository<T>().GetCount(where);

View File

@ -77,7 +77,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
public Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
public Task<Episode?> GetOrDefault(int showID, int seasonNumber, int episodeNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID
&& x.SeasonNumber == seasonNumber
@ -85,9 +85,9 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
public Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
public Task<Episode?> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
return _database.Episodes.FirstOrDefaultAsync(x => x.Show!.Slug == showSlug
&& x.SeasonNumber == seasonNumber
&& x.EpisodeNumber == episodeNumber).Then(SetBackingImage);
}
@ -95,7 +95,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public async Task<Episode> Get(int showID, int seasonNumber, int episodeNumber)
{
Episode ret = await GetOrDefault(showID, seasonNumber, episodeNumber);
Episode? ret = await GetOrDefault(showID, seasonNumber, episodeNumber);
if (ret == null)
throw new ItemNotFoundException($"No episode S{seasonNumber}E{episodeNumber} found on the show {showID}.");
return ret;
@ -104,24 +104,30 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public async Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber)
{
Episode ret = await GetOrDefault(showSlug, seasonNumber, episodeNumber);
Episode? ret = await GetOrDefault(showSlug, seasonNumber, episodeNumber);
if (ret == null)
throw new ItemNotFoundException($"No episode S{seasonNumber}E{episodeNumber} found on the show {showSlug}.");
return ret;
}
/// <inheritdoc />
public Task<Episode> GetAbsolute(int showID, int absoluteNumber)
public async Task<Episode> GetAbsolute(int showID, int absoluteNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID
Episode? ret = await _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == showID
&& x.AbsoluteNumber == absoluteNumber).Then(SetBackingImage);
if (ret == null)
throw new ItemNotFoundException();
return ret;
}
/// <inheritdoc />
public Task<Episode> GetAbsolute(string showSlug, int absoluteNumber)
public async Task<Episode> GetAbsolute(string showSlug, int absoluteNumber)
{
return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
Episode? ret = await _database.Episodes.FirstOrDefaultAsync(x => x.Show!.Slug == showSlug
&& x.AbsoluteNumber == absoluteNumber).Then(SetBackingImage);
if (ret == null)
throw new ItemNotFoundException();
return ret;
}
/// <inheritdoc />
@ -131,13 +137,13 @@ namespace Kyoo.Core.Controllers
_database.Episodes
.Include(x => x.Show)
.Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null)
.Where(_database.Like<Episode>(x => x.Name, $"%{query}%"))
.Where(_database.Like<Episode>(x => x.Name!, $"%{query}%"))
)
.Take(20)
.ToListAsync();
foreach (Episode ep in ret)
{
ep.Show.Episodes = null;
ep.Show!.Episodes = null;
SetBackingImage(ep);
}
return ret;
@ -152,7 +158,7 @@ namespace Kyoo.Core.Controllers
await _database.SaveChangesAsync(() =>
obj.SeasonNumber != null && obj.EpisodeNumber != null
? Get(obj.ShowId, obj.SeasonNumber.Value, obj.EpisodeNumber.Value)
: GetAbsolute(obj.ShowId, obj.AbsoluteNumber.Value));
: GetAbsolute(obj.ShowId, obj.AbsoluteNumber!.Value));
OnResourceCreated(obj);
return obj;
}

View File

@ -85,7 +85,7 @@ namespace Kyoo.Core.Controllers
/// <param name="query">The query to sort.</param>
/// <param name="sortBy">How to sort the query</param>
/// <returns>The newly sorted query.</returns>
protected IOrderedQueryable<T> Sort(IQueryable<T> query, Sort<T> sortBy = null)
protected IOrderedQueryable<T> Sort(IQueryable<T> query, Sort<T>? sortBy = null)
{
sortBy ??= DefaultSort;
@ -138,7 +138,7 @@ namespace Kyoo.Core.Controllers
: (greaterThan ? Expression.GreaterThan : Expression.LessThan);
}
private record SortIndicator(string key, bool desc, string? seed);
private record SortIndicator(string Key, bool Desc, string? Seed);
/// <summary>
/// Create a filter (where) expression on the query to skip everything before/after the referenceID.
@ -158,7 +158,7 @@ namespace Kyoo.Core.Controllers
/// <param name="next">True if the following page should be returned, false for the previous.</param>
/// <returns>An expression ready to be added to a Where close of a sorted query to handle the AfterID</returns>
protected Expression<Func<T, bool>> KeysetPaginate(
Sort<T> sort,
Sort<T>? sort,
T reference,
bool next = true)
{
@ -170,7 +170,7 @@ namespace Kyoo.Core.Controllers
void GetRandomSortKeys(string seed, out Expression left, out Expression right)
{
MethodInfo concat = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) });
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 xrng = Expression.Call(concat, Expression.Constant(seed), id);
right = Expression.Call(typeof(DatabaseContext), nameof(DatabaseContext.MD5), null, Expression.Constant($"{seed}{reference.Id}"));
@ -193,14 +193,14 @@ namespace Kyoo.Core.Controllers
IEnumerable<SortIndicator> sorts = GetSortsBy(sort)
.Append(new SortIndicator("Id", false, null));
BinaryExpression filter = null;
BinaryExpression? filter = null;
List<SortIndicator> previousSteps = new();
// TODO: Add an outer query >= for perf
// PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic
foreach ((string key, bool desc, string? seed) in sorts)
{
BinaryExpression compare = null;
PropertyInfo property = key != "random"
BinaryExpression? compare = null;
PropertyInfo? property = key != "random"
? typeof(T).GetProperty(key)
: null;
@ -268,7 +268,7 @@ namespace Kyoo.Core.Controllers
return Expression.Lambda<Func<T, bool>>(filter!, x);
}
protected void SetBackingImage(T obj)
protected void SetBackingImage(T? obj)
{
if (obj is not IThumbnails thumbs)
return;
@ -298,7 +298,7 @@ namespace Kyoo.Core.Controllers
/// <returns>The tracked resource with the given ID</returns>
protected virtual async Task<T> GetWithTracking(int id)
{
T ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.Id == id);
T? ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.Id == id);
SetBackingImage(ret);
if (ret == null)
throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}");
@ -308,7 +308,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public virtual async Task<T> Get(int id)
{
T ret = await GetOrDefault(id);
T? ret = await GetOrDefault(id);
if (ret == null)
throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}");
return ret;
@ -317,7 +317,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public virtual async Task<T> Get(string slug)
{
T ret = await GetOrDefault(slug);
T? ret = await GetOrDefault(slug);
if (ret == null)
throw new ItemNotFoundException($"No {typeof(T).Name} found with the slug {slug}");
return ret;
@ -326,20 +326,20 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public virtual async Task<T> Get(Expression<Func<T, bool>> where)
{
T ret = await GetOrDefault(where);
T? ret = await GetOrDefault(where);
if (ret == null)
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
return ret;
}
/// <inheritdoc />
public virtual Task<T> GetOrDefault(int id)
public virtual Task<T?> GetOrDefault(int id)
{
return Database.Set<T>().FirstOrDefaultAsync(x => x.Id == id).Then(SetBackingImage);
}
/// <inheritdoc />
public virtual Task<T> GetOrDefault(string slug)
public virtual Task<T?> GetOrDefault(string slug)
{
if (slug == "random")
return Database.Set<T>().OrderBy(x => EF.Functions.Random()).FirstOrDefaultAsync().Then(SetBackingImage);
@ -347,7 +347,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc />
public virtual Task<T> GetOrDefault(Expression<Func<T, bool>> where, Sort<T> sortBy = default)
public virtual Task<T?> GetOrDefault(Expression<Func<T, bool>> where, Sort<T>? sortBy = default)
{
return Sort(Database.Set<T>(), sortBy).FirstOrDefaultAsync(where).Then(SetBackingImage);
}
@ -356,9 +356,9 @@ namespace Kyoo.Core.Controllers
public abstract Task<ICollection<T>> Search(string query);
/// <inheritdoc/>
public virtual async Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
Sort<T> sort = default,
Pagination limit = default)
public virtual async Task<ICollection<T>> GetAll(Expression<Func<T, bool>>? where = null,
Sort<T>? sort = default,
Pagination? limit = default)
{
return (await ApplyFilters(Database.Set<T>(), where, sort, limit))
.Select(SetBackingImageSelf).ToList();
@ -373,9 +373,9 @@ namespace Kyoo.Core.Controllers
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <returns>The filtered query</returns>
protected async Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
Expression<Func<T, bool>> where = null,
Sort<T> sort = default,
Pagination limit = default)
Expression<Func<T, bool>>? where = null,
Sort<T>? sort = default,
Pagination? limit = default)
{
query = Sort(query, sort);
if (where != null)
@ -395,7 +395,7 @@ namespace Kyoo.Core.Controllers
}
/// <inheritdoc/>
public virtual Task<int> GetCount(Expression<Func<T, bool>> where = null)
public virtual Task<int> GetCount(Expression<Func<T, bool>>? where = null)
{
IQueryable<T> query = Database.Set<T>();
if (where != null)
@ -411,11 +411,11 @@ namespace Kyoo.Core.Controllers
{
await _thumbs.DownloadImages(thumbs);
if (thumbs.Poster != null)
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry.State = EntityState.Added;
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry!.State = EntityState.Added;
if (thumbs.Thumbnail != null)
Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry.State = EntityState.Added;
Database.Entry(thumbs).Reference(x => x.Thumbnail).TargetEntry!.State = EntityState.Added;
if (thumbs.Logo != null)
Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry.State = EntityState.Added;
Database.Entry(thumbs).Reference(x => x.Logo).TargetEntry!.State = EntityState.Added;
}
SetBackingImage(obj);
return obj;
@ -444,7 +444,7 @@ namespace Kyoo.Core.Controllers
{
try
{
T old = await GetOrDefault(obj.Slug);
T? old = await GetOrDefault(obj.Slug);
if (old != null)
return old;
@ -544,7 +544,7 @@ namespace Kyoo.Core.Controllers
{
try
{
MethodInfo setter = typeof(T).GetProperty(nameof(resource.Slug))!.GetSetMethod();
MethodInfo? setter = typeof(T).GetProperty(nameof(resource.Slug))!.GetSetMethod();
if (setter != null)
setter.Invoke(resource, new object[] { resource.Slug + '!' });
else

View File

@ -126,7 +126,7 @@ namespace Kyoo.Core.Controllers
if (changed.People != null)
{
await Database.Entry(resource).Collection(x => x.People).LoadAsync();
await Database.Entry(resource).Collection(x => x.People!).LoadAsync();
resource.People = changed.People;
}
}

View File

@ -94,7 +94,7 @@ namespace Kyoo.Core.Controllers
{
foreach (PeopleRole role in resource.Roles)
{
role.Show = _database.LocalEntity<Show>(role.Show.Slug)
role.Show = _database.LocalEntity<Show>(role.Show!.Slug)
?? await _shows.Value.CreateIfNotExists(role.Show);
role.ShowID = role.Show.Id;
_database.Entry(role).State = EntityState.Added;
@ -109,7 +109,7 @@ namespace Kyoo.Core.Controllers
if (changed.Roles != null)
{
await Database.Entry(resource).Collection(x => x.Roles).LoadAsync();
await Database.Entry(resource).Collection(x => x.Roles!).LoadAsync();
resource.Roles = changed.Roles;
}
}
@ -125,9 +125,9 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetFromShow(int showID,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return Task.FromResult<ICollection<PeopleRole>>(new List<PeopleRole>());
// ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
@ -146,9 +146,9 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return Task.FromResult<ICollection<PeopleRole>>(new List<PeopleRole>());
// ICollection<PeopleRole> people = await ApplyFilters(_database.PeopleRoles
@ -169,9 +169,9 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetFromPeople(int id,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return Task.FromResult<ICollection<PeopleRole>>(new List<PeopleRole>());
// ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles
@ -189,9 +189,9 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc />
public Task<ICollection<PeopleRole>> GetFromPeople(string slug,
Expression<Func<PeopleRole, bool>> where = null,
Sort<PeopleRole> sort = default,
Pagination limit = default)
Expression<Func<PeopleRole, bool>>? where = null,
Sort<PeopleRole>? sort = default,
Pagination? limit = default)
{
return Task.FromResult<ICollection<PeopleRole>>(new List<PeopleRole>());
// ICollection<PeopleRole> roles = await ApplyFilters(_database.PeopleRoles

View File

@ -71,7 +71,7 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public async Task<Season> Get(int showID, int seasonNumber)
{
Season ret = await GetOrDefault(showID, seasonNumber);
Season? ret = await GetOrDefault(showID, seasonNumber);
if (ret == null)
throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showID}");
return ret;
@ -80,23 +80,23 @@ namespace Kyoo.Core.Controllers
/// <inheritdoc/>
public async Task<Season> Get(string showSlug, int seasonNumber)
{
Season ret = await GetOrDefault(showSlug, seasonNumber);
Season? ret = await GetOrDefault(showSlug, seasonNumber);
if (ret == null)
throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showSlug}");
return ret;
}
/// <inheritdoc/>
public Task<Season> GetOrDefault(int showID, int seasonNumber)
public Task<Season?> GetOrDefault(int showID, int seasonNumber)
{
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowId == showID
&& x.SeasonNumber == seasonNumber).Then(SetBackingImage);
}
/// <inheritdoc/>
public Task<Season> GetOrDefault(string showSlug, int seasonNumber)
public Task<Season?> GetOrDefault(string showSlug, int seasonNumber)
{
return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug
return _database.Seasons.FirstOrDefaultAsync(x => x.Show!.Slug == showSlug
&& x.SeasonNumber == seasonNumber).Then(SetBackingImage);
}
@ -105,7 +105,7 @@ namespace Kyoo.Core.Controllers
{
return (await Sort(
_database.Seasons
.Where(_database.Like<Season>(x => x.Name, $"%{query}%"))
.Where(_database.Like<Season>(x => x.Name!, $"%{query}%"))
)
.Take(20)
.ToListAsync())

View File

@ -21,6 +21,7 @@ using System.Linq;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Postgresql;
using Kyoo.Utils;
using Microsoft.EntityFrameworkCore;
@ -129,17 +130,20 @@ namespace Kyoo.Core.Controllers
if (changed.People != null)
{
await Database.Entry(resource).Collection(x => x.People).LoadAsync();
await Database.Entry(resource).Collection(x => x.People!).LoadAsync();
resource.People = changed.People;
}
}
/// <inheritdoc />
public Task<string> GetSlug(int showID)
public async Task<string> GetSlug(int showID)
{
return _database.Shows.Where(x => x.Id == showID)
string? ret = await _database.Shows.Where(x => x.Id == showID)
.Select(x => x.Slug)
.FirstOrDefaultAsync();
if (ret == null)
throw new ItemNotFoundException();
return ret;
}
/// <inheritdoc />

View File

@ -69,7 +69,7 @@ namespace Kyoo.Core.Controllers
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
if (obj.Logo != null)
_database.Entry(obj).Reference(x => x.Logo).TargetEntry.State = EntityState.Added;
_database.Entry(obj).Reference(x => x.Logo).TargetEntry!.State = EntityState.Added;
await _database.SaveChangesAsync(() => Get(obj.Slug));
OnResourceCreated(obj);
return obj;

View File

@ -84,7 +84,7 @@ namespace Kyoo.Core
options.InvalidModelStateResponseFactory = ctx =>
{
string[] errors = ctx.ModelState
.SelectMany(x => x.Value.Errors)
.SelectMany(x => x.Value!.Errors)
.Select(x => x.ErrorMessage)
.ToArray();
return new BadRequestObjectResult(new RequestError(errors));

View File

@ -1,47 +0,0 @@
// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Kyoo.Core
{
/// <summary>
/// A class containing helper methods.
/// </summary>
public static class Helper
{
/// <summary>
/// An helper method to get json content from an http server. This is a temporary thing and will probably be
/// replaced by a call to the function of the same name in the <c>System.Net.Http.Json</c> namespace when .net6
/// gets released.
/// </summary>
/// <param name="client">The http server to use.</param>
/// <param name="url">The url to retrieve</param>
/// <typeparam name="T">The type of object to convert</typeparam>
/// <returns>A T representing the json contained at the given url.</returns>
public static async Task<T> GetFromJsonAsync<T>(this HttpClient client, string url)
{
HttpResponseMessage ret = await client.GetAsync(url);
ret.EnsureSuccessStatusCode();
string content = await ret.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
}

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<AssemblyName>Kyoo.Core</AssemblyName>
<RootNamespace>Kyoo.Core</RootNamespace>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>

View File

@ -48,9 +48,9 @@ namespace Kyoo.Core.Api
/// <param name="right">The second parameter to compare.</param>
/// <returns>A comparison expression compatible with strings</returns>
public static BinaryExpression StringCompatibleExpression(
[NotNull] Func<Expression, Expression, BinaryExpression> operand,
[NotNull] Expression left,
[NotNull] Expression right)
Func<Expression, Expression, BinaryExpression> operand,
Expression left,
Expression right)
{
if (left.Type != typeof(string))
return operand(left, right);
@ -69,14 +69,14 @@ namespace Kyoo.Core.Api
/// <typeparam name="T">The type to create filters for.</typeparam>
/// <exception cref="ArgumentException">A filter is invalid.</exception>
/// <returns>An expression representing the filters that can be used anywhere or compiled</returns>
public static Expression<Func<T, bool>> ParseWhere<T>(Dictionary<string, string> where,
Expression<Func<T, bool>> defaultWhere = null)
public static Expression<Func<T, bool>>? ParseWhere<T>(Dictionary<string, string>? where,
Expression<Func<T, bool>>? defaultWhere = null)
{
if (where == null || where.Count == 0)
return defaultWhere;
ParameterExpression param = defaultWhere?.Parameters.First() ?? Expression.Parameter(typeof(T));
Expression expression = defaultWhere?.Body;
Expression? expression = defaultWhere?.Body;
foreach ((string key, string desired) in where)
{
@ -91,17 +91,17 @@ namespace Kyoo.Core.Api
value = desired.Substring(desired.IndexOf(':') + 1);
}
PropertyInfo property = typeof(T).GetProperty(key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
PropertyInfo? property = typeof(T).GetProperty(key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase);
if (property == null)
throw new ArgumentException($"No filterable parameter with the name {key}.");
MemberExpression propertyExpr = Expression.Property(param, property);
ConstantExpression valueExpr = null;
ConstantExpression? valueExpr = null;
bool isList = typeof(IEnumerable).IsAssignableFrom(propertyExpr.Type) && propertyExpr.Type != typeof(string);
if (operand != "ctn" && !typeof(IResource).IsAssignableFrom(propertyExpr.Type) && !isList)
{
Type propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
object val;
object? val;
try
{
val = string.IsNullOrEmpty(value) || value.Equals("null", StringComparison.OrdinalIgnoreCase)
@ -110,7 +110,7 @@ namespace Kyoo.Core.Api
}
catch (InvalidCastException)
{
throw new ArgumentException("Comparing two differents value's type.");
throw new ArgumentException("Comparing two different value's type.");
}
valueExpr = Expression.Constant(val, property.PropertyType);
@ -166,7 +166,7 @@ namespace Kyoo.Core.Api
if (xProperty.Type == typeof(string))
{
// x.PROPRETY.Contains(value);
return Expression.Call(xProperty, typeof(string).GetMethod("Contains", new[] { typeof(string) }), Expression.Constant(value));
return Expression.Call(xProperty, typeof(string).GetMethod("Contains", new[] { typeof(string) })!, Expression.Constant(value));
}
// x.PROPERTY is either a List<> or a []
@ -176,7 +176,8 @@ namespace Kyoo.Core.Api
{
return Expression.Call(typeof(Enumerable), "Contains", new[] { inner }, xProperty, Expression.Constant(value));
}
else if (inner.IsEnum && Enum.TryParse(inner, value, true, out object enumValue))
if (inner.IsEnum && Enum.TryParse(inner, value, true, out object? enumValue))
{
return Expression.Call(typeof(Enumerable), "Contains", new[] { inner }, xProperty, Expression.Constant(enumValue));
}
@ -185,7 +186,7 @@ namespace Kyoo.Core.Api
throw new ArgumentException("Contain (ctn) not appliable for this property.");
// x => x.PROPERTY.Any(y => y.Slug == value)
Expression ret = null;
Expression? ret = null;
ParameterExpression y = Expression.Parameter(inner, "y");
foreach (string val in value.Split(','))
{
@ -197,7 +198,7 @@ namespace Kyoo.Core.Api
else
ret = Expression.AndAlso(ret, iteration);
}
return ret;
return ret!;
}
}
}

View File

@ -18,6 +18,7 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
@ -69,7 +70,7 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<T>> Get(Identifier identifier)
{
T ret = await identifier.Match(
T? ret = await identifier.Match(
id => Repository.GetOrDefault(id),
slug => Repository.GetOrDefault(slug)
);
@ -235,7 +236,11 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
public async Task<IActionResult> Delete([FromQuery] Dictionary<string, string> where)
{
await Repository.DeleteAll(ApiHelper.ParseWhere<T>(where));
Expression<Func<T, bool>>? w = ApiHelper.ParseWhere<T>(where);
if (w == null)
return BadRequest(new RequestError("Incule a filter to delete items, all items won't be deleted."));
await Repository.DeleteAll(w);
return NoContent();
}
}

View File

@ -59,14 +59,14 @@ namespace Kyoo.Core.Api
private async Task<IActionResult> _GetImage(Identifier identifier, string image, ImageQuality? quality)
{
T resource = await identifier.Match(
T? resource = await identifier.Match(
id => Repository.GetOrDefault(id),
slug => Repository.GetOrDefault(slug)
);
if (resource == null)
return NotFound();
string path = _thumbs.GetImagePath(resource, image, quality ?? ImageQuality.High);
if (path == null || !System.IO.File.Exists(path))
if (!System.IO.File.Exists(path))
return NotFound();
if (!identifier.Match(id => false, slug => slug == "random"))

View File

@ -42,7 +42,7 @@ namespace Kyoo.Core.Api
/// <inheritdoc />
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.ActionArguments.TryGetValue("where", out object dic) && dic is Dictionary<string, string> where)
if (context.ActionArguments.TryGetValue("where", out object? dic) && dic is Dictionary<string, string> where)
{
Dictionary<string, string> nWhere = new(where, StringComparer.InvariantCultureIgnoreCase);
nWhere.Remove("fields");
@ -55,7 +55,7 @@ namespace Kyoo.Core.Api
}
List<string> fields = context.HttpContext.Request.Query["fields"]
.SelectMany(x => x.Split(','))
.SelectMany(x => x!.Split(','))
.ToList();
if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
@ -77,7 +77,7 @@ namespace Kyoo.Core.Api
fields = fields
.Select(x =>
{
string property = properties
string? property = properties
.FirstOrDefault(y
=> string.Equals(x, y.Name, StringComparison.InvariantCultureIgnoreCase))
?.Name;
@ -88,6 +88,7 @@ namespace Kyoo.Core.Api
);
return null;
})
.OfType<string>()
.ToList();
if (context.Result != null)
return;
@ -111,12 +112,12 @@ namespace Kyoo.Core.Api
return;
ILibraryManager library = context.HttpContext.RequestServices.GetRequiredService<ILibraryManager>();
ICollection<string> fields = (ICollection<string>)context.HttpContext.Items["fields"];
Type pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>));
ICollection<string> fields = (ICollection<string>)context.HttpContext.Items["fields"]!;
Type? pageType = Utility.GetGenericDefinition(result.DeclaredType, typeof(Page<>));
if (pageType != null)
{
foreach (IResource resource in ((dynamic)result.Value).Items)
foreach (IResource resource in ((dynamic)result.Value!).Items)
{
foreach (string field in fields!)
await library.Load(resource, field);
@ -125,7 +126,7 @@ namespace Kyoo.Core.Api
else if (result.DeclaredType.IsAssignableTo(typeof(IResource)))
{
foreach (string field in fields!)
await library.Load((IResource)result.Value, field);
await library.Load((IResource)result.Value!, field);
}
}
}

View File

@ -53,16 +53,16 @@ namespace Kyoo.Core.Api
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
LoadableRelationAttribute relation = member.GetCustomAttribute<LoadableRelationAttribute>();
LoadableRelationAttribute? relation = member.GetCustomAttribute<LoadableRelationAttribute>();
if (relation != null)
{
property.ShouldSerialize = _ =>
{
string resType = (string)_httpContextAccessor.HttpContext!.Items["ResourceType"];
string resType = (string)_httpContextAccessor.HttpContext!.Items["ResourceType"]!;
if (member.DeclaringType!.Name != resType)
return false;
ICollection<string> fields = (ICollection<string>)_httpContextAccessor.HttpContext!.Items["fields"];
return fields!.Contains(member.Name);
ICollection<string> fields = (ICollection<string>)_httpContextAccessor.HttpContext!.Items["fields"]!;
return fields.Contains(member.Name);
};
}

View File

@ -31,10 +31,16 @@ namespace Kyoo.Core.Api
public class PeopleRoleConverter : JsonConverter<PeopleRole>
{
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, PeopleRole value, JsonSerializer serializer)
public override void WriteJson(JsonWriter writer, PeopleRole? value, JsonSerializer serializer)
{
ICollection<PeopleRole> oldPeople = value.Show?.People;
ICollection<PeopleRole> oldRoles = value.People?.Roles;
if (value == null)
{
writer.WriteNull();
return;
}
ICollection<PeopleRole>? oldPeople = value.Show?.People;
ICollection<PeopleRole>? oldRoles = value.People?.Roles;
if (value.Show != null)
value.Show.People = null;
if (value.People != null)
@ -54,7 +60,7 @@ namespace Kyoo.Core.Api
/// <inheritdoc />
public override PeopleRole ReadJson(JsonReader reader,
Type objectType,
PeopleRole existingValue,
PeopleRole? existingValue,
bool hasExistingValue,
JsonSerializer serializer)
{

View File

@ -85,7 +85,7 @@ namespace Kyoo.Core.Api
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Expression<Func<PeopleRole, bool>>? whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
ICollection<PeopleRole> resources = await identifier.Match(

View File

@ -83,7 +83,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio.Slug)),
ApiHelper.ParseWhere(where, identifier.Matcher<Show>(x => x.StudioId, x => x.Studio!.Slug)),
Sort<Show>.From(sortBy),
pagination
);

View File

@ -84,7 +84,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Show> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections)),
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Show, Collection>(x => x.Collections!)),
Sort<Show>.From(sortBy),
pagination
);

View File

@ -73,7 +73,7 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Show>> GetShow(Identifier identifier)
{
return await _libraryManager.Get(identifier.IsContainedIn<Show, Episode>(x => x.Episodes));
return await _libraryManager.Get(identifier.IsContainedIn<Show, Episode>(x => x.Episodes!));
}
/// <summary>
@ -93,10 +93,10 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Season>> GetSeason(Identifier identifier)
{
Season ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Season, Episode>(x => x.Episodes));
Season? ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Season, Episode>(x => x.Episodes!));
if (ret != null)
return ret;
Episode episode = await identifier.Match(
Episode? episode = await identifier.Match(
id => _libraryManager.GetOrDefault<Episode>(id),
slug => _libraryManager.GetOrDefault<Episode>(slug)
);

View File

@ -108,7 +108,7 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Studio>> GetStudio(Identifier identifier)
{
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Movie>(x => x.Movies));
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Movie>(x => x.Movies!));
}
/// <summary>
@ -136,7 +136,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Movie>(x => x.Movies)),
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Movie>(x => x.Movies!)),
Sort<Collection>.From(sortBy),
pagination
);

View File

@ -84,7 +84,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season.Slug)),
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonId, x => x.Season!.Slug)),
Sort<Episode>.From(sortBy),
pagination
);
@ -109,7 +109,7 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Show>> GetShow(Identifier identifier)
{
Show ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Show, Season>(x => x.Seasons));
Show? ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Show, Season>(x => x.Seasons!));
if (ret == null)
return NotFound();
return ret;

View File

@ -86,7 +86,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Season> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowId, x => x.Show.Slug)),
ApiHelper.ParseWhere(where, identifier.Matcher<Season>(x => x.ShowId, x => x.Show!.Slug)),
Sort<Season>.From(sortBy),
pagination
);
@ -121,7 +121,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show.Slug)),
ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.ShowId, x => x.Show!.Slug)),
Sort<Episode>.From(sortBy),
pagination
);
@ -155,7 +155,7 @@ namespace Kyoo.Core.Api
[FromQuery] Dictionary<string, string> where,
[FromQuery] Pagination pagination)
{
Expression<Func<PeopleRole, bool>> whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Expression<Func<PeopleRole, bool>>? whereQuery = ApiHelper.ParseWhere<PeopleRole>(where);
Sort<PeopleRole> sort = Sort<PeopleRole>.From(sortBy);
ICollection<PeopleRole> resources = await identifier.Match(
@ -180,7 +180,7 @@ namespace Kyoo.Core.Api
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Studio>> GetStudio(Identifier identifier)
{
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Show>(x => x.Shows));
return await _libraryManager.Get(identifier.IsContainedIn<Studio, Show>(x => x.Shows!));
}
/// <summary>
@ -208,7 +208,7 @@ namespace Kyoo.Core.Api
[FromQuery] Pagination pagination)
{
ICollection<Collection> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows)),
ApiHelper.ParseWhere(where, identifier.IsContainedIn<Collection, Show>(x => x.Shows!)),
Sort<Collection>.From(sortBy),
pagination
);

View File

@ -180,8 +180,8 @@ namespace Kyoo.Postgresql
modelBuilder.Entity<T>()
.Property(x => x.ExternalId)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<Dictionary<string, MetadataId>>(v, (JsonSerializerOptions)null)
v => JsonSerializer.Serialize(v, (JsonSerializerOptions?)null),
v => JsonSerializer.Deserialize<Dictionary<string, MetadataId>>(v, (JsonSerializerOptions?)null)!
)
.HasColumnType("json");
}
@ -215,8 +215,8 @@ namespace Kyoo.Postgresql
/// <typeparam name="T">The owning type of the relationship</typeparam>
/// <typeparam name="T2">The owned type of the relationship</typeparam>
private void _HasManyToMany<T, T2>(ModelBuilder modelBuilder,
Expression<Func<T, IEnumerable<T2>>> firstNavigation,
Expression<Func<T2, IEnumerable<T>>> secondNavigation)
Expression<Func<T, IEnumerable<T2>?>> firstNavigation,
Expression<Func<T2, IEnumerable<T>?>> secondNavigation)
where T : class, IResource
where T2 : class, IResource
{
@ -360,7 +360,7 @@ namespace Kyoo.Postgresql
public T GetTemporaryObject<T>(T model)
where T : class, IResource
{
T tmp = Set<T>().Local.FirstOrDefault(x => x.Id == model.Id);
T? tmp = Set<T>().Local.FirstOrDefault(x => x.Id == model.Id);
if (tmp != null)
return tmp;
Entry(model).State = EntityState.Unchanged;
@ -509,8 +509,7 @@ namespace Kyoo.Postgresql
/// <param name="slug">The slug of the resource to check</param>
/// <typeparam name="T">The type of entity to check</typeparam>
/// <returns>The local entity representing the resource with the given slug if it exists or null.</returns>
[CanBeNull]
public T LocalEntity<T>(string slug)
public T? LocalEntity<T>(string slug)
where T : class, IResource
{
return ChangeTracker.Entries<T>()

View File

@ -106,7 +106,7 @@ namespace Kyoo.Postgresql
modelBuilder.HasPostgresEnum<Genre>();
modelBuilder.HasPostgresEnum<ItemKind>();
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5)))
modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(MD5))!)
.HasTranslation(args =>
new SqlFunctionExpression(
"md5",