using System; using System.Collections.Generic; using System.Linq; using API.Data.Misc; using API.Entities; using API.Entities.Enums; using API.Entities.Metadata; using API.Entities.Person; namespace API.Extensions.QueryExtensions; #nullable enable /// /// Responsible for restricting Entities based on an AgeRestriction /// public static class RestrictByAgeExtensions { public static IQueryable RestrictAgainstAgeRestriction(this IQueryable 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 RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; var q = queryable.Where(s => s.SeriesMetadata.AgeRating <= restriction.AgeRating); if (!restriction.IncludeUnknowns) { return q.Where(s => s.SeriesMetadata.AgeRating != AgeRating.Unknown); } return q; } public static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; var q = queryable.Where(chapter => chapter.Volume.Series.Metadata.AgeRating <= restriction.AgeRating); if (!restriction.IncludeUnknowns) { return q.Where(s => s.Volume.Series.Metadata.AgeRating != AgeRating.Unknown); } return q; } public static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; var q = queryable.Where(cp => cp.Chapter.Volume.Series.Metadata.AgeRating <= restriction.AgeRating); if (!restriction.IncludeUnknowns) { return q.Where(cp => cp.Chapter.Volume.Series.Metadata.AgeRating != AgeRating.Unknown); } return q; } public static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; if (restriction.IncludeUnknowns) { return queryable.Where(c => c.Items.All(sm => sm.Metadata.AgeRating <= restriction.AgeRating)); } return queryable.Where(c => c.Items.All(sm => sm.Metadata.AgeRating <= restriction.AgeRating && sm.Metadata.AgeRating > AgeRating.Unknown)); } /// /// Returns all Genres where any of the linked Series/Chapters are less than or equal to restriction age rating /// /// /// /// public static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; if (restriction.IncludeUnknowns) { return queryable.Where(c => c.SeriesMetadatas.Any(sm => sm.AgeRating <= restriction.AgeRating) || c.Chapters.Any(cp => cp.AgeRating <= restriction.AgeRating)); } return queryable.Where(c => c.SeriesMetadatas.Any(sm => sm.AgeRating <= restriction.AgeRating && sm.AgeRating != AgeRating.Unknown) || c.Chapters.Any(cp => cp.AgeRating <= restriction.AgeRating && cp.AgeRating != AgeRating.Unknown) ); } public static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; if (restriction.IncludeUnknowns) { return queryable.Where(c => c.SeriesMetadatas.Any(sm => sm.AgeRating <= restriction.AgeRating) || c.Chapters.Any(cp => cp.AgeRating <= restriction.AgeRating)); } return queryable.Where(c => c.SeriesMetadatas.Any(sm => sm.AgeRating <= restriction.AgeRating && sm.AgeRating != AgeRating.Unknown) || c.Chapters.Any(cp => cp.AgeRating <= restriction.AgeRating && cp.AgeRating != AgeRating.Unknown) ); } public static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; if (restriction.IncludeUnknowns) { return queryable.Where(c => c.SeriesMetadataPeople.Any(sm => sm.SeriesMetadata.AgeRating <= restriction.AgeRating) || c.ChapterPeople.Any(cp => cp.Chapter.AgeRating <= restriction.AgeRating)); } return queryable.Where(c => c.SeriesMetadataPeople.Any(sm => sm.SeriesMetadata.AgeRating <= restriction.AgeRating && sm.SeriesMetadata.AgeRating != AgeRating.Unknown) || c.ChapterPeople.Any(cp => cp.Chapter.AgeRating <= restriction.AgeRating && cp.Chapter.AgeRating != AgeRating.Unknown) ); } public static IQueryable RestrictAgainstAgeRestriction(this IQueryable 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; } private static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction, int userId) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; var q = queryable.Where(r => r.Series.Metadata.AgeRating <= restriction.AgeRating || r.AppUserId == userId); if (!restriction.IncludeUnknowns) { return q.Where(a => a.Series.Metadata.AgeRating != AgeRating.Unknown || a.AppUserId == userId); } return q; } private static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction, int userId) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; var q = queryable.Where(r => r.Series.Metadata.AgeRating <= restriction.AgeRating || r.AppUserId == userId); if (!restriction.IncludeUnknowns) { return q.Where(a => a.Series.Metadata.AgeRating != AgeRating.Unknown || a.AppUserId == userId); } return q; } private static IQueryable RestrictAgainstAgeRestriction(this IQueryable queryable, AgeRestriction restriction, int userId) { if (restriction.AgeRating == AgeRating.NotApplicable) return queryable; var q = queryable.Where(a => a.Series.Metadata.AgeRating <= restriction.AgeRating || a.AppUserId == userId); if (!restriction.IncludeUnknowns) { return q.Where(a => a.Series.Metadata.AgeRating != AgeRating.Unknown || a.AppUserId == userId); } return q; } // TODO: After updating to .net 10, leverage new Complex Data type queries to inline all db operations here /// /// Filter annotations by social preferences of users /// /// /// /// List of user preferences for every user on the server /// public static IQueryable RestrictBySocialPreferences(this IQueryable queryable, int userId, IList userPreferences) { var preferencesById = userPreferences.ToDictionary(p => p.AppUserId, p => p.SocialPreferences); var socialPreferences = preferencesById[userId]; if (socialPreferences.ViewOtherAnnotations) { // We are unable to do dictionary lookups in Sqlite; This means we need to translate them to X IN Y. var sharingUserIds = userPreferences .Where(p => p.SocialPreferences.ShareAnnotations) .Select(p => p.AppUserId) .ToHashSet(); // Only include the users' annotations, or those of users that are sharing queryable = queryable.Where(a => a.AppUserId == userId || sharingUserIds.Contains(a.AppUserId)); // For other users' annotation foreach (var sharingUserId in sharingUserIds.Where(id => id != userId)) { // Filter out libs if enabled var libs = preferencesById[sharingUserId].SocialLibraries; if (libs.Count > 0) { queryable = queryable.Where(a => a.AppUserId != sharingUserId || libs.Contains(a.LibraryId)); } // Filter on age rating var ageRating = preferencesById[sharingUserId].SocialMaxAgeRating; var includeUnknowns = preferencesById[sharingUserId].SocialIncludeUnknowns; if (ageRating != AgeRating.NotApplicable) { queryable = queryable.Where(a => a.AppUserId != sharingUserId || a.Series.Metadata.AgeRating <= ageRating) .WhereIf(!includeUnknowns, a => a.AppUserId != sharingUserId || a.Series.Metadata.AgeRating != AgeRating.Unknown); } } } else { queryable = queryable.Where(a => a.AppUserId == userId); } return queryable .WhereIf(socialPreferences.SocialLibraries.Count > 0, a => a.AppUserId == userId || socialPreferences.SocialLibraries.Contains(a.LibraryId)) .RestrictAgainstAgeRestriction(new AgeRestriction { AgeRating = socialPreferences.SocialMaxAgeRating, IncludeUnknowns = socialPreferences.SocialIncludeUnknowns, }, userId); } // TODO: After updating to .net 10, leverage new Complex Data type queries to inline all db operations here /// /// Filter user reviews social preferences of users /// /// /// /// List of user preferences for every user on the server /// public static IQueryable RestrictBySocialPreferences(this IQueryable queryable, int userId, IList userPreferences) { var preferencesById = userPreferences.ToDictionary(p => p.AppUserId, p => p.SocialPreferences); var socialPreferences = preferencesById[userId]; var sharingUserIds = userPreferences .Where(p => p.SocialPreferences.ShareReviews) .Select(p => p.AppUserId) .ToHashSet(); queryable = queryable.Where(r => r.AppUserId == userId || sharingUserIds.Contains(r.AppUserId)); foreach (var sharingUserId in sharingUserIds.Where(id => id != userId)) { var libs = preferencesById[sharingUserId].SocialLibraries; if (libs.Count > 0) { queryable = queryable.Where(r => r.AppUserId != sharingUserId || libs.Contains(r.Series.LibraryId)); } var ageRating = preferencesById[sharingUserId].SocialMaxAgeRating; var includeUnknowns = preferencesById[sharingUserId].SocialIncludeUnknowns; if (ageRating != AgeRating.NotApplicable) { queryable = queryable.Where(r => r.AppUserId != sharingUserId || r.Series.Metadata.AgeRating <= ageRating) .WhereIf(!includeUnknowns, r => r.AppUserId != sharingUserId || r.Series.Metadata.AgeRating != AgeRating.Unknown); } } return queryable .WhereIf(socialPreferences.SocialLibraries.Count > 0, r => r.AppUserId == userId || socialPreferences.SocialLibraries.Contains(r.Series.LibraryId)) .RestrictAgainstAgeRestriction(new AgeRestriction { AgeRating = socialPreferences.SocialMaxAgeRating, IncludeUnknowns = socialPreferences.SocialIncludeUnknowns, }, userId); } // TODO: After updating to .net 10, leverage new Complex Data type queries to inline all db operations here /// /// Filter user chapter reviews social preferences of users /// /// /// /// List of user preferences for every user on the server /// public static IQueryable RestrictBySocialPreferences(this IQueryable queryable, int userId, IList userPreferences) { var preferencesById = userPreferences.ToDictionary(p => p.AppUserId, p => p.SocialPreferences); var socialPreferences = preferencesById[userId]; var sharingUserIds = userPreferences .Where(p => p.SocialPreferences.ShareReviews) .Select(p => p.AppUserId) .ToHashSet(); queryable = queryable.Where(r => r.AppUserId == userId || sharingUserIds.Contains(r.AppUserId)); foreach (var sharingUserId in sharingUserIds.Where(id => id != userId)) { var libs = preferencesById[sharingUserId].SocialLibraries; if (libs.Count > 0) { queryable = queryable.Where(r => r.AppUserId != sharingUserId || libs.Contains(r.Series.LibraryId)); } var ageRating = preferencesById[sharingUserId].SocialMaxAgeRating; var includeUnknowns = preferencesById[sharingUserId].SocialIncludeUnknowns; if (ageRating != AgeRating.NotApplicable) { queryable = queryable.Where(r => r.AppUserId != sharingUserId || r.Series.Metadata.AgeRating <= ageRating) .WhereIf(!includeUnknowns, r => r.AppUserId != sharingUserId || r.Series.Metadata.AgeRating != AgeRating.Unknown); } } return queryable .WhereIf(socialPreferences.SocialLibraries.Count > 0, r => r.AppUserId == userId || socialPreferences.SocialLibraries.Contains(r.Series.LibraryId)) .RestrictAgainstAgeRestriction(new AgeRestriction { AgeRating = socialPreferences.SocialMaxAgeRating, IncludeUnknowns = socialPreferences.SocialIncludeUnknowns, }, userId); } }