Merge branch 'develop' of https://github.com/Kareadita/Kavita into develop

This commit is contained in:
Robbie Davis 2023-02-20 16:55:20 -05:00
commit 8a62d54c0b
19 changed files with 192 additions and 49 deletions

View File

@ -334,7 +334,7 @@ public class LibraryController : BaseApiController
library.IncludeInDashboard = dto.IncludeInDashboard; library.IncludeInDashboard = dto.IncludeInDashboard;
library.IncludeInRecommended = dto.IncludeInRecommended; library.IncludeInRecommended = dto.IncludeInRecommended;
library.IncludeInSearch = dto.IncludeInSearch; library.IncludeInSearch = dto.IncludeInSearch;
library.ManageCollections = dto.CreateCollections; library.ManageCollections = dto.ManageCollections;
_unitOfWork.LibraryRepository.Update(library); _unitOfWork.LibraryRepository.Update(library);

View File

@ -23,6 +23,6 @@ public class UpdateLibraryDto
[Required] [Required]
public bool IncludeInSearch { get; init; } public bool IncludeInSearch { get; init; }
[Required] [Required]
public bool CreateCollections { get; init; } public bool ManageCollections { get; init; }
} }

View File

@ -0,0 +1,135 @@
using System;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace API.Data;
/// <summary>
/// v0.7 introduced UTC dates and GMT+1 users would sometimes have dates stored as '0000-12-31 23:00:00'.
/// This Migration will update those dates.
/// </summary>
public static class MigrateBrokenGMT1Dates
{
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
{
// if current version is > 0.7, then we can exit and not perform
var settings = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
if (Version.Parse(settings.InstallVersion) > new Version(0, 7, 0, 2))
{
return;
}
logger.LogCritical("Running MigrateBrokenGMT1Dates migration. Please be patient, this may take some time depending on the size of your library. Do not abort, this can break your Database");
#region Series
logger.LogInformation("Updating Dates on Series...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE Series SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE Series SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
UPDATE Series SET LastChapterAddedUtc = '0001-01-01 00:00:00' WHERE LastChapterAddedUtc = '0000-12-31 23:00:00';
UPDATE Series SET LastFolderScannedUtc = '0001-01-01 00:00:00' WHERE LastFolderScannedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Series...Done");
#endregion
#region Library
logger.LogInformation("Updating Dates on Libraries...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE Library SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE Library SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Libraries...Done");
#endregion
#region Volume
try
{
logger.LogInformation("Updating Dates on Volumes...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE Volume SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE Volume SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Volumes...Done");
}
catch (Exception ex)
{
logger.LogCritical(ex, "Updating Dates on Volumes...Failed");
}
#endregion
#region Chapter
try
{
logger.LogInformation("Updating Dates on Chapters...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE Chapter SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE Chapter SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Chapters...Done");
}
catch (Exception ex)
{
logger.LogCritical(ex, "Updating Dates on Chapters...Failed");
}
#endregion
#region AppUserBookmark
logger.LogInformation("Updating Dates on Bookmarks...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE AppUserBookmark SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE AppUserBookmark SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Bookmarks...Done");
#endregion
#region AppUserProgress
logger.LogInformation("Updating Dates on Progress...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE AppUserProgresses SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE AppUserProgresses SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Progress...Done");
#endregion
#region Device
logger.LogInformation("Updating Dates on Device...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE Device SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE Device SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
UPDATE Device SET LastUsedUtc = '0001-01-01 00:00:00' WHERE LastUsedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on Device...Done");
#endregion
#region MangaFile
logger.LogInformation("Updating Dates on MangaFile...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE MangaFile SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE MangaFile SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
UPDATE MangaFile SET LastFileAnalysisUtc = '0001-01-01 00:00:00' WHERE LastFileAnalysisUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on MangaFile...Done");
#endregion
#region ReadingList
logger.LogInformation("Updating Dates on ReadingList...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE ReadingList SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE ReadingList SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on ReadingList...Done");
#endregion
#region SiteTheme
logger.LogInformation("Updating Dates on SiteTheme...");
await dataContext.Database.ExecuteSqlRawAsync(@"
UPDATE SiteTheme SET CreatedUtc = '0001-01-01 00:00:00' WHERE CreatedUtc = '0000-12-31 23:00:00';
UPDATE SiteTheme SET LastModifiedUtc = '0001-01-01 00:00:00' WHERE LastModifiedUtc = '0000-12-31 23:00:00';
");
logger.LogInformation("Updating Dates on SiteTheme...Done");
#endregion
logger.LogInformation("MigrateBrokenGMT1Dates migration finished");
}
}

View File

@ -4,7 +4,8 @@ namespace API.Extensions;
public static class StringExtensions public static class StringExtensions
{ {
private static readonly Regex SentenceCaseRegex = new Regex(@"(^[a-z])|\.\s+(.)", RegexOptions.ExplicitCapture | RegexOptions.Compiled); private static readonly Regex SentenceCaseRegex = new Regex(@"(^[a-z])|\.\s+(.)",
RegexOptions.ExplicitCapture | RegexOptions.Compiled, Services.Tasks.Scanner.Parser.Parser.RegexTimeout);
public static string SentenceCase(this string value) public static string SentenceCase(this string value)
{ {

View File

@ -44,12 +44,11 @@ public class Program
.CreateBootstrapLogger(); .CreateBootstrapLogger();
var directoryService = new DirectoryService(null, new FileSystem()); var directoryService = new DirectoryService(null, new FileSystem());
// Before anything, check if JWT has been generated properly or if user still has default // Before anything, check if JWT has been generated properly or if user still has default
if (!Configuration.CheckIfJwtTokenSet() && if (!Configuration.CheckIfJwtTokenSet() &&
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development) Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") != Environments.Development)
{ {
Console.WriteLine("Generating JWT TokenKey for encrypting user sessions..."); Log.Logger.Information("Generating JWT TokenKey for encrypting user sessions...");
var rBytes = new byte[128]; var rBytes = new byte[128];
RandomNumberGenerator.Create().GetBytes(rBytes); RandomNumberGenerator.Create().GetBytes(rBytes);
Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty); Configuration.JwtToken = Convert.ToBase64String(rBytes).Replace("/", string.Empty);
@ -174,7 +173,7 @@ public class Program
webBuilder.UseKestrel((opts) => webBuilder.UseKestrel((opts) =>
{ {
var ipAddresses = Configuration.IpAddresses; var ipAddresses = Configuration.IpAddresses;
if (ipAddresses == null || ipAddresses.Length == 0) if (string.IsNullOrEmpty(ipAddresses))
{ {
opts.ListenAnyIP(HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; }); opts.ListenAnyIP(HttpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; });
} }

View File

@ -216,7 +216,7 @@ public class BookmarkService : IBookmarkService
foreach (var chapter in chapters) foreach (var chapter in chapters)
{ {
var newFile = await SaveAsWebP(coverDirectory, chapter.CoverImage, coverDirectory); var newFile = await SaveAsWebP(coverDirectory, chapter.CoverImage, coverDirectory);
chapter.CoverImage = newFile; chapter.CoverImage = Path.GetFileName(newFile);
_unitOfWork.ChapterRepository.Update(chapter); _unitOfWork.ChapterRepository.Update(chapter);
await _unitOfWork.CommitAsync(); await _unitOfWork.CommitAsync();
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
@ -272,7 +272,6 @@ public class BookmarkService : IBookmarkService
{ {
// Convert target file to webp then delete original target file and update bookmark // Convert target file to webp then delete original target file and update bookmark
var originalFile = filename;
try try
{ {
var targetFile = await _imageService.ConvertToWebP(fullSourcePath, fullTargetDirectory); var targetFile = await _imageService.ConvertToWebP(fullSourcePath, fullTargetDirectory);
@ -283,7 +282,7 @@ public class BookmarkService : IBookmarkService
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Could not convert image {FilePath}", filename); _logger.LogError(ex, "Could not convert image {FilePath}", filename);
newFilename = originalFile; newFilename = filename;
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -90,9 +90,11 @@ public class DirectoryService : IDirectoryService
private static readonly Regex ExcludeDirectories = new Regex( private static readonly Regex ExcludeDirectories = new Regex(
@"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle", @"@eaDir|\.DS_Store|\.qpkg|__MACOSX|@Recently-Snapshot|@recycle",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase,
Tasks.Scanner.Parser.Parser.RegexTimeout);
private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)", private static readonly Regex FileCopyAppend = new Regex(@"\(\d+\)",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase,
Tasks.Scanner.Parser.Parser.RegexTimeout);
public static readonly string BackupDirectory = Path.Join(Directory.GetCurrentDirectory(), "config", "backups"); public static readonly string BackupDirectory = Path.Join(Directory.GetCurrentDirectory(), "config", "backups");
public DirectoryService(ILogger<DirectoryService> logger, IFileSystem fileSystem) public DirectoryService(ILogger<DirectoryService> logger, IFileSystem fileSystem)
@ -203,7 +205,8 @@ public class DirectoryService : IDirectoryService
if (fileNameRegex != string.Empty) if (fileNameRegex != string.Empty)
{ {
var reSearchPattern = new Regex(fileNameRegex, RegexOptions.IgnoreCase); var reSearchPattern = new Regex(fileNameRegex, RegexOptions.IgnoreCase,
Tasks.Scanner.Parser.Parser.RegexTimeout);
return FileSystem.Directory.EnumerateFiles(path, "*", searchOption) return FileSystem.Directory.EnumerateFiles(path, "*", searchOption)
.Where(file => .Where(file =>
{ {

View File

@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Linq;
using System.Threading.Tasks;
using API.Data; using API.Data;
using API.SignalR.Presence; using API.SignalR.Presence;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
@ -36,8 +37,8 @@ public class EventHub : IEventHub
var users = _messageHub.Clients.All; var users = _messageHub.Clients.All;
if (onlyAdmins) if (onlyAdmins)
{ {
var admins = await _presenceTracker.GetOnlineAdmins(); var admins = await _presenceTracker.GetOnlineAdminIds();
users = _messageHub.Clients.Users(admins); users = _messageHub.Clients.Users(admins.Select(i => i.ToString()).ToArray());
} }

View File

@ -26,13 +26,13 @@ public class LogHub : Hub<ILogHub>
public override async Task OnConnectedAsync() public override async Task OnConnectedAsync()
{ {
await _tracker.UserConnected(Context.User.GetUsername(), Context.ConnectionId); await _tracker.UserConnected(Context.User.GetUserId(), Context.ConnectionId);
await base.OnConnectedAsync(); await base.OnConnectedAsync();
} }
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
{ {
await _tracker.UserDisconnected(Context.User.GetUsername(), Context.ConnectionId); await _tracker.UserDisconnected(Context.User.GetUserId(), Context.ConnectionId);
await base.OnDisconnectedAsync(exception); await base.OnDisconnectedAsync(exception);
} }

View File

@ -22,7 +22,7 @@ public class MessageHub : Hub
public override async Task OnConnectedAsync() public override async Task OnConnectedAsync()
{ {
await _tracker.UserConnected(Context.User.GetUsername(), Context.ConnectionId); await _tracker.UserConnected(Context.User.GetUserId(), Context.ConnectionId);
var currentUsers = await PresenceTracker.GetOnlineUsers(); var currentUsers = await PresenceTracker.GetOnlineUsers();
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers); await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);
@ -33,7 +33,7 @@ public class MessageHub : Hub
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
{ {
await _tracker.UserDisconnected(Context.User.GetUsername(), Context.ConnectionId); await _tracker.UserDisconnected(Context.User.GetUserId(), Context.ConnectionId);
var currentUsers = await PresenceTracker.GetOnlineUsers(); var currentUsers = await PresenceTracker.GetOnlineUsers();
await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers); await Clients.All.SendAsync(MessageFactory.OnlineUsers, currentUsers);

View File

@ -8,15 +8,16 @@ namespace API.SignalR.Presence;
public interface IPresenceTracker public interface IPresenceTracker
{ {
Task UserConnected(string username, string connectionId); Task UserConnected(int userId, string connectionId);
Task UserDisconnected(string username, string connectionId); Task UserDisconnected(int userId, string connectionId);
Task<string[]> GetOnlineAdmins(); Task<int[]> GetOnlineAdminIds();
Task<List<string>> GetConnectionsForUser(string username); Task<List<string>> GetConnectionsForUser(int userId);
} }
internal class ConnectionDetail internal class ConnectionDetail
{ {
public string UserName { get; set; }
public List<string> ConnectionIds { get; set; } public List<string> ConnectionIds { get; set; }
public bool IsAdmin { get; set; } public bool IsAdmin { get; set; }
} }
@ -28,28 +29,29 @@ internal class ConnectionDetail
public class PresenceTracker : IPresenceTracker public class PresenceTracker : IPresenceTracker
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;
private static readonly Dictionary<string, ConnectionDetail> OnlineUsers = new Dictionary<string, ConnectionDetail>(); private static readonly Dictionary<int, ConnectionDetail> OnlineUsers = new Dictionary<int, ConnectionDetail>();
public PresenceTracker(IUnitOfWork unitOfWork) public PresenceTracker(IUnitOfWork unitOfWork)
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
} }
public async Task UserConnected(string username, string connectionId) public async Task UserConnected(int userId, string connectionId)
{ {
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username); var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return; if (user == null) return;
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user); var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
lock (OnlineUsers) lock (OnlineUsers)
{ {
if (OnlineUsers.ContainsKey(username)) if (OnlineUsers.ContainsKey(userId))
{ {
OnlineUsers[username].ConnectionIds.Add(connectionId); OnlineUsers[userId].ConnectionIds.Add(connectionId);
} }
else else
{ {
OnlineUsers.Add(username, new ConnectionDetail() OnlineUsers.Add(userId, new ConnectionDetail()
{ {
UserName = user.UserName,
ConnectionIds = new List<string>() {connectionId}, ConnectionIds = new List<string>() {connectionId},
IsAdmin = isAdmin IsAdmin = isAdmin
}); });
@ -61,17 +63,17 @@ public class PresenceTracker : IPresenceTracker
await _unitOfWork.CommitAsync(); await _unitOfWork.CommitAsync();
} }
public Task UserDisconnected(string username, string connectionId) public Task UserDisconnected(int userId, string connectionId)
{ {
lock (OnlineUsers) lock (OnlineUsers)
{ {
if (!OnlineUsers.ContainsKey(username)) return Task.CompletedTask; if (!OnlineUsers.ContainsKey(userId)) return Task.CompletedTask;
OnlineUsers[username].ConnectionIds.Remove(connectionId); OnlineUsers[userId].ConnectionIds.Remove(connectionId);
if (OnlineUsers[username].ConnectionIds.Count == 0) if (OnlineUsers[userId].ConnectionIds.Count == 0)
{ {
OnlineUsers.Remove(username); OnlineUsers.Remove(userId);
} }
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -82,15 +84,15 @@ public class PresenceTracker : IPresenceTracker
string[] onlineUsers; string[] onlineUsers;
lock (OnlineUsers) lock (OnlineUsers)
{ {
onlineUsers = OnlineUsers.OrderBy(k => k.Key).Select(k => k.Key).ToArray(); onlineUsers = OnlineUsers.OrderBy(k => k.Value.UserName).Select(k => k.Value.UserName).ToArray();
} }
return Task.FromResult(onlineUsers); return Task.FromResult(onlineUsers);
} }
public Task<string[]> GetOnlineAdmins() public Task<int[]> GetOnlineAdminIds()
{ {
string[] onlineUsers; int[] onlineUsers;
lock (OnlineUsers) lock (OnlineUsers)
{ {
onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin).OrderBy(k => k.Key).Select(k => k.Key).ToArray(); onlineUsers = OnlineUsers.Where(pair => pair.Value.IsAdmin).OrderBy(k => k.Key).Select(k => k.Key).ToArray();
@ -100,12 +102,12 @@ public class PresenceTracker : IPresenceTracker
return Task.FromResult(onlineUsers); return Task.FromResult(onlineUsers);
} }
public Task<List<string>> GetConnectionsForUser(string username) public Task<List<string>> GetConnectionsForUser(int userId)
{ {
List<string> connectionIds; List<string> connectionIds;
lock (OnlineUsers) lock (OnlineUsers)
{ {
connectionIds = OnlineUsers.GetValueOrDefault(username)?.ConnectionIds; connectionIds = OnlineUsers.GetValueOrDefault(userId)?.ConnectionIds;
} }
return Task.FromResult(connectionIds ?? new List<string>()); return Task.FromResult(connectionIds ?? new List<string>());

View File

@ -234,6 +234,9 @@ public class Startup
await MigrateUserProgressLibraryId.Migrate(unitOfWork, logger); await MigrateUserProgressLibraryId.Migrate(unitOfWork, logger);
await MigrateToUtcDates.Migrate(unitOfWork, dataContext, logger); await MigrateToUtcDates.Migrate(unitOfWork, dataContext, logger);
// v0.7
await MigrateBrokenGMT1Dates.Migrate(unitOfWork, dataContext, logger);
// Update the version in the DB after all migrations are run // Update the version in the DB after all migrations are run
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion); var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);
installVersion.Value = BuildInfo.Version.ToString(); installVersion.Value = BuildInfo.Version.ToString();

View File

@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Company>kavitareader.com</Company> <Company>kavitareader.com</Company>
<Product>Kavita</Product> <Product>Kavita</Product>
<AssemblyVersion>0.7.0.2</AssemblyVersion> <AssemblyVersion>0.7.1.1</AssemblyVersion>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<TieredPGO>true</TieredPGO> <TieredPGO>true</TieredPGO>
</PropertyGroup> </PropertyGroup>

View File

@ -15,5 +15,5 @@ export interface Library {
includeInDashboard: boolean; includeInDashboard: boolean;
includeInRecommended: boolean; includeInRecommended: boolean;
includeInSearch: boolean; includeInSearch: boolean;
createCollections: boolean; manageCollections: boolean;
} }

View File

@ -96,7 +96,7 @@ export class MessageHubService {
private hubConnection!: HubConnection; private hubConnection!: HubConnection;
private messagesSource = new ReplaySubject<Message<any>>(1); private messagesSource = new ReplaySubject<Message<any>>(1);
private onlineUsersSource = new BehaviorSubject<string[]>([]); private onlineUsersSource = new BehaviorSubject<number[]>([]); // UserIds
/** /**
* Any events that come from the backend * Any events that come from the backend
@ -142,7 +142,7 @@ export class MessageHubService {
.start() .start()
.catch(err => console.error(err)); .catch(err => console.error(err));
this.hubConnection.on(EVENTS.OnlineUsers, (usernames: string[]) => { this.hubConnection.on(EVENTS.OnlineUsers, (usernames: number[]) => {
this.onlineUsersSource.next(usernames); this.onlineUsersSource.next(usernames);
}); });

View File

@ -39,7 +39,7 @@
<li *ngFor="let member of members; let idx = index;" class="list-group-item no-hover"> <li *ngFor="let member of members; let idx = index;" class="list-group-item no-hover">
<div> <div>
<h4> <h4>
<i class="presence fa fa-circle" title="Active" aria-hidden="true" *ngIf="false && (messageHub.onlineUsers$ | async)?.includes(member.username)"></i> <i class="presence fa fa-circle" title="Active" aria-hidden="true" *ngIf="false && (messageHub.onlineUsers$ | async)?.includes(member.id)"></i>
<span id="member-name--{{idx}}">{{member.username | titlecase}} </span> <span id="member-name--{{idx}}">{{member.username | titlecase}} </span>
<span *ngIf="member.username === loggedInUsername"> <span *ngIf="member.username === loggedInUsername">
<i class="fas fa-star" aria-hidden="true"></i> <i class="fas fa-star" aria-hidden="true"></i>

View File

@ -96,7 +96,7 @@
<div class="col-md-12 col-sm-12 pe-2 mb-2"> <div class="col-md-12 col-sm-12 pe-2 mb-2">
<div class="mb-3 mt-1"> <div class="mb-3 mt-1">
<div class="form-check form-switch"> <div class="form-check form-switch">
<input type="checkbox" id="manage-collections" role="switch" formControlName="createCollections" class="form-check-input" aria-labelledby="auto-close-label"> <input type="checkbox" id="manage-collections" role="switch" formControlName="manageCollections" class="form-check-input" aria-labelledby="auto-close-label">
<label class="form-check-label" for="manage-collections">Manage Collections</label> <label class="form-check-label" for="manage-collections">Manage Collections</label>
</div> </div>
</div> </div>

View File

@ -118,7 +118,7 @@ export class LibrarySettingsModalComponent implements OnInit, OnDestroy {
this.libraryForm.get('includeInDashboard')?.setValue(this.library.includeInDashboard); this.libraryForm.get('includeInDashboard')?.setValue(this.library.includeInDashboard);
this.libraryForm.get('includeInRecommended')?.setValue(this.library.includeInRecommended); this.libraryForm.get('includeInRecommended')?.setValue(this.library.includeInRecommended);
this.libraryForm.get('includeInSearch')?.setValue(this.library.includeInSearch); this.libraryForm.get('includeInSearch')?.setValue(this.library.includeInSearch);
this.libraryForm.get('createCollections')?.setValue(this.library.createCollections); this.libraryForm.get('manageCollections')?.setValue(this.library.manageCollections);
this.selectedFolders = this.library.folders; this.selectedFolders = this.library.folders;
this.madeChanges = false; this.madeChanges = false;
this.cdRef.markForCheck(); this.cdRef.markForCheck();

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.6.1.41" "version": "0.7.0.2"
}, },
"servers": [ "servers": [
{ {
@ -14070,13 +14070,13 @@
}, },
"UpdateLibraryDto": { "UpdateLibraryDto": {
"required": [ "required": [
"createCollections",
"folders", "folders",
"folderWatching", "folderWatching",
"id", "id",
"includeInDashboard", "includeInDashboard",
"includeInRecommended", "includeInRecommended",
"includeInSearch", "includeInSearch",
"manageCollections",
"name", "name",
"type" "type"
], ],
@ -14111,7 +14111,7 @@
"includeInSearch": { "includeInSearch": {
"type": "boolean" "type": "boolean"
}, },
"createCollections": { "manageCollections": {
"type": "boolean" "type": "boolean"
} }
}, },