From 49daca943e60da9cc6653222a17da80ea4e67956 Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Fri, 14 Jul 2023 14:12:03 -0500 Subject: [PATCH] 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. --- API/Constants/ResponseCacheProfiles.cs | 2 +- API/Controllers/LicenseController.cs | 2 +- API/Controllers/RatingController.cs | 21 +++++- API/Controllers/RecommendedController.cs | 2 +- API/Controllers/ReviewController.cs | 2 +- API/Controllers/ServerController.cs | 14 ++-- API/DTOs/SeriesDetail/UserReviewDto.cs | 9 ++- API/Data/Repositories/SeriesRepository.cs | 14 +++- API/Data/Seed.cs | 2 + API/Services/Plus/LicenseService.cs | 4 +- API/Services/Plus/ScrobblingService.cs | 10 ++- API/Services/ReviewService.cs | 2 + API/Startup.cs | 2 +- Kavita.Common/HashUtil.cs | 4 +- .../src/app/_services/scrobbling.service.ts | 3 +- UI/Web/src/app/_services/series.service.ts | 15 +++-- .../review-card/review-card.component.html | 8 ++- .../review-card/review-card.component.scss | 9 ++- .../review-card/review-card.component.ts | 6 +- .../_single-module/review-card/user-review.ts | 3 + .../admin/dashboard/dashboard.component.html | 5 +- .../manage-settings.component.html | 2 +- UI/Web/src/app/pipe/provider-image.pipe.ts | 2 + UI/Web/src/app/pipe/provider-name.pipe.ts | 23 +++++++ .../external-rating.component.html | 17 +++-- .../external-rating.component.scss | 18 +++++ .../external-rating.component.ts | 19 +++++- UI/Web/src/environments/environment.ts | 2 +- openapi.json | 65 +++++++++++++++++-- 29 files changed, 231 insertions(+), 56 deletions(-) create mode 100644 UI/Web/src/app/pipe/provider-name.pipe.ts diff --git a/API/Constants/ResponseCacheProfiles.cs b/API/Constants/ResponseCacheProfiles.cs index 1a092d84e..d7dcaf95b 100644 --- a/API/Constants/ResponseCacheProfiles.cs +++ b/API/Constants/ResponseCacheProfiles.cs @@ -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"; } diff --git a/API/Controllers/LicenseController.cs b/API/Controllers/LicenseController.cs index b00f6d07f..e0280f78c 100644 --- a/API/Controllers/LicenseController.cs +++ b/API/Controllers/LicenseController.cs @@ -65,7 +65,7 @@ public class LicenseController : BaseApiController } /// - /// Updates server license. Returns true if updated and valid + /// Updates server license /// /// Caches the result /// diff --git a/API/Controllers/RatingController.cs b/API/Controllers/RatingController.cs index accd6ccaa..4b816bab7 100644 --- a/API/Controllers/RatingController.cs +++ b/API/Controllers/RatingController.cs @@ -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 _logger; + private readonly IUnitOfWork _unitOfWork; private readonly IEasyCachingProvider _cacheProvider; public const string CacheKey = "rating_"; public RatingController(ILicenseService licenseService, IRatingService ratingService, - ILogger logger, IEasyCachingProviderFactory cachingProviderFactory) + ILogger 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 /// /// [HttpGet] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = new []{"seriesId"})] public async Task>> GetRating(int seriesId) { + if (!await _licenseService.HasActiveLicense()) { - return Ok(new List()); + return Ok(Enumerable.Empty()); } 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> GetOverallRating(int seriesId) + { + return Ok(new RatingDto() + { + Provider = ScrobbleProvider.Kavita, + AverageScore = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId), + FavoriteCount = 0 + }); } } diff --git a/API/Controllers/RecommendedController.cs b/API/Controllers/RecommendedController.cs index 14c9b7bfc..efadb6d60 100644 --- a/API/Controllers/RecommendedController.cs +++ b/API/Controllers/RecommendedController.cs @@ -39,7 +39,7 @@ public class RecommendedController : BaseApiController /// /// [HttpGet("recommendations")] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = new []{"seriesId"})] public async Task> GetRecommendations(int seriesId) { var userId = User.GetUserId(); diff --git a/API/Controllers/ReviewController.cs b/API/Controllers/ReviewController.cs index 938e5fc77..27508f1ce 100644 --- a/API/Controllers/ReviewController.cs +++ b/API/Controllers/ReviewController.cs @@ -51,7 +51,7 @@ public class ReviewController : BaseApiController /// /// [HttpGet] - [ResponseCache(CacheProfileName = ResponseCacheProfiles.Recommendation, VaryByQueryKeys = new []{"seriesId"})] + [ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = new []{"seriesId"})] public async Task>> GetReviews(int seriesId) { var userId = User.GetUserId(); diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs index 03b8689f8..22b871529 100644 --- a/API/Controllers/ServerController.cs +++ b/API/Controllers/ServerController.cs @@ -241,7 +241,7 @@ public class ServerController : BaseApiController /// - /// Bust Review and Recommendation Cache + /// Bust Kavita+ Cache /// /// [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(); } diff --git a/API/DTOs/SeriesDetail/UserReviewDto.cs b/API/DTOs/SeriesDetail/UserReviewDto.cs index f4f94ebd7..4f74dadbb 100644 --- a/API/DTOs/SeriesDetail/UserReviewDto.cs +++ b/API/DTOs/SeriesDetail/UserReviewDto.cs @@ -1,4 +1,6 @@ -namespace API.DTOs.SeriesDetail; +using API.Services.Plus; + +namespace API.DTOs.SeriesDetail; /// /// Represents a User Review for a given Series @@ -48,4 +50,9 @@ public class UserReviewDto /// The main body with just text, for review preview /// public string? BodyJustText { get; set; } + + /// + /// If this review is External, which Provider did it come from + /// + public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.Kavita; } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 80b00a8c5..dfa85dcad 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -135,8 +135,8 @@ public interface ISeriesRepository Task> GetLibraryIdsForSeriesAsync(); Task> GetSeriesMetadataForIds(IEnumerable seriesIds); Task> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat, bool customOnly = true); - Task GetSeriesDtoByNamesAndMetadataIdsForUser(int userId, IEnumerable names, LibraryType libraryType, string aniListUrl, string malUrl); + Task GetAverageUserRating(int seriesId); } public class SeriesRepository : ISeriesRepository @@ -1658,6 +1658,18 @@ public class SeriesRepository : ISeriesRepository .FirstOrDefaultAsync(); // Some users may have improperly configured libraries } + /// + /// Returns the Average rating for all users within Kavita instance + /// + /// + public async Task 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 IsSeriesInWantToRead(int userId, int seriesId) { var libraryIds = await _context.Library.GetUserLibraries(userId).ToListAsync(); diff --git a/API/Data/Seed.cs b/API/Data/Seed.cs index d903496ae..93c01d6ed 100644 --- a/API/Data/Seed.cs +++ b/API/Data/Seed.cs @@ -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(); } diff --git a/API/Services/Plus/LicenseService.cs b/API/Services/Plus/LicenseService.cs index ddd67791e..0a2b25cea 100644 --- a/API/Services/Plus/LicenseService.cs +++ b/API/Services/Plus/LicenseService.cs @@ -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); diff --git a/API/Services/Plus/ScrobblingService.cs b/API/Services/Plus/ScrobblingService.cs index 61ab9d688..1d1829c3b 100644 --- a/API/Services/Plus/ScrobblingService.cs +++ b/API/Services/Plus/ScrobblingService.cs @@ -22,9 +22,17 @@ using Microsoft.Extensions.Logging; namespace API.Services.Plus; +/// +/// Misleading name but is the source of data (like a review coming from AniList) +/// public enum ScrobbleProvider { - AniList = 1 + /// + /// For now, this means data comes from within this instance of Kavita + /// + Kavita = 0, + AniList = 1, + Mal = 2, } public interface IScrobblingService diff --git a/API/Services/ReviewService.cs b/API/Services/ReviewService.cs index 597e7bbe9..a0f4d18f5 100644 --- a/API/Services/ReviewService.cs +++ b/API/Services/ReviewService.cs @@ -35,6 +35,7 @@ internal class MediaReviewDto /// 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 }); diff --git a/API/Startup.cs b/API/Startup.cs index 90a104158..64f2a3f73 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -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, diff --git a/Kavita.Common/HashUtil.cs b/Kavita.Common/HashUtil.cs index d3757085d..aace6bd13 100644 --- a/Kavita.Common/HashUtil.cs +++ b/Kavita.Common/HashUtil.cs @@ -51,9 +51,7 @@ public static class HashUtil .AddComponent("ProcessorCount", new DeviceIdComponent($"{Environment.ProcessorCount}")) .AddComponent("OSPlatform", new DeviceIdComponent($"{Environment.OSVersion.Platform}")) .OnWindows(windows => windows - .AddSystemUuid() - .AddMotherboardSerialNumber() - .AddSystemDriveSerialNumber()) + .AddProcessorId()) .OnLinux(linux => { var osInfo = RunAndCapture("uname", "-a"); diff --git a/UI/Web/src/app/_services/scrobbling.service.ts b/UI/Web/src/app/_services/scrobbling.service.ts index adb974467..e815410f7 100644 --- a/UI/Web/src/app/_services/scrobbling.service.ts +++ b/UI/Web/src/app/_services/scrobbling.service.ts @@ -24,8 +24,9 @@ import {UtilityService} from "../shared/_services/utility.service"; import {ReadingList} from "../_models/reading-list"; export enum ScrobbleProvider { + Kavita = 0, AniList= 1, - Mal = 2 + Mal = 2, } @Injectable({ diff --git a/UI/Web/src/app/_services/series.service.ts b/UI/Web/src/app/_services/series.service.ts index 3b95e0fa8..b7359a8f6 100644 --- a/UI/Web/src/app/_services/series.service.ts +++ b/UI/Web/src/app/_services/series.service.ts @@ -32,12 +32,12 @@ export class SeriesService { paginatedSeriesForTagsResults: PaginatedResult = new PaginatedResult(); constructor(private httpClient: HttpClient, private imageService: ImageService, - private utilityService: UtilityService, private filterUtilitySerivce: FilterUtilitiesService) { } + private utilityService: UtilityService, private filterUtilityService: FilterUtilitiesService) { } getAllSeries(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); - const data = this.filterUtilitySerivce.createSeriesFilter(filter); + const data = this.filterUtilityService.createSeriesFilter(filter); return this.httpClient.post>(this.baseUrl + 'series/all', data, {observe: 'response', params}).pipe( map((response: any) => { @@ -49,7 +49,7 @@ export class SeriesService { getSeriesForLibrary(libraryId: number, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); - const data = this.filterUtilitySerivce.createSeriesFilter(filter); + const data = this.filterUtilityService.createSeriesFilter(filter); return this.httpClient.post>(this.baseUrl + 'series?libraryId=' + libraryId, data, {observe: 'response', params}).pipe( map((response: any) => { @@ -103,7 +103,7 @@ export class SeriesService { } getRecentlyAdded(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { - const data = this.filterUtilitySerivce.createSeriesFilter(filter); + const data = this.filterUtilityService.createSeriesFilter(filter); let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); @@ -119,7 +119,7 @@ export class SeriesService { } getWantToRead(pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter): Observable> { - const data = this.filterUtilitySerivce.createSeriesFilter(filter); + const data = this.filterUtilityService.createSeriesFilter(filter); let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); @@ -138,7 +138,7 @@ export class SeriesService { } getOnDeck(libraryId: number = 0, pageNum?: number, itemsPerPage?: number, filter?: SeriesFilter) { - const data = this.filterUtilitySerivce.createSeriesFilter(filter); + const data = this.filterUtilityService.createSeriesFilter(filter); let params = new HttpParams(); params = this.utilityService.addPaginationIfExists(params, pageNum, itemsPerPage); @@ -223,4 +223,7 @@ export class SeriesService { getRatings(seriesId: number) { return this.httpClient.get>(this.baseUrl + 'rating?seriesId=' + seriesId); } + getOverallRating(seriesId: number) { + return this.httpClient.get(this.baseUrl + 'rating/overall?seriesId=' + seriesId); + } } diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.html b/UI/Web/src/app/_single-module/review-card/review-card.component.html index 46360a1a6..5e912aba8 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.html +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.html @@ -19,9 +19,15 @@

- + diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.scss b/UI/Web/src/app/_single-module/review-card/review-card.component.scss index f1794eba6..62bf3e443 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.scss +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.scss @@ -8,7 +8,10 @@ z-index: 20; top: 38px; left: 38px; - color: var(--review-card-star-color); +} + +.fa-star { + color: var(--review-card-star-color); } .card-text { @@ -29,10 +32,6 @@ overflow: hidden; } -.card-footer { - width: 288px; -} - .card { cursor: pointer; } diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.ts b/UI/Web/src/app/_single-module/review-card/review-card.component.ts index 13811735f..154f0cd72 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.ts +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.ts @@ -1,5 +1,5 @@ import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core'; -import {CommonModule} from '@angular/common'; +import {CommonModule, NgOptimizedImage} from '@angular/common'; import {UserReview} from "./user-review"; import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; import {ReviewCardModalComponent} from "../review-card-modal/review-card-modal.component"; @@ -7,11 +7,13 @@ import {AccountService} from "../../_services/account.service"; import {ReviewSeriesModalComponent} from "../review-series-modal/review-series-modal.component"; import {ReadMoreComponent} from "../../shared/read-more/read-more.component"; import {DefaultValuePipe} from "../../pipe/default-value.pipe"; +import {ImageComponent} from "../../shared/image/image.component"; +import {ProviderImagePipe} from "../../pipe/provider-image.pipe"; @Component({ selector: 'app-review-card', standalone: true, - imports: [CommonModule, ReadMoreComponent, DefaultValuePipe], + imports: [CommonModule, ReadMoreComponent, DefaultValuePipe, ImageComponent, NgOptimizedImage, ProviderImagePipe], templateUrl: './review-card.component.html', styleUrls: ['./review-card.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush diff --git a/UI/Web/src/app/_single-module/review-card/user-review.ts b/UI/Web/src/app/_single-module/review-card/user-review.ts index 1e0371fc8..f735d9548 100644 --- a/UI/Web/src/app/_single-module/review-card/user-review.ts +++ b/UI/Web/src/app/_single-module/review-card/user-review.ts @@ -1,3 +1,5 @@ +import {ScrobbleProvider} from "../../_services/scrobbling.service"; + export interface UserReview { seriesId: number; libraryId: number; @@ -8,4 +10,5 @@ export interface UserReview { isExternal: boolean; bodyJustText?: string; externalUrl?: string; + provider: ScrobbleProvider; } diff --git a/UI/Web/src/app/admin/dashboard/dashboard.component.html b/UI/Web/src/app/admin/dashboard/dashboard.component.html index 72dcdd961..bde8a9633 100644 --- a/UI/Web/src/app/admin/dashboard/dashboard.component.html +++ b/UI/Web/src/app/admin/dashboard/dashboard.component.html @@ -36,12 +36,9 @@ -

Kavita+ is a premium subscription service which unlocks features for all users on this Kavita instance. Buy a subscription to unlock premium benefits today! FAQ

+

Kavita+ is a premium subscription service which unlocks features for all users on this Kavita instance. Buy a subscription to unlock premium benefits today! FAQ

- - Nothing here yet. This will be built out in a future update. - diff --git a/UI/Web/src/app/admin/manage-settings/manage-settings.component.html b/UI/Web/src/app/admin/manage-settings/manage-settings.component.html index ae15a31b2..36bdd6dd2 100644 --- a/UI/Web/src/app/admin/manage-settings/manage-settings.component.html +++ b/UI/Web/src/app/admin/manage-settings/manage-settings.component.html @@ -100,7 +100,7 @@
  - The amount of memory allowed for caching heavy APIs. Default is 50MB. + The amount of memory allowed for caching heavy APIs. Default is 75MB. The amount of memory allowed for caching heavy APIs. Default is 50MB. -
+
- {{userRating * 20}}% + {{userRating * 20}} + + {{overallRating}}% + %
-
+
{{rating.averageScore}}% @@ -23,5 +26,9 @@ - + {{userRating * 20}}% + + + + {{rating.favoriteCount}} diff --git a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss index 816baab5e..bf59d1ef5 100644 --- a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss +++ b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.scss @@ -2,3 +2,21 @@ padding-left: 0px; padding-right: 0px; } + +.sm-popover { + width: 150px; + + > .popover-body { + padding-top: 0px; + } +} + +.md-popover { + width: 214px; + + > .popover-body { + padding-top: 0px; + } +} + + diff --git a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts index 8471d6090..e4b4d10a4 100644 --- a/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts +++ b/UI/Web/src/app/series-detail/_components/external-rating/external-rating.component.ts @@ -1,4 +1,12 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, Input, OnInit} from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject, + Input, + OnInit, + ViewEncapsulation +} from '@angular/core'; import {CommonModule, NgOptimizedImage} from '@angular/common'; import {SeriesService} from "../../../_services/series.service"; import {Rating} from "../../../_models/rating"; @@ -7,14 +15,16 @@ import {NgbPopover, NgbRating} from "@ng-bootstrap/ng-bootstrap"; import {LoadingComponent} from "../../../shared/loading/loading.component"; import {AccountService} from "../../../_services/account.service"; import {LibraryType} from "../../../_models/library"; +import {ProviderNamePipe} from "../../../pipe/provider-name.pipe"; @Component({ selector: 'app-external-rating', standalone: true, - imports: [CommonModule, ProviderImagePipe, NgOptimizedImage, NgbRating, NgbPopover, LoadingComponent], + imports: [CommonModule, ProviderImagePipe, NgOptimizedImage, NgbRating, NgbPopover, LoadingComponent, ProviderNamePipe], templateUrl: './external-rating.component.html', styleUrls: ['./external-rating.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None }) export class ExternalRatingComponent implements OnInit { @Input({required: true}) seriesId!: number; @@ -26,10 +36,13 @@ export class ExternalRatingComponent implements OnInit { ratings: Array = []; isLoading: boolean = false; + overallRating: number = -1; ngOnInit() { + this.seriesService.getOverallRating(this.seriesId).subscribe(r => this.overallRating = r.averageScore); + this.accountService.hasValidLicense$.subscribe((res) => { if (!res) return; this.isLoading = true; diff --git a/UI/Web/src/environments/environment.ts b/UI/Web/src/environments/environment.ts index 05f42e187..11db3c23e 100644 --- a/UI/Web/src/environments/environment.ts +++ b/UI/Web/src/environments/environment.ts @@ -6,7 +6,7 @@ export const environment = { production: false, apiUrl: 'http://localhost:5000/api/', hubUrl: 'http://localhost:5000/hubs/', - buyLink: 'https://buy.stripe.com/test_8wM4ie2dg5j77o4cMO?prefilled_promo_code=FREETRIAL', + buyLink: 'https://buy.stripe.com/test_9AQ5mi058h1PcIo3cf?prefilled_promo_code=FREETRIAL', manageLink: 'https://billing.stripe.com/p/login/test_14kfZocuh6Tz5ag7ss' }; diff --git a/openapi.json b/openapi.json index 6fcfd6a94..d5a266433 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.4.1" + "version": "0.7.4.2" }, "servers": [ { @@ -2827,7 +2827,7 @@ "tags": [ "License" ], - "summary": "Updates server license. Returns true if updated and valid", + "summary": "Updates server license", "description": "Caches the result", "requestBody": { "content": { @@ -3947,6 +3947,45 @@ } } }, + "/api/Rating/overall": { + "get": { + "tags": [ + "Rating" + ], + "parameters": [ + { + "name": "seriesId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/RatingDto" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/RatingDto" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/RatingDto" + } + } + } + } + } + } + }, "/api/Reader/pdf": { "get": { "tags": [ @@ -6525,9 +6564,12 @@ "in": "query", "schema": { "enum": [ - 1 + 0, + 1, + 2 ], "type": "integer", + "description": "Misleading name but is the source of data (like a review coming from AniList)", "format": "int32" } } @@ -8540,7 +8582,7 @@ "tags": [ "Server" ], - "summary": "Bust Review and Recommendation Cache", + "summary": "Bust Kavita+ Cache", "responses": { "200": { "description": "Success" @@ -13731,9 +13773,12 @@ }, "provider": { "enum": [ - 1 + 0, + 1, + 2 ], "type": "integer", + "description": "Misleading name but is the source of data (like a review coming from AniList)", "format": "int32" } }, @@ -17180,6 +17225,16 @@ "type": "string", "description": "The main body with just text, for review preview", "nullable": true + }, + "provider": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer", + "description": "If this review is External, which Provider did it come from", + "format": "int32" } }, "additionalProperties": false,