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.Extensions; using API.Extensions.QueryExtensions; using API.Helpers; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.EntityFrameworkCore; namespace API.Data.Repositories; public interface IPersonRepository { void Attach(Person person); void Attach(IEnumerable person); void Remove(Person person); void Remove(ChapterPeople person); void Remove(SeriesMetadataPeople person); void Update(Person person); Task> GetAllPeople(); Task> GetAllPersonDtosAsync(int userId); Task> GetAllPersonDtosByRoleAsync(int userId, PersonRole role); Task RemoveAllPeopleNoLongerAssociated(); Task> GetAllPeopleDtosForLibrariesAsync(int userId, List? libraryIds = null); Task GetCountAsync(); Task GetCoverImageAsync(int personId); Task GetCoverImageByNameAsync(string name); Task> GetRolesForPersonByName(string name, int userId); Task> GetAllWritersAndSeriesCount(int userId, UserParams userParams); Task GetPersonById(int personId); Task GetPersonDtoByName(string name, int userId); Task GetPersonByName(string name); Task> GetSeriesKnownFor(int personId); Task> GetChaptersForPersonByRole(int personId, int userId, PersonRole role); Task> GetPeopleByNames(List normalizedNames); } public class PersonRepository : IPersonRepository { private readonly DataContext _context; private readonly IMapper _mapper; public PersonRepository(DataContext context, IMapper mapper) { _context = context; _mapper = mapper; } public void Attach(Person person) { _context.Person.Attach(person); } public void Attach(IEnumerable person) { _context.Person.AttachRange(person); } public void Remove(Person person) { _context.Person.Remove(person); } public void Remove(ChapterPeople person) { _context.ChapterPeople.Remove(person); } public void Remove(SeriesMetadataPeople person) { _context.SeriesMetadataPeople.Remove(person); } public void Update(Person person) { _context.Person.Update(person); } public async Task RemoveAllPeopleNoLongerAssociated() { var peopleWithNoConnections = await _context.Person .Include(p => p.SeriesMetadataPeople) .Include(p => p.ChapterPeople) .Where(p => p.SeriesMetadataPeople.Count == 0 && p.ChapterPeople.Count == 0) .AsSplitQuery() .ToListAsync(); _context.Person.RemoveRange(peopleWithNoConnections); await _context.SaveChangesAsync(); } public async Task> GetAllPeopleDtosForLibrariesAsync(int userId, List? libraryIds = null) { var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); var userLibs = await _context.Library.GetUserLibraries(userId).ToListAsync(); if (libraryIds is {Count: > 0}) { userLibs = userLibs.Where(libraryIds.Contains).ToList(); } return await _context.Series .Where(s => userLibs.Contains(s.LibraryId)) .RestrictAgainstAgeRestriction(ageRating) .SelectMany(s => s.Metadata.People.Select(p => p.Person)) .Distinct() .OrderBy(p => p.Name) .AsNoTracking() .AsSplitQuery() .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } public async Task GetCountAsync() { return await _context.Person.CountAsync(); } public async Task GetCoverImageAsync(int personId) { return await _context.Person .Where(c => c.Id == personId) .Select(c => c.CoverImage) .SingleOrDefaultAsync(); } public async Task GetCoverImageByNameAsync(string name) { var normalized = name.ToNormalized(); return await _context.Person .Where(c => c.NormalizedName == normalized) .Select(c => c.CoverImage) .SingleOrDefaultAsync(); } public async Task> GetRolesForPersonByName(string name, int userId) { // TODO: This will need to check both series and chapters (in cases where komf only updates series) var normalized = name.ToNormalized(); var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); return await _context.Person .Where(p => p.NormalizedName == normalized) .RestrictAgainstAgeRestriction(ageRating) .SelectMany(p => p.ChapterPeople.Select(cp => cp.Role)) .Distinct() .ToListAsync(); } public async Task> GetAllWritersAndSeriesCount(int userId, UserParams userParams) { List roles = [PersonRole.Writer, PersonRole.CoverArtist]; var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); var query = _context.Person .Where(p => p.SeriesMetadataPeople.Any(smp => roles.Contains(smp.Role)) || p.ChapterPeople.Any(cmp => roles.Contains(cmp.Role))) .RestrictAgainstAgeRestriction(ageRating) .Select(p => new BrowsePersonDto { Id = p.Id, Name = p.Name, Description = p.Description, CoverImage = p.CoverImage, SeriesCount = p.SeriesMetadataPeople .Where(smp => roles.Contains(smp.Role)) .Select(smp => smp.SeriesMetadata.SeriesId) .Distinct() .Count(), IssueCount = p.ChapterPeople .Where(cp => roles.Contains(cp.Role)) .Select(cp => cp.Chapter.Id) .Distinct() .Count() }) .OrderBy(p => p.Name); return await PagedList.CreateAsync(query, userParams.PageNumber, userParams.PageSize); } public async Task GetPersonById(int personId) { return await _context.Person.Where(p => p.Id == personId) .FirstOrDefaultAsync(); } public async Task GetPersonDtoByName(string name, int userId) { var normalized = name.ToNormalized(); var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); return await _context.Person .Where(p => p.NormalizedName == normalized) .RestrictAgainstAgeRestriction(ageRating) .ProjectTo(_mapper.ConfigurationProvider) .FirstOrDefaultAsync(); } public async Task GetPersonByName(string name) { return await _context.Person.FirstOrDefaultAsync(p => p.NormalizedName == name.ToNormalized()); } public async Task> GetSeriesKnownFor(int personId) { return await _context.Person .Where(p => p.Id == personId) .SelectMany(p => p.SeriesMetadataPeople) .Select(smp => smp.SeriesMetadata) .Select(sm => sm.Series) .Distinct() .OrderByDescending(s => s.ExternalSeriesMetadata.AverageExternalRating) .Take(20) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } public async Task> GetChaptersForPersonByRole(int personId, int userId, PersonRole role) { var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); return await _context.ChapterPeople .Where(cp => cp.PersonId == personId && cp.Role == role) .Select(cp => cp.Chapter) .RestrictAgainstAgeRestriction(ageRating) .OrderBy(ch => ch.SortOrder) .Take(20) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } public async Task> GetPeopleByNames(List normalizedNames) { return await _context.Person .Where(p => normalizedNames.Contains(p.NormalizedName)) .OrderBy(p => p.Name) .ToListAsync(); } public async Task> GetAllPeople() { return await _context.Person .OrderBy(p => p.Name) .ToListAsync(); } public async Task> GetAllPersonDtosAsync(int userId) { var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); return await _context.Person .OrderBy(p => p.Name) .RestrictAgainstAgeRestriction(ageRating) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } public async Task> GetAllPersonDtosByRoleAsync(int userId, PersonRole role) { var ageRating = await _context.AppUser.GetUserAgeRestriction(userId); return await _context.Person .Where(p => p.SeriesMetadataPeople.Any(smp => smp.Role == role) || p.ChapterPeople.Any(cp => cp.Role == role)) // Filter by role in both series and chapters .OrderBy(p => p.Name) .RestrictAgainstAgeRestriction(ageRating) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } }