using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Constants; using API.Data; using API.DTOs; using API.DTOs.Recommendation; using API.Extensions; using API.Helpers; using API.Services.Plus; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; using Newtonsoft.Json; namespace API.Controllers; public class RecommendedController : BaseApiController { private readonly IUnitOfWork _unitOfWork; private readonly IRecommendationService _recommendationService; private readonly ILicenseService _licenseService; private readonly IMemoryCache _cache; public const string CacheKey = "recommendation-"; public RecommendedController(IUnitOfWork unitOfWork, IRecommendationService recommendationService, ILicenseService licenseService, IMemoryCache cache) { _unitOfWork = unitOfWork; _recommendationService = recommendationService; _licenseService = licenseService; _cache = cache; } /// /// For Kavita+ users, this will return recommendations on the server. /// /// /// [HttpGet("recommendations")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})] public async Task> GetRecommendations(int seriesId) { var userId = User.GetUserId(); if (!await _licenseService.HasActiveLicense()) { return Ok(new RecommendationDto()); } if (!await _unitOfWork.UserRepository.HasAccessToSeries(userId, seriesId)) { return BadRequest("User does not have access to this Series"); } var cacheKey = $"{CacheKey}-{seriesId}-{userId}"; if (_cache.TryGetValue(cacheKey, out string cachedData)) { return Ok(JsonConvert.DeserializeObject(cachedData)); } var ret = await _recommendationService.GetRecommendationsForSeries(userId, seriesId); var cacheEntryOptions = new MemoryCacheEntryOptions() .SetSize(ret.OwnedSeries.Count() + ret.ExternalSeries.Count()) .SetAbsoluteExpiration(TimeSpan.FromHours(10)); _cache.Set(cacheKey, JsonConvert.SerializeObject(ret), cacheEntryOptions); return Ok(ret); } /// /// Quick Reads are series that should be readable in less than 10 in total and are not Ongoing in release. /// /// Library to restrict series to /// Pagination /// [HttpGet("quick-reads")] public async Task>> GetQuickReads(int libraryId, [FromQuery] UserParams userParams) { userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetQuickReads(User.GetUserId(), libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Quick Catchup Reads are series that should be readable in less than 10 in total and are Ongoing in release. /// /// Library to restrict series to /// /// [HttpGet("quick-catchup-reads")] public async Task>> GetQuickCatchupReads(int libraryId, [FromQuery] UserParams userParams) { userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetQuickCatchupReads(User.GetUserId(), libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Highly Rated based on other users ratings. Will pull series with ratings > 4.0, weighted by count of other users. /// /// Library to restrict series to /// Pagination /// [HttpGet("highly-rated")] public async Task>> GetHighlyRated(int libraryId, [FromQuery] UserParams userParams) { var userId = User.GetUserId(); userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetHighlyRated(userId, libraryId, userParams); await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Chooses a random genre and shows series that are in that without reading progress /// /// Library to restrict series to /// Genre Id /// Pagination /// [HttpGet("more-in")] public async Task>> GetMoreIn(int libraryId, int genreId, [FromQuery] UserParams userParams) { var userId = User.GetUserId(); userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetMoreIn(userId, libraryId, genreId, userParams); await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Series that are fully read by the user in no particular order /// /// Library to restrict series to /// Pagination /// [HttpGet("rediscover")] public async Task>> GetRediscover(int libraryId, [FromQuery] UserParams userParams) { userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetRediscover(User.GetUserId(), libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } }