.NET 6 Coding Patterns + Unit Tests (#823)
* Refactored all files to have Interfaces within the same file. Started moving over to file-scoped namespaces. * Refactored common methods for getting underlying file's cover, pages, and extracting into 1 interface. * More refactoring around removing dependence on explicit filetype testing for getting information. * Code is buildable, tests are broken. Huge refactor (not completed) which makes most of DirectoryService testable with a mock filesystem (and thus the services that utilize it). * Finished porting DirectoryService to use mocked filesystem implementation. * Added a null check * Added a null check * Finished all unit tests for DirectoryService. * Some misc cleanup on the code * Fixed up some bugs from refactoring scan loop. * Implemented CleanupService testing and refactored more of DirectoryService to be non-static. Fixed a bug where cover file cleanup wasn't properly finding files due to a regex bug. * Fixed an issue in CleanupBackup() where we weren't properly selecting database files older than 30 days. Finished CleanupService Tests. * Refactored Flatten and RemoveNonImages to directory service to allow CacheService to be testable. * Finally have CacheService tested. Rewrote GetCachedPagePath() to be much more straightforward & performant. * Updated DefaultParserTests.cs to contain all existing tests and follow new test layout format. * All tests fixed up
This commit is contained in:
parent
bf1876ff44
commit
bbe8f800f6
115 changed files with 6734 additions and 5370 deletions
|
|
@ -2,78 +2,84 @@
|
|||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IAppUserProgressRepository
|
||||
{
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
void Update(AppUserProgress userProgress);
|
||||
Task<int> CleanupAbandonedChapters();
|
||||
Task<bool> UserHasProgress(LibraryType libraryType, int userId);
|
||||
Task<AppUserProgress> GetUserProgressAsync(int chapterId, int userId);
|
||||
}
|
||||
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
||||
public AppUserProgressRepository(DataContext context)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public AppUserProgressRepository(DataContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
public void Update(AppUserProgress userProgress)
|
||||
{
|
||||
_context.Entry(userProgress).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/// <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 rowsToRemove = await _context.AppUserProgresses
|
||||
.Where(progress => !chapterIds.Contains(progress.ChapterId))
|
||||
.ToListAsync();
|
||||
var rowsToRemoveBookmarks = await _context.AppUserBookmark
|
||||
.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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_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();
|
||||
|
||||
/// <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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
return await _context.Series
|
||||
.Include(s => s.Library)
|
||||
.Where(s => seriesIds.Contains(s.Id) && s.Library.Type == libraryType)
|
||||
.AsNoTracking()
|
||||
.AnyAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserProgress> GetUserProgressAsync(int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserProgresses
|
||||
.Where(p => p.ChapterId == chapterId && p.AppUserId == userId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
public async Task<AppUserProgress> GetUserProgressAsync(int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserProgresses
|
||||
.Where(p => p.ChapterId == chapterId && p.AppUserId == userId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,195 +1,207 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IChapterRepository
|
||||
{
|
||||
public class ChapterRepository : IChapterRepository
|
||||
void Update(Chapter chapter);
|
||||
Task<IEnumerable<Chapter>> GetChaptersByIdsAsync(IList<int> chapterIds);
|
||||
Task<IChapterInfoDto> GetChapterInfoDtoAsync(int chapterId);
|
||||
Task<int> GetChapterTotalPagesAsync(int chapterId);
|
||||
Task<Chapter> GetChapterAsync(int chapterId);
|
||||
Task<ChapterDto> GetChapterDtoAsync(int chapterId);
|
||||
Task<IList<MangaFile>> GetFilesForChapterAsync(int chapterId);
|
||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||
Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds);
|
||||
Task<string> GetChapterCoverImageAsync(int chapterId);
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
Task<IEnumerable<string>> GetCoverImagesForLockedChaptersAsync();
|
||||
}
|
||||
public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public ChapterRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public ChapterRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
public void Update(Chapter chapter)
|
||||
{
|
||||
_context.Entry(chapter).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Update(Chapter chapter)
|
||||
{
|
||||
_context.Entry(chapter).State = EntityState.Modified;
|
||||
}
|
||||
public async Task<IEnumerable<Chapter>> GetChaptersByIdsAsync(IList<int> chapterIds)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => chapterIds.Contains(c.Id))
|
||||
.Include(c => c.Volume)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Chapter>> GetChaptersByIdsAsync(IList<int> chapterIds)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => chapterIds.Contains(c.Id))
|
||||
.Include(c => c.Volume)
|
||||
.ToListAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Populates a partial IChapterInfoDto
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IChapterInfoDto> GetChapterInfoDtoAsync(int chapterId)
|
||||
{
|
||||
var chapterInfo = await _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Join(_context.Volume, c => c.VolumeId, v => v.Id, (chapter, volume) => new
|
||||
{
|
||||
ChapterNumber = chapter.Range,
|
||||
VolumeNumber = volume.Number,
|
||||
VolumeId = volume.Id,
|
||||
chapter.IsSpecial,
|
||||
chapter.TitleName,
|
||||
volume.SeriesId,
|
||||
chapter.Pages,
|
||||
})
|
||||
.Join(_context.Series, data => data.SeriesId, series => series.Id, (data, series) => new
|
||||
{
|
||||
data.ChapterNumber,
|
||||
data.VolumeNumber,
|
||||
data.VolumeId,
|
||||
data.IsSpecial,
|
||||
data.SeriesId,
|
||||
data.Pages,
|
||||
data.TitleName,
|
||||
SeriesFormat = series.Format,
|
||||
SeriesName = series.Name,
|
||||
series.LibraryId
|
||||
})
|
||||
.Select(data => new ChapterInfoDto()
|
||||
{
|
||||
ChapterNumber = data.ChapterNumber,
|
||||
VolumeNumber = data.VolumeNumber + string.Empty,
|
||||
VolumeId = data.VolumeId,
|
||||
IsSpecial = data.IsSpecial,
|
||||
SeriesId =data.SeriesId,
|
||||
SeriesFormat = data.SeriesFormat,
|
||||
SeriesName = data.SeriesName,
|
||||
LibraryId = data.LibraryId,
|
||||
Pages = data.Pages,
|
||||
ChapterTitle = data.TitleName
|
||||
})
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Populates a partial IChapterInfoDto
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IChapterInfoDto> GetChapterInfoDtoAsync(int chapterId)
|
||||
{
|
||||
var chapterInfo = await _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Join(_context.Volume, c => c.VolumeId, v => v.Id, (chapter, volume) => new
|
||||
{
|
||||
ChapterNumber = chapter.Range,
|
||||
VolumeNumber = volume.Number,
|
||||
VolumeId = volume.Id,
|
||||
chapter.IsSpecial,
|
||||
chapter.TitleName,
|
||||
volume.SeriesId,
|
||||
chapter.Pages,
|
||||
})
|
||||
.Join(_context.Series, data => data.SeriesId, series => series.Id, (data, series) => new
|
||||
{
|
||||
data.ChapterNumber,
|
||||
data.VolumeNumber,
|
||||
data.VolumeId,
|
||||
data.IsSpecial,
|
||||
data.SeriesId,
|
||||
data.Pages,
|
||||
data.TitleName,
|
||||
SeriesFormat = series.Format,
|
||||
SeriesName = series.Name,
|
||||
series.LibraryId
|
||||
})
|
||||
.Select(data => new ChapterInfoDto()
|
||||
{
|
||||
ChapterNumber = data.ChapterNumber,
|
||||
VolumeNumber = data.VolumeNumber + string.Empty,
|
||||
VolumeId = data.VolumeId,
|
||||
IsSpecial = data.IsSpecial,
|
||||
SeriesId =data.SeriesId,
|
||||
SeriesFormat = data.SeriesFormat,
|
||||
SeriesName = data.SeriesName,
|
||||
LibraryId = data.LibraryId,
|
||||
Pages = data.Pages,
|
||||
ChapterTitle = data.TitleName
|
||||
})
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
return chapterInfo;
|
||||
}
|
||||
|
||||
return chapterInfo;
|
||||
}
|
||||
public Task<int> GetChapterTotalPagesAsync(int chapterId)
|
||||
{
|
||||
return _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Select(c => c.Pages)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
|
||||
public Task<int> GetChapterTotalPagesAsync(int chapterId)
|
||||
{
|
||||
return _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Select(c => c.Pages)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
return chapter;
|
||||
}
|
||||
|
||||
return chapter;
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns non-tracked files for a given chapterId
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<MangaFile>> GetFilesForChapterAsync(int chapterId)
|
||||
{
|
||||
return await _context.MangaFile
|
||||
.Where(c => chapterId == c.ChapterId)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns non-tracked files for a given chapterId
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<MangaFile>> GetFilesForChapterAsync(int chapterId)
|
||||
{
|
||||
return await _context.MangaFile
|
||||
.Where(c => chapterId == c.ChapterId)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns a Chapter for an Id. Includes linked <see cref="MangaFile"/>s.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Chapter> GetChapterAsync(int chapterId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Chapter for an Id. Includes linked <see cref="MangaFile"/>s.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Chapter> GetChapterAsync(int chapterId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns Chapters for a volume id.
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<Chapter>> GetChaptersAsync(int volumeId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.VolumeId == volumeId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Chapters for a volume id.
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<Chapter>> GetChaptersAsync(int volumeId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.VolumeId == volumeId)
|
||||
.ToListAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the cover image for a chapter id.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetChapterCoverImageAsync(int chapterId)
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cover image for a chapter id.
|
||||
/// </summary>
|
||||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetChapterCoverImageAsync(int chapterId)
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Select(c => c.CoverImage)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
return await _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Select(c => c.CoverImage)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<IList<string>> GetAllCoverImagesAsync()
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Select(c => c.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<string>> GetAllCoverImagesAsync()
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Select(c => c.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns cover images for locked chapters
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<string>> GetCoverImagesForLockedChaptersAsync()
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.CoverImageLocked)
|
||||
.Select(c => c.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns cover images for locked chapters
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<string>> GetCoverImagesForLockedChaptersAsync()
|
||||
{
|
||||
return await _context.Chapter
|
||||
.Where(c => c.CoverImageLocked)
|
||||
.Select(c => c.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns non-tracked files for a set of <paramref name="chapterIds"/>
|
||||
/// </summary>
|
||||
/// <param name="chapterIds">List of chapter Ids</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds)
|
||||
{
|
||||
return await _context.MangaFile
|
||||
.Where(c => chapterIds.Contains(c.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns non-tracked files for a set of <paramref name="chapterIds"/>
|
||||
/// </summary>
|
||||
/// <param name="chapterIds">List of chapter Ids</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds)
|
||||
{
|
||||
return await _context.MangaFile
|
||||
.Where(c => chapterIds.Contains(c.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,123 +3,136 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using API.DTOs.CollectionTags;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface ICollectionTagRepository
|
||||
{
|
||||
public class CollectionTagRepository : ICollectionTagRepository
|
||||
void Add(CollectionTag tag);
|
||||
void Remove(CollectionTag tag);
|
||||
Task<IEnumerable<CollectionTagDto>> GetAllTagDtosAsync();
|
||||
Task<IEnumerable<CollectionTagDto>> SearchTagDtosAsync(string searchQuery);
|
||||
Task<string> GetCoverImageAsync(int collectionTagId);
|
||||
Task<IEnumerable<CollectionTagDto>> GetAllPromotedTagDtosAsync();
|
||||
Task<CollectionTag> GetTagAsync(int tagId);
|
||||
Task<CollectionTag> GetFullTagAsync(int tagId);
|
||||
void Update(CollectionTag tag);
|
||||
Task<int> RemoveTagsWithoutSeries();
|
||||
Task<IEnumerable<CollectionTag>> GetAllTagsAsync();
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
}
|
||||
public class CollectionTagRepository : ICollectionTagRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public CollectionTagRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public CollectionTagRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
public void Add(CollectionTag tag)
|
||||
{
|
||||
_context.CollectionTag.Add(tag);
|
||||
}
|
||||
|
||||
public void Add(CollectionTag tag)
|
||||
{
|
||||
_context.CollectionTag.Add(tag);
|
||||
}
|
||||
public void Remove(CollectionTag tag)
|
||||
{
|
||||
_context.CollectionTag.Remove(tag);
|
||||
}
|
||||
|
||||
public void Remove(CollectionTag tag)
|
||||
{
|
||||
_context.CollectionTag.Remove(tag);
|
||||
}
|
||||
public void Update(CollectionTag tag)
|
||||
{
|
||||
_context.Entry(tag).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Update(CollectionTag tag)
|
||||
{
|
||||
_context.Entry(tag).State = EntityState.Modified;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes any collection tags without any series
|
||||
/// </summary>
|
||||
public async Task<int> RemoveTagsWithoutSeries()
|
||||
{
|
||||
var tagsToDelete = await _context.CollectionTag
|
||||
.Include(c => c.SeriesMetadatas)
|
||||
.Where(c => c.SeriesMetadatas.Count == 0)
|
||||
.ToListAsync();
|
||||
_context.RemoveRange(tagsToDelete);
|
||||
|
||||
/// <summary>
|
||||
/// Removes any collection tags without any series
|
||||
/// </summary>
|
||||
public async Task<int> RemoveTagsWithoutSeries()
|
||||
{
|
||||
var tagsToDelete = await _context.CollectionTag
|
||||
.Include(c => c.SeriesMetadatas)
|
||||
.Where(c => c.SeriesMetadatas.Count == 0)
|
||||
.ToListAsync();
|
||||
_context.RemoveRange(tagsToDelete);
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return await _context.SaveChangesAsync();
|
||||
}
|
||||
public async Task<IEnumerable<CollectionTag>> GetAllTagsAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CollectionTag>> GetAllTagsAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<IList<string>> GetAllCoverImagesAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Select(t => t.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IList<string>> GetAllCoverImagesAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Select(t => t.CoverImage)
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<IEnumerable<CollectionTagDto>> GetAllTagDtosAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Select(c => c)
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CollectionTagDto>> GetAllTagDtosAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Select(c => c)
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<IEnumerable<CollectionTagDto>> GetAllPromotedTagDtosAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Promoted)
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CollectionTagDto>> GetAllPromotedTagDtosAsync()
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Promoted)
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<CollectionTag> GetTagAsync(int tagId)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Id == tagId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<CollectionTag> GetTagAsync(int tagId)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Id == tagId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<CollectionTag> GetFullTagAsync(int tagId)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Id == tagId)
|
||||
.Include(c => c.SeriesMetadatas)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<CollectionTag> GetFullTagAsync(int tagId)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Id == tagId)
|
||||
.Include(c => c.SeriesMetadatas)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<IEnumerable<CollectionTagDto>> SearchTagDtosAsync(string searchQuery)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(s => EF.Functions.Like(s.Title, $"%{searchQuery}%")
|
||||
|| EF.Functions.Like(s.NormalizedTitle, $"%{searchQuery}%"))
|
||||
.OrderBy(s => s.Title)
|
||||
.AsNoTracking()
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CollectionTagDto>> SearchTagDtosAsync(string searchQuery)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(s => EF.Functions.Like(s.Title, $"%{searchQuery}%")
|
||||
|| EF.Functions.Like(s.NormalizedTitle, $"%{searchQuery}%"))
|
||||
.OrderBy(s => s.Title)
|
||||
.AsNoTracking()
|
||||
.OrderBy(c => c.NormalizedTitle)
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<string> GetCoverImageAsync(int collectionTagId)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Id == collectionTagId)
|
||||
.Select(c => c.CoverImage)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<string> GetCoverImageAsync(int collectionTagId)
|
||||
{
|
||||
return await _context.CollectionTag
|
||||
.Where(c => c.Id == collectionTagId)
|
||||
.Select(c => c.CoverImage)
|
||||
.AsNoTracking()
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,20 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IGenreRepository
|
||||
{
|
||||
void Attach(Genre genre);
|
||||
void Remove(Genre genre);
|
||||
Task<Genre> FindByNameAsync(string genreName);
|
||||
Task<IList<Genre>> GetAllGenres();
|
||||
Task RemoveAllGenreNoLongerAssociated(bool removeExternal = false);
|
||||
}
|
||||
|
||||
public class GenreRepository : IGenreRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
|
@ -51,6 +59,6 @@ public class GenreRepository : IGenreRepository
|
|||
|
||||
public async Task<IList<Genre>> GetAllGenres()
|
||||
{
|
||||
return await _context.Genre.ToListAsync();;
|
||||
return await _context.Genre.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,194 +5,208 @@ using System.Threading.Tasks;
|
|||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
[Flags]
|
||||
public enum LibraryIncludes
|
||||
{
|
||||
|
||||
[Flags]
|
||||
public enum LibraryIncludes
|
||||
{
|
||||
None = 1,
|
||||
Series = 2,
|
||||
AppUser = 4,
|
||||
Folders = 8,
|
||||
// Ratings = 16
|
||||
}
|
||||
|
||||
public class LibraryRepository : ILibraryRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public LibraryRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Add(Library library)
|
||||
{
|
||||
_context.Library.Add(library);
|
||||
}
|
||||
|
||||
public void Update(Library library)
|
||||
{
|
||||
_context.Entry(library).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Delete(Library library)
|
||||
{
|
||||
_context.Library.Remove(library);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName)
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
|
||||
.OrderBy(l => l.Name)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSingleQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Library>> GetLibrariesAsync()
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteLibrary(int libraryId)
|
||||
{
|
||||
var library = await GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders | LibraryIncludes.Series);
|
||||
_context.Library.Remove(library);
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Library>> GetLibrariesForUserIdAsync(int userId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(l => l.AppUsers.Select(ap => ap.Id).Contains(userId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<LibraryType> GetLibraryTypeAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(l => l.Id == libraryId)
|
||||
.AsNoTracking()
|
||||
.Select(l => l.Type)
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync()
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(f => f.Folders)
|
||||
.OrderBy(l => l.Name)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Library> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes)
|
||||
{
|
||||
|
||||
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>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Library> GetFullLibraryForIdAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
.Include(l => l.Series)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(l => l.Series)
|
||||
.ThenInclude(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a heavy call, pulls all entities for a Library, except this version only grabs for one series id
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Library> GetFullLibraryForIdAsync(int libraryId, int seriesId)
|
||||
{
|
||||
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
.Include(l => l.Series.Where(s => s.Id == seriesId))
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(l => l.Series.Where(s => s.Id == seriesId))
|
||||
.ThenInclude(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> LibraryExists(string libraryName)
|
||||
{
|
||||
return await _context.Library
|
||||
.AsNoTracking()
|
||||
.AnyAsync(x => x.Name == libraryName);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibrariesForUserAsync(AppUser user)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(library => library.AppUsers.Contains(user))
|
||||
.Include(l => l.Folders)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
None = 1,
|
||||
Series = 2,
|
||||
AppUser = 4,
|
||||
Folders = 8,
|
||||
// Ratings = 16
|
||||
}
|
||||
|
||||
public interface ILibraryRepository
|
||||
{
|
||||
void Add(Library library);
|
||||
void Update(Library library);
|
||||
void Delete(Library library);
|
||||
Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync();
|
||||
Task<bool> LibraryExists(string libraryName);
|
||||
Task<Library> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes);
|
||||
Task<Library> GetFullLibraryForIdAsync(int libraryId);
|
||||
Task<Library> GetFullLibraryForIdAsync(int libraryId, int seriesId);
|
||||
Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName);
|
||||
Task<IEnumerable<Library>> GetLibrariesAsync();
|
||||
Task<bool> DeleteLibrary(int libraryId);
|
||||
Task<IEnumerable<Library>> GetLibrariesForUserIdAsync(int userId);
|
||||
Task<LibraryType> GetLibraryTypeAsync(int libraryId);
|
||||
}
|
||||
|
||||
public class LibraryRepository : ILibraryRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public LibraryRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Add(Library library)
|
||||
{
|
||||
_context.Library.Add(library);
|
||||
}
|
||||
|
||||
public void Update(Library library)
|
||||
{
|
||||
_context.Entry(library).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Delete(Library library)
|
||||
{
|
||||
_context.Library.Remove(library);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName)
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
|
||||
.OrderBy(l => l.Name)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSingleQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Library>> GetLibrariesAsync()
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteLibrary(int libraryId)
|
||||
{
|
||||
var library = await GetLibraryForIdAsync(libraryId, LibraryIncludes.Folders | LibraryIncludes.Series);
|
||||
_context.Library.Remove(library);
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Library>> GetLibrariesForUserIdAsync(int userId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(l => l.AppUsers.Select(ap => ap.Id).Contains(userId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<LibraryType> GetLibraryTypeAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(l => l.Id == libraryId)
|
||||
.AsNoTracking()
|
||||
.Select(l => l.Type)
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync()
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(f => f.Folders)
|
||||
.OrderBy(l => l.Name)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Library> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes)
|
||||
{
|
||||
|
||||
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>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Library> GetFullLibraryForIdAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
.Include(l => l.Series)
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(l => l.Series)
|
||||
.ThenInclude(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a heavy call, pulls all entities for a Library, except this version only grabs for one series id
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Library> GetFullLibraryForIdAsync(int libraryId, int seriesId)
|
||||
{
|
||||
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
.Include(l => l.Series.Where(s => s.Id == seriesId))
|
||||
.ThenInclude(s => s.Metadata)
|
||||
.Include(l => l.Series.Where(s => s.Id == seriesId))
|
||||
.ThenInclude(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> LibraryExists(string libraryName)
|
||||
{
|
||||
return await _context.Library
|
||||
.AsNoTracking()
|
||||
.AnyAsync(x => x.Name == libraryName);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibrariesForUserAsync(AppUser user)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(library => library.AppUsers.Contains(user))
|
||||
.Include(l => l.Folders)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,59 +2,65 @@
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IPersonRepository
|
||||
{
|
||||
public class PersonRepository : IPersonRepository
|
||||
void Attach(Person person);
|
||||
void Remove(Person person);
|
||||
Task<IList<Person>> GetAllPeople();
|
||||
Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false);
|
||||
}
|
||||
|
||||
public class PersonRepository : IPersonRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public PersonRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public PersonRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
public void Attach(Person person)
|
||||
{
|
||||
_context.Person.Attach(person);
|
||||
}
|
||||
|
||||
public void Attach(Person person)
|
||||
{
|
||||
_context.Person.Attach(person);
|
||||
}
|
||||
public void Remove(Person person)
|
||||
{
|
||||
_context.Person.Remove(person);
|
||||
}
|
||||
|
||||
public void Remove(Person person)
|
||||
{
|
||||
_context.Person.Remove(person);
|
||||
}
|
||||
public async Task<Person> FindByNameAsync(string name)
|
||||
{
|
||||
var normalizedName = Parser.Parser.Normalize(name);
|
||||
return await _context.Person
|
||||
.Where(p => normalizedName.Equals(p.NormalizedName))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<Person> FindByNameAsync(string name)
|
||||
{
|
||||
var normalizedName = Parser.Parser.Normalize(name);
|
||||
return await _context.Person
|
||||
.Where(p => normalizedName.Equals(p.NormalizedName))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false)
|
||||
{
|
||||
var peopleWithNoConnections = await _context.Person
|
||||
.Include(p => p.SeriesMetadatas)
|
||||
.Include(p => p.ChapterMetadatas)
|
||||
.Where(p => p.SeriesMetadatas.Count == 0 && p.ChapterMetadatas.Count == 0)
|
||||
.ToListAsync();
|
||||
|
||||
public async Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false)
|
||||
{
|
||||
var peopleWithNoConnections = await _context.Person
|
||||
.Include(p => p.SeriesMetadatas)
|
||||
.Include(p => p.ChapterMetadatas)
|
||||
.Where(p => p.SeriesMetadatas.Count == 0 && p.ChapterMetadatas.Count == 0)
|
||||
.ToListAsync();
|
||||
_context.Person.RemoveRange(peopleWithNoConnections);
|
||||
|
||||
_context.Person.RemoveRange(peopleWithNoConnections);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IList<Person>> GetAllPeople()
|
||||
{
|
||||
return await _context.Person
|
||||
.ToListAsync();
|
||||
}
|
||||
public async Task<IList<Person>> GetAllPeople()
|
||||
{
|
||||
return await _context.Person
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,175 +4,187 @@ using System.Threading.Tasks;
|
|||
using API.DTOs.ReadingLists;
|
||||
using API.Entities;
|
||||
using API.Helpers;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IReadingListRepository
|
||||
{
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams);
|
||||
Task<ReadingList> GetReadingListByIdAsync(int readingListId);
|
||||
Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId);
|
||||
Task<ReadingListDto> GetReadingListDtoByIdAsync(int readingListId, int userId);
|
||||
Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items);
|
||||
Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title);
|
||||
Task<IEnumerable<ReadingListItem>> GetReadingListItemsByIdAsync(int readingListId);
|
||||
void Remove(ReadingListItem item);
|
||||
void BulkRemove(IEnumerable<ReadingListItem> items);
|
||||
void Update(ReadingList list);
|
||||
}
|
||||
|
||||
public class ReadingListRepository : IReadingListRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public ReadingListRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public ReadingListRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
public void Update(ReadingList list)
|
||||
{
|
||||
_context.Entry(list).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Update(ReadingList list)
|
||||
{
|
||||
_context.Entry(list).State = EntityState.Modified;
|
||||
}
|
||||
public void Remove(ReadingListItem item)
|
||||
{
|
||||
_context.ReadingListItem.Remove(item);
|
||||
}
|
||||
|
||||
public void Remove(ReadingListItem item)
|
||||
{
|
||||
_context.ReadingListItem.Remove(item);
|
||||
}
|
||||
|
||||
public void BulkRemove(IEnumerable<ReadingListItem> items)
|
||||
{
|
||||
_context.ReadingListItem.RemoveRange(items);
|
||||
}
|
||||
public void BulkRemove(IEnumerable<ReadingListItem> items)
|
||||
{
|
||||
_context.ReadingListItem.RemoveRange(items);
|
||||
}
|
||||
|
||||
|
||||
public async Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams)
|
||||
{
|
||||
var query = _context.ReadingList
|
||||
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
||||
.OrderBy(l => l.LastModified)
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking();
|
||||
public async Task<PagedList<ReadingListDto>> GetReadingListDtosForUserAsync(int userId, bool includePromoted, UserParams userParams)
|
||||
{
|
||||
var query = _context.ReadingList
|
||||
.Where(l => l.AppUserId == userId || (includePromoted && l.Promoted ))
|
||||
.OrderBy(l => l.LastModified)
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking();
|
||||
|
||||
return await PagedList<ReadingListDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
return await PagedList<ReadingListDto>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
||||
public async Task<ReadingList> GetReadingListByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId)
|
||||
.Include(r => r.Items.OrderBy(item => item.Order))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<ReadingList> GetReadingListByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId)
|
||||
.Include(r => r.Items.OrderBy(item => item.Order))
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId)
|
||||
{
|
||||
var userLibraries = _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(user => user.Id == userId))
|
||||
.AsNoTracking()
|
||||
.Select(library => library.Id)
|
||||
.ToList();
|
||||
public async Task<IEnumerable<ReadingListItemDto>> GetReadingListItemDtosByIdAsync(int readingListId, int userId)
|
||||
{
|
||||
var userLibraries = _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(user => user.Id == userId))
|
||||
.AsNoTracking()
|
||||
.Select(library => library.Id)
|
||||
.ToList();
|
||||
|
||||
var items = await _context.ReadingListItem
|
||||
.Where(s => s.ReadingListId == readingListId)
|
||||
.Join(_context.Chapter, s => s.ChapterId, chapter => chapter.Id, (data, chapter) => new
|
||||
{
|
||||
TotalPages = chapter.Pages,
|
||||
ChapterNumber = chapter.Range,
|
||||
readingListItem = data
|
||||
})
|
||||
.Join(_context.Volume, s => s.readingListItem.VolumeId, volume => volume.Id, (data, volume) => new
|
||||
var items = await _context.ReadingListItem
|
||||
.Where(s => s.ReadingListId == readingListId)
|
||||
.Join(_context.Chapter, s => s.ChapterId, chapter => chapter.Id, (data, chapter) => new
|
||||
{
|
||||
TotalPages = chapter.Pages,
|
||||
ChapterNumber = chapter.Range,
|
||||
readingListItem = data
|
||||
})
|
||||
.Join(_context.Volume, s => s.readingListItem.VolumeId, volume => volume.Id, (data, volume) => new
|
||||
{
|
||||
data.readingListItem,
|
||||
data.TotalPages,
|
||||
data.ChapterNumber,
|
||||
VolumeId = volume.Id,
|
||||
VolumeNumber = volume.Name,
|
||||
})
|
||||
.Join(_context.Series, s => s.readingListItem.SeriesId, series => series.Id,
|
||||
(data, s) => new
|
||||
{
|
||||
SeriesName = s.Name,
|
||||
SeriesFormat = s.Format,
|
||||
s.LibraryId,
|
||||
data.readingListItem,
|
||||
data.TotalPages,
|
||||
data.ChapterNumber,
|
||||
VolumeId = volume.Id,
|
||||
VolumeNumber = volume.Name,
|
||||
data.VolumeNumber,
|
||||
data.VolumeId
|
||||
})
|
||||
.Join(_context.Series, s => s.readingListItem.SeriesId, series => series.Id,
|
||||
(data, s) => new
|
||||
{
|
||||
SeriesName = s.Name,
|
||||
SeriesFormat = s.Format,
|
||||
s.LibraryId,
|
||||
data.readingListItem,
|
||||
data.TotalPages,
|
||||
data.ChapterNumber,
|
||||
data.VolumeNumber,
|
||||
data.VolumeId
|
||||
})
|
||||
.Select(data => new ReadingListItemDto()
|
||||
{
|
||||
Id = data.readingListItem.Id,
|
||||
ChapterId = data.readingListItem.ChapterId,
|
||||
Order = data.readingListItem.Order,
|
||||
SeriesId = data.readingListItem.SeriesId,
|
||||
SeriesName = data.SeriesName,
|
||||
SeriesFormat = data.SeriesFormat,
|
||||
PagesTotal = data.TotalPages,
|
||||
ChapterNumber = data.ChapterNumber,
|
||||
VolumeNumber = data.VolumeNumber,
|
||||
LibraryId = data.LibraryId,
|
||||
VolumeId = data.VolumeId,
|
||||
ReadingListId = data.readingListItem.ReadingListId
|
||||
})
|
||||
.Where(o => userLibraries.Contains(o.LibraryId))
|
||||
.OrderBy(rli => rli.Order)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
// Attach progress information
|
||||
var fetchedChapterIds = items.Select(i => i.ChapterId);
|
||||
var progresses = await _context.AppUserProgresses
|
||||
.Where(p => fetchedChapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var progress in progresses)
|
||||
.Select(data => new ReadingListItemDto()
|
||||
{
|
||||
var progressItem = items.SingleOrDefault(i => i.ChapterId == progress.ChapterId && i.ReadingListId == readingListId);
|
||||
if (progressItem == null) continue;
|
||||
Id = data.readingListItem.Id,
|
||||
ChapterId = data.readingListItem.ChapterId,
|
||||
Order = data.readingListItem.Order,
|
||||
SeriesId = data.readingListItem.SeriesId,
|
||||
SeriesName = data.SeriesName,
|
||||
SeriesFormat = data.SeriesFormat,
|
||||
PagesTotal = data.TotalPages,
|
||||
ChapterNumber = data.ChapterNumber,
|
||||
VolumeNumber = data.VolumeNumber,
|
||||
LibraryId = data.LibraryId,
|
||||
VolumeId = data.VolumeId,
|
||||
ReadingListId = data.readingListItem.ReadingListId
|
||||
})
|
||||
.Where(o => userLibraries.Contains(o.LibraryId))
|
||||
.OrderBy(rli => rli.Order)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
progressItem.PagesRead = progress.PagesRead;
|
||||
}
|
||||
// Attach progress information
|
||||
var fetchedChapterIds = items.Select(i => i.ChapterId);
|
||||
var progresses = await _context.AppUserProgresses
|
||||
.Where(p => fetchedChapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByIdAsync(int readingListId, int userId)
|
||||
foreach (var progress in progresses)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId && (r.AppUserId == userId || r.Promoted))
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
var progressItem = items.SingleOrDefault(i => i.ChapterId == progress.ChapterId && i.ReadingListId == readingListId);
|
||||
if (progressItem == null) continue;
|
||||
|
||||
progressItem.PagesRead = progress.PagesRead;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items)
|
||||
{
|
||||
var chapterIds = items.Select(i => i.ChapterId).Distinct().ToList();
|
||||
var userProgress = await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId && chapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var progress = userProgress.Where(p => p.ChapterId == item.ChapterId);
|
||||
item.PagesRead = progress.Sum(p => p.PagesRead);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Title.Equals(title))
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItem>> GetReadingListItemsByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingListItem
|
||||
.Where(r => r.ReadingListId == readingListId)
|
||||
.OrderBy(r => r.Order)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByIdAsync(int readingListId, int userId)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Id == readingListId && (r.AppUserId == userId || r.Promoted))
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items)
|
||||
{
|
||||
var chapterIds = items.Select(i => i.ChapterId).Distinct().ToList();
|
||||
var userProgress = await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId && chapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var progress = userProgress.Where(p => p.ChapterId == item.ChapterId);
|
||||
item.PagesRead = progress.Sum(p => p.PagesRead);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<ReadingListDto> GetReadingListDtoByTitleAsync(string title)
|
||||
{
|
||||
return await _context.ReadingList
|
||||
.Where(r => r.Title.Equals(title))
|
||||
.ProjectTo<ReadingListDto>(_mapper.ConfigurationProvider)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ReadingListItem>> GetReadingListItemsByIdAsync(int readingListId)
|
||||
{
|
||||
return await _context.ReadingListItem
|
||||
.Where(r => r.ReadingListId == readingListId)
|
||||
.OrderBy(r => r.Order)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
using API.Entities;
|
||||
using API.Entities.Metadata;
|
||||
using API.Interfaces.Repositories;
|
||||
using API.Entities.Metadata;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface ISeriesMetadataRepository
|
||||
{
|
||||
public class SeriesMetadataRepository : ISeriesMetadataRepository
|
||||
void Update(SeriesMetadata seriesMetadata);
|
||||
}
|
||||
|
||||
public class SeriesMetadataRepository : ISeriesMetadataRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
||||
public SeriesMetadataRepository(DataContext context)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public SeriesMetadataRepository(DataContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public void Update(SeriesMetadata seriesMetadata)
|
||||
{
|
||||
_context.SeriesMetadata.Update(seriesMetadata);
|
||||
}
|
||||
public void Update(SeriesMetadata seriesMetadata)
|
||||
{
|
||||
_context.SeriesMetadata.Update(seriesMetadata);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,45 +4,50 @@ using System.Threading.Tasks;
|
|||
using API.DTOs.Settings;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface ISettingsRepository
|
||||
{
|
||||
public class SettingsRepository : ISettingsRepository
|
||||
void Update(ServerSetting settings);
|
||||
Task<ServerSettingDto> GetSettingsDtoAsync();
|
||||
Task<ServerSetting> GetSettingAsync(ServerSettingKey key);
|
||||
Task<IEnumerable<ServerSetting>> GetSettingsAsync();
|
||||
}
|
||||
public class SettingsRepository : ISettingsRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public SettingsRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public SettingsRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
public void Update(ServerSetting settings)
|
||||
{
|
||||
_context.Entry(settings).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Update(ServerSetting settings)
|
||||
{
|
||||
_context.Entry(settings).State = EntityState.Modified;
|
||||
}
|
||||
public async Task<ServerSettingDto> GetSettingsDtoAsync()
|
||||
{
|
||||
var settings = await _context.ServerSetting
|
||||
.Select(x => x)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
return _mapper.Map<ServerSettingDto>(settings);
|
||||
}
|
||||
|
||||
public async Task<ServerSettingDto> GetSettingsDtoAsync()
|
||||
{
|
||||
var settings = await _context.ServerSetting
|
||||
.Select(x => x)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
return _mapper.Map<ServerSettingDto>(settings);
|
||||
}
|
||||
public Task<ServerSetting> GetSettingAsync(ServerSettingKey key)
|
||||
{
|
||||
return _context.ServerSetting.SingleOrDefaultAsync(x => x.Key == key);
|
||||
}
|
||||
|
||||
public Task<ServerSetting> GetSettingAsync(ServerSettingKey key)
|
||||
{
|
||||
return _context.ServerSetting.SingleOrDefaultAsync(x => x.Key == key);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ServerSetting>> GetSettingsAsync()
|
||||
{
|
||||
return await _context.ServerSetting.ToListAsync();
|
||||
}
|
||||
public async Task<IEnumerable<ServerSetting>> GetSettingsAsync()
|
||||
{
|
||||
return await _context.ServerSetting.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,254 +6,276 @@ using API.Constants;
|
|||
using API.DTOs;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
[Flags]
|
||||
public enum AppUserIncludes
|
||||
{
|
||||
[Flags]
|
||||
public enum AppUserIncludes
|
||||
None = 1,
|
||||
Progress = 2,
|
||||
Bookmarks = 4,
|
||||
ReadingLists = 8,
|
||||
Ratings = 16
|
||||
}
|
||||
|
||||
public interface IUserRepository
|
||||
{
|
||||
void Update(AppUser user);
|
||||
void Update(AppUserPreferences preferences);
|
||||
void Update(AppUserBookmark bookmark);
|
||||
public void Delete(AppUser user);
|
||||
Task<IEnumerable<MemberDto>> GetMembersAsync();
|
||||
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
|
||||
Task<IEnumerable<AppUser>> GetNonAdminUsersAsync();
|
||||
Task<bool> IsUserAdmin(AppUser user);
|
||||
Task<AppUserRating> GetUserRating(int seriesId, int userId);
|
||||
Task<AppUserPreferences> GetPreferencesAsync(string username);
|
||||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId);
|
||||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForVolume(int userId, int volumeId);
|
||||
Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForChapter(int userId, int chapterId);
|
||||
Task<IEnumerable<BookmarkDto>> GetAllBookmarkDtos(int userId);
|
||||
Task<AppUserBookmark> GetBookmarkForPage(int page, int chapterId, int userId);
|
||||
Task<int> GetUserIdByApiKeyAsync(string apiKey);
|
||||
Task<AppUser> GetUserByUsernameAsync(string username, AppUserIncludes includeFlags = AppUserIncludes.None);
|
||||
Task<AppUser> GetUserByIdAsync(int userId, AppUserIncludes includeFlags = AppUserIncludes.None);
|
||||
Task<int> GetUserIdByUsernameAsync(string username);
|
||||
Task<AppUser> GetUserWithReadingListsByUsernameAsync(string username);
|
||||
}
|
||||
|
||||
public class UserRepository : IUserRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public UserRepository(DataContext context, UserManager<AppUser> userManager, IMapper mapper)
|
||||
{
|
||||
None = 1,
|
||||
Progress = 2,
|
||||
Bookmarks = 4,
|
||||
ReadingLists = 8,
|
||||
Ratings = 16
|
||||
_context = context;
|
||||
_userManager = userManager;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public class UserRepository : IUserRepository
|
||||
public void Update(AppUser user)
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
private readonly IMapper _mapper;
|
||||
_context.Entry(user).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public UserRepository(DataContext context, UserManager<AppUser> userManager, IMapper mapper)
|
||||
public void Update(AppUserPreferences preferences)
|
||||
{
|
||||
_context.Entry(preferences).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Update(AppUserBookmark bookmark)
|
||||
{
|
||||
_context.Entry(bookmark).State = EntityState.Modified;
|
||||
}
|
||||
|
||||
public void Delete(AppUser user)
|
||||
{
|
||||
_context.AppUser.Remove(user);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A one stop shop to get a tracked AppUser instance with any number of JOINs generated by passing bitwise flags.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="includeFlags">Includes() you want. Pass multiple with flag1 | flag2 </param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserByUsernameAsync(string username, AppUserIncludes includeFlags = AppUserIncludes.None)
|
||||
{
|
||||
var query = _context.Users
|
||||
.Where(x => x.UserName == username);
|
||||
|
||||
query = AddIncludesToQuery(query, includeFlags);
|
||||
|
||||
return await query.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A one stop shop to get a tracked AppUser instance with any number of JOINs generated by passing bitwise flags.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="includeFlags">Includes() you want. Pass multiple with flag1 | flag2 </param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserByIdAsync(int userId, AppUserIncludes includeFlags = AppUserIncludes.None)
|
||||
{
|
||||
var query = _context.Users
|
||||
.Where(x => x.Id == userId);
|
||||
|
||||
query = AddIncludesToQuery(query, includeFlags);
|
||||
|
||||
return await query.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserBookmark> GetBookmarkForPage(int page, int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(b => b.Page == page && b.ChapterId == chapterId && b.AppUserId == userId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
private static IQueryable<AppUser> AddIncludesToQuery(IQueryable<AppUser> query, AppUserIncludes includeFlags)
|
||||
{
|
||||
if (includeFlags.HasFlag(AppUserIncludes.Bookmarks))
|
||||
{
|
||||
_context = context;
|
||||
_userManager = userManager;
|
||||
_mapper = mapper;
|
||||
query = query.Include(u => u.Bookmarks);
|
||||
}
|
||||
|
||||
public void Update(AppUser user)
|
||||
if (includeFlags.HasFlag(AppUserIncludes.Progress))
|
||||
{
|
||||
_context.Entry(user).State = EntityState.Modified;
|
||||
query = query.Include(u => u.Progresses);
|
||||
}
|
||||
|
||||
public void Update(AppUserPreferences preferences)
|
||||
if (includeFlags.HasFlag(AppUserIncludes.ReadingLists))
|
||||
{
|
||||
_context.Entry(preferences).State = EntityState.Modified;
|
||||
query = query.Include(u => u.ReadingLists);
|
||||
}
|
||||
|
||||
public void Update(AppUserBookmark bookmark)
|
||||
if (includeFlags.HasFlag(AppUserIncludes.Ratings))
|
||||
{
|
||||
_context.Entry(bookmark).State = EntityState.Modified;
|
||||
query = query.Include(u => u.Ratings);
|
||||
}
|
||||
|
||||
public void Delete(AppUser user)
|
||||
{
|
||||
_context.AppUser.Remove(user);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A one stop shop to get a tracked AppUser instance with any number of JOINs generated by passing bitwise flags.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="includeFlags">Includes() you want. Pass multiple with flag1 | flag2 </param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserByUsernameAsync(string username, AppUserIncludes includeFlags = AppUserIncludes.None)
|
||||
{
|
||||
var query = _context.Users
|
||||
.Where(x => x.UserName == username);
|
||||
|
||||
query = AddIncludesToQuery(query, includeFlags);
|
||||
/// <summary>
|
||||
/// This fetches the Id for a user. Use whenever you just need an ID.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> GetUserIdByUsernameAsync(string username)
|
||||
{
|
||||
return await _context.Users
|
||||
.Where(x => x.UserName == username)
|
||||
.Select(u => u.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
return await query.SingleOrDefaultAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets an AppUser by username. Returns back Reading List and their Items.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserWithReadingListsByUsernameAsync(string username)
|
||||
{
|
||||
return await _context.Users
|
||||
.Include(u => u.ReadingLists)
|
||||
.ThenInclude(l => l.Items)
|
||||
.SingleOrDefaultAsync(x => x.UserName == username);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A one stop shop to get a tracked AppUser instance with any number of JOINs generated by passing bitwise flags.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="includeFlags">Includes() you want. Pass multiple with flag1 | flag2 </param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserByIdAsync(int userId, AppUserIncludes includeFlags = AppUserIncludes.None)
|
||||
{
|
||||
var query = _context.Users
|
||||
.Where(x => x.Id == userId);
|
||||
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
|
||||
{
|
||||
return await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole);
|
||||
}
|
||||
|
||||
query = AddIncludesToQuery(query, includeFlags);
|
||||
public async Task<IEnumerable<AppUser>> GetNonAdminUsersAsync()
|
||||
{
|
||||
return await _userManager.GetUsersInRoleAsync(PolicyConstants.PlebRole);
|
||||
}
|
||||
|
||||
return await query.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<bool> IsUserAdmin(AppUser user)
|
||||
{
|
||||
return await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole);
|
||||
}
|
||||
|
||||
public async Task<AppUserBookmark> GetBookmarkForPage(int page, int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(b => b.Page == page && b.ChapterId == chapterId && b.AppUserId == userId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
public async Task<AppUserRating> GetUserRating(int seriesId, int userId)
|
||||
{
|
||||
return await _context.AppUserRating.Where(r => r.SeriesId == seriesId && r.AppUserId == userId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
private static IQueryable<AppUser> AddIncludesToQuery(IQueryable<AppUser> query, AppUserIncludes includeFlags)
|
||||
{
|
||||
if (includeFlags.HasFlag(AppUserIncludes.Bookmarks))
|
||||
public async Task<AppUserPreferences> GetPreferencesAsync(string username)
|
||||
{
|
||||
return await _context.AppUserPreferences
|
||||
.Include(p => p.AppUser)
|
||||
.SingleOrDefaultAsync(p => p.AppUser.UserName == username);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId && x.SeriesId == seriesId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForVolume(int userId, int volumeId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId && x.VolumeId == volumeId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForChapter(int userId, int chapterId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId && x.ChapterId == chapterId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetAllBookmarkDtos(int userId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the UserId by API Key. This does not include any extra information
|
||||
/// </summary>
|
||||
/// <param name="apiKey"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> GetUserIdByApiKeyAsync(string apiKey)
|
||||
{
|
||||
return await _context.AppUser
|
||||
.Where(u => u.ApiKey.Equals(apiKey))
|
||||
.Select(u => u.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<MemberDto>> GetMembersAsync()
|
||||
{
|
||||
return await _context.Users
|
||||
.Include(x => x.Libraries)
|
||||
.Include(r => r.UserRoles)
|
||||
.ThenInclude(r => r.Role)
|
||||
.OrderBy(u => u.UserName)
|
||||
.Select(u => new MemberDto
|
||||
{
|
||||
query = query.Include(u => u.Bookmarks);
|
||||
}
|
||||
|
||||
if (includeFlags.HasFlag(AppUserIncludes.Progress))
|
||||
{
|
||||
query = query.Include(u => u.Progresses);
|
||||
}
|
||||
|
||||
if (includeFlags.HasFlag(AppUserIncludes.ReadingLists))
|
||||
{
|
||||
query = query.Include(u => u.ReadingLists);
|
||||
}
|
||||
|
||||
if (includeFlags.HasFlag(AppUserIncludes.Ratings))
|
||||
{
|
||||
query = query.Include(u => u.Ratings);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This fetches the Id for a user. Use whenever you just need an ID.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> GetUserIdByUsernameAsync(string username)
|
||||
{
|
||||
return await _context.Users
|
||||
.Where(x => x.UserName == username)
|
||||
.Select(u => u.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an AppUser by username. Returns back Reading List and their Items.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<AppUser> GetUserWithReadingListsByUsernameAsync(string username)
|
||||
{
|
||||
return await _context.Users
|
||||
.Include(u => u.ReadingLists)
|
||||
.ThenInclude(l => l.Items)
|
||||
.SingleOrDefaultAsync(x => x.UserName == username);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
|
||||
{
|
||||
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)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserPreferences> GetPreferencesAsync(string username)
|
||||
{
|
||||
return await _context.AppUserPreferences
|
||||
.Include(p => p.AppUser)
|
||||
.SingleOrDefaultAsync(p => p.AppUser.UserName == username);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForSeries(int userId, int seriesId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId && x.SeriesId == seriesId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForVolume(int userId, int volumeId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId && x.VolumeId == volumeId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetBookmarkDtosForChapter(int userId, int chapterId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId && x.ChapterId == chapterId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BookmarkDto>> GetAllBookmarkDtos(int userId)
|
||||
{
|
||||
return await _context.AppUserBookmark
|
||||
.Where(x => x.AppUserId == userId)
|
||||
.OrderBy(x => x.Page)
|
||||
.AsNoTracking()
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the UserId by API Key. This does not include any extra information
|
||||
/// </summary>
|
||||
/// <param name="apiKey"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<int> GetUserIdByApiKeyAsync(string apiKey)
|
||||
{
|
||||
return await _context.AppUser
|
||||
.Where(u => u.ApiKey.Equals(apiKey))
|
||||
.Select(u => u.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<MemberDto>> GetMembersAsync()
|
||||
{
|
||||
return await _context.Users
|
||||
.Include(x => x.Libraries)
|
||||
.Include(r => r.UserRoles)
|
||||
.ThenInclude(r => r.Role)
|
||||
.OrderBy(u => u.UserName)
|
||||
.Select(u => new MemberDto
|
||||
Id = u.Id,
|
||||
Username = u.UserName,
|
||||
Created = u.Created,
|
||||
LastActive = u.LastActive,
|
||||
Roles = u.UserRoles.Select(r => r.Role.Name).ToList(),
|
||||
Libraries = u.Libraries.Select(l => new LibraryDto
|
||||
{
|
||||
Id = u.Id,
|
||||
Username = u.UserName,
|
||||
Created = u.Created,
|
||||
LastActive = u.LastActive,
|
||||
Roles = u.UserRoles.Select(r => r.Role.Name).ToList(),
|
||||
Libraries = u.Libraries.Select(l => new LibraryDto
|
||||
{
|
||||
Name = l.Name,
|
||||
Type = l.Type,
|
||||
LastScanned = l.LastScanned,
|
||||
Folders = l.Folders.Select(x => x.Path).ToList()
|
||||
}).ToList()
|
||||
})
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
Name = l.Name,
|
||||
Type = l.Type,
|
||||
LastScanned = l.LastScanned,
|
||||
Folders = l.Folders.Select(x => x.Path).ToList()
|
||||
}).ToList()
|
||||
})
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,206 +4,220 @@ using System.Threading.Tasks;
|
|||
using API.Comparators;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Interfaces.Repositories;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories
|
||||
namespace API.Data.Repositories;
|
||||
|
||||
public interface IVolumeRepository
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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
|
||||
.Where(c => volumeId == c.VolumeId)
|
||||
.Include(c => c.Files)
|
||||
.SelectMany(c => c.Files)
|
||||
.AsNoTracking()
|
||||
.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
|
||||
.Where(v => v.Id == volumeId)
|
||||
.Select(v => v.CoverImage)
|
||||
.AsNoTracking()
|
||||
.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
|
||||
.Where(c => volumeIds.Contains(c.VolumeId))
|
||||
.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)
|
||||
.ThenInclude(c => c.People) // TODO: Measure cost of this
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
void Add(Volume volume);
|
||||
void Update(Volume volume);
|
||||
void Remove(Volume volume);
|
||||
Task<IList<MangaFile>> GetFilesForVolume(int volumeId);
|
||||
Task<string> GetVolumeCoverImageAsync(int volumeId);
|
||||
Task<IList<int>> GetChapterIdsByVolumeIds(IReadOnlyList<int> volumeIds);
|
||||
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId);
|
||||
Task<Volume> GetVolumeAsync(int volumeId);
|
||||
Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId);
|
||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(IList<int> seriesIds, bool includeChapters = false);
|
||||
Task<IEnumerable<Volume>> GetVolumes(int seriesId);
|
||||
Task<Volume> 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);
|
||||
}
|
||||
|
||||
/// <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
|
||||
.Where(c => volumeId == c.VolumeId)
|
||||
.Include(c => c.Files)
|
||||
.SelectMany(c => c.Files)
|
||||
.AsNoTracking()
|
||||
.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
|
||||
.Where(v => v.Id == volumeId)
|
||||
.Select(v => v.CoverImage)
|
||||
.AsNoTracking()
|
||||
.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
|
||||
.Where(c => volumeIds.Contains(c.VolumeId))
|
||||
.Select(c => c.Id)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all volumes that contain a seriesId in passed array.
|
||||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <param name="includeChapters">Include chapter entities</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)
|
||||
.ThenInclude(c => c.People)
|
||||
.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