Collection Rework (#2830)
This commit is contained in:
parent
0dacc061f1
commit
deaaccb96a
93 changed files with 5413 additions and 1120 deletions
|
@ -1,13 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs.CollectionTags;
|
||||
using API.DTOs.Collection;
|
||||
using API.Entities;
|
||||
using API.Entities.Metadata;
|
||||
using API.Helpers.Builders;
|
||||
using API.Extensions;
|
||||
using API.Services.Plus;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
|
||||
|
@ -16,15 +15,9 @@ namespace API.Services;
|
|||
|
||||
public interface ICollectionTagService
|
||||
{
|
||||
Task<bool> TagExistsByName(string name);
|
||||
Task<bool> DeleteTag(CollectionTag tag);
|
||||
Task<bool> UpdateTag(CollectionTagDto dto);
|
||||
Task<bool> AddTagToSeries(CollectionTag? tag, IEnumerable<int> seriesIds);
|
||||
Task<bool> RemoveTagFromSeries(CollectionTag? tag, IEnumerable<int> seriesIds);
|
||||
Task<CollectionTag> GetTagOrCreate(int tagId, string title);
|
||||
void AddTagToSeriesMetadata(CollectionTag? tag, SeriesMetadata metadata);
|
||||
CollectionTag CreateTag(string title);
|
||||
Task<bool> RemoveTagsWithoutSeries();
|
||||
Task<bool> DeleteTag(int tagId, AppUser user);
|
||||
Task<bool> UpdateTag(AppUserCollectionDto dto, int userId);
|
||||
Task<bool> RemoveTagFromSeries(AppUserCollection? tag, IEnumerable<int> seriesIds);
|
||||
}
|
||||
|
||||
|
||||
|
@ -39,37 +32,44 @@ public class CollectionTagService : ICollectionTagService
|
|||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a collection exists with the name
|
||||
/// </summary>
|
||||
/// <param name="name">If empty or null, will return true as that is invalid</param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> TagExistsByName(string name)
|
||||
public async Task<bool> DeleteTag(int tagId, AppUser user)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name.Trim())) return true;
|
||||
return await _unitOfWork.CollectionTagRepository.TagExists(name);
|
||||
}
|
||||
var collectionTag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(tagId);
|
||||
if (collectionTag == null) return true;
|
||||
|
||||
user.Collections.Remove(collectionTag);
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return true;
|
||||
|
||||
public async Task<bool> DeleteTag(CollectionTag tag)
|
||||
{
|
||||
_unitOfWork.CollectionTagRepository.Remove(tag);
|
||||
return await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateTag(CollectionTagDto dto)
|
||||
|
||||
public async Task<bool> UpdateTag(AppUserCollectionDto dto, int userId)
|
||||
{
|
||||
var existingTag = await _unitOfWork.CollectionTagRepository.GetTagAsync(dto.Id);
|
||||
var existingTag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(dto.Id);
|
||||
if (existingTag == null) throw new KavitaException("collection-doesnt-exist");
|
||||
if (existingTag.AppUserId != userId) throw new KavitaException("access-denied");
|
||||
|
||||
var title = dto.Title.Trim();
|
||||
if (string.IsNullOrEmpty(title)) throw new KavitaException("collection-tag-title-required");
|
||||
if (!title.Equals(existingTag.Title) && await TagExistsByName(dto.Title))
|
||||
|
||||
// Ensure the title doesn't exist on the user's account already
|
||||
if (!title.Equals(existingTag.Title) && await _unitOfWork.CollectionTagRepository.CollectionExists(dto.Title, userId))
|
||||
throw new KavitaException("collection-tag-duplicate");
|
||||
|
||||
existingTag.SeriesMetadatas ??= new List<SeriesMetadata>();
|
||||
existingTag.Title = title;
|
||||
existingTag.NormalizedTitle = Tasks.Scanner.Parser.Parser.Normalize(dto.Title);
|
||||
existingTag.Promoted = dto.Promoted;
|
||||
existingTag.Items ??= new List<Series>();
|
||||
if (existingTag.Source == ScrobbleProvider.Kavita)
|
||||
{
|
||||
existingTag.Title = title;
|
||||
existingTag.NormalizedTitle = dto.Title.ToNormalized();
|
||||
}
|
||||
|
||||
var roles = await _unitOfWork.UserRepository.GetRoles(userId);
|
||||
if (roles.Contains(PolicyConstants.AdminRole) || roles.Contains(PolicyConstants.PromoteRole))
|
||||
{
|
||||
existingTag.Promoted = dto.Promoted;
|
||||
}
|
||||
existingTag.CoverImageLocked = dto.CoverImageLocked;
|
||||
_unitOfWork.CollectionTagRepository.Update(existingTag);
|
||||
|
||||
|
@ -96,89 +96,31 @@ public class CollectionTagService : ICollectionTagService
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a set of Series to a Collection
|
||||
/// Removes series from Collection tag. Will recalculate max age rating.
|
||||
/// </summary>
|
||||
/// <param name="tag">A full Tag</param>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> AddTagToSeries(CollectionTag? tag, IEnumerable<int> seriesIds)
|
||||
public async Task<bool> RemoveTagFromSeries(AppUserCollection? tag, IEnumerable<int> seriesIds)
|
||||
{
|
||||
if (tag == null) return false;
|
||||
var metadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIdsAsync(seriesIds);
|
||||
foreach (var metadata in metadatas)
|
||||
{
|
||||
AddTagToSeriesMetadata(tag, metadata);
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return true;
|
||||
return await _unitOfWork.CommitAsync();
|
||||
}
|
||||
tag.Items ??= new List<Series>();
|
||||
tag.Items = tag.Items.Where(s => !seriesIds.Contains(s.Id)).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a collection tag to a SeriesMetadata
|
||||
/// </summary>
|
||||
/// <remarks>Does not commit</remarks>
|
||||
/// <param name="tag"></param>
|
||||
/// <param name="metadata"></param>
|
||||
/// <returns></returns>
|
||||
public void AddTagToSeriesMetadata(CollectionTag? tag, SeriesMetadata metadata)
|
||||
{
|
||||
if (tag == null) return;
|
||||
metadata.CollectionTags ??= new List<CollectionTag>();
|
||||
if (metadata.CollectionTags.Any(t => t.NormalizedTitle.Equals(tag.NormalizedTitle, StringComparison.InvariantCulture))) return;
|
||||
|
||||
metadata.CollectionTags.Add(tag);
|
||||
if (metadata.Id != 0)
|
||||
{
|
||||
_unitOfWork.SeriesMetadataRepository.Update(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveTagFromSeries(CollectionTag? tag, IEnumerable<int> seriesIds)
|
||||
{
|
||||
if (tag == null) return false;
|
||||
tag.SeriesMetadatas ??= new List<SeriesMetadata>();
|
||||
foreach (var seriesIdToRemove in seriesIds)
|
||||
{
|
||||
tag.SeriesMetadatas.Remove(tag.SeriesMetadatas.Single(sm => sm.SeriesId == seriesIdToRemove));
|
||||
}
|
||||
|
||||
|
||||
if (tag.SeriesMetadatas.Count == 0)
|
||||
if (tag.Items.Count == 0)
|
||||
{
|
||||
_unitOfWork.CollectionTagRepository.Remove(tag);
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return true;
|
||||
|
||||
return await _unitOfWork.CommitAsync();
|
||||
}
|
||||
var result = await _unitOfWork.CommitAsync();
|
||||
if (tag.Items.Count > 0)
|
||||
{
|
||||
await _unitOfWork.CollectionTagRepository.UpdateCollectionAgeRating(tag);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to fetch the full tag, else returns a new tag. Adds to tracking but does not commit
|
||||
/// </summary>
|
||||
/// <param name="tagId"></param>
|
||||
/// <param name="title"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<CollectionTag> GetTagOrCreate(int tagId, string title)
|
||||
{
|
||||
return await _unitOfWork.CollectionTagRepository.GetTagAsync(tagId, CollectionTagIncludes.SeriesMetadata) ?? CreateTag(title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This just creates the entity and adds to tracking. Use <see cref="GetTagOrCreate"/> for checks of duplication.
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <returns></returns>
|
||||
public CollectionTag CreateTag(string title)
|
||||
{
|
||||
var tag = new CollectionTagBuilder(title).Build();
|
||||
_unitOfWork.CollectionTagRepository.Add(tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveTagsWithoutSeries()
|
||||
{
|
||||
return await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries() > 0;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -278,7 +278,7 @@ public class MetadataService : IMetadataService
|
|||
await _unitOfWork.TagRepository.RemoveAllTagNoLongerAssociated();
|
||||
await _unitOfWork.PersonRepository.RemoveAllPeopleNoLongerAssociated();
|
||||
await _unitOfWork.GenreRepository.RemoveAllGenreNoLongerAssociated();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveCollectionsWithoutSeries();
|
||||
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
|
||||
|
||||
}
|
||||
|
|
|
@ -115,12 +115,6 @@ public class SeriesService : ISeriesService
|
|||
if (series == null) return false;
|
||||
|
||||
series.Metadata ??= new SeriesMetadataBuilder()
|
||||
.WithCollectionTags(updateSeriesMetadataDto.CollectionTags.Select(dto =>
|
||||
new CollectionTagBuilder(dto.Title)
|
||||
.WithId(dto.Id)
|
||||
.WithSummary(dto.Summary)
|
||||
.WithIsPromoted(dto.Promoted)
|
||||
.Build()).ToList())
|
||||
.Build();
|
||||
|
||||
if (series.Metadata.AgeRating != updateSeriesMetadataDto.SeriesMetadata.AgeRating)
|
||||
|
@ -163,28 +157,16 @@ public class SeriesService : ISeriesService
|
|||
series.Metadata.WebLinks = string.Empty;
|
||||
} else
|
||||
{
|
||||
series.Metadata.WebLinks = string.Join(",", updateSeriesMetadataDto.SeriesMetadata?.WebLinks
|
||||
.Split(",")
|
||||
series.Metadata.WebLinks = string.Join(',', updateSeriesMetadataDto.SeriesMetadata?.WebLinks
|
||||
.Split(',')
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.Select(s => s.Trim())!
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (updateSeriesMetadataDto.CollectionTags.Count > 0)
|
||||
{
|
||||
var allCollectionTags = (await _unitOfWork.CollectionTagRepository
|
||||
.GetAllTagsByNamesAsync(updateSeriesMetadataDto.CollectionTags.Select(t => Parser.Normalize(t.Title)))).ToList();
|
||||
series.Metadata.CollectionTags ??= new List<CollectionTag>();
|
||||
UpdateCollectionsList(updateSeriesMetadataDto.CollectionTags, series, allCollectionTags, tag =>
|
||||
{
|
||||
series.Metadata.CollectionTags.Add(tag);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (updateSeriesMetadataDto.SeriesMetadata?.Genres != null &&
|
||||
updateSeriesMetadataDto.SeriesMetadata.Genres.Any())
|
||||
updateSeriesMetadataDto.SeriesMetadata.Genres.Count != 0)
|
||||
{
|
||||
var allGenres = (await _unitOfWork.GenreRepository.GetAllGenresByNamesAsync(updateSeriesMetadataDto.SeriesMetadata.Genres.Select(t => Parser.Normalize(t.Title)))).ToList();
|
||||
series.Metadata.Genres ??= new List<Genre>();
|
||||
|
@ -320,12 +302,6 @@ 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 (updateSeriesMetadataDto.CollectionTags == null) return true;
|
||||
foreach (var tag in updateSeriesMetadataDto.CollectionTags)
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.SeriesAddedToCollection,
|
||||
MessageFactory.SeriesAddedToCollectionEvent(tag.Id, seriesId), false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -337,46 +313,6 @@ public class SeriesService : ISeriesService
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static void UpdateCollectionsList(ICollection<CollectionTagDto>? tags, Series series, IReadOnlyCollection<CollectionTag> allTags,
|
||||
Action<CollectionTag> handleAdd)
|
||||
{
|
||||
// TODO: Move UpdateCollectionsList to a helper so we can easily test
|
||||
if (tags == null) return;
|
||||
// I want a union of these 2 lists. Return only elements that are in both lists, but the list types are different
|
||||
var existingTags = series.Metadata.CollectionTags.ToList();
|
||||
foreach (var existing in existingTags)
|
||||
{
|
||||
if (tags.SingleOrDefault(t => t.Id == existing.Id) == null)
|
||||
{
|
||||
// Remove tag
|
||||
series.Metadata.CollectionTags.Remove(existing);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, all tags that aren't in dto have been removed.
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
var existingTag = allTags.SingleOrDefault(t => t.Title == tag.Title);
|
||||
if (existingTag != null)
|
||||
{
|
||||
if (series.Metadata.CollectionTags.All(t => t.Title != tag.Title))
|
||||
{
|
||||
handleAdd(existingTag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new tag
|
||||
handleAdd(new CollectionTagBuilder(tag.Title)
|
||||
.WithId(tag.Id)
|
||||
.WithSummary(tag.Summary)
|
||||
.WithIsPromoted(tag.Promoted)
|
||||
.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
@ -461,7 +397,7 @@ public class SeriesService : ISeriesService
|
|||
}
|
||||
|
||||
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveCollectionsWithoutSeries();
|
||||
_taskScheduler.CleanupChapters(allChapterIds.ToArray());
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ public class CleanupService : ICleanupService
|
|||
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
|
||||
await _unitOfWork.PersonRepository.RemoveAllPeopleNoLongerAssociated();
|
||||
await _unitOfWork.GenreRepository.RemoveAllGenreNoLongerAssociated();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveCollectionsWithoutSeries();
|
||||
await _unitOfWork.ReadingListRepository.RemoveReadingListsWithoutSeries();
|
||||
}
|
||||
|
||||
|
|
|
@ -467,14 +467,13 @@ public class ParseScannedFiles
|
|||
}
|
||||
|
||||
|
||||
chapters = infos
|
||||
.OrderByNatural(info => info.Chapters)
|
||||
.ToList();
|
||||
|
||||
|
||||
// If everything is a special but we don't have any SpecialIndex, then order naturally and use 0, 1, 2
|
||||
if (specialTreatment)
|
||||
{
|
||||
chapters = infos
|
||||
.OrderByNatural(info => Parser.Parser.RemoveExtensionIfSupported(info.Filename)!)
|
||||
.ToList();
|
||||
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
chapter.IssueOrder = counter;
|
||||
|
@ -483,6 +482,9 @@ public class ParseScannedFiles
|
|||
return;
|
||||
}
|
||||
|
||||
chapters = infos
|
||||
.OrderByNatural(info => info.Chapters)
|
||||
.ToList();
|
||||
|
||||
counter = 0f;
|
||||
var prevIssue = string.Empty;
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Metadata;
|
||||
using API.Data.Repositories;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
|
@ -371,12 +372,26 @@ public class ProcessSeries : IProcessSeries
|
|||
|
||||
if (!string.IsNullOrEmpty(firstChapter?.SeriesGroup) && library.ManageCollections)
|
||||
{
|
||||
// Get the default admin to associate these tags to
|
||||
var defaultAdmin = await _unitOfWork.UserRepository.GetDefaultAdminUser(AppUserIncludes.Collections);
|
||||
if (defaultAdmin == null) return;
|
||||
|
||||
_logger.LogDebug("Collection tag(s) found for {SeriesName}, updating collections", series.Name);
|
||||
foreach (var collection in firstChapter.SeriesGroup.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var t = await _tagManagerService.GetCollectionTag(collection);
|
||||
if (t == null) continue;
|
||||
_collectionTagService.AddTagToSeriesMetadata(t, series.Metadata);
|
||||
var t = await _tagManagerService.GetCollectionTag(collection, defaultAdmin);
|
||||
if (t.Item1 == null) continue;
|
||||
|
||||
var tag = t.Item1;
|
||||
|
||||
// Check if the Series is already on the tag
|
||||
if (tag.Items.Any(s => s.MatchesSeriesByName(series.NormalizedName, series.NormalizedLocalizedName)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tag.Items.Add(series);
|
||||
await _unitOfWork.CollectionTagRepository.UpdateCollectionAgeRating(tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -28,7 +29,7 @@ public interface ITagManagerService
|
|||
Task<Genre?> GetGenre(string genre);
|
||||
Task<Tag?> GetTag(string tag);
|
||||
Task<Person?> GetPerson(string name, PersonRole role);
|
||||
Task<CollectionTag?> GetCollectionTag(string name);
|
||||
Task<Tuple<AppUserCollection?, bool>> GetCollectionTag(string? tag, AppUser userWithCollections);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -41,7 +42,7 @@ public class TagManagerService : ITagManagerService
|
|||
private Dictionary<string, Genre> _genres;
|
||||
private Dictionary<string, Tag> _tags;
|
||||
private Dictionary<string, Person> _people;
|
||||
private Dictionary<string, CollectionTag> _collectionTags;
|
||||
private Dictionary<string, AppUserCollection> _collectionTags;
|
||||
|
||||
private readonly SemaphoreSlim _genreSemaphore = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim _tagSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
@ -57,10 +58,10 @@ public class TagManagerService : ITagManagerService
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
_genres = new Dictionary<string, Genre>();
|
||||
_tags = new Dictionary<string, Tag>();
|
||||
_people = new Dictionary<string, Person>();
|
||||
_collectionTags = new Dictionary<string, CollectionTag>();
|
||||
_genres = [];
|
||||
_tags = [];
|
||||
_people = [];
|
||||
_collectionTags = [];
|
||||
}
|
||||
|
||||
public async Task Prime()
|
||||
|
@ -71,7 +72,8 @@ public class TagManagerService : ITagManagerService
|
|||
.GroupBy(GetPersonKey)
|
||||
.Select(g => g.First())
|
||||
.ToDictionary(GetPersonKey);
|
||||
_collectionTags = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync(CollectionTagIncludes.SeriesMetadata))
|
||||
var defaultAdmin = await _unitOfWork.UserRepository.GetDefaultAdminUser()!;
|
||||
_collectionTags = (await _unitOfWork.CollectionTagRepository.GetCollectionsForUserAsync(defaultAdmin.Id, CollectionIncludes.Series))
|
||||
.ToDictionary(t => t.NormalizedTitle);
|
||||
|
||||
}
|
||||
|
@ -183,28 +185,30 @@ public class TagManagerService : ITagManagerService
|
|||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<CollectionTag?> GetCollectionTag(string tag)
|
||||
public async Task<Tuple<AppUserCollection?, bool>> GetCollectionTag(string? tag, AppUser userWithCollections)
|
||||
{
|
||||
if (string.IsNullOrEmpty(tag)) return null;
|
||||
if (string.IsNullOrEmpty(tag)) return Tuple.Create<AppUserCollection?, bool>(null, false);
|
||||
|
||||
await _collectionTagSemaphore.WaitAsync();
|
||||
AppUserCollection? result;
|
||||
try
|
||||
{
|
||||
if (_collectionTags.TryGetValue(tag.ToNormalized(), out var result))
|
||||
if (_collectionTags.TryGetValue(tag.ToNormalized(), out result))
|
||||
{
|
||||
return result;
|
||||
return Tuple.Create<AppUserCollection?, bool>(result, false);
|
||||
}
|
||||
|
||||
// We need to create a new Genre
|
||||
result = new CollectionTagBuilder(tag).Build();
|
||||
_unitOfWork.CollectionTagRepository.Add(result);
|
||||
result = new AppUserCollectionBuilder(tag).Build();
|
||||
userWithCollections.Collections.Add(result);
|
||||
_unitOfWork.UserRepository.Update(userWithCollections);
|
||||
await _unitOfWork.CommitAsync();
|
||||
_collectionTags.Add(result.NormalizedTitle, result);
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_collectionTagSemaphore.Release();
|
||||
}
|
||||
return Tuple.Create<AppUserCollection?, bool>(result, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ public class StatsService : IStatsService
|
|||
|
||||
HasBookmarks = (await _unitOfWork.UserRepository.GetAllBookmarksAsync()).Any(),
|
||||
NumberOfLibraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).Count(),
|
||||
NumberOfCollections = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).Count(),
|
||||
NumberOfCollections = (await _unitOfWork.CollectionTagRepository.GetAllCollectionsAsync()).Count(),
|
||||
NumberOfReadingLists = await _unitOfWork.ReadingListRepository.Count(),
|
||||
OPDSEnabled = serverSettings.EnableOpds,
|
||||
NumberOfUsers = (await _unitOfWork.UserRepository.GetAllUsersAsync()).Count(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue