Reading List Detail Overhaul + More Bugfixes and Polish (#3687)

Co-authored-by: Yongun Seong <yseong.p@gmail.com>
This commit is contained in:
Joe Milazzo 2025-03-29 19:47:53 -05:00 committed by GitHub
parent b2ee651fb8
commit dad212bfb9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 5056 additions and 729 deletions

View file

@ -1,10 +1,7 @@
using System.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Entities.Enums;
using API.Entities.Person;
using API.Extensions;
@ -31,15 +28,13 @@ public interface IPersonRepository
Task<IList<PersonDto>> GetAllPersonDtosByRoleAsync(int userId, PersonRole role);
Task RemoveAllPeopleNoLongerAssociated();
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(int userId, List<int>? libraryIds = null);
Task<int> GetCountAsync();
Task<string> GetCoverImageAsync(int personId);
Task<string?> GetCoverImageAsync(int personId);
Task<string?> GetCoverImageByNameAsync(string name);
Task<IEnumerable<PersonRole>> GetRolesForPersonByName(int personId, int userId);
Task<PagedList<BrowsePersonDto>> GetAllWritersAndSeriesCount(int userId, UserParams userParams);
Task<Person?> GetPersonById(int personId);
Task<PersonDto?> GetPersonDtoByName(string name, int userId);
Task<Person> GetPersonByName(string name);
Task<bool> IsNameUnique(string name);
Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId);
@ -126,12 +121,8 @@ public class PersonRepository : IPersonRepository
.ToListAsync();
}
public async Task<int> GetCountAsync()
{
return await _context.Person.CountAsync();
}
public async Task<string> GetCoverImageAsync(int personId)
public async Task<string?> GetCoverImageAsync(int personId)
{
return await _context.Person
.Where(c => c.Id == personId)
@ -139,7 +130,7 @@ public class PersonRepository : IPersonRepository
.SingleOrDefaultAsync();
}
public async Task<string> GetCoverImageByNameAsync(string name)
public async Task<string?> GetCoverImageByNameAsync(string name)
{
var normalized = name.ToNormalized();
return await _context.Person
@ -208,7 +199,7 @@ public class PersonRepository : IPersonRepository
.FirstOrDefaultAsync();
}
public async Task<PersonDto> GetPersonDtoByName(string name, int userId)
public async Task<PersonDto?> GetPersonDtoByName(string name, int userId)
{
var normalized = name.ToNormalized();
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
@ -220,11 +211,6 @@ public class PersonRepository : IPersonRepository
.FirstOrDefaultAsync();
}
public async Task<Person> GetPersonByName(string name)
{
return await _context.Person.FirstOrDefaultAsync(p => p.NormalizedName == name.ToNormalized());
}
public async Task<bool> IsNameUnique(string name)
{
return !(await _context.Person.AnyAsync(p => p.Name == name));

View file

@ -49,12 +49,14 @@ public interface IReadingListRepository
Task<IList<string>> GetRandomCoverImagesAsync(int readingListId);
Task<IList<string>> GetAllCoverImagesAsync();
Task<bool> ReadingListExists(string name);
IEnumerable<PersonDto> GetReadingListCharactersAsync(int readingListId);
IEnumerable<PersonDto> GetReadingListPeopleAsync(int readingListId, PersonRole role);
Task<ReadingListCast> GetReadingListAllPeopleAsync(int readingListId);
Task<IList<ReadingList>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
Task<int> RemoveReadingListsWithoutSeries();
Task<ReadingList?> GetReadingListByTitleAsync(string name, int userId, ReadingListIncludes includes = ReadingListIncludes.Items);
Task<IEnumerable<ReadingList>> GetReadingListsByIds(IList<int> ids, ReadingListIncludes includes = ReadingListIncludes.Items);
Task<IEnumerable<ReadingList>> GetReadingListsBySeriesId(int seriesId, ReadingListIncludes includes = ReadingListIncludes.Items);
Task<ReadingListInfoDto?> GetReadingListInfoAsync(int readingListId);
}
public class ReadingListRepository : IReadingListRepository
@ -121,12 +123,12 @@ public class ReadingListRepository : IReadingListRepository
.AnyAsync(x => x.NormalizedTitle != null && x.NormalizedTitle.Equals(normalized));
}
public IEnumerable<PersonDto> GetReadingListCharactersAsync(int readingListId)
public IEnumerable<PersonDto> GetReadingListPeopleAsync(int readingListId, PersonRole role)
{
return _context.ReadingListItem
.Where(item => item.ReadingListId == readingListId)
.SelectMany(item => item.Chapter.People)
.Where(p => p.Role == PersonRole.Character)
.Where(p => p.Role == role)
.OrderBy(p => p.Person.NormalizedName)
.Select(p => p.Person)
.Distinct()
@ -134,6 +136,77 @@ public class ReadingListRepository : IReadingListRepository
.AsEnumerable();
}
public async Task<ReadingListCast> GetReadingListAllPeopleAsync(int readingListId)
{
var allPeople = await _context.ReadingListItem
.Where(item => item.ReadingListId == readingListId)
.SelectMany(item => item.Chapter.People)
.OrderBy(p => p.Person.NormalizedName)
.Select(p => new
{
Role = p.Role,
Person = _mapper.Map<PersonDto>(p.Person)
})
.Distinct()
.ToListAsync();
// Create the ReadingListCast object
var cast = new ReadingListCast();
// Group people by role and populate the appropriate collections
foreach (var personGroup in allPeople.GroupBy(p => p.Role))
{
var people = personGroup.Select(pg => pg.Person).ToList();
switch (personGroup.Key)
{
case PersonRole.Writer:
cast.Writers = people;
break;
case PersonRole.CoverArtist:
cast.CoverArtists = people;
break;
case PersonRole.Publisher:
cast.Publishers = people;
break;
case PersonRole.Character:
cast.Characters = people;
break;
case PersonRole.Penciller:
cast.Pencillers = people;
break;
case PersonRole.Inker:
cast.Inkers = people;
break;
case PersonRole.Imprint:
cast.Imprints = people;
break;
case PersonRole.Colorist:
cast.Colorists = people;
break;
case PersonRole.Letterer:
cast.Letterers = people;
break;
case PersonRole.Editor:
cast.Editors = people;
break;
case PersonRole.Translator:
cast.Translators = people;
break;
case PersonRole.Team:
cast.Teams = people;
break;
case PersonRole.Location:
cast.Locations = people;
break;
case PersonRole.Other:
break;
}
}
return cast;
}
public async Task<IList<ReadingList>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
{
var extension = encodeFormat.GetExtension();
@ -181,6 +254,33 @@ public class ReadingListRepository : IReadingListRepository
.ToListAsync();
}
/// <summary>
/// Returns a Partial ReadingListInfoDto. The HourEstimate needs to be calculated outside the repo
/// </summary>
/// <param name="readingListId"></param>
/// <returns></returns>
public async Task<ReadingListInfoDto?> GetReadingListInfoAsync(int readingListId)
{
// Get sum of these across all ReadingListItems: long wordCount, int pageCount, bool isEpub (assume false if any ReadingListeItem.Series.Format is non-epub)
var readingList = await _context.ReadingList
.Where(rl => rl.Id == readingListId)
.Include(rl => rl.Items)
.ThenInclude(item => item.Series)
.Include(rl => rl.Items)
.ThenInclude(item => item.Volume)
.Include(rl => rl.Items)
.ThenInclude(item => item.Chapter)
.Select(rl => new ReadingListInfoDto()
{
WordCount = rl.Items.Sum(item => item.Chapter.WordCount),
Pages = rl.Items.Sum(item => item.Chapter.Pages),
IsAllEpub = rl.Items.All(item => item.Series.Format == MangaFormat.Epub),
})
.FirstOrDefaultAsync();
return readingList;
}
public void Remove(ReadingListItem item)
{

View file

@ -167,11 +167,11 @@ public class ScrobbleRepository : IScrobbleRepository
var query = _context.ScrobbleEvent
.Where(e => e.AppUserId == userId)
.Include(e => e.Series)
.SortBy(filter.Field, filter.IsDescending)
.WhereIf(!string.IsNullOrEmpty(filter.Query), s =>
EF.Functions.Like(s.Series.Name, $"%{filter.Query}%")
)
.WhereIf(!filter.IncludeReviews, e => e.ScrobbleEventType != ScrobbleEventType.Review)
.SortBy(filter.Field, filter.IsDescending)
.AsSplitQuery()
.ProjectTo<ScrobbleEventDto>(_mapper.ConfigurationProvider);