mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
* Added an FAQ link on the Kavita+ tab. * Don't query Kavita+ for ratings on comic libraries as there is no upstream provider yet. * Jumpbar keys are a little hard to click * Fixed an issue where libraries that don't allow scrobbling could be scrobbled when generating past history with read events. * Made the min/max release year on metadata filter number and removed the spin arrows for styling. * Fixed disable tabs color contrast due to bootstrap undocumented change. * Refactored whole codebase to unify caching mechanism. Upped the default cache memory amount to 75 to account for the extra data load. Still LRU. Fixed an issue where Cache key was using Port instead. Refactored all the Configuration code to use strongly typed deserialization. * Fixed an issue where get latest progress would throw an exception if there was no progress due to LINQ and MAX query. * Fixed a bug where Send to Device wasn't present on Series cards. * Hooked up the ability to change the cache size for Kavita via the UI.
191 lines
7.2 KiB
C#
191 lines
7.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using API.Data.ManualMigrations;
|
|
using API.DTOs;
|
|
using API.Entities;
|
|
using API.Entities.Enums;
|
|
using AutoMapper;
|
|
using AutoMapper.QueryableExtensions;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace API.Data.Repositories;
|
|
#nullable enable
|
|
public interface IAppUserProgressRepository
|
|
{
|
|
void Update(AppUserProgress userProgress);
|
|
Task<int> CleanupAbandonedChapters();
|
|
Task<bool> UserHasProgress(LibraryType libraryType, int userId);
|
|
Task<AppUserProgress?> GetUserProgressAsync(int chapterId, int userId);
|
|
Task<bool> HasAnyProgressOnSeriesAsync(int seriesId, int userId);
|
|
/// <summary>
|
|
/// This is built exclusively for <see cref="MigrateUserProgressLibraryId"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
Task<AppUserProgress?> GetAnyProgress();
|
|
Task<IEnumerable<AppUserProgress>> GetUserProgressForSeriesAsync(int seriesId, int userId);
|
|
Task<IEnumerable<AppUserProgress>> GetAllProgress();
|
|
Task<DateTime> GetLatestProgress();
|
|
Task<ProgressDto> GetUserProgressDtoAsync(int chapterId, int userId);
|
|
Task<bool> AnyUserProgressForSeriesAsync(int seriesId, int userId);
|
|
Task<int> GetHighestFullyReadChapterForSeries(int seriesId, int userId);
|
|
Task<int> GetHighestFullyReadVolumeForSeries(int seriesId, int userId);
|
|
}
|
|
#nullable disable
|
|
public class AppUserProgressRepository : IAppUserProgressRepository
|
|
{
|
|
private readonly DataContext _context;
|
|
private readonly IMapper _mapper;
|
|
|
|
public AppUserProgressRepository(DataContext context, IMapper mapper)
|
|
{
|
|
_context = context;
|
|
_mapper = mapper;
|
|
}
|
|
|
|
public void Update(AppUserProgress userProgress)
|
|
{
|
|
_context.Entry(userProgress).State = EntityState.Modified;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will remove any entries that have chapterIds that no longer exists. This will execute the save as well.
|
|
/// </summary>
|
|
public async Task<int> CleanupAbandonedChapters()
|
|
{
|
|
var chapterIds = _context.Chapter.Select(c => c.Id);
|
|
|
|
var rowsToRemove = await _context.AppUserProgresses
|
|
.Where(progress => !chapterIds.Contains(progress.ChapterId))
|
|
.ToListAsync();
|
|
|
|
var rowsToRemoveBookmarks = await _context.AppUserBookmark
|
|
.Where(progress => !chapterIds.Contains(progress.ChapterId))
|
|
.ToListAsync();
|
|
|
|
var rowsToRemoveReadingLists = await _context.ReadingListItem
|
|
.Where(item => !chapterIds.Contains(item.ChapterId))
|
|
.ToListAsync();
|
|
|
|
_context.RemoveRange(rowsToRemove);
|
|
_context.RemoveRange(rowsToRemoveBookmarks);
|
|
_context.RemoveRange(rowsToRemoveReadingLists);
|
|
return await _context.SaveChangesAsync() > 0 ? rowsToRemove.Count : 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if user has any progress against a library of passed type
|
|
/// </summary>
|
|
/// <param name="libraryType"></param>
|
|
/// <param name="userId"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> UserHasProgress(LibraryType libraryType, int userId)
|
|
{
|
|
var seriesIds = await _context.AppUserProgresses
|
|
.Where(aup => aup.PagesRead > 0 && aup.AppUserId == userId)
|
|
.AsNoTracking()
|
|
.Select(aup => aup.SeriesId)
|
|
.ToListAsync();
|
|
|
|
if (seriesIds.Count == 0) return false;
|
|
|
|
return await _context.Series
|
|
.Include(s => s.Library)
|
|
.Where(s => seriesIds.Contains(s.Id) && s.Library.Type == libraryType)
|
|
.AsNoTracking()
|
|
.AnyAsync();
|
|
}
|
|
|
|
public async Task<bool> HasAnyProgressOnSeriesAsync(int seriesId, int userId)
|
|
{
|
|
return await _context.AppUserProgresses
|
|
.AnyAsync(aup => aup.PagesRead > 0 && aup.AppUserId == userId && aup.SeriesId == seriesId);
|
|
}
|
|
|
|
#nullable enable
|
|
public async Task<AppUserProgress?> GetAnyProgress()
|
|
{
|
|
return await _context.AppUserProgresses.FirstOrDefaultAsync();
|
|
}
|
|
#nullable disable
|
|
|
|
/// <summary>
|
|
/// This will return any user progress. This filters out progress rows that have no pages read.
|
|
/// </summary>
|
|
/// <param name="seriesId"></param>
|
|
/// <param name="userId"></param>
|
|
/// <returns></returns>
|
|
public async Task<IEnumerable<AppUserProgress>> GetUserProgressForSeriesAsync(int seriesId, int userId)
|
|
{
|
|
return await _context.AppUserProgresses
|
|
.Where(p => p.SeriesId == seriesId && p.AppUserId == userId && p.PagesRead > 0)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<IEnumerable<AppUserProgress>> GetAllProgress()
|
|
{
|
|
return await _context.AppUserProgresses.ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the latest progress in UTC
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public async Task<DateTime> GetLatestProgress()
|
|
{
|
|
return await _context.AppUserProgresses
|
|
.Select(d => d.LastModifiedUtc)
|
|
.OrderByDescending(d => d)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
public async Task<ProgressDto> GetUserProgressDtoAsync(int chapterId, int userId)
|
|
{
|
|
return await _context.AppUserProgresses
|
|
.Where(p => p.AppUserId == userId && p.ChapterId == chapterId)
|
|
.ProjectTo<ProgressDto>(_mapper.ConfigurationProvider)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
public async Task<bool> AnyUserProgressForSeriesAsync(int seriesId, int userId)
|
|
{
|
|
return await _context.AppUserProgresses
|
|
.Where(p => p.SeriesId == seriesId && p.AppUserId == userId && p.PagesRead > 0)
|
|
.AnyAsync();
|
|
}
|
|
|
|
public async Task<int> GetHighestFullyReadChapterForSeries(int seriesId, int userId)
|
|
{
|
|
var list = await _context.AppUserProgresses
|
|
.Join(_context.Chapter, appUserProgresses => appUserProgresses.ChapterId, chapter => chapter.Id,
|
|
(appUserProgresses, chapter) => new {appUserProgresses, chapter})
|
|
.Where(p => p.appUserProgresses.SeriesId == seriesId && p.appUserProgresses.AppUserId == userId &&
|
|
p.appUserProgresses.PagesRead >= p.chapter.Pages)
|
|
.Select(p => p.chapter.Number)
|
|
.ToListAsync();
|
|
return list.Count == 0 ? 0 : list.DefaultIfEmpty().Where(d => d != null).Max(d => (int) Math.Floor(float.Parse(d)));
|
|
}
|
|
|
|
public async Task<int> GetHighestFullyReadVolumeForSeries(int seriesId, int userId)
|
|
{
|
|
var list = await _context.AppUserProgresses
|
|
.Join(_context.Chapter, appUserProgresses => appUserProgresses.ChapterId, chapter => chapter.Id,
|
|
(appUserProgresses, chapter) => new {appUserProgresses, chapter})
|
|
.Where(p => p.appUserProgresses.SeriesId == seriesId && p.appUserProgresses.AppUserId == userId &&
|
|
p.appUserProgresses.PagesRead >= p.chapter.Pages)
|
|
.Select(p => p.chapter.Volume.Number)
|
|
.ToListAsync();
|
|
return list.Count == 0 ? 0 : list.DefaultIfEmpty().Max();
|
|
}
|
|
|
|
#nullable enable
|
|
public async Task<AppUserProgress?> GetUserProgressAsync(int chapterId, int userId)
|
|
{
|
|
return await _context.AppUserProgresses
|
|
.Where(p => p.ChapterId == chapterId && p.AppUserId == userId)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
#nullable disable
|
|
}
|