Merged develop into main
This commit is contained in:
commit
aa710529f0
151 changed files with 4393 additions and 1703 deletions
|
|
@ -1,7 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
|
|
@ -11,6 +12,17 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
namespace API.Data.Repositories
|
||||
{
|
||||
|
||||
[Flags]
|
||||
public enum LibraryIncludes
|
||||
{
|
||||
None = 1,
|
||||
Series = 2,
|
||||
AppUser = 4,
|
||||
Folders = 8,
|
||||
// Ratings = 16
|
||||
}
|
||||
|
||||
public class LibraryRepository : ILibraryRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
|
@ -58,7 +70,7 @@ namespace API.Data.Repositories
|
|||
|
||||
public async Task<bool> DeleteLibrary(int libraryId)
|
||||
{
|
||||
var library = await GetLibraryForIdAsync(libraryId);
|
||||
var library = await GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders | LibraryIncludes.Series);
|
||||
_context.Library.Remove(library);
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
|
@ -91,14 +103,37 @@ namespace API.Data.Repositories
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Library> GetLibraryForIdAsync(int libraryId)
|
||||
public async Task<Library> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
.Include(l => l.Series)
|
||||
.SingleAsync();
|
||||
|
||||
var query = _context.Library
|
||||
.Where(x => x.Id == libraryId);
|
||||
|
||||
query = AddIncludesToQuery(query, includes);
|
||||
return await query.SingleAsync();
|
||||
}
|
||||
|
||||
private static IQueryable<Library> AddIncludesToQuery(IQueryable<Library> query, LibraryIncludes includeFlags)
|
||||
{
|
||||
if (includeFlags.HasFlag(LibraryIncludes.Folders))
|
||||
{
|
||||
query = query.Include(l => l.Folders);
|
||||
}
|
||||
|
||||
if (includeFlags.HasFlag(LibraryIncludes.Series))
|
||||
{
|
||||
query = query.Include(l => l.Series);
|
||||
}
|
||||
|
||||
if (includeFlags.HasFlag(LibraryIncludes.AppUser))
|
||||
{
|
||||
query = query.Include(l => l.AppUsers);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This returns a Library with all it's Series -> Volumes -> Chapters. This is expensive. Should only be called when needed.
|
||||
/// </summary>
|
||||
|
|
@ -106,7 +141,6 @@ namespace API.Data.Repositories
|
|||
/// <returns></returns>
|
||||
public async Task<Library> GetFullLibraryForIdAsync(int libraryId)
|
||||
{
|
||||
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ namespace API.Data.Repositories
|
|||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId)
|
||||
.Include(r => r.Items)
|
||||
.Include(r => r.Items.OrderBy(item => item.Order))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.Data.Scanner;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces.Repositories;
|
||||
using API.Services.Tasks;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
|
@ -26,9 +26,9 @@ namespace API.Data.Repositories
|
|||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Add(Series series)
|
||||
public void Attach(Series series)
|
||||
{
|
||||
_context.Series.Add(series);
|
||||
_context.Series.Attach(series);
|
||||
}
|
||||
|
||||
public void Update(Series series)
|
||||
|
|
@ -36,19 +36,9 @@ namespace API.Data.Repositories
|
|||
_context.Entry(series).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public async Task<bool> SaveAllAsync()
|
||||
public void Remove(Series series)
|
||||
{
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public bool SaveAll()
|
||||
{
|
||||
return _context.SaveChanges() > 0;
|
||||
}
|
||||
|
||||
public async Task<Series> GetSeriesByNameAsync(string name)
|
||||
{
|
||||
return await _context.Series.SingleOrDefaultAsync(x => x.Name == name);
|
||||
_context.Series.Remove(series);
|
||||
}
|
||||
|
||||
public async Task<bool> DoesSeriesNameExistInLibrary(string name)
|
||||
|
|
@ -64,11 +54,6 @@ namespace API.Data.Repositories
|
|||
.CountAsync() > 1;
|
||||
}
|
||||
|
||||
public Series GetSeriesByName(string name)
|
||||
{
|
||||
return _context.Series.SingleOrDefault(x => x.Name == name);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Series>> GetSeriesForLibraryIdAsync(int libraryId)
|
||||
{
|
||||
return await _context.Series
|
||||
|
|
@ -77,6 +62,43 @@ namespace API.Data.Repositories
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for <see cref="ScannerService"/> to
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<PagedList<Series>> GetFullSeriesForLibraryIdAsync(int libraryId, UserParams userParams)
|
||||
{
|
||||
var query = _context.Series
|
||||
.Where(s => s.LibraryId == libraryId)
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.OrderBy(s => s.SortName);
|
||||
|
||||
return await PagedList<Series>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a heavy call. Returns all entities down to Files and Library and Series Metadata.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Series> GetFullSeriesForSeriesIdAsync(int seriesId)
|
||||
{
|
||||
return await _context.Series
|
||||
.Where(s => s.Id == seriesId)
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.Library)
|
||||
.Include(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<PagedList<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId, UserParams userParams, FilterDto filter)
|
||||
{
|
||||
var formats = filter.GetSqlFilter();
|
||||
|
|
@ -103,41 +125,12 @@ namespace API.Data.Repositories
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId)
|
||||
{
|
||||
var volumes = await _context.Volume
|
||||
.Where(vol => vol.SeriesId == seriesId)
|
||||
.Include(vol => vol.Chapters)
|
||||
.OrderBy(volume => volume.Number)
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
await AddVolumeModifiers(userId, volumes);
|
||||
SortSpecialChapters(volumes);
|
||||
|
||||
return volumes;
|
||||
}
|
||||
|
||||
private static void SortSpecialChapters(IEnumerable<VolumeDto> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<Volume>> 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public async Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId, int userId)
|
||||
{
|
||||
|
|
@ -151,55 +144,8 @@ namespace API.Data.Repositories
|
|||
return seriesList[0];
|
||||
}
|
||||
|
||||
public async Task<Volume> GetVolumeAsync(int volumeId)
|
||||
{
|
||||
return await _context.Volume
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
||||
}
|
||||
|
||||
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId)
|
||||
{
|
||||
return await _context.Volume
|
||||
.Where(vol => vol.Id == volumeId)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.SingleAsync();
|
||||
|
||||
}
|
||||
|
||||
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId)
|
||||
{
|
||||
var volume = await _context.Volume
|
||||
.Where(vol => vol.Id == volumeId)
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.SingleAsync(vol => vol.Id == volumeId);
|
||||
|
||||
var volumeList = new List<VolumeDto>() {volume};
|
||||
await AddVolumeModifiers(userId, volumeList);
|
||||
|
||||
return volumeList[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all volumes that contain a seriesId in passed array.
|
||||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> 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();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteSeriesAsync(int seriesId)
|
||||
{
|
||||
|
|
@ -209,11 +155,12 @@ namespace API.Data.Repositories
|
|||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<Volume> GetVolumeByIdAsync(int volumeId)
|
||||
{
|
||||
return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Volumes, Metadata, and Collection Tags
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Series> GetSeriesByIdAsync(int seriesId)
|
||||
{
|
||||
return await _context.Series
|
||||
|
|
@ -244,7 +191,7 @@ namespace API.Data.Repositories
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns a list of tuples<chapterId, seriesId> back for each series id passed
|
||||
/// This returns a dictonary mapping seriesId -> list of chapters back for each series id passed
|
||||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -301,24 +248,7 @@ namespace API.Data.Repositories
|
|||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
private async Task AddVolumeModifiers(int userId, IReadOnlyCollection<VolumeDto> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of Series that were added, ordered by Created desc
|
||||
|
|
@ -497,5 +427,63 @@ namespace API.Data.Repositories
|
|||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of series for a given library (or all libraries if libraryId is 0)
|
||||
/// </summary>
|
||||
/// <param name="libraryId">Defaults to 0, library to restrict count to</param>
|
||||
/// <returns></returns>
|
||||
private async Task<int> GetSeriesCount(int libraryId = 0)
|
||||
{
|
||||
if (libraryId > 0)
|
||||
{
|
||||
return await _context.Series
|
||||
.Where(s => s.LibraryId == libraryId)
|
||||
.CountAsync();
|
||||
}
|
||||
return await _context.Series.CountAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of series that should be processed in parallel to optimize speed and memory. Minimum of 50
|
||||
/// </summary>
|
||||
/// <param name="libraryId">Defaults to 0 meaning no library</param>
|
||||
/// <returns></returns>
|
||||
private async Task<Tuple<int, int>> GetChunkSize(int libraryId = 0)
|
||||
{
|
||||
// TODO: Think about making this bigger depending on number of files a user has in said library
|
||||
// and number of cores and amount of memory. We can then make an optimal choice
|
||||
var totalSeries = await GetSeriesCount(libraryId);
|
||||
var procCount = Math.Max(Environment.ProcessorCount - 1, 1);
|
||||
|
||||
if (totalSeries < procCount * 2 || totalSeries < 50)
|
||||
{
|
||||
return new Tuple<int, int>(totalSeries, totalSeries);
|
||||
}
|
||||
|
||||
|
||||
return new Tuple<int, int>(totalSeries, Math.Max(totalSeries / procCount, 50));
|
||||
}
|
||||
|
||||
public async Task<Chunk> GetChunkInfo(int libraryId = 0)
|
||||
{
|
||||
var (totalSeries, chunkSize) = await GetChunkSize(libraryId);
|
||||
|
||||
if (totalSeries == 0) return new Chunk()
|
||||
{
|
||||
TotalChunks = 0,
|
||||
TotalSize = 0,
|
||||
ChunkSize = 0
|
||||
};
|
||||
|
||||
var totalChunks = Math.Max((int) Math.Ceiling((totalSeries * 1.0) / chunkSize), 1);
|
||||
|
||||
return new Chunk()
|
||||
{
|
||||
TotalSize = totalSeries,
|
||||
ChunkSize = chunkSize,
|
||||
TotalChunks = totalChunks
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Settings;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces.Repositories;
|
||||
|
|
@ -35,6 +35,15 @@ namespace API.Data.Repositories
|
|||
return _mapper.Map<ServerSettingDto>(settings);
|
||||
}
|
||||
|
||||
public ServerSettingDto GetSettingsDto()
|
||||
{
|
||||
var settings = _context.ServerSetting
|
||||
.Select(x => x)
|
||||
.AsNoTracking()
|
||||
.ToList();
|
||||
return _mapper.Map<ServerSettingDto>(settings);
|
||||
}
|
||||
|
||||
public Task<ServerSetting> GetSettingAsync(ServerSettingKey key)
|
||||
{
|
||||
return _context.ServerSetting.SingleOrDefaultAsync(x => x.Key == key);
|
||||
|
|
|
|||
|
|
@ -153,6 +153,16 @@ namespace API.Data.Repositories
|
|||
return await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUser>> GetNonAdminUsersAsync()
|
||||
{
|
||||
return await _userManager.GetUsersInRoleAsync(PolicyConstants.PlebRole);
|
||||
}
|
||||
|
||||
public async Task<bool> IsUserAdmin(AppUser user)
|
||||
{
|
||||
return await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole);
|
||||
}
|
||||
|
||||
public async Task<AppUserRating> GetUserRating(int seriesId, int userId)
|
||||
{
|
||||
return await _context.AppUserRating.Where(r => r.SeriesId == seriesId && r.AppUserId == userId)
|
||||
|
|
@ -237,8 +247,8 @@ namespace API.Data.Repositories
|
|||
Libraries = u.Libraries.Select(l => new LibraryDto
|
||||
{
|
||||
Name = l.Name,
|
||||
CoverImage = l.CoverImage,
|
||||
Type = l.Type,
|
||||
LastScanned = l.LastScanned,
|
||||
Folders = l.Folders.Select(x => x.Path).ToList()
|
||||
}).ToList()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Comparators;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
|
|
@ -15,10 +14,17 @@ namespace API.Data.Repositories
|
|||
public class VolumeRepository : IVolumeRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public VolumeRepository(DataContext context)
|
||||
public VolumeRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Add(Volume volume)
|
||||
{
|
||||
_context.Volume.Add(volume);
|
||||
}
|
||||
|
||||
public void Update(Volume volume)
|
||||
|
|
@ -26,6 +32,16 @@ namespace API.Data.Repositories
|
|||
_context.Entry(volume).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Remove(Volume volume)
|
||||
{
|
||||
_context.Volume.Remove(volume);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of non-tracked files for a given volume.
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<MangaFile>> GetFilesForVolume(int volumeId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
|
|
@ -36,6 +52,11 @@ namespace API.Data.Repositories
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cover image file for the given volume
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetVolumeCoverImageAsync(int volumeId)
|
||||
{
|
||||
return await _context.Volume
|
||||
|
|
@ -45,6 +66,11 @@ namespace API.Data.Repositories
|
|||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all chapter Ids belonging to a list of Volume Ids
|
||||
/// </summary>
|
||||
/// <param name="volumeIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds)
|
||||
{
|
||||
return await _context.Chapter
|
||||
|
|
@ -52,5 +78,131 @@ namespace API.Data.Repositories
|
|||
.Select(c => c.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all volumes that contain a seriesId in passed array.
|
||||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an individual Volume including Chapters and Files and Reading Progress for a given volumeId
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId)
|
||||
{
|
||||
var volume = await _context.Volume
|
||||
.Where(vol => vol.Id == volumeId)
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.SingleAsync(vol => vol.Id == volumeId);
|
||||
|
||||
var volumeList = new List<VolumeDto>() {volume};
|
||||
await AddVolumeModifiers(userId, volumeList);
|
||||
|
||||
return volumeList[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the full Volumes including Chapters and Files for a given series
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<Volume>> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single volume with Chapter and Files
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Volume> GetVolumeAsync(int volumeId)
|
||||
{
|
||||
return await _context.Volume
|
||||
.Include(vol => vol.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns all volumes for a given series with progress information attached. Includes all Chapters as well.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId)
|
||||
{
|
||||
var volumes = await _context.Volume
|
||||
.Where(vol => vol.SeriesId == seriesId)
|
||||
.Include(vol => vol.Chapters)
|
||||
.OrderBy(volume => volume.Number)
|
||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
await AddVolumeModifiers(userId, volumes);
|
||||
SortSpecialChapters(volumes);
|
||||
|
||||
return volumes;
|
||||
}
|
||||
|
||||
public async Task<Volume> GetVolumeByIdAsync(int volumeId)
|
||||
{
|
||||
return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId);
|
||||
}
|
||||
|
||||
|
||||
private static void SortSpecialChapters(IEnumerable<VolumeDto> 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<VolumeDto> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue