Smart Filter Polish & New Filters (#2283)

This commit is contained in:
Joe Milazzo 2023-09-15 09:39:06 -07:00 committed by GitHub
parent 0d8c081093
commit 45f6fb67d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 375 additions and 181 deletions

View file

@ -40,6 +40,10 @@ public enum FilterField
/// <summary>
/// On Want To Read or Not
/// </summary>
WantToRead = 26
WantToRead = 26,
/// <summary>
/// Last time User Read
/// </summary>
ReadingDate = 27
}

View file

@ -868,8 +868,6 @@ public class SeriesRepository : ISeriesRepository
.HasGenre(hasGenresFilter, FilterComparison.Contains, filter.Genres)
.HasFormat(filter.Formats != null && filter.Formats.Count > 0, FilterComparison.Contains, filter.Formats!)
.HasAverageReadTime(true, FilterComparison.GreaterThanEqual, 0)
// TODO: This needs different treatment
.HasPeople(hasPeopleFilter, FilterComparison.Contains, allPeopleIds)
.WhereIf(onlyParentSeries,
@ -917,6 +915,7 @@ public class SeriesRepository : ISeriesRepository
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead),
SortField.ReleaseYear => query.OrderBy(s => s.Metadata.ReleaseYear),
SortField.ReadProgress => query.OrderBy(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
_ => query
};
}
@ -930,6 +929,7 @@ public class SeriesRepository : ISeriesRepository
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead),
SortField.ReleaseYear => query.OrderByDescending(s => s.Metadata.ReleaseYear),
SortField.ReadProgress => query.OrderByDescending(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
_ => query
};
}
@ -1089,6 +1089,7 @@ public class SeriesRepository : ISeriesRepository
FilterField.Formats => query.HasFormat(true, statement.Comparison, (IList<MangaFormat>) value),
FilterField.ReleaseYear => query.HasReleaseYear(true, statement.Comparison, (int) value),
FilterField.ReadTime => query.HasAverageReadTime(true, statement.Comparison, (int) value),
FilterField.ReadingDate => query.HasReadingDate(true, statement.Comparison, (DateTime) value, userId),
_ => throw new ArgumentOutOfRangeException()
};
}

View file

@ -439,8 +439,8 @@ public class UserRepository : IUserRepository
var filterSeriesQuery = query.Join(_context.Series, b => b.SeriesId, s => s.Id,
(bookmark, series) => new BookmarkSeriesPair()
{
bookmark = bookmark,
series = series
Bookmark = bookmark,
Series = series
});
var filterStatement = filter.Statements.FirstOrDefault(f => f.Field == FilterField.SeriesName);
@ -457,34 +457,34 @@ public class UserRepository : IUserRepository
switch (filterStatement.Comparison)
{
case FilterComparison.Equal:
filterSeriesQuery = filterSeriesQuery.Where(s => s.series.Name.Equals(queryString)
|| s.series.OriginalName.Equals(queryString)
|| s.series.LocalizedName.Equals(queryString)
|| s.series.SortName.Equals(queryString));
filterSeriesQuery = filterSeriesQuery.Where(s => s.Series.Name.Equals(queryString)
|| s.Series.OriginalName.Equals(queryString)
|| s.Series.LocalizedName.Equals(queryString)
|| s.Series.SortName.Equals(queryString));
break;
case FilterComparison.BeginsWith:
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"{queryString}%")
||EF.Functions.Like(s.series.OriginalName, $"{queryString}%")
|| EF.Functions.Like(s.series.LocalizedName, $"{queryString}%")
|| EF.Functions.Like(s.series.SortName, $"{queryString}%"));
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.Series.Name, $"{queryString}%")
||EF.Functions.Like(s.Series.OriginalName, $"{queryString}%")
|| EF.Functions.Like(s.Series.LocalizedName, $"{queryString}%")
|| EF.Functions.Like(s.Series.SortName, $"{queryString}%"));
break;
case FilterComparison.EndsWith:
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"%{queryString}")
||EF.Functions.Like(s.series.OriginalName, $"%{queryString}")
|| EF.Functions.Like(s.series.LocalizedName, $"%{queryString}")
|| EF.Functions.Like(s.series.SortName, $"%{queryString}"));
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.Series.Name, $"%{queryString}")
||EF.Functions.Like(s.Series.OriginalName, $"%{queryString}")
|| EF.Functions.Like(s.Series.LocalizedName, $"%{queryString}")
|| EF.Functions.Like(s.Series.SortName, $"%{queryString}"));
break;
case FilterComparison.Matches:
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.series.Name, $"%{queryString}%")
||EF.Functions.Like(s.series.OriginalName, $"%{queryString}%")
|| EF.Functions.Like(s.series.LocalizedName, $"%{queryString}%")
|| EF.Functions.Like(s.series.SortName, $"%{queryString}%"));
filterSeriesQuery = filterSeriesQuery.Where(s => EF.Functions.Like(s.Series.Name, $"%{queryString}%")
||EF.Functions.Like(s.Series.OriginalName, $"%{queryString}%")
|| EF.Functions.Like(s.Series.LocalizedName, $"%{queryString}%")
|| EF.Functions.Like(s.Series.SortName, $"%{queryString}%"));
break;
case FilterComparison.NotEqual:
filterSeriesQuery = filterSeriesQuery.Where(s => s.series.Name != queryString
|| s.series.OriginalName != queryString
|| s.series.LocalizedName != queryString
|| s.series.SortName != queryString);
filterSeriesQuery = filterSeriesQuery.Where(s => s.Series.Name != queryString
|| s.Series.OriginalName != queryString
|| s.Series.LocalizedName != queryString
|| s.Series.SortName != queryString);
break;
case FilterComparison.MustContains:
case FilterComparison.NotContains:
@ -504,7 +504,7 @@ public class UserRepository : IUserRepository
return await ApplyLimit(filterSeriesQuery
.Sort(filter.SortOptions)
.AsSplitQuery(), filter.LimitTo)
.Select(o => o.bookmark)
.Select(o => o.Bookmark)
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
.ToListAsync();
}

View file

@ -6,8 +6,8 @@ namespace API.Extensions.QueryExtensions.Filtering;
public class BookmarkSeriesPair
{
public AppUserBookmark bookmark { get; set; }
public Series series { get; set; }
public AppUserBookmark Bookmark { get; set; }
public Series Series { get; set; }
}
public static class BookmarkSort
@ -31,12 +31,13 @@ public static class BookmarkSort
{
query = sortOptions.SortField switch
{
SortField.SortName => query.OrderBy(s => s.series.SortName.ToLower()),
SortField.CreatedDate => query.OrderBy(s => s.series.Created),
SortField.LastModifiedDate => query.OrderBy(s => s.series.LastModified),
SortField.LastChapterAdded => query.OrderBy(s => s.series.LastChapterAdded),
SortField.TimeToRead => query.OrderBy(s => s.series.AvgHoursToRead),
SortField.ReleaseYear => query.OrderBy(s => s.series.Metadata.ReleaseYear),
SortField.SortName => query.OrderBy(s => s.Series.SortName.ToLower()),
SortField.CreatedDate => query.OrderBy(s => s.Series.Created),
SortField.LastModifiedDate => query.OrderBy(s => s.Series.LastModified),
SortField.LastChapterAdded => query.OrderBy(s => s.Series.LastChapterAdded),
SortField.TimeToRead => query.OrderBy(s => s.Series.AvgHoursToRead),
SortField.ReleaseYear => query.OrderBy(s => s.Series.Metadata.ReleaseYear),
SortField.ReadProgress => query.OrderBy(s => s.Series.Progress.Where(p => p.SeriesId == s.Series.Id).Select(p => p.LastModified).Max()),
_ => query
};
}
@ -44,12 +45,13 @@ public static class BookmarkSort
{
query = sortOptions.SortField switch
{
SortField.SortName => query.OrderByDescending(s => s.series.SortName.ToLower()),
SortField.CreatedDate => query.OrderByDescending(s => s.series.Created),
SortField.LastModifiedDate => query.OrderByDescending(s => s.series.LastModified),
SortField.LastChapterAdded => query.OrderByDescending(s => s.series.LastChapterAdded),
SortField.TimeToRead => query.OrderByDescending(s => s.series.AvgHoursToRead),
SortField.ReleaseYear => query.OrderByDescending(s => s.series.Metadata.ReleaseYear),
SortField.SortName => query.OrderByDescending(s => s.Series.SortName.ToLower()),
SortField.CreatedDate => query.OrderByDescending(s => s.Series.Created),
SortField.LastModifiedDate => query.OrderByDescending(s => s.Series.LastModified),
SortField.LastChapterAdded => query.OrderByDescending(s => s.Series.LastChapterAdded),
SortField.TimeToRead => query.OrderByDescending(s => s.Series.AvgHoursToRead),
SortField.ReleaseYear => query.OrderByDescending(s => s.Series.Metadata.ReleaseYear),
SortField.ReadProgress => query.OrderByDescending(s => s.Series.Progress.Where(p => p.SeriesId == s.Series.Id).Select(p => p.LastModified).Max()),
_ => query
};
}

View file

@ -288,6 +288,64 @@ public static class SeriesFilter
return queryable.Where(s => ids.Contains(s.Id));
}
public static IQueryable<Series> HasReadingDate(this IQueryable<Series> queryable, bool condition,
FilterComparison comparison, DateTime? date, int userId)
{
if (!condition || !date.HasValue) return queryable;
var subQuery = queryable
.Include(s => s.Progress)
.Where(s => s.Progress != null)
.Select(s => new
{
Series = s,
MaxDate = s.Progress.Where(p => p != null && p.AppUserId == userId)
.Select(p => (DateTime?) p.LastModified)
.DefaultIfEmpty()
.Max()
})
.Where(s => s.MaxDate != null)
.AsEnumerable();
switch (comparison)
{
case FilterComparison.Equal:
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate.Equals(date));
break;
case FilterComparison.IsAfter:
case FilterComparison.GreaterThan:
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate > date);
break;
case FilterComparison.GreaterThanEqual:
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate >= date);
break;
case FilterComparison.IsBefore:
case FilterComparison.LessThan:
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate < date);
break;
case FilterComparison.LessThanEqual:
subQuery = subQuery.Where(s => s.MaxDate != null && s.MaxDate <= date);
break;
case FilterComparison.NotEqual:
subQuery = subQuery.Where(s => s.MaxDate != null && !s.MaxDate.Equals(date));
break;
case FilterComparison.Matches:
case FilterComparison.Contains:
case FilterComparison.NotContains:
case FilterComparison.BeginsWith:
case FilterComparison.EndsWith:
case FilterComparison.IsInLast:
case FilterComparison.IsNotInLast:
case FilterComparison.MustContains:
throw new KavitaException($"{comparison} not applicable for Series.ReadProgress");
default:
throw new ArgumentOutOfRangeException(nameof(comparison), comparison, null);
}
var ids = subQuery.Select(s => s.Series.Id).ToList();
return queryable.Where(s => ids.Contains(s.Id));
}
public static IQueryable<Series> HasTags(this IQueryable<Series> queryable, bool condition,
FilterComparison comparison, IList<int> tags)
{

View file

@ -31,7 +31,7 @@ public static class SeriesSort
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
SortField.TimeToRead => query.OrderBy(s => s.AvgHoursToRead),
SortField.ReleaseYear => query.OrderBy(s => s.Metadata.ReleaseYear),
//SortField.ReadProgress => query.OrderBy()
SortField.ReadProgress => query.OrderBy(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
_ => query
};
}
@ -45,6 +45,7 @@ public static class SeriesSort
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
SortField.TimeToRead => query.OrderByDescending(s => s.AvgHoursToRead),
SortField.ReleaseYear => query.OrderByDescending(s => s.Metadata.ReleaseYear),
SortField.ReadProgress => query.OrderByDescending(s => s.Progress.Where(p => p.SeriesId == s.Id).Select(p => p.LastModified).Max()),
_ => query
};
}

View file

@ -36,12 +36,12 @@ public class AutoMapperProfiles : Profile
public AutoMapperProfiles()
{
CreateMap<BookmarkSeriesPair, BookmarkDto>()
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.bookmark.Id))
.ForMember(dest => dest.Page, opt => opt.MapFrom(src => src.bookmark.Page))
.ForMember(dest => dest.VolumeId, opt => opt.MapFrom(src => src.bookmark.VolumeId))
.ForMember(dest => dest.SeriesId, opt => opt.MapFrom(src => src.bookmark.SeriesId))
.ForMember(dest => dest.ChapterId, opt => opt.MapFrom(src => src.bookmark.ChapterId))
.ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.series));
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Bookmark.Id))
.ForMember(dest => dest.Page, opt => opt.MapFrom(src => src.Bookmark.Page))
.ForMember(dest => dest.VolumeId, opt => opt.MapFrom(src => src.Bookmark.VolumeId))
.ForMember(dest => dest.SeriesId, opt => opt.MapFrom(src => src.Bookmark.SeriesId))
.ForMember(dest => dest.ChapterId, opt => opt.MapFrom(src => src.Bookmark.ChapterId))
.ForMember(dest => dest.Series, opt => opt.MapFrom(src => src.Series));
CreateMap<LibraryDto, Library>();
CreateMap<Volume, VolumeDto>();
CreateMap<MangaFile, MangaFileDto>();

View file

@ -69,6 +69,7 @@ public static class FilterFieldValueConverter
.ToList(), typeof(IList<int>)),
FilterField.WantToRead => (bool.Parse(value), typeof(bool)),
FilterField.ReadProgress => (int.Parse(value), typeof(int)),
FilterField.ReadingDate => (DateTime.Parse(value), typeof(DateTime?)),
FilterField.Formats => (value.Split(',')
.Select(x => (MangaFormat) Enum.Parse(typeof(MangaFormat), x))
.ToList(), typeof(IList<MangaFormat>)),