Polish Round 2 (#2845)

This commit is contained in:
Joe Milazzo 2024-04-11 17:01:34 -05:00 committed by GitHub
parent c47fec4648
commit 5195f08c2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 63 additions and 22 deletions

View File

@ -0,0 +1,31 @@
using API.Services;
using Xunit;
namespace API.Tests.Services;
public class TokenServiceTests
{
[Fact]
public void HasTokenExpired_OldToken()
{
// ValidTo: 1/1/1990
var result = TokenService.HasTokenExpired("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjYzMzgzMDM5OX0.KM_cUKSaCJL3ts0Qim3ZHUeJT7yf-wKoLdKb0rx0VbU");
Assert.True(result);
}
[Fact]
public void HasTokenExpired_ValidInFuture()
{
// ValidTo: 4/11/2200
var result = TokenService.HasTokenExpired("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjcyNjg0ODYzOTl9.nZrN5USbUmMYDKwkPoMtEAhTeYTeaikgAeSzDPj5kZQ");
Assert.False(result);
}
[Fact]
public void HasTokenExpired_NoToken()
{
var result = TokenService.HasTokenExpired("");
Assert.True(result);
}
}

View File

@ -30,9 +30,9 @@ public enum LibraryType
[Description("Light Novel")] [Description("Light Novel")]
LightNovel = 4, LightNovel = 4,
/// <summary> /// <summary>
/// Uses Comic regex for filename parsing, uses ComicVine type of Parsing. Will replace Comic type in future /// Uses Comic regex for filename parsing, uses Comic Vine type of Parsing. Will replace Comic type in future
/// </summary> /// </summary>
[Description("Comic (ComicVine)")] [Description("Comic (Comic Vine)")]
ComicVine = 5, ComicVine = 5,
} }

View File

@ -108,7 +108,7 @@ public class DeviceService : IDeviceService
public async Task<bool> SendTo(IReadOnlyList<int> chapterIds, int deviceId) public async Task<bool> SendTo(IReadOnlyList<int> chapterIds, int deviceId)
{ {
var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
if (!settings.IsEmailSetup()) if (!settings.IsEmailSetupForSendToDevice())
throw new KavitaException("send-to-kavita-email"); throw new KavitaException("send-to-kavita-email");
var device = await _unitOfWork.DeviceRepository.GetDeviceById(deviceId); var device = await _unitOfWork.DeviceRepository.GetDeviceById(deviceId);
@ -123,9 +123,16 @@ public class DeviceService : IDeviceService
throw new KavitaException("send-to-size-limit"); throw new KavitaException("send-to-size-limit");
try
{
device.UpdateLastUsed(); device.UpdateLastUsed();
_unitOfWork.DeviceRepository.Update(device); _unitOfWork.DeviceRepository.Update(device);
await _unitOfWork.CommitAsync(); await _unitOfWork.CommitAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue updating device last used time");
}
var success = await _emailService.SendFilesToEmail(new SendToDto() var success = await _emailService.SendFilesToEmail(new SendToDto()
{ {

View File

@ -126,7 +126,7 @@ public class ScrobblingService : IScrobblingService
var users = await _unitOfWork.UserRepository.GetAllUsersAsync(); var users = await _unitOfWork.UserRepository.GetAllUsersAsync();
foreach (var user in users) foreach (var user in users)
{ {
if (string.IsNullOrEmpty(user.AniListAccessToken) || !_tokenService.HasTokenExpired(user.AniListAccessToken)) continue; if (string.IsNullOrEmpty(user.AniListAccessToken) || !TokenService.HasTokenExpired(user.AniListAccessToken)) continue;
_logger.LogInformation("User {UserName}'s AniList token has expired! They need to regenerate it for scrobbling to work", user.UserName); _logger.LogInformation("User {UserName}'s AniList token has expired! They need to regenerate it for scrobbling to work", user.UserName);
await _eventHub.SendMessageToAsync(MessageFactory.ScrobblingKeyExpired, await _eventHub.SendMessageToAsync(MessageFactory.ScrobblingKeyExpired,
MessageFactory.ScrobblingKeyExpiredEvent(ScrobbleProvider.AniList), user.Id); MessageFactory.ScrobblingKeyExpiredEvent(ScrobbleProvider.AniList), user.Id);
@ -151,7 +151,7 @@ public class ScrobblingService : IScrobblingService
private async Task<bool> HasTokenExpired(string token, ScrobbleProvider provider) private async Task<bool> HasTokenExpired(string token, ScrobbleProvider provider)
{ {
if (string.IsNullOrEmpty(token) || if (string.IsNullOrEmpty(token) ||
!_tokenService.HasTokenExpired(token)) return false; !TokenService.HasTokenExpired(token)) return false;
var license = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey); var license = await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.LicenseKey);
if (string.IsNullOrEmpty(license.Value)) return true; if (string.IsNullOrEmpty(license.Value)) return true;
@ -778,7 +778,7 @@ public class ScrobblingService : IScrobblingService
continue; continue;
} }
if (_tokenService.HasTokenExpired(evt.AppUser.AniListAccessToken)) if (TokenService.HasTokenExpired(evt.AppUser.AniListAccessToken))
{ {
_unitOfWork.ScrobbleRepository.Attach(new ScrobbleError() _unitOfWork.ScrobbleRepository.Attach(new ScrobbleError()
{ {

View File

@ -561,7 +561,7 @@ public class ReadingListService : IReadingListService
// How can we match properly with ComicVine library when year is part of the series unless we do this in 2 passes and see which has a better match // How can we match properly with ComicVine library when year is part of the series unless we do this in 2 passes and see which has a better match
if (!userSeries.Any()) if (userSeries.Count == 0)
{ {
// Report that no series exist in the reading list // Report that no series exist in the reading list
importSummary.Results.Add(new CblBookResult importSummary.Results.Add(new CblBookResult

View File

@ -24,8 +24,7 @@ public interface ITokenService
Task<string> CreateToken(AppUser user); Task<string> CreateToken(AppUser user);
Task<TokenRequestDto?> ValidateRefreshToken(TokenRequestDto request); Task<TokenRequestDto?> ValidateRefreshToken(TokenRequestDto request);
Task<string> CreateRefreshToken(AppUser user); Task<string> CreateRefreshToken(AppUser user);
Task<string> GetJwtFromUser(AppUser user); Task<string?> GetJwtFromUser(AppUser user);
bool HasTokenExpired(string token);
} }
@ -135,18 +134,21 @@ public class TokenService : ITokenService
} }
} }
public async Task<string> GetJwtFromUser(AppUser user) public async Task<string?> GetJwtFromUser(AppUser user)
{ {
var userClaims = await _userManager.GetClaimsAsync(user); var userClaims = await _userManager.GetClaimsAsync(user);
var jwtClaim = userClaims.FirstOrDefault(claim => claim.Type == "jwt"); var jwtClaim = userClaims.FirstOrDefault(claim => claim.Type == "jwt");
return jwtClaim?.Value; return jwtClaim?.Value;
} }
public bool HasTokenExpired(string? token) public static bool HasTokenExpired(string? token)
{ {
if (string.IsNullOrEmpty(token)) return true; if (string.IsNullOrEmpty(token)) return true;
var tokenHandler = new JwtSecurityTokenHandler(); var tokenHandler = new JwtSecurityTokenHandler();
var tokenContent = tokenHandler.ReadJwtToken(token); var tokenContent = tokenHandler.ReadJwtToken(token);
return tokenContent.ValidTo >= DateTime.UtcNow; var validToUtc = tokenContent.ValidTo.ToUniversalTime();
return validToUtc < DateTime.UtcNow;
} }
} }

View File

@ -70,6 +70,7 @@ export class ManageEmailSettingsComponent implements OnInit {
this.settingsForm.get('port')?.setValue(587); this.settingsForm.get('port')?.setValue(587);
this.settingsForm.get('sizeLimit')?.setValue(26214400); this.settingsForm.get('sizeLimit')?.setValue(26214400);
this.settingsForm.get('enableSsl')?.setValue(true); this.settingsForm.get('enableSsl')?.setValue(true);
this.settingsForm.markAsDirty();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }
@ -78,6 +79,7 @@ export class ManageEmailSettingsComponent implements OnInit {
this.settingsForm.get('port')?.setValue(587 ); this.settingsForm.get('port')?.setValue(587 );
this.settingsForm.get('sizeLimit')?.setValue(1048576); this.settingsForm.get('sizeLimit')?.setValue(1048576);
this.settingsForm.get('enableSsl')?.setValue(true); this.settingsForm.get('enableSsl')?.setValue(true);
this.settingsForm.markAsDirty();
this.cdRef.markForCheck(); this.cdRef.markForCheck();
} }

View File

@ -26,6 +26,7 @@
<virtual-scroller [ngClass]="{'empty': items.length === 0 && !isLoading}" #scroll [items]="items" [bufferAmount]="bufferAmount" [parentScroll]="parentScroll"> <virtual-scroller [ngClass]="{'empty': items.length === 0 && !isLoading}" #scroll [items]="items" [bufferAmount]="bufferAmount" [parentScroll]="parentScroll">
<div class="grid row g-0" #container> <div class="grid row g-0" #container>
<!-- TODO: @for (item of scroll.viewPortItems; track trackByIdentity; let i = $index;) { works -->
<div class="card col-auto mt-2 mb-2" <div class="card col-auto mt-2 mb-2"
(click)="tryToSaveJumpKey(item)" (click)="tryToSaveJumpKey(item)"
*ngFor="let item of scroll.viewPortItems; trackBy:trackByIdentity; index as i" id="jumpbar-index--{{i}}" *ngFor="let item of scroll.viewPortItems; trackBy:trackByIdentity; index as i" id="jumpbar-index--{{i}}"

View File

@ -76,7 +76,7 @@ export class AllCollectionsComponent implements OnInit {
jumpbarKeys: Array<JumpKey> = []; jumpbarKeys: Array<JumpKey> = [];
isAdmin$: Observable<boolean> = of(false); isAdmin$: Observable<boolean> = of(false);
filterOpen: EventEmitter<boolean> = new EventEmitter(); filterOpen: EventEmitter<boolean> = new EventEmitter();
trackByIdentity = (index: number, item: UserCollection) => `${item.id}_${item.title}`; trackByIdentity = (index: number, item: UserCollection) => `${item.id}_${item.title}_${item.owner}_${item.promoted}`;
user!: User; user!: User;
@HostListener('document:keydown.shift', ['$event']) @HostListener('document:keydown.shift', ['$event'])
@ -146,13 +146,14 @@ export class AllCollectionsComponent implements OnInit {
switch (action.action) { switch (action.action) {
case Action.Promote: case Action.Promote:
this.collectionService.promoteMultipleCollections([collectionTag.id], true).subscribe(); this.collectionService.promoteMultipleCollections([collectionTag.id], true).subscribe(_ => this.loadPage());
break; break;
case Action.UnPromote: case Action.UnPromote:
this.collectionService.promoteMultipleCollections([collectionTag.id], false).subscribe(); this.collectionService.promoteMultipleCollections([collectionTag.id], false).subscribe(_ => this.loadPage());
break; break;
case(Action.Delete): case(Action.Delete):
this.collectionService.deleteTag(collectionTag.id).subscribe(res => { this.collectionService.deleteTag(collectionTag.id).subscribe(res => {
this.loadPage();
this.toastr.success(res); this.toastr.success(res);
}); });
break; break;

View File

@ -114,9 +114,6 @@ export class SeriesMetadataDetailComponent implements OnChanges, OnInit {
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
this.hasExtendedProperties = this.seriesMetadata.colorists.length > 0 || this.hasExtendedProperties = this.seriesMetadata.colorists.length > 0 ||
this.seriesMetadata.editors.length > 0 || this.seriesMetadata.editors.length > 0 ||
this.seriesMetadata.coverArtists.length > 0 || this.seriesMetadata.coverArtists.length > 0 ||

View File

@ -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.14.14" "version": "0.7.14.15"
}, },
"servers": [ "servers": [
{ {