Kavita/API/Extensions/QueryableExtensions.cs
Joe Milazzo 5d1dd7b3f0
.NET 7 + Spring Cleaning (#1677)
* Updated to net7.0

* Updated GA to .net 7

* Updated System.IO.Abstractions to use New factory.

* Converted Regex into SourceGenerator in Parser.

* Updated more regex to source generators.

* Enabled Nullability and more regex changes throughout codebase.

* Parser is 100% GeneratedRegexified

* Lots of nullability code

* Enabled nullability for all repositories.

* Fixed another unit test

* Refactored some code around and took care of some todos.

* Updating code for nullability and cleaning up methods that aren't used anymore. Refctored all uses of Parser.Normalize() to use new extension

* More nullability exercises. 500 warnings to go.

* Fixed a bug where custom file uploads for entities wouldn't save in webP.

* Nullability is done for all DTOs

* Fixed all unit tests and nullability for the project. Only OPDS is left which will be done with an upcoming OPDS enhancement.

* Use localization in book service after validating

* Code smells

* Switched to preview build of swashbuckle for .net7 support

* Fixed up merge issues

* Disable emulate comic book when on single page reader

* Fixed a regression where double page renderer wouldn't layout the images correctly

* Updated to swashbuckle which support .net 7

* Fixed a bad GA action

* Some code cleanup

* More code smells

* Took care of most of nullable issues

* Fixed a broken test due to having more than one test run in parallel

* I'm really not sure why the unit tests are failing or are so extremely slow on .net 7

* Updated all dependencies

* Fixed up build and removed hardcoded framework from build scripts. (this merge removes Regex Source generators). Unit tests are completely busted.

* Unit tests and code cleanup. Needs shakeout now.

* Adjusted Series model since a few fields are not-nullable. Removed dead imports on the project.

* Refactored to use Builder pattern for all unit tests.

* Switched nullability down to warnings. It wasn't possible to switch due to constraint issues in DB Migration.
2023-03-05 12:55:13 -08:00

291 lines
9.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using API.Data.Misc;
using API.Data.Repositories;
using API.Entities;
using API.Entities.Enums;
using Microsoft.EntityFrameworkCore;
namespace API.Extensions;
public static class QueryableExtensions
{
public static IQueryable<Series> RestrictAgainstAgeRestriction(this IQueryable<Series> queryable, AgeRestriction restriction)
{
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
var q = queryable.Where(s => s.Metadata.AgeRating <= restriction.AgeRating);
if (!restriction.IncludeUnknowns)
{
return q.Where(s => s.Metadata.AgeRating != AgeRating.Unknown);
}
return q;
}
public static IQueryable<CollectionTag> RestrictAgainstAgeRestriction(this IQueryable<CollectionTag> queryable, AgeRestriction restriction)
{
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
if (restriction.IncludeUnknowns)
{
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating));
}
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
}
public static IQueryable<Genre> RestrictAgainstAgeRestriction(this IQueryable<Genre> queryable, AgeRestriction restriction)
{
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
if (restriction.IncludeUnknowns)
{
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating));
}
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
}
public static IQueryable<Tag> RestrictAgainstAgeRestriction(this IQueryable<Tag> queryable, AgeRestriction restriction)
{
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
if (restriction.IncludeUnknowns)
{
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating));
}
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
}
public static IQueryable<Person> RestrictAgainstAgeRestriction(this IQueryable<Person> queryable, AgeRestriction restriction)
{
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
if (restriction.IncludeUnknowns)
{
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating));
}
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
}
public static IQueryable<ReadingList> RestrictAgainstAgeRestriction(this IQueryable<ReadingList> queryable, AgeRestriction restriction)
{
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
var q = queryable.Where(rl => rl.AgeRating <= restriction.AgeRating);
if (!restriction.IncludeUnknowns)
{
return q.Where(rl => rl.AgeRating != AgeRating.Unknown);
}
return q;
}
public static Task<AgeRestriction> GetUserAgeRestriction(this DbSet<AppUser> queryable, int userId)
{
if (userId < 1)
{
return Task.FromResult(new AgeRestriction()
{
AgeRating = AgeRating.NotApplicable,
IncludeUnknowns = true
});
}
return queryable
.AsNoTracking()
.Where(u => u.Id == userId)
.Select(u =>
new AgeRestriction(){
AgeRating = u.AgeRestriction,
IncludeUnknowns = u.AgeRestrictionIncludeUnknowns
})
.SingleAsync();
}
public static IQueryable<CollectionTag> Includes(this IQueryable<CollectionTag> queryable,
CollectionTagIncludes includes)
{
if (includes.HasFlag(CollectionTagIncludes.SeriesMetadata))
{
queryable = queryable.Include(c => c.SeriesMetadatas);
}
return queryable.AsSplitQuery();
}
public static IQueryable<Chapter> Includes(this IQueryable<Chapter> queryable,
ChapterIncludes includes)
{
if (includes.HasFlag(ChapterIncludes.Volumes))
{
queryable = queryable.Include(v => v.Volume);
}
if (includes.HasFlag(ChapterIncludes.Files))
{
queryable = queryable
.Include(c => c.Files);
}
return queryable.AsSplitQuery();
}
public static IQueryable<Series> Includes(this IQueryable<Series> query,
SeriesIncludes includeFlags)
{
if (includeFlags.HasFlag(SeriesIncludes.Library))
{
query = query.Include(u => u.Library);
}
if (includeFlags.HasFlag(SeriesIncludes.Volumes))
{
query = query.Include(s => s.Volumes);
}
if (includeFlags.HasFlag(SeriesIncludes.Chapters))
{
query = query
.Include(s => s.Volumes)
.ThenInclude(v => v.Chapters);
}
if (includeFlags.HasFlag(SeriesIncludes.Related))
{
query = query.Include(s => s.Relations)
.ThenInclude(r => r.TargetSeries)
.Include(s => s.RelationOf);
}
if (includeFlags.HasFlag(SeriesIncludes.Metadata))
{
query = query.Include(s => s.Metadata)
.ThenInclude(m => m.CollectionTags.OrderBy(g => g.NormalizedTitle))
.Include(s => s.Metadata)
.ThenInclude(m => m.Genres.OrderBy(g => g.NormalizedTitle))
.Include(s => s.Metadata)
.ThenInclude(m => m.People)
.Include(s => s.Metadata)
.ThenInclude(m => m.Tags.OrderBy(g => g.NormalizedTitle));
}
return query.AsSplitQuery();
}
public static IQueryable<AppUser> Includes(this IQueryable<AppUser> query, AppUserIncludes includeFlags)
{
if (includeFlags.HasFlag(AppUserIncludes.Bookmarks))
{
query = query.Include(u => u.Bookmarks);
}
if (includeFlags.HasFlag(AppUserIncludes.Progress))
{
query = query.Include(u => u.Progresses);
}
if (includeFlags.HasFlag(AppUserIncludes.ReadingLists))
{
query = query.Include(u => u.ReadingLists);
}
if (includeFlags.HasFlag(AppUserIncludes.ReadingListsWithItems))
{
query = query.Include(u => u.ReadingLists)
.ThenInclude(r => r.Items);
}
if (includeFlags.HasFlag(AppUserIncludes.Ratings))
{
query = query.Include(u => u.Ratings);
}
if (includeFlags.HasFlag(AppUserIncludes.UserPreferences))
{
query = query.Include(u => u.UserPreferences);
}
if (includeFlags.HasFlag(AppUserIncludes.WantToRead))
{
query = query.Include(u => u.WantToRead);
}
if (includeFlags.HasFlag(AppUserIncludes.Devices))
{
query = query.Include(u => u.Devices);
}
return query.AsSplitQuery();
}
/// <summary>
/// Applies restriction based on if the Library has restrictions (like include in search)
/// </summary>
/// <param name="query"></param>
/// <param name="context"></param>
/// <returns></returns>
public static IQueryable<Library> IsRestricted(this IQueryable<Library> query, QueryContext context)
{
if (context.HasFlag(QueryContext.None)) return query;
if (context.HasFlag(QueryContext.Dashboard))
{
query = query.Where(l => l.IncludeInDashboard);
}
if (context.HasFlag(QueryContext.Recommended))
{
query = query.Where(l => l.IncludeInRecommended);
}
if (context.HasFlag(QueryContext.Search))
{
query = query.Where(l => l.IncludeInSearch);
}
return query;
}
/// <summary>
/// Returns all libraries for a given user
/// </summary>
/// <param name="library"></param>
/// <param name="userId"></param>
/// <param name="queryContext"></param>
/// <returns></returns>
public static IQueryable<int> GetUserLibraries(this IQueryable<Library> library, int userId, QueryContext queryContext = QueryContext.None)
{
return library
.Include(l => l.AppUsers)
.Where(lib => lib.AppUsers.Any(user => user.Id == userId))
.IsRestricted(queryContext)
.AsNoTracking()
.AsSplitQuery()
.Select(lib => lib.Id);
}
public static IEnumerable<DateTime> Range(this DateTime startDate, int numberOfDays) =>
Enumerable.Range(0, numberOfDays).Select(e => startDate.AddDays(e));
public static IQueryable<T> WhereIf<T>(this IQueryable<T> queryable, bool condition,
Expression<Func<T, bool>> predicate)
{
return condition ? queryable.Where(predicate) : queryable;
}
}