mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-02 05:04:14 -04:00
Nightly Issues (#2618)
This commit is contained in:
parent
0ff6d4a6fc
commit
d145dca0e7
@ -11,7 +11,7 @@
|
|||||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.4" />
|
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="20.0.4" />
|
||||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="20.0.4" />
|
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="20.0.4" />
|
||||||
<PackageReference Include="xunit" Version="2.6.5" />
|
<PackageReference Include="xunit" Version="2.6.6" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
<PackageReference Include="Flurl" Version="3.0.7" />
|
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||||
<PackageReference Include="Hangfire" Version="1.8.7" />
|
<PackageReference Include="Hangfire" Version="1.8.7" />
|
||||||
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
|
<PackageReference Include="Hangfire.InMemory" Version="0.7.0" />
|
||||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.0" />
|
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.4.0" />
|
||||||
@ -81,7 +81,7 @@
|
|||||||
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||||
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
|
<PackageReference Include="Nager.ArticleNumber" Version="1.0.7" />
|
||||||
<PackageReference Include="NetVips" Version="2.4.0" />
|
<PackageReference Include="NetVips" Version="2.4.0" />
|
||||||
<PackageReference Include="NetVips.Native" Version="8.15.0" />
|
<PackageReference Include="NetVips.Native" Version="8.15.1" />
|
||||||
<PackageReference Include="NReco.Logging.File" Version="1.2.0" />
|
<PackageReference Include="NReco.Logging.File" Version="1.2.0" />
|
||||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
|
||||||
@ -92,15 +92,15 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
<PackageReference Include="Serilog.Sinks.SignalR.Core" Version="0.1.2" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.35.0" />
|
<PackageReference Include="SharpCompress" Version="0.36.0" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.15.0.81779">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.2.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
|
<PackageReference Include="System.IO.Abstractions" Version="20.0.4" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
|
<PackageReference Include="System.Drawing.Common" Version="8.0.1" />
|
||||||
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
|
<PackageReference Include="VersOne.Epub" Version="3.3.1" />
|
||||||
|
@ -31,12 +31,7 @@ public class LicenseController(
|
|||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
||||||
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
|
public async Task<ActionResult<bool>> HasValidLicense(bool forceCheck = false)
|
||||||
{
|
{
|
||||||
var ret = await licenseService.HasActiveLicense(forceCheck);
|
return Ok(await licenseService.HasActiveLicense(forceCheck));
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
await taskScheduler.ScheduleKavitaPlusTasks();
|
|
||||||
}
|
|
||||||
return Ok(ret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -190,23 +190,12 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc
|
|||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = ["seriesId"])]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.KavitaPlus, VaryByQueryKeys = ["seriesId"])]
|
||||||
public async Task<ActionResult<SeriesDetailPlusDto>> GetKavitaPlusSeriesDetailData(int seriesId)
|
public async Task<ActionResult<SeriesDetailPlusDto>> GetKavitaPlusSeriesDetailData(int seriesId)
|
||||||
{
|
{
|
||||||
var seriesDetail = new SeriesDetailPlusDto();
|
|
||||||
if (!await licenseService.HasActiveLicense())
|
if (!await licenseService.HasActiveLicense())
|
||||||
{
|
{
|
||||||
seriesDetail.Recommendations = null;
|
return Ok(null);
|
||||||
seriesDetail.Ratings = Enumerable.Empty<RatingDto>();
|
|
||||||
return Ok(seriesDetail);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
seriesDetail = await metadataService.GetSeriesDetail(User.GetUserId(), seriesId);
|
return Ok(await metadataService.GetSeriesDetail(User.GetUserId(), seriesId));
|
||||||
|
|
||||||
// Temp solution, needs to be updated with new API
|
|
||||||
// seriesDetail.Ratings = await ratingService.GetRatings(seriesId);
|
|
||||||
// seriesDetail.Reviews = await reviewService.GetReviewsForSeries(User.GetUserId(), seriesId);
|
|
||||||
// seriesDetail.Recommendations =
|
|
||||||
// await recommendationService.GetRecommendationsForSeries(User.GetUserId(), seriesId);
|
|
||||||
|
|
||||||
return Ok(seriesDetail);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,7 @@ public class LibraryRepository : ILibraryRepository
|
|||||||
return await _context.Library
|
return await _context.Library
|
||||||
.Include(l => l.AppUsers)
|
.Include(l => l.AppUsers)
|
||||||
.Includes(includes)
|
.Includes(includes)
|
||||||
|
.AsSplitQuery()
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Constants;
|
||||||
using API.Data.ManualMigrations;
|
using API.Data.ManualMigrations;
|
||||||
using API.Data.Misc;
|
using API.Data.Misc;
|
||||||
using API.Data.Scanner;
|
using API.Data.Scanner;
|
||||||
@ -31,6 +32,7 @@ using API.Services.Tasks;
|
|||||||
using API.Services.Tasks.Scanner;
|
using API.Services.Tasks.Scanner;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.QueryableExtensions;
|
using AutoMapper.QueryableExtensions;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
|
|
||||||
@ -152,14 +154,16 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
{
|
{
|
||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
private readonly UserManager<AppUser> _userManager;
|
||||||
|
|
||||||
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled,
|
private readonly Regex _yearRegex = new Regex(@"\d{4}", RegexOptions.Compiled,
|
||||||
Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||||
|
|
||||||
public SeriesRepository(DataContext context, IMapper mapper)
|
public SeriesRepository(DataContext context, IMapper mapper, UserManager<AppUser> userManager)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(Series series)
|
public void Add(Series series)
|
||||||
@ -462,6 +466,10 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.SelectMany(v => v.Chapters)
|
.SelectMany(v => v.Chapters)
|
||||||
.SelectMany(c => c.Files.Select(f => f.Id));
|
.SelectMany(c => c.Files.Select(f => f.Id));
|
||||||
|
|
||||||
|
// Need to check if an admin
|
||||||
|
var user = await _context.AppUser.FirstAsync(u => u.Id == userId);
|
||||||
|
if (await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole))
|
||||||
|
{
|
||||||
result.Files = await _context.MangaFile
|
result.Files = await _context.MangaFile
|
||||||
.Where(m => EF.Functions.Like(m.FilePath, $"%{searchQuery}%") && fileIds.Contains(m.Id))
|
.Where(m => EF.Functions.Like(m.FilePath, $"%{searchQuery}%") && fileIds.Contains(m.Id))
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
@ -469,7 +477,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.OrderBy(f => f.FilePath)
|
.OrderBy(f => f.FilePath)
|
||||||
.ProjectTo<MangaFileDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<MangaFileDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
result.Chapters = await _context.Chapter
|
result.Chapters = await _context.Chapter
|
||||||
.Include(c => c.Files)
|
.Include(c => c.Files)
|
||||||
|
@ -48,7 +48,7 @@ public class UnitOfWork : IUnitOfWork
|
|||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper);
|
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper, _userManager);
|
||||||
public IUserRepository UserRepository => new UserRepository(_context, _userManager, _mapper);
|
public IUserRepository UserRepository => new UserRepository(_context, _userManager, _mapper);
|
||||||
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
||||||
|
|
||||||
|
@ -114,12 +114,20 @@ public class ExternalMetadataService : IExternalMetadataService
|
|||||||
Reviews = result.Reviews
|
Reviews = result.Reviews
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (FlurlHttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.StatusCode == 404)
|
||||||
{
|
{
|
||||||
_logger.LogError(e, "An error happened during the request to Kavita+ API");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An error happened during the request to Kavita+ API");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<RecommendationDto> ProcessRecommendations(Series series, AppUser user, IEnumerable<MediaRecommendationDto> recs)
|
private async Task<RecommendationDto> ProcessRecommendations(Series series, AppUser user, IEnumerable<MediaRecommendationDto> recs)
|
||||||
{
|
{
|
||||||
|
@ -151,9 +151,8 @@ public class TaskScheduler : ITaskScheduler
|
|||||||
{
|
{
|
||||||
// KavitaPlus based (needs license check)
|
// KavitaPlus based (needs license check)
|
||||||
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
|
var license = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey)).Value;
|
||||||
if (!await _licenseService.HasActiveSubscription(license))
|
if (string.IsNullOrEmpty(license) || !await _licenseService.HasActiveSubscription(license))
|
||||||
{
|
{
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RecurringJob.AddOrUpdate(CheckScrobblingTokens, () => _scrobblingService.CheckExternalAccessTokens(), Cron.Daily, RecurringJobOptions);
|
RecurringJob.AddOrUpdate(CheckScrobblingTokens, () => _scrobblingService.CheckExternalAccessTokens(), Cron.Daily, RecurringJobOptions);
|
||||||
|
@ -122,10 +122,16 @@ public class ProcessSeries : IProcessSeries
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "There was an exception finding existing series for {SeriesName} with Localized name of {LocalizedName} for library {LibraryId}. This indicates you have duplicate series with same name or localized name in the library. Correct this and rescan", firstInfo.Series, firstInfo.LocalizedSeries, library.Id);
|
var series2 = await _unitOfWork.SeriesRepository.GetFullSeriesByAnyName(firstInfo.LocalizedSeries, string.Empty, library.Id, firstInfo.Format, false);
|
||||||
|
var details = $"Series 1: {firstInfo.Series} Series 2: {series2.Name}" + "\n" +
|
||||||
|
$"Localized: {firstInfo.LocalizedSeries} Localized: {series2.LocalizedName}" + "\n" +
|
||||||
|
$"Filename: {_directoryService.FileSystem.FileInfo.New(firstInfo.FullFilePath).Directory} Filename: {series2.FolderPath}";
|
||||||
|
_logger.LogError(ex, "Scanner found a Series {SeriesName} which matched another Series {LocalizedName} in a different folder parallel to Library {LibraryName} root folder. This is not allowed. Please correct",
|
||||||
|
firstInfo.Series, firstInfo.LocalizedSeries, library.Name);
|
||||||
|
|
||||||
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
await _eventHub.SendMessageAsync(MessageFactory.Error,
|
||||||
MessageFactory.ErrorEvent($"There was an exception finding existing series for {firstInfo.Series} with Localized name of {firstInfo.LocalizedSeries} for library {library.Id}",
|
MessageFactory.ErrorEvent($"Scanner found a Series {firstInfo.Series} which matched another Series {firstInfo.LocalizedSeries} in a different folder parallel to Library {library.Name} root folder. This is not allowed. Please correct",
|
||||||
"This indicates you have duplicate series with same name or localized name in the library. Correct this and rescan."));
|
details));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,16 +349,26 @@ public class Startup
|
|||||||
opts.IncludeQueryInRequestPath = true;
|
opts.IncludeQueryInRequestPath = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var allowIframing = Configuration.AllowIFraming;
|
||||||
|
|
||||||
app.Use(async (context, next) =>
|
app.Use(async (context, next) =>
|
||||||
{
|
{
|
||||||
context.Response.Headers[HeaderNames.Vary] =
|
context.Response.Headers[HeaderNames.Vary] =
|
||||||
new[] { "Accept-Encoding" };
|
new[] { "Accept-Encoding" };
|
||||||
|
|
||||||
|
|
||||||
|
if (!allowIframing)
|
||||||
|
{
|
||||||
// Don't let the site be iframed outside the same origin (clickjacking)
|
// Don't let the site be iframed outside the same origin (clickjacking)
|
||||||
context.Response.Headers.XFrameOptions = Configuration.XFrameOptions;
|
context.Response.Headers.XFrameOptions = "SAMEORIGIN";
|
||||||
|
|
||||||
// Setup CSP to ensure we load assets only from these origins
|
// Setup CSP to ensure we load assets only from these origins
|
||||||
context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'none';");
|
context.Response.Headers.Add("Content-Security-Policy", "frame-ancestors 'none';");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogCritical("appsetting.json has allow iframing on! This may allow for clickjacking on the server. User beware");
|
||||||
|
}
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
});
|
});
|
||||||
|
@ -3,6 +3,5 @@
|
|||||||
"Port": 5000,
|
"Port": 5000,
|
||||||
"IpAddresses": "",
|
"IpAddresses": "",
|
||||||
"BaseUrl": "/test/",
|
"BaseUrl": "/test/",
|
||||||
"Cache": 90,
|
"Cache": 90
|
||||||
"XFrameOrigins": "SAMEORIGIN"
|
|
||||||
}
|
}
|
@ -13,7 +13,6 @@ public static class Configuration
|
|||||||
public const string DefaultBaseUrl = "/";
|
public const string DefaultBaseUrl = "/";
|
||||||
public const int DefaultHttpPort = 5000;
|
public const int DefaultHttpPort = 5000;
|
||||||
public const int DefaultTimeOutSecs = 90;
|
public const int DefaultTimeOutSecs = 90;
|
||||||
public const string DefaultXFrameOptions = "SAMEORIGIN";
|
|
||||||
public const long DefaultCacheMemory = 75;
|
public const long DefaultCacheMemory = 75;
|
||||||
private static readonly string AppSettingsFilename = Path.Join("config", GetAppSettingFilename());
|
private static readonly string AppSettingsFilename = Path.Join("config", GetAppSettingFilename());
|
||||||
|
|
||||||
@ -49,7 +48,7 @@ public static class Configuration
|
|||||||
set => SetCacheSize(GetAppSettingFilename(), value);
|
set => SetCacheSize(GetAppSettingFilename(), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string XFrameOptions => GetXFrameOptions(GetAppSettingFilename());
|
public static bool AllowIFraming => GetAllowIFraming(GetAppSettingFilename());
|
||||||
|
|
||||||
private static string GetAppSettingFilename()
|
private static string GetAppSettingFilename()
|
||||||
{
|
{
|
||||||
@ -293,26 +292,21 @@ public static class Configuration
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region XFrameOrigins
|
#region AllowIFraming
|
||||||
private static string GetXFrameOptions(string filePath)
|
private static bool GetAllowIFraming(string filePath)
|
||||||
{
|
{
|
||||||
if (OsInfo.IsDocker)
|
|
||||||
{
|
|
||||||
return DefaultBaseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = File.ReadAllText(filePath);
|
var json = File.ReadAllText(filePath);
|
||||||
var jsonObj = JsonSerializer.Deserialize<AppSettings>(json);
|
var jsonObj = JsonSerializer.Deserialize<AppSettings>(json);
|
||||||
return !string.IsNullOrEmpty(jsonObj.XFrameOrigins) ? jsonObj.XFrameOrigins : DefaultXFrameOptions;
|
return jsonObj.AllowIFraming;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error reading app settings: " + ex.Message);
|
Console.WriteLine("Error reading app settings: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DefaultXFrameOptions;
|
return false;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -328,6 +322,6 @@ public static class Configuration
|
|||||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||||
public long Cache { get; set; } = DefaultCacheMemory;
|
public long Cache { get; set; } = DefaultCacheMemory;
|
||||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||||
public string XFrameOrigins { get; set; } = DefaultXFrameOptions;
|
public bool AllowIFraming { get; set; } = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.16.0.82469">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
24
UI/Web/hash-localization.js
Normal file
24
UI/Web/hash-localization.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
const fs = require('fs');
|
||||||
|
const glob = require('glob');
|
||||||
|
|
||||||
|
const jsonFilesDir = 'dist/browser/assets/langs/'; // Adjust the path to your JSON files
|
||||||
|
const outputDir = 'dist/browser/assets/langs'; // Directory to store minified files
|
||||||
|
|
||||||
|
function generateChecksum(str, algorithm, encoding) {
|
||||||
|
return crypto
|
||||||
|
.createHash(algorithm || 'md5')
|
||||||
|
.update(str, 'utf8')
|
||||||
|
.digest(encoding || 'hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
glob.sync(`${jsonFilesDir}**/*.json`).forEach(path => {
|
||||||
|
const [_, lang] = path.split('dist\\browser\\assets\\langs\\');
|
||||||
|
const content = fs.readFileSync(path, { encoding: 'utf-8' });
|
||||||
|
result[lang.replace('.json', '')] = generateChecksum(content);
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.writeFileSync('./i18n-cache-busting.json', JSON.stringify(result));
|
||||||
|
fs.writeFileSync(`dist/browser/i18n-cache-busting.json`, JSON.stringify(result));
|
1
UI/Web/i18n-cache-busting.json
Normal file
1
UI/Web/i18n-cache-busting.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"zh_Hant":"05191aaae25a26a8597559e8318f97db","zh_Hans":"ca663a190b259b41ac365b6b5537558e","uk":"ccf59f571821ab842882378395ccf48c","tr":"5d6427179210cc370400b816c9d1116d","th":"1e27a1e1cadb2b9f92d85952bffaab95","sk":"24de417448b577b4899e917b70a43263","ru":"c547f0995c167817dd2408e4e9279de2","pt_BR":"44c7cd3da6baad38887fb03ac4ec5581","pt":"af4162a48f01c5260d6436e7e000c5ef","pl":"c6488fdb9a1ecfe5cde6bd1c264902aa","nl":"3ff322f7b24442bd6bceb5c692146d4f","nb_NO":"99914b932bd37a50b983c5e7c90ae93b","ms":"9fdfcc11a2e8a58a4baa691b93d93ff7","ko":"447e24f9f60e1b9f36bc0b087d059dbd","ja":"2f46a5ad1364a71255dd76c0094a9264","it":"4ef0a0ef56bab4650eda37e0dd841982","id":"0beab79883a28035c393768fb8c8ecbd","hi":"d850bb49ec6b5a5ccf9986823f095ab8","fr":"b41b7065960b431a9833140e9014189e","es":"151b6c17ef7382da9f0f22b87a346b7d","en":"d07463979db4cc7ab6e0089889cfc730","de":"f8e3ec31790044be07e222703ed0575a","cs":"0f0c433b9fd641977e89a42e92e4b884"}
|
@ -3,10 +3,11 @@
|
|||||||
"version": "0.7.12.1",
|
"version": "0.7.12.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "npm run cache-langs && ng serve",
|
||||||
"build": "ng build",
|
"build": "npm run cache-langs && ng build",
|
||||||
"minify-langs": "node minify-json.js",
|
"minify-langs": "node minify-json.js",
|
||||||
"prod": "ng build --configuration production && npm run minify-langs",
|
"cache-langs": "node hash-localization.js",
|
||||||
|
"prod": "ng build --configuration production && npm run minify-langs && npm run cache-langs",
|
||||||
"explore": "ng build --stats-json && webpack-bundle-analyzer dist/stats.json",
|
"explore": "ng build --stats-json && webpack-bundle-analyzer dist/stats.json",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e"
|
"e2e": "ng e2e"
|
||||||
|
@ -93,6 +93,8 @@ export class AppComponent implements OnInit {
|
|||||||
// Bootstrap anything that's needed
|
// Bootstrap anything that's needed
|
||||||
this.themeService.getThemes().subscribe();
|
this.themeService.getThemes().subscribe();
|
||||||
this.libraryService.getLibraryNames().pipe(take(1), shareReplay({refCount: true, bufferSize: 1})).subscribe();
|
this.libraryService.getLibraryNames().pipe(take(1), shareReplay({refCount: true, bufferSize: 1})).subscribe();
|
||||||
|
// On load, make an initial call for valid license
|
||||||
|
this.accountService.hasValidLicense().subscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,7 +277,6 @@ export class CardItemComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
this.download$ = this.downloadService.activeDownloads$.pipe(takeUntilDestroyed(this.destroyRef), map((events) => {
|
||||||
console.log('Card Item download obv called for entity: ', this.entity);
|
|
||||||
return this.downloadService.mapToEntityType(events, this.entity);
|
return this.downloadService.mapToEntityType(events, this.entity);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ export class EventsWidgetComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
activeEvents: number = 0;
|
activeEvents: number = 0;
|
||||||
|
|
||||||
debugMode: boolean = true;
|
debugMode: boolean = false;
|
||||||
|
|
||||||
protected readonly EVENTS = EVENTS;
|
protected readonly EVENTS = EVENTS;
|
||||||
|
|
||||||
|
@ -692,16 +692,6 @@ export class SeriesDetailComponent implements OnInit, AfterContentChecked {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadRecommendations() {
|
|
||||||
// this.seriesService.getRecommendationsForSeries(this.seriesId).subscribe(rec => {
|
|
||||||
// rec.ownedSeries.map(r => {
|
|
||||||
// this.seriesService.getMetadata(r.id).subscribe(m => r.summary = m.summary);
|
|
||||||
// });
|
|
||||||
// this.combinedRecs = [...rec.ownedSeries, ...rec.externalSeries];
|
|
||||||
// this.hasRecommendations = this.combinedRecs.length > 0;
|
|
||||||
// this.cdRef.markForCheck();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
loadPlusMetadata(seriesId: number) {
|
loadPlusMetadata(seriesId: number) {
|
||||||
this.metadataService.getSeriesMetadataFromPlus(seriesId).subscribe(data => {
|
this.metadataService.getSeriesMetadataFromPlus(seriesId).subscribe(data => {
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<i class="fa-solid fa-circle-check ms-1 confirm-icon" aria-hidden="true" [ngbTooltip]="t('token-valid')"></i>
|
<i class="fa-solid fa-circle-check ms-1 confirm-icon" aria-hidden="true" [ngbTooltip]="t('token-valid')"></i>
|
||||||
<span class="visually-hidden">{{t('token-valid')}}</span>
|
<span class="visually-hidden">{{t('token-valid')}}</span>
|
||||||
} @else {
|
} @else {
|
||||||
<i class="fa-solid fa-circle ms-1 confirm-icon error" aria-hidden="true" [ngbTooltip]="t('token-not-valid')"></i>
|
<i class="fa-solid fa-circle ms-1 confirm-icon error" aria-hidden="true" [ngbTooltip]="t('token-expired')"></i>
|
||||||
<span class="visually-hidden">{{t('token-not-valid')}}</span>
|
<span class="visually-hidden">{{t('token-expired')}}</span>
|
||||||
}
|
}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
|
@ -436,9 +436,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@defer (when tab.fragment === FragmentID.Scrobbling; prefetch on idle) {
|
@defer (when tab.fragment === FragmentID.Scrobbling; prefetch on idle) {
|
||||||
|
@if(hasActiveLicense) {
|
||||||
<app-user-scrobble-history></app-user-scrobble-history>
|
<app-user-scrobble-history></app-user-scrobble-history>
|
||||||
<app-user-holds></app-user-holds>
|
<app-user-holds></app-user-holds>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -81,6 +81,19 @@ enum FragmentID {
|
|||||||
})
|
})
|
||||||
export class UserPreferencesComponent implements OnInit, OnDestroy {
|
export class UserPreferencesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
private readonly destroyRef = inject(DestroyRef);
|
||||||
|
private readonly accountService = inject(AccountService);
|
||||||
|
private readonly toastr = inject(ToastrService);
|
||||||
|
private readonly bookService = inject(BookService);
|
||||||
|
private readonly titleService = inject(Title);
|
||||||
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
private readonly settingsService = inject(SettingsService);
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
|
private readonly localizationService = inject(LocalizationService);
|
||||||
|
protected readonly AccordionPanelID = AccordionPanelID;
|
||||||
|
protected readonly FragmentID = FragmentID;
|
||||||
|
|
||||||
readingDirectionsTranslated = readingDirections.map(this.translatePrefOptions);
|
readingDirectionsTranslated = readingDirections.map(this.translatePrefOptions);
|
||||||
scalingOptionsTranslated = scalingOptions.map(this.translatePrefOptions);
|
scalingOptionsTranslated = scalingOptions.map(this.translatePrefOptions);
|
||||||
pageSplitOptionsTranslated = pageSplitOptions.map(this.translatePrefOptions);
|
pageSplitOptionsTranslated = pageSplitOptions.map(this.translatePrefOptions);
|
||||||
@ -115,19 +128,9 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
opdsEnabled: boolean = false;
|
opdsEnabled: boolean = false;
|
||||||
opdsUrl: string = '';
|
opdsUrl: string = '';
|
||||||
makeUrl: (val: string) => string = (val: string) => { return this.opdsUrl; };
|
makeUrl: (val: string) => string = (val: string) => { return this.opdsUrl; };
|
||||||
|
hasActiveLicense = false;
|
||||||
|
|
||||||
|
|
||||||
private readonly destroyRef = inject(DestroyRef);
|
|
||||||
private readonly accountService = inject(AccountService);
|
|
||||||
private readonly toastr = inject(ToastrService);
|
|
||||||
private readonly bookService = inject(BookService);
|
|
||||||
private readonly titleService = inject(Title);
|
|
||||||
private readonly route = inject(ActivatedRoute);
|
|
||||||
private readonly settingsService = inject(SettingsService);
|
|
||||||
private readonly router = inject(Router);
|
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
|
||||||
private readonly localizationService = inject(LocalizationService);
|
|
||||||
protected readonly AccordionPanelID = AccordionPanelID;
|
|
||||||
protected readonly FragmentID = FragmentID;
|
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -144,9 +147,12 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.accountService.hasValidLicense().subscribe(res => {
|
|
||||||
|
|
||||||
|
this.accountService.hasValidLicense$.pipe(take(1), takeUntilDestroyed(this.destroyRef)).subscribe(res => {
|
||||||
if (res) {
|
if (res) {
|
||||||
this.tabs.push({title: 'scrobbling-tab', fragment: FragmentID.Scrobbling});
|
this.tabs.push({title: 'scrobbling-tab', fragment: FragmentID.Scrobbling});
|
||||||
|
this.hasActiveLicense = true;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +165,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||||
|
@ -284,6 +284,7 @@
|
|||||||
"scrobbling-providers": {
|
"scrobbling-providers": {
|
||||||
"title": "Scrobbling Providers",
|
"title": "Scrobbling Providers",
|
||||||
"requires": "This feature requires an active {{product}} license",
|
"requires": "This feature requires an active {{product}} license",
|
||||||
|
"token-valid": "Token Valid",
|
||||||
"token-expired": "Token Expired",
|
"token-expired": "Token Expired",
|
||||||
"no-token-set": "No Token Set",
|
"no-token-set": "No Token Set",
|
||||||
"token-set": "Token Set",
|
"token-set": "Token Set",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import {HttpClient} from "@angular/common/http";
|
import {HttpClient} from "@angular/common/http";
|
||||||
import {Translation, TranslocoLoader} from "@ngneat/transloco";
|
import {Translation, TranslocoLoader} from "@ngneat/transloco";
|
||||||
|
import cacheBusting from 'i18n-cache-busting.json'; // allowSyntheticDefaultImports must be true
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class HttpLoader implements TranslocoLoader {
|
export class HttpLoader implements TranslocoLoader {
|
||||||
@ -9,6 +9,7 @@ export class HttpLoader implements TranslocoLoader {
|
|||||||
|
|
||||||
getTranslation(langPath: string) {
|
getTranslation(langPath: string) {
|
||||||
const tokens = langPath.split('/');
|
const tokens = langPath.split('/');
|
||||||
return this.http.get<Translation>(`assets/langs/${tokens[tokens.length - 1]}.json`);
|
const langCode = tokens[tokens.length - 1];
|
||||||
|
return this.http.get<Translation>(`assets/langs/${langCode}.json?v=${(cacheBusting as { [key: string]: string })[langCode]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "ES2022",
|
"module": "ES2022",
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2022",
|
"ES2022",
|
||||||
"dom"
|
"dom"
|
||||||
@ -31,6 +32,7 @@
|
|||||||
"strictTemplates": true,
|
"strictTemplates": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
"extendedDiagnostics": {
|
"extendedDiagnostics": {
|
||||||
"nullishCoalescingNotNullable": "warning",
|
"nullishCoalescingNotNullable": "warning",
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"name": "GPL-3.0",
|
"name": "GPL-3.0",
|
||||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||||
},
|
},
|
||||||
"version": "0.7.12.4"
|
"version": "0.7.12.5"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user