Overall Ratings (#2129)
* Corrected tooltip for Cache * Ensure we sync the DB to what's in appsettings.json for Cache key. * Change the fingerprinting method for Windows installs exclusively to avoid churn due to how security updates are handled. * Hooked up the ability to see where reviews are from via an icon on the review card, rather than having to click or know that MAL has "external Review" as title. * Updated FAQ for Kavita+ to link directly to the FAQ * Added the ability for all ratings on a series to be shown to the user. Added favorite count on AL and MAL * Cleaned up so the check for Kavita+ license doesn't seem like it's running when no license is registered. * Tweaked the test instance buy link to test new product.
This commit is contained in:
parent
34f828e750
commit
49daca943e
29 changed files with 231 additions and 56 deletions
|
|
@ -16,5 +16,5 @@ public static class ResponseCacheProfiles
|
|||
public const string Instant = "Instant";
|
||||
public const string Month = "Month";
|
||||
public const string LicenseCache = "LicenseCache";
|
||||
public const string Recommendation = "Recommendation";
|
||||
public const string KavitaPlus = "KavitaPlus";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public class LicenseController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates server license. Returns true if updated and valid
|
||||
/// Updates server license
|
||||
/// </summary>
|
||||
/// <remarks>Caches the result</remarks>
|
||||
/// <returns></returns>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
using API.Services.Plus;
|
||||
using EasyCaching.Core;
|
||||
|
|
@ -18,15 +20,17 @@ public class RatingController : BaseApiController
|
|||
private readonly ILicenseService _licenseService;
|
||||
private readonly IRatingService _ratingService;
|
||||
private readonly ILogger<RatingController> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IEasyCachingProvider _cacheProvider;
|
||||
public const string CacheKey = "rating_";
|
||||
|
||||
public RatingController(ILicenseService licenseService, IRatingService ratingService,
|
||||
ILogger<RatingController> logger, IEasyCachingProviderFactory cachingProviderFactory)
|
||||
ILogger<RatingController> logger, IEasyCachingProviderFactory cachingProviderFactory, IUnitOfWork unitOfWork)
|
||||
{
|
||||
_licenseService = licenseService;
|
||||
_ratingService = ratingService;
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
|
||||
_cacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
|
||||
}
|
||||
|
|
@ -37,12 +41,13 @@ public class RatingController : BaseApiController
|
|||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = new []{"seriesId"})]
|
||||
public async Task<ActionResult<IEnumerable<RatingDto>>> GetRating(int seriesId)
|
||||
{
|
||||
|
||||
if (!await _licenseService.HasActiveLicense())
|
||||
{
|
||||
return Ok(new List<RatingDto>());
|
||||
return Ok(Enumerable.Empty<RatingDto>());
|
||||
}
|
||||
|
||||
var cacheKey = CacheKey + seriesId;
|
||||
|
|
@ -56,6 +61,16 @@ public class RatingController : BaseApiController
|
|||
await _cacheProvider.SetAsync(cacheKey, ratings, TimeSpan.FromHours(24));
|
||||
_logger.LogDebug("Caching external rating for {Key}", cacheKey);
|
||||
return Ok(ratings);
|
||||
}
|
||||
|
||||
[HttpGet("overall")]
|
||||
public async Task<ActionResult<RatingDto>> GetOverallRating(int seriesId)
|
||||
{
|
||||
return Ok(new RatingDto()
|
||||
{
|
||||
Provider = ScrobbleProvider.Kavita,
|
||||
AverageScore = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId),
|
||||
FavoriteCount = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class RecommendedController : BaseApiController
|
|||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("recommendations")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = new []{"seriesId"})]
|
||||
public async Task<ActionResult<RecommendationDto>> GetRecommendations(int seriesId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public class ReviewController : BaseApiController
|
|||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
[HttpGet]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = new []{"seriesId"})]
|
||||
public async Task<ActionResult<IEnumerable<UserReviewDto>>> GetReviews(int seriesId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ public class ServerController : BaseApiController
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// Bust Review and Recommendation Cache
|
||||
/// Bust Kavita+ Cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize("RequireAdminRole")]
|
||||
|
|
@ -250,12 +250,12 @@ public class ServerController : BaseApiController
|
|||
{
|
||||
_logger.LogInformation("Busting Kavita+ Cache");
|
||||
var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusReviews);
|
||||
await provider.FlushAsync();
|
||||
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRecommendations);
|
||||
await provider.FlushAsync();
|
||||
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
|
||||
await provider.FlushAsync();
|
||||
return Ok();
|
||||
await provider.FlushAsync();
|
||||
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRecommendations);
|
||||
await provider.FlushAsync();
|
||||
provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
|
||||
await provider.FlushAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace API.DTOs.SeriesDetail;
|
||||
using API.Services.Plus;
|
||||
|
||||
namespace API.DTOs.SeriesDetail;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a User Review for a given Series
|
||||
|
|
@ -48,4 +50,9 @@ public class UserReviewDto
|
|||
/// The main body with just text, for review preview
|
||||
/// </summary>
|
||||
public string? BodyJustText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If this review is External, which Provider did it come from
|
||||
/// </summary>
|
||||
public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.Kavita;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,8 +135,8 @@ public interface ISeriesRepository
|
|||
Task<IDictionary<int, int>> GetLibraryIdsForSeriesAsync();
|
||||
Task<IList<SeriesMetadataDto>> GetSeriesMetadataForIds(IEnumerable<int> seriesIds);
|
||||
Task<IList<Series>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat, bool customOnly = true);
|
||||
|
||||
Task<SeriesDto?> GetSeriesDtoByNamesAndMetadataIdsForUser(int userId, IEnumerable<string> names, LibraryType libraryType, string aniListUrl, string malUrl);
|
||||
Task<int> GetAverageUserRating(int seriesId);
|
||||
}
|
||||
|
||||
public class SeriesRepository : ISeriesRepository
|
||||
|
|
@ -1658,6 +1658,18 @@ public class SeriesRepository : ISeriesRepository
|
|||
.FirstOrDefaultAsync(); // Some users may have improperly configured libraries
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Average rating for all users within Kavita instance
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
public async Task<int> GetAverageUserRating(int seriesId)
|
||||
{
|
||||
var avg = (await _context.AppUserRating
|
||||
.Where(r => r.SeriesId == seriesId)
|
||||
.AverageAsync(r => (int?) r.Rating));
|
||||
return avg.HasValue ? (int) avg.Value : 0;
|
||||
}
|
||||
|
||||
public async Task<bool> IsSeriesInWantToRead(int userId, int seriesId)
|
||||
{
|
||||
var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync();
|
||||
|
|
|
|||
|
|
@ -133,6 +133,8 @@ public static class Seed
|
|||
directoryService.CacheDirectory + string.Empty;
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.BackupDirectory).Value =
|
||||
DirectoryService.BackupDirectory + string.Empty;
|
||||
context.ServerSetting.First(s => s.Key == ServerSettingKey.CacheSize).Value =
|
||||
Configuration.CacheSize + string.Empty;
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,11 +130,13 @@ public class LicenseService : ILicenseService
|
|||
{
|
||||
try
|
||||
{
|
||||
var license = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
|
||||
if (string.IsNullOrEmpty(license.Value)) return;
|
||||
|
||||
_logger.LogInformation("Validating Kavita+ License");
|
||||
var provider = _cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.License);
|
||||
await provider.FlushAsync();
|
||||
|
||||
var license = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
|
||||
var isValid = await IsLicenseValid(license.Value);
|
||||
await provider.SetAsync(CacheKey, isValid, _licenseCacheTimeout);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,17 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace API.Services.Plus;
|
||||
|
||||
/// <summary>
|
||||
/// Misleading name but is the source of data (like a review coming from AniList)
|
||||
/// </summary>
|
||||
public enum ScrobbleProvider
|
||||
{
|
||||
AniList = 1
|
||||
/// <summary>
|
||||
/// For now, this means data comes from within this instance of Kavita
|
||||
/// </summary>
|
||||
Kavita = 0,
|
||||
AniList = 1,
|
||||
Mal = 2,
|
||||
}
|
||||
|
||||
public interface IScrobblingService
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ internal class MediaReviewDto
|
|||
/// </summary>
|
||||
public string RawBody { get; set; }
|
||||
public string Username { get; set; }
|
||||
public ScrobbleProvider Provider { get; set; }
|
||||
}
|
||||
|
||||
public interface IReviewService
|
||||
|
|
@ -74,6 +75,7 @@ public class ReviewService : IReviewService
|
|||
LibraryId = series.LibraryId,
|
||||
SeriesId = series.Id,
|
||||
IsExternal = true,
|
||||
Provider = r.Provider,
|
||||
BodyJustText = GetCharacters(r.Body),
|
||||
ExternalUrl = r.SiteUrl
|
||||
});
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ public class Startup
|
|||
Location = ResponseCacheLocation.Client,
|
||||
NoStore = false
|
||||
});
|
||||
options.CacheProfiles.Add(ResponseCacheProfiles.Recommendation,
|
||||
options.CacheProfiles.Add(ResponseCacheProfiles.KavitaPlus,
|
||||
new CacheProfile()
|
||||
{
|
||||
Duration = TimeSpan.FromDays(30).Seconds,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue