using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Constants; using API.DTOs; using API.DTOs.Recommendation; using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; using API.Entities.Metadata; using API.Extensions; using API.Extensions.QueryExtensions; using AutoMapper; using AutoMapper.QueryableExtensions; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; namespace API.Data.Repositories; public interface IExternalSeriesMetadataRepository { void Attach(ExternalSeriesMetadata metadata); void Attach(ExternalRating rating); void Attach(ExternalReview review); void Remove(IEnumerable? reviews); void Remove(IEnumerable? ratings); void Remove(IEnumerable? recommendations); Task GetExternalSeriesMetadata(int seriesId); Task ExternalSeriesMetadataNeedsRefresh(int seriesId); Task GetSeriesDetailPlusDto(int seriesId); Task LinkRecommendationsToSeries(Series series); Task IsBlacklistedSeries(int seriesId); Task CreateBlacklistedSeries(int seriesId); Task RemoveFromBlacklist(int seriesId); } public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepository { private readonly DataContext _context; private readonly IMapper _mapper; public ExternalSeriesMetadataRepository(DataContext context, IMapper mapper) { _context = context; _mapper = mapper; } public void Attach(ExternalSeriesMetadata metadata) { _context.ExternalSeriesMetadata.Attach(metadata); } public void Attach(ExternalRating rating) { _context.ExternalRating.Attach(rating); } public void Attach(ExternalReview review) { _context.ExternalReview.Attach(review); } public void Remove(IEnumerable? reviews) { if (reviews == null) return; _context.ExternalReview.RemoveRange(reviews); } public void Remove(IEnumerable ratings) { if (ratings == null) return; _context.ExternalRating.RemoveRange(ratings); } public void Remove(IEnumerable recommendations) { if (recommendations == null) return; _context.ExternalRecommendation.RemoveRange(recommendations); } /// /// Returns the ExternalSeriesMetadata entity for the given Series including all linked tables /// /// /// public Task GetExternalSeriesMetadata(int seriesId) { return _context.ExternalSeriesMetadata .Where(s => s.SeriesId == seriesId) .Include(s => s.ExternalReviews) .Include(s => s.ExternalRatings.OrderBy(r => r.AverageScore)) .Include(s => s.ExternalRecommendations.OrderBy(r => r.Id)) .AsSplitQuery() .FirstOrDefaultAsync(); } public async Task ExternalSeriesMetadataNeedsRefresh(int seriesId) { var row = await _context.ExternalSeriesMetadata .Where(s => s.SeriesId == seriesId) .FirstOrDefaultAsync(); return row == null || row.ValidUntilUtc <= DateTime.UtcNow; } public async Task GetSeriesDetailPlusDto(int seriesId) { var seriesDetailDto = await _context.ExternalSeriesMetadata .Where(m => m.SeriesId == seriesId) .Include(m => m.ExternalRatings) .Include(m => m.ExternalReviews) .Include(m => m.ExternalRecommendations) .FirstOrDefaultAsync(); if (seriesDetailDto == null) { return null; // or handle the case when seriesDetailDto is not found } var externalSeriesRecommendations = seriesDetailDto.ExternalRecommendations .Where(r => r.SeriesId == null) .Select(r => _mapper.Map(r)) .ToList(); var ownedIds = seriesDetailDto.ExternalRecommendations .Where(r => r.SeriesId != null) .Select(r => r.SeriesId) .ToList(); var ownedSeriesRecommendations = await _context.Series .Where(s => ownedIds.Contains(s.Id)) .OrderBy(s => s.SortName.ToLower()) .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); IEnumerable reviews = new List(); if (seriesDetailDto.ExternalReviews != null && seriesDetailDto.ExternalReviews.Any()) { reviews = seriesDetailDto.ExternalReviews .Select(r => { var ret = _mapper.Map(r); ret.IsExternal = true; return ret; }) .OrderByDescending(r => r.Score); } IEnumerable ratings = new List(); if (seriesDetailDto.ExternalRatings != null && seriesDetailDto.ExternalRatings.Any()) { ratings = seriesDetailDto.ExternalRatings .Select(r => _mapper.Map(r)); } var seriesDetailPlusDto = new SeriesDetailPlusDto() { Ratings = ratings, Reviews = reviews, Recommendations = new RecommendationDto() { ExternalSeries = externalSeriesRecommendations, OwnedSeries = ownedSeriesRecommendations } }; return seriesDetailPlusDto; } /// /// Searches Recommendations without a SeriesId on record and attempts to link based on Series Name/Localized Name /// /// /// public async Task LinkRecommendationsToSeries(Series series) { var recMatches = await _context.ExternalRecommendation .Where(r => r.SeriesId == null || r.SeriesId == 0) .Where(r => EF.Functions.Like(r.Name, series.Name) || EF.Functions.Like(r.Name, series.LocalizedName)) .ToListAsync(); foreach (var rec in recMatches) { rec.SeriesId = series.Id; } await _context.SaveChangesAsync(); } public Task IsBlacklistedSeries(int seriesId) { return _context.SeriesBlacklist.AnyAsync(s => s.SeriesId == seriesId); } /// /// Creates a new instance against SeriesId and Saves to the DB /// /// public async Task CreateBlacklistedSeries(int seriesId) { if (seriesId <= 0) return; await _context.SeriesBlacklist.AddAsync(new SeriesBlacklist() { SeriesId = seriesId }); await _context.SaveChangesAsync(); } /// /// Removes the Series from Blacklist and Saves to the DB /// /// public async Task RemoveFromBlacklist(int seriesId) { var seriesBlacklist = await _context.SeriesBlacklist.FirstOrDefaultAsync(sb => sb.SeriesId == seriesId); if (seriesBlacklist != null) { // Remove the SeriesBlacklist entity from the context _context.SeriesBlacklist.Remove(seriesBlacklist); // Save the changes to the database await _context.SaveChangesAsync(); } } }