using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Comparators; using API.DTOs; using API.Entities; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; namespace API.Data.Repositories; public interface IVolumeRepository { void Add(Volume volume); void Update(Volume volume); void Remove(Volume volume); Task> GetFilesForVolume(int volumeId); Task GetVolumeCoverImageAsync(int volumeId); Task> GetChapterIdsByVolumeIds(IReadOnlyList volumeIds); Task> GetVolumesDtoAsync(int seriesId, int userId); Task GetVolumeAsync(int volumeId); Task GetVolumeDtoAsync(int volumeId, int userId); Task> GetVolumesForSeriesAsync(IList seriesIds, bool includeChapters = false); Task> GetVolumes(int seriesId); Task GetVolumeByIdAsync(int volumeId); } public class VolumeRepository : IVolumeRepository { private readonly DataContext _context; private readonly IMapper _mapper; public VolumeRepository(DataContext context, IMapper mapper) { _context = context; _mapper = mapper; } public void Add(Volume volume) { _context.Volume.Add(volume); } public void Update(Volume volume) { _context.Entry(volume).State = EntityState.Modified; } public void Remove(Volume volume) { _context.Volume.Remove(volume); } /// /// Returns a list of non-tracked files for a given volume. /// /// /// public async Task> GetFilesForVolume(int volumeId) { return await _context.Chapter .Where(c => volumeId == c.VolumeId) .Include(c => c.Files) .SelectMany(c => c.Files) .AsNoTracking() .ToListAsync(); } /// /// Returns the cover image file for the given volume /// /// /// public async Task GetVolumeCoverImageAsync(int volumeId) { return await _context.Volume .Where(v => v.Id == volumeId) .Select(v => v.CoverImage) .AsNoTracking() .SingleOrDefaultAsync(); } /// /// Returns all chapter Ids belonging to a list of Volume Ids /// /// /// public async Task> GetChapterIdsByVolumeIds(IReadOnlyList volumeIds) { return await _context.Chapter .Where(c => volumeIds.Contains(c.VolumeId)) .Select(c => c.Id) .ToListAsync(); } /// /// Returns all volumes that contain a seriesId in passed array. /// /// /// Include chapter entities /// public async Task> GetVolumesForSeriesAsync(IList seriesIds, bool includeChapters = false) { var query = _context.Volume .Where(v => seriesIds.Contains(v.SeriesId)); if (includeChapters) { query = query.Include(v => v.Chapters); } return await query.ToListAsync(); } /// /// Returns an individual Volume including Chapters and Files and Reading Progress for a given volumeId /// /// /// /// public async Task GetVolumeDtoAsync(int volumeId, int userId) { var volume = await _context.Volume .Where(vol => vol.Id == volumeId) .Include(vol => vol.Chapters) .ThenInclude(c => c.Files) .ProjectTo(_mapper.ConfigurationProvider) .SingleAsync(vol => vol.Id == volumeId); var volumeList = new List() {volume}; await AddVolumeModifiers(userId, volumeList); return volumeList[0]; } /// /// Returns the full Volumes including Chapters and Files for a given series /// /// /// public async Task> GetVolumes(int seriesId) { return await _context.Volume .Where(vol => vol.SeriesId == seriesId) .Include(vol => vol.Chapters) .ThenInclude(c => c.Files) .OrderBy(vol => vol.Number) .ToListAsync(); } /// /// Returns a single volume with Chapter and Files /// /// /// public async Task GetVolumeAsync(int volumeId) { return await _context.Volume .Include(vol => vol.Chapters) .ThenInclude(c => c.Files) .SingleOrDefaultAsync(vol => vol.Id == volumeId); } /// /// Returns all volumes for a given series with progress information attached. Includes all Chapters as well. /// /// /// /// public async Task> GetVolumesDtoAsync(int seriesId, int userId) { var volumes = await _context.Volume .Where(vol => vol.SeriesId == seriesId) .Include(vol => vol.Chapters) .ThenInclude(c => c.People) .Include(vol => vol.Chapters) .ThenInclude(c => c.Tags) .OrderBy(volume => volume.Number) .ProjectTo(_mapper.ConfigurationProvider) .AsNoTracking() .AsSplitQuery() .ToListAsync(); await AddVolumeModifiers(userId, volumes); SortSpecialChapters(volumes); return volumes; } public async Task GetVolumeByIdAsync(int volumeId) { return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId); } private static void SortSpecialChapters(IEnumerable volumes) { var sorter = new NaturalSortComparer(); foreach (var v in volumes.Where(vDto => vDto.Number == 0)) { v.Chapters = v.Chapters.OrderBy(x => x.Range, sorter).ToList(); } } private async Task AddVolumeModifiers(int userId, IReadOnlyCollection volumes) { var volIds = volumes.Select(s => s.Id); var userProgress = await _context.AppUserProgresses .Where(p => p.AppUserId == userId && volIds.Contains(p.VolumeId)) .AsNoTracking() .ToListAsync(); foreach (var v in volumes) { foreach (var c in v.Chapters) { c.PagesRead = userProgress.Where(p => p.ChapterId == c.Id).Sum(p => p.PagesRead); } v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead); } } }