From f660a1cd0612805d760bbaf8a792dd670673e7ca Mon Sep 17 00:00:00 2001 From: Joe Milazzo Date: Thu, 25 Jan 2024 11:09:44 -0600 Subject: [PATCH] Bugfixes + Potential iOS Webtoon Reader Fix (#2650) --- API.Tests/Converters/CronConverterTests.cs | 8 +- API.Tests/Parser/MangaParserTests.cs | 1 + API/Controllers/DeviceController.cs | 3 +- API/Controllers/MetadataController.cs | 15 ++- API/Controllers/ServerController.cs | 11 +- API/Controllers/SettingsController.cs | 13 -- .../ExternalSeriesMetadataRepository.cs | 14 ++- API/Data/Repositories/SeriesRepository.cs | 7 +- API/Helpers/Converters/CronConverter.cs | 8 +- API/Services/EmailService.cs | 17 +-- API/Services/Plus/ExternalMetadataService.cs | 56 ++------- API/Services/SeriesService.cs | 9 +- API/Services/Tasks/Scanner/Parser/Parser.cs | 7 +- API/Services/Tasks/Scanner/ProcessSeries.cs | 2 + API/Services/Tasks/VersionUpdaterService.cs | 7 ++ UI/Web/i18n-cache-busting.json | 1 - UI/Web/package-lock.json | 24 +++- UI/Web/src/app/_services/server.service.ts | 2 +- .../manage-settings.component.ts | 2 + .../manage-users/manage-users.component.html | 2 +- .../_components/dashboard.component.html | 2 +- .../_components/dashboard.component.ts | 13 +- .../infinite-scroller.component.ts | 6 +- UI/Web/src/assets/langs/en.json | 9 +- openapi.json | 115 ++++++------------ 25 files changed, 157 insertions(+), 197 deletions(-) delete mode 100644 UI/Web/i18n-cache-busting.json diff --git a/API.Tests/Converters/CronConverterTests.cs b/API.Tests/Converters/CronConverterTests.cs index f54c2ca20..4e214e8f1 100644 --- a/API.Tests/Converters/CronConverterTests.cs +++ b/API.Tests/Converters/CronConverterTests.cs @@ -1,18 +1,20 @@ using API.Helpers.Converters; +using Hangfire; using Xunit; namespace API.Tests.Converters; - +#nullable enable public class CronConverterTests { [Theory] [InlineData("daily", "0 0 * * *")] [InlineData("disabled", "0 0 31 2 *")] [InlineData("weekly", "0 0 * * 1")] - [InlineData("", "0 0 31 2 *")] + [InlineData("0 0 31 2 *", "0 0 31 2 *")] [InlineData("sdfgdf", "sdfgdf")] [InlineData("* * * * *", "* * * * *")] - public void ConvertTest(string input, string expected) + [InlineData(null, "0 0 * * *")] // daily + public void ConvertTest(string? input, string expected) { Assert.Equal(expected, CronConverter.ConvertToCronNotation(input)); } diff --git a/API.Tests/Parser/MangaParserTests.cs b/API.Tests/Parser/MangaParserTests.cs index fe307092e..126e781d6 100644 --- a/API.Tests/Parser/MangaParserTests.cs +++ b/API.Tests/Parser/MangaParserTests.cs @@ -293,6 +293,7 @@ public class MangaParserTests [InlineData("Bleach 001-003", "1-3")] [InlineData("Accel World Volume 2", "0")] [InlineData("Historys Strongest Disciple Kenichi_v11_c90-98", "90-98")] + [InlineData("Historys Strongest Disciple Kenichi c01-c04", "1-4")] public void ParseChaptersTest(string filename, string expected) { Assert.Equal(expected, API.Services.Tasks.Scanner.Parser.Parser.ParseChapter(filename)); diff --git a/API/Controllers/DeviceController.cs b/API/Controllers/DeviceController.cs index c25b392e6..05d9af478 100644 --- a/API/Controllers/DeviceController.cs +++ b/API/Controllers/DeviceController.cs @@ -142,7 +142,8 @@ public class DeviceController : BaseApiController if (dto.SeriesId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "SeriesId")); if (dto.DeviceId < 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "DeviceId")); - if (await _emailService.IsDefaultEmailService()) + var isEmailSetup = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).IsEmailSetup(); + if (!isEmailSetup) return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email")); var userId = User.GetUserId(); diff --git a/API/Controllers/MetadataController.cs b/API/Controllers/MetadataController.cs index bbba068e3..ed9560464 100644 --- a/API/Controllers/MetadataController.cs +++ b/API/Controllers/MetadataController.cs @@ -8,6 +8,7 @@ using API.Data; using API.DTOs; using API.DTOs.Filtering; using API.DTOs.Metadata; +using API.DTOs.Recommendation; using API.DTOs.SeriesDetail; using API.Entities.Enums; using API.Extensions; @@ -207,13 +208,18 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc .OrderByDescending(review => review.Username.Equals(user.UserName) ? 1 : 0) .ToList(); - var cacheKey = CacheKey + seriesId + "_" + user.Id; + var cacheKey = CacheKey + seriesId; var results = await _cacheProvider.GetAsync(cacheKey); if (results.HasValue) { var cachedResult = results.Value; userReviews.AddRange(cachedResult.Reviews); cachedResult.Reviews = ReviewService.SelectSpectrumOfReviews(userReviews); + if (!await unitOfWork.UserRepository.IsUserAdminAsync(user)) + { + cachedResult.Recommendations.ExternalSeries = new List(); + } + return cachedResult; } @@ -221,10 +227,13 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc if (ret == null) return Ok(null); userReviews.AddRange(ret.Reviews); ret.Reviews = ReviewService.SelectSpectrumOfReviews(userReviews); - - await _cacheProvider.SetAsync(cacheKey, ret, TimeSpan.FromHours(24)); + if (!await unitOfWork.UserRepository.IsUserAdminAsync(user)) + { + ret.Recommendations.ExternalSeries = new List(); + } + return Ok(ret); } diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs index d42413985..5c08a2cc5 100644 --- a/API/Controllers/ServerController.cs +++ b/API/Controllers/ServerController.cs @@ -189,6 +189,15 @@ public class ServerController : BaseApiController return Ok(await _versionUpdaterService.CheckForUpdate()); } + /// + /// Returns how many versions out of date this install is + /// + [HttpGet("check-out-of-date")] + public async Task> CheckHowOutOfDate() + { + return Ok(await _versionUpdaterService.GetNumberOfReleasesBehind()); + } + /// /// Pull the Changelog for Kavita from Github and display @@ -260,7 +269,7 @@ public class ServerController : BaseApiController /// /// [Authorize("RequireAdminRole")] - [HttpPost("bust-review-and-rec-cache")] + [HttpPost("bust-kavitaplus-cache")] public async Task BustReviewAndRecCache() { _logger.LogInformation("Busting Kavita+ Cache"); diff --git a/API/Controllers/SettingsController.cs b/API/Controllers/SettingsController.cs index 9449e0079..0a80e60fb 100644 --- a/API/Controllers/SettingsController.cs +++ b/API/Controllers/SettingsController.cs @@ -124,19 +124,6 @@ public class SettingsController : BaseApiController return Ok(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()); } - /// - /// Sends a test email from the Email Service. - /// - /// - /// - [Authorize(Policy = "RequireAdminRole")] - [HttpPost("test-email-url")] - public async Task> TestEmailServiceUrl(TestEmailDto dto) - { - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId()); - return Ok(await _emailService.SendTestEmail(user!.Email)); - } - /// /// Is the minimum information setup for Email to work /// diff --git a/API/Data/Repositories/ExternalSeriesMetadataRepository.cs b/API/Data/Repositories/ExternalSeriesMetadataRepository.cs index 1b5f9a928..160c1f24f 100644 --- a/API/Data/Repositories/ExternalSeriesMetadataRepository.cs +++ b/API/Data/Repositories/ExternalSeriesMetadataRepository.cs @@ -85,9 +85,9 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor { return _context.ExternalSeriesMetadata .Where(s => s.SeriesId == seriesId) - .Include(s => s.ExternalReviews.Take(25)) - .Include(s => s.ExternalRatings.Take(25)) - .Include(s => s.ExternalRecommendations.Take(25)) + .Include(s => s.ExternalReviews.Take(limit)) + .Include(s => s.ExternalRatings.Take(limit)) + .Include(s => s.ExternalRecommendations.Take(limit)) .AsSplitQuery() .FirstOrDefaultAsync(); } @@ -138,7 +138,13 @@ public class ExternalSeriesMetadataRepository : IExternalSeriesMetadataRepositor var seriesDetailPlusDto = new SeriesDetailPlusDto() { Ratings = seriesDetailDto.ExternalRatings.Select(r => _mapper.Map(r)), - Reviews = seriesDetailDto.ExternalReviews.OrderByDescending(r => r.Score).Select(r => _mapper.Map(r)), + Reviews = seriesDetailDto.ExternalReviews.OrderByDescending(r => r.Score) + .Select(r => + { + var ret = _mapper.Map(r); + ret.IsExternal = true; + return ret; + }), Recommendations = new RecommendationDto() { ExternalSeries = externalSeriesRecommendations, diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index b1b3d1396..d4af1802a 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -490,6 +490,10 @@ public class SeriesRepository : ISeriesRepository .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(); } + else + { + result.Files = new List(); + } result.Chapters = await _context.Chapter .Include(c => c.Files) @@ -1930,7 +1934,8 @@ public class SeriesRepository : ISeriesRepository { // If there is 0 or 1 rating and that rating is you, return 0 back var countOfRatingsThatAreUser = await _context.AppUserRating - .Where(r => r.SeriesId == seriesId && r.HasBeenRated).CountAsync(u => u.AppUserId == userId); + .Where(r => r.SeriesId == seriesId && r.HasBeenRated) + .CountAsync(u => u.AppUserId == userId); if (countOfRatingsThatAreUser == 1) { return 0; diff --git a/API/Helpers/Converters/CronConverter.cs b/API/Helpers/Converters/CronConverter.cs index 4443b4c8a..f1f0ebc1b 100644 --- a/API/Helpers/Converters/CronConverter.cs +++ b/API/Helpers/Converters/CronConverter.cs @@ -12,8 +12,14 @@ public static class CronConverter "daily", "weekly", }; - public static string ConvertToCronNotation(string source) + /// + /// Converts to Cron Notation + /// + /// Defaults to daily + /// + public static string ConvertToCronNotation(string? source) { + if (string.IsNullOrEmpty(source)) return Cron.Daily(); return source.ToLower() switch { "daily" => Cron.Daily(), diff --git a/API/Services/EmailService.cs b/API/Services/EmailService.cs index 8158618a4..afe582145 100644 --- a/API/Services/EmailService.cs +++ b/API/Services/EmailService.cs @@ -7,12 +7,8 @@ using System.Net; using System.Threading.Tasks; using API.Data; using API.DTOs.Email; -using API.Entities.Enums; -using Flurl.Http; using Kavita.Common; -using Kavita.Common.EnvironmentInfo; using MailKit.Security; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using MimeKit; @@ -38,7 +34,6 @@ public interface IEmailService Task SendForgotPasswordEmail(PasswordResetEmailDto dto); Task SendFilesToEmail(SendToDto data); Task SendTestEmail(string adminEmail); - Task IsDefaultEmailService(); Task SendEmailChangeEmail(ConfirmationEmailDto data); bool IsValidEmail(string email); } @@ -47,7 +42,6 @@ public class EmailService : IEmailService { private readonly ILogger _logger; private readonly IUnitOfWork _unitOfWork; - private readonly IDownloadService _downloadService; private readonly IDirectoryService _directoryService; private const string TemplatePath = @"{0}.html"; @@ -57,11 +51,10 @@ public class EmailService : IEmailService public const string DefaultApiUrl = "https://email.kavitareader.com"; - public EmailService(ILogger logger, IUnitOfWork unitOfWork, IDownloadService downloadService, IDirectoryService directoryService) + public EmailService(ILogger logger, IUnitOfWork unitOfWork, IDirectoryService directoryService) { _logger = logger; _unitOfWork = unitOfWork; - _downloadService = downloadService; _directoryService = directoryService; } @@ -114,14 +107,6 @@ public class EmailService : IEmailService return result; } - - [Obsolete] - public async Task IsDefaultEmailService() - { - return (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.EmailServiceUrl))!.Value! - .Equals(DefaultApiUrl); - } - /// /// Sends an email that has a link that will finalize an Email Change /// diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index ad7050be6..9a157479e 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -36,7 +36,7 @@ internal class ExternalMetadataIdsDto public MediaFormat? PlusMediaFormat { get; set; } = MediaFormat.Unknown; } -internal class SeriesDetailPlusAPIDto +internal class SeriesDetailPlusApiDto { public IEnumerable Recommendations { get; set; } public IEnumerable Reviews { get; set; } @@ -108,7 +108,7 @@ public class ExternalMetadataService : IExternalMetadataService if (!needsRefresh) { // Convert into DTOs and return - return await SerializeExternalSeriesDetail(seriesId, externalSeriesMetadata, user, series); + return await SerializeExternalSeriesDetail(seriesId, series.LibraryId, user); } try @@ -123,7 +123,7 @@ public class ExternalMetadataService : IExternalMetadataService .WithHeader("Content-Type", "application/json") .WithTimeout(TimeSpan.FromSeconds(Configuration.DefaultTimeOutSecs)) .PostJsonAsync(new PlusSeriesDtoBuilder(series).Build()) - .ReceiveJson(); + .ReceiveJson(); // Clear out existing results @@ -149,7 +149,7 @@ public class ExternalMetadataService : IExternalMetadataService // Recommendations externalSeriesMetadata.ExternalRecommendations ??= new List(); - var recs = await ProcessRecommendations(series, user!, result.Recommendations, externalSeriesMetadata); + var recs = await ProcessRecommendations(series, user, result.Recommendations, externalSeriesMetadata); externalSeriesMetadata.LastUpdatedUtc = DateTime.UtcNow; externalSeriesMetadata.AverageExternalRating = (int) externalSeriesMetadata.ExternalRatings @@ -161,14 +161,12 @@ public class ExternalMetadataService : IExternalMetadataService await _unitOfWork.CommitAsync(); - var ret = new SeriesDetailPlusDto() + return new SeriesDetailPlusDto() { Recommendations = recs, Ratings = result.Ratings, Reviews = result.Reviews }; - - return ret; } catch (FlurlHttpException ex) { @@ -185,46 +183,9 @@ public class ExternalMetadataService : IExternalMetadataService return null; } - private async Task SerializeExternalSeriesDetail(int seriesId, ExternalSeriesMetadata externalSeriesMetadata, - AppUser user, Series series) + private async Task SerializeExternalSeriesDetail(int seriesId, int libraryId, AppUser user) { - var seriesIdsOnServer = externalSeriesMetadata.ExternalRecommendations - .Where(r => r.SeriesId is > 0) - .Select(s => (int) s.SeriesId!) - .ToList(); - - var ownedSeries = (await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(seriesIdsOnServer, user.Id)) - .ToList(); - var canSeeExternalSeries = user is {AgeRestriction: AgeRating.NotApplicable} && - await _unitOfWork.UserRepository.IsUserAdminAsync(user); - var externalSeries = new List(); - if (canSeeExternalSeries) - { - externalSeries = externalSeriesMetadata.ExternalRecommendations - .Where(r => r.SeriesId is null or 0) - .Select(r => _mapper.Map(r)) - .ToList(); - } - - var ret = await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesDetailPlusDto(seriesId, series.LibraryId, user); - - return new SeriesDetailPlusDto() - { - Ratings = externalSeriesMetadata.ExternalRatings.Select(r => _mapper.Map(r)), - Reviews = externalSeriesMetadata.ExternalReviews.OrderByDescending(r => r.Score).Select(r => - { - var review = _mapper.Map(r); - review.SeriesId = seriesId; - review.LibraryId = series.LibraryId; - review.IsExternal = true; - return review; - }), - Recommendations = new RecommendationDto() - { - ExternalSeries = externalSeries, - OwnedSeries = ownedSeries - } - }; + return await _unitOfWork.ExternalSeriesMetadataRepository.GetSeriesDetailPlusDto(seriesId, libraryId, user); } private async Task GetExternalSeriesMetadataForSeries(int seriesId, Series series) @@ -249,8 +210,6 @@ public class ExternalMetadataService : IExternalMetadataService OwnedSeries = new List() }; - var canSeeExternalSeries = user is {AgeRestriction: AgeRating.NotApplicable} && - await _unitOfWork.UserRepository.IsUserAdminAsync(user); // NOTE: This can result in a series being recommended that shares the same name but different format foreach (var rec in recs) { @@ -276,7 +235,6 @@ public class ExternalMetadataService : IExternalMetadataService continue; } - if (!canSeeExternalSeries) continue; // We can show this based on user permissions if (string.IsNullOrEmpty(rec.Name) || string.IsNullOrEmpty(rec.SiteUrl) || string.IsNullOrEmpty(rec.CoverUrl)) continue; recDto.ExternalSeries.Add(new ExternalSeriesDto() diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index 17b8f0c8a..32d4abbc8 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -32,7 +32,7 @@ namespace API.Services; public interface ISeriesService { Task GetSeriesDetail(int seriesId, int userId); - Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto, int userId = 0); + Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto); Task UpdateRating(AppUser user, UpdateSeriesRatingDto updateSeriesRatingDto); Task DeleteMultipleSeries(IList seriesIds); Task UpdateRelatedSeries(UpdateRelatedSeriesDto dto); @@ -111,9 +111,8 @@ public class SeriesService : ISeriesService /// Updates the Series Metadata. /// /// - /// If 0, does not bust any cache /// - public async Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto, int userId = 0) + public async Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto) { var hasWebLinksChanged = false; try @@ -315,10 +314,10 @@ public class SeriesService : ISeriesService _logger.LogError(ex, "There was an issue cleaning up DB entries. This may happen if Komf is spamming updates. Nightly cleanup will work"); } - if (hasWebLinksChanged && userId > 0) + if (hasWebLinksChanged) { _logger.LogDebug("Clearing cache as series weblinks may have changed"); - await _cacheProvider.RemoveAsync(MetadataController.CacheKey + seriesId + userId); + await _cacheProvider.RemoveAsync(MetadataController.CacheKey + seriesId); } diff --git a/API/Services/Tasks/Scanner/Parser/Parser.cs b/API/Services/Tasks/Scanner/Parser/Parser.cs index b30fd9f7f..a557e6458 100644 --- a/API/Services/Tasks/Scanner/Parser/Parser.cs +++ b/API/Services/Tasks/Scanner/Parser/Parser.cs @@ -543,7 +543,7 @@ public static class Parser { // Historys Strongest Disciple Kenichi_v11_c90-98.zip, ...c90.5-100.5 new Regex( - @"(\b|_)(c|ch)(\.?\s?)(?(\d+(\.\d)?)(-\d+(\.\d)?)?)", + @"(\b|_)(c|ch)(\.?\s?)(?(\d+(\.\d)?)(-c?\d+(\.\d)?)?)", MatchOptions, RegexTimeout), // [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip new Regex( @@ -761,6 +761,11 @@ public static class Parser var from = RemoveLeadingZeroes(tokens[0]); if (tokens.Length != 2) return from; + // Occasionally users will use c01-c02 instead of c01-02, clean any leftover c + if (tokens[1].StartsWith("c", StringComparison.InvariantCultureIgnoreCase)) + { + tokens[1] = tokens[1].Replace("c", string.Empty, StringComparison.InvariantCultureIgnoreCase); + } var to = RemoveLeadingZeroes(hasPart ? AddChapterPart(tokens[1]) : tokens[1]); return $"{from}-{to}"; } diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index 16a99fef6..d9ff92282 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -752,6 +752,8 @@ public class ProcessSeries : IProcessSeries .Where(s => !string.IsNullOrEmpty(s)) .Select(s => s.Trim()) ); + + // For each weblink, try to parse out some MetadataIds and store in the Chapter directly for matching (CBL) } if (!string.IsNullOrEmpty(comicInfo.Isbn)) diff --git a/API/Services/Tasks/VersionUpdaterService.cs b/API/Services/Tasks/VersionUpdaterService.cs index 5dbcd1d15..ca1bd9519 100644 --- a/API/Services/Tasks/VersionUpdaterService.cs +++ b/API/Services/Tasks/VersionUpdaterService.cs @@ -48,6 +48,7 @@ public interface IVersionUpdaterService Task CheckForUpdate(); Task PushUpdate(UpdateNotificationDto update); Task> GetAllReleases(); + Task GetNumberOfReleasesBehind(); } public class VersionUpdaterService : IVersionUpdaterService @@ -87,6 +88,12 @@ public class VersionUpdaterService : IVersionUpdaterService return updates.Select(CreateDto).Where(d => d != null)!; } + public async Task GetNumberOfReleasesBehind() + { + var updates = await GetAllReleases(); + return updates.TakeWhile(update => update.UpdateVersion != update.CurrentVersion).Count(); + } + private UpdateNotificationDto? CreateDto(GithubReleaseMetadata? update) { if (update == null || string.IsNullOrEmpty(update.Tag_Name)) return null; diff --git a/UI/Web/i18n-cache-busting.json b/UI/Web/i18n-cache-busting.json deleted file mode 100644 index a58449549..000000000 --- a/UI/Web/i18n-cache-busting.json +++ /dev/null @@ -1 +0,0 @@ -{"zh_Hant":"05191aaae25a26a8597559e8318f97db","zh_Hans":"7edb04f6c2439da2cde73996aed08029","uk":"ccf59f571821ab842882378395ccf48c","tr":"5d6427179210cc370400b816c9d1116d","th":"1e27a1e1cadb2b9f92d85952bffaab95","sk":"24de417448b577b4899e917b70a43263","ru":"c547f0995c167817dd2408e4e9279de2","pt_BR":"5acd3a08c1d9aabfae5a74a438cff79b","pt":"af4162a48f01c5260d6436e7e000c5ef","pl":"c6488fdb9a1ecfe5cde6bd1c264902aa","nl":"3ff322f7b24442bd6bceb5c692146d4f","nb_NO":"99914b932bd37a50b983c5e7c90ae93b","ms":"9fdfcc11a2e8a58a4baa691b93d93ff7","ko":"447e24f9f60e1b9f36bc0b087d059dbd","ja":"27bec4796972f0338404ebdb5829af14","it":"4ef0a0ef56bab4650eda37e0dd841982","id":"cfaff69f0a68d9b6196b6c11986508f8","hi":"d850bb49ec6b5a5ccf9986823f095ab8","fr":"c648c43f9ea0bb20ddb00c0566bbd85a","es":"5816bb68d1d64c40de890c0be0222c71","en":"9b15b7b325483ec581eca99a4bb93f93","de":"c3a4fd22b51fd5a675363a6a35d1611e","cs":"bd76bfbd0e5538378dfe99d034b2adfe"} \ No newline at end of file diff --git a/UI/Web/package-lock.json b/UI/Web/package-lock.json index 7b5015aae..38435a054 100644 --- a/UI/Web/package-lock.json +++ b/UI/Web/package-lock.json @@ -681,6 +681,7 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.1.0.tgz", "integrity": "sha512-WDpO4WvC5ItjaRexnpFpKPpT+cu+5GYkWF8h74iHhfxOgU+gaQiMWERHylWCqF25AzmhKu0iI3ZZtaIJ6qqwog==", + "dev": true, "dependencies": { "@babel/core": "7.23.2", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -5674,6 +5675,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5919,6 +5921,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, "engines": { "node": ">=8" } @@ -6224,6 +6227,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "funding": [ { "type": "individual", @@ -6472,7 +6476,8 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true }, "node_modules/cookie": { "version": "0.5.0", @@ -7362,6 +7367,7 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -7371,6 +7377,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -8450,6 +8457,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -9224,6 +9232,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -11000,6 +11009,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -12390,6 +12400,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -12400,7 +12411,8 @@ "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "dev": true }, "node_modules/regenerate": { "version": "1.4.2", @@ -12852,7 +12864,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true + "dev": true }, "node_modules/sass": { "version": "1.69.7", @@ -12968,6 +12980,7 @@ "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==", + "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -12982,6 +12995,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -12992,7 +13006,8 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/send": { "version": "0.18.0", @@ -14099,6 +14114,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/UI/Web/src/app/_services/server.service.ts b/UI/Web/src/app/_services/server.service.ts index e4936efd9..3ccdad395 100644 --- a/UI/Web/src/app/_services/server.service.ts +++ b/UI/Web/src/app/_services/server.service.ts @@ -63,7 +63,7 @@ export class ServerService { } bustCache() { - return this.http.post(this.baseUrl + 'server/bust-review-and-rec-cache', {}); + return this.http.post(this.baseUrl + 'server/bust-kavitaplus-cache', {}); } getMediaErrors() { diff --git a/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts b/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts index e80688360..358d68796 100644 --- a/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts +++ b/UI/Web/src/app/admin/manage-settings/manage-settings.component.ts @@ -45,6 +45,7 @@ export class ManageSettingsComponent implements OnInit { this.settingsForm.addControl('cacheDirectory', new FormControl(this.serverSettings.cacheDirectory, [Validators.required])); this.settingsForm.addControl('taskScan', new FormControl(this.serverSettings.taskScan, [Validators.required])); this.settingsForm.addControl('taskBackup', new FormControl(this.serverSettings.taskBackup, [Validators.required])); + this.settingsForm.addControl('taskCleanup', new FormControl(this.serverSettings.taskCleanup, [Validators.required])); this.settingsForm.addControl('ipAddresses', new FormControl(this.serverSettings.ipAddresses, [Validators.required, Validators.pattern(ValidIpAddress)])); this.settingsForm.addControl('port', new FormControl(this.serverSettings.port, [Validators.required])); this.settingsForm.addControl('loggingLevel', new FormControl(this.serverSettings.loggingLevel, [Validators.required])); @@ -77,6 +78,7 @@ export class ManageSettingsComponent implements OnInit { this.settingsForm.get('cacheDirectory')?.setValue(this.serverSettings.cacheDirectory); this.settingsForm.get('scanTask')?.setValue(this.serverSettings.taskScan); this.settingsForm.get('taskBackup')?.setValue(this.serverSettings.taskBackup); + this.settingsForm.get('taskCleanup')?.setValue(this.serverSettings.taskCleanup); this.settingsForm.get('ipAddresses')?.setValue(this.serverSettings.ipAddresses); this.settingsForm.get('port')?.setValue(this.serverSettings.port); this.settingsForm.get('loggingLevel')?.setValue(this.serverSettings.loggingLevel); diff --git a/UI/Web/src/app/admin/manage-users/manage-users.component.html b/UI/Web/src/app/admin/manage-users/manage-users.component.html index 9232a069e..5ea7d766d 100644 --- a/UI/Web/src/app/admin/manage-users/manage-users.component.html +++ b/UI/Web/src/app/admin/manage-users/manage-users.component.html @@ -48,7 +48,7 @@ {{t('roles-title')}} {{null | defaultValue}} - Admin + {{t('admin')}} {{role}} diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.html b/UI/Web/src/app/dashboard/_components/dashboard.component.html index 9b0008c31..47a2cb9d3 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.html +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.html @@ -55,7 +55,7 @@ + (reload)="reloadStream(stream.id, true)" (dataChanged)="reloadStream(stream.id)"> } diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.ts b/UI/Web/src/app/dashboard/_components/dashboard.component.ts index ce011ceb7..5da160d30 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.ts +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.ts @@ -193,12 +193,17 @@ export class DashboardComponent implements OnInit { this.cdRef.markForCheck(); } - reloadStream(streamId: number) { + reloadStream(streamId: number, onDeck = false) { const index = this.streams.findIndex(s => s.id === streamId); if (index < 0) return; - this.streams[index] = {...this.streams[index]}; - console.log('swapped out stream: ', this.streams[index]); - this.cdRef.detectChanges(); + if (onDeck) { + // TODO: Need to figure out a better way to refresh just one stream + this.refreshStreams$.next(); + this.cdRef.markForCheck(); + } else { + this.streams[index] = {...this.streams[index]}; + this.cdRef.markForCheck(); + } } async handleRecentlyAddedChapterClick(item: RecentlyAddedItem) { diff --git a/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts b/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts index 5329fee8b..4ac1c465c 100644 --- a/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts +++ b/UI/Web/src/app/manga-reader/_components/infinite-scroller/infinite-scroller.component.ts @@ -206,9 +206,9 @@ export class InfiniteScrollerComponent implements OnInit, OnChanges, OnDestroy { .pipe(debounceTime(20), takeUntilDestroyed(this.destroyRef)) .subscribe((event) => this.handleScrollEvent(event)); - fromEvent(this.isFullscreenMode ? this.readerElemRef.nativeElement : this.document.body, 'scrollend') - .pipe(debounceTime(20), takeUntilDestroyed(this.destroyRef)) - .subscribe((event) => this.handleScrollEndEvent(event)); + // fromEvent(this.isFullscreenMode ? this.readerElemRef.nativeElement : this.document.body, 'scrollend') + // .pipe(debounceTime(20), takeUntilDestroyed(this.destroyRef)) + // .subscribe((event) => this.handleScrollEndEvent(event)); } ngOnInit(): void { diff --git a/UI/Web/src/assets/langs/en.json b/UI/Web/src/assets/langs/en.json index 2ed8cefcc..1a89ac738 100644 --- a/UI/Web/src/assets/langs/en.json +++ b/UI/Web/src/assets/langs/en.json @@ -558,7 +558,7 @@ "invite-user": { "title": "Invite User", "close": "{{common.close}}", - "description": "Invite a user to your server by entering their email. They'll receive an email to create an account. For this to work you must have the Host Name field set in the Email tab or be accessing your instance through it's remote URL.

Alternatively, you can use a username, but note that password resets won't be available for username-based accounts. If you do not want to use our email service, you can host your own email service.", + "description": "Invite a user to your server by entering their email. They'll receive an email to create an account. For this to work you must have the Host Name and Email setting fields set in the Email tab, otherwise a link will be presented to you to setup on the user's behalf.

The email is not required to be valid.", "email": "{{common.email}}", "required-field": "{{common.required-field}}", "setup-user-title": "User invited", @@ -1223,8 +1223,8 @@ "title": "Recurring Tasks", "library-scan-label": "Library Scan", "library-scan-tooltip": "How often Kavita will scan and refresh metadata around library files.", - "library-database-backup-label": "Library Database Backup", - "library-database-backup-tooltip": "How often Kavita will backup the database.", + "library-database-backup-label": "Kavita Backup", + "library-database-backup-tooltip": "How often Kavita will backup the database and other related files.", "cleanup-label": "Cleanup", "cleanup-tooltip": "How often Kavita will run cleanup tasks. This can be heavy and should be performed at midnight in most cases", "adhoc-tasks-title": "Ad-hoc Tasks", @@ -1237,7 +1237,7 @@ "required": "{{validation.required-field}}", "custom-label": "Custom Schedule (Cron Notation)", - "cron-notation": "You must use cron notation for custom scheduling", + "cron-notation": "This is not valid Cron Notation", "recurring-tasks-title": "{{title}}", "last-executed-header": "Last Executed", @@ -1302,6 +1302,7 @@ "change-password-alt": "Change Password {{user}}", "resend": "Resend", "setup": "Setup", + "admin": "Admin", "last-active-title": "Last Active:", "roles-title": "Roles:", "none": "None", diff --git a/openapi.json b/openapi.json index 7e462ad79..aa0986bd9 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.13.1" + "version": "0.7.13.2" }, "servers": [ { @@ -9488,6 +9488,39 @@ } } }, + "/api/Server/check-out-of-date": { + "get": { + "tags": [ + "Server" + ], + "summary": "Returns how many versions out of date this install is", + "responses": { + "200": { + "description": "Success", + "content": { + "text/plain": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "text/json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + } + } + } + }, "/api/Server/changelog": { "get": { "tags": [ @@ -9649,7 +9682,7 @@ } } }, - "/api/Server/bust-review-and-rec-cache": { + "/api/Server/bust-kavitaplus-cache": { "post": { "tags": [ "Server" @@ -9869,56 +9902,6 @@ } } }, - "/api/Settings/test-email-url": { - "post": { - "tags": [ - "Settings" - ], - "summary": "Sends a test email from the Email Service.", - "requestBody": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TestEmailDto" - } - }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/TestEmailDto" - } - }, - "application/*+json": { - "schema": { - "$ref": "#/components/schemas/TestEmailDto" - } - } - } - }, - "responses": { - "200": { - "description": "Success", - "content": { - "text/plain": { - "schema": { - "$ref": "#/components/schemas/EmailTestResultDto" - } - }, - "application/json": { - "schema": { - "$ref": "#/components/schemas/EmailTestResultDto" - } - }, - "text/json": { - "schema": { - "$ref": "#/components/schemas/EmailTestResultDto" - } - } - } - } - } - } - }, "/api/Settings/is-email-setup": { "get": { "tags": [ @@ -14782,24 +14765,6 @@ }, "additionalProperties": false }, - "EmailTestResultDto": { - "type": "object", - "properties": { - "successful": { - "type": "boolean" - }, - "errorMessage": { - "type": "string", - "nullable": true - }, - "emailAddress": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false, - "description": "Represents if Test Email Service URL was successful or not and if any error occured" - }, "ExternalRating": { "type": "object", "properties": { @@ -19110,16 +19075,6 @@ }, "additionalProperties": false }, - "TestEmailDto": { - "type": "object", - "properties": { - "url": { - "type": "string", - "nullable": true - } - }, - "additionalProperties": false - }, "TokenRequestDto": { "type": "object", "properties": {