mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Release Shakeout Day 1 (#1591)
* Fixed an issue where reading list were not able to update their summary due to a duplicate title check. * Misc code smell cleanup * Updated .net dependencies and removed unneeded ones * Fixed an issue where removing a series from want to read list page wouldn't update the page correctly * Fixed age restriction not applied to Recommended page * Ensure that Genres and Tags are age restricted gated * Persons are now age gated as well * When you choose a cover, the new cover will properly be selected and will focus on it, in the cases there are many other covers available. * Fixed caching profiles * Added in a special hook when deleting a library to clear all series Relations before we delete
This commit is contained in:
parent
03bd2e9103
commit
b802e1e1b0
@ -7,8 +7,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||||
<PackageReference Include="NSubstitute" Version="4.4.0" />
|
<PackageReference Include="NSubstitute" Version="4.4.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
|
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
|
||||||
<PackageReference Include="xunit" Version="2.4.2" />
|
<PackageReference Include="xunit" Version="2.4.2" />
|
||||||
|
@ -100,6 +100,159 @@ public class QueryableExtensionsTests
|
|||||||
Assert.Equal(expectedCount, filtered.Count());
|
Assert.Equal(expectedCount, filtered.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true, 2)]
|
||||||
|
[InlineData(false, 1)]
|
||||||
|
public void RestrictAgainstAgeRestriction_Genre_ShouldRestrictEverythingAboveTeen(bool includeUnknowns, int expectedCount)
|
||||||
|
{
|
||||||
|
var items = new List<Genre>()
|
||||||
|
{
|
||||||
|
new Genre()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Genre()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Unknown,
|
||||||
|
},
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Genre()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.X18Plus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var filtered = items.AsQueryable().RestrictAgainstAgeRestriction(new AgeRestriction()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
IncludeUnknowns = includeUnknowns
|
||||||
|
});
|
||||||
|
Assert.Equal(expectedCount, filtered.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true, 2)]
|
||||||
|
[InlineData(false, 1)]
|
||||||
|
public void RestrictAgainstAgeRestriction_Tag_ShouldRestrictEverythingAboveTeen(bool includeUnknowns, int expectedCount)
|
||||||
|
{
|
||||||
|
var items = new List<Tag>()
|
||||||
|
{
|
||||||
|
new Tag()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Tag()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Unknown,
|
||||||
|
},
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Tag()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.X18Plus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var filtered = items.AsQueryable().RestrictAgainstAgeRestriction(new AgeRestriction()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
IncludeUnknowns = includeUnknowns
|
||||||
|
});
|
||||||
|
Assert.Equal(expectedCount, filtered.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true, 2)]
|
||||||
|
[InlineData(false, 1)]
|
||||||
|
public void RestrictAgainstAgeRestriction_Person_ShouldRestrictEverythingAboveTeen(bool includeUnknowns, int expectedCount)
|
||||||
|
{
|
||||||
|
var items = new List<Person>()
|
||||||
|
{
|
||||||
|
new Person()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Person()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Unknown,
|
||||||
|
},
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Person()
|
||||||
|
{
|
||||||
|
SeriesMetadatas = new List<SeriesMetadata>()
|
||||||
|
{
|
||||||
|
new SeriesMetadata()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.X18Plus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var filtered = items.AsQueryable().RestrictAgainstAgeRestriction(new AgeRestriction()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.Teen,
|
||||||
|
IncludeUnknowns = includeUnknowns
|
||||||
|
});
|
||||||
|
Assert.Equal(expectedCount, filtered.Count());
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(true, 2)]
|
[InlineData(true, 2)]
|
||||||
[InlineData(false, 1)]
|
[InlineData(false, 1)]
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.0" />
|
||||||
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.4" />
|
<PackageReference Include="Docnet.Core" Version="2.4.0-alpha.4" />
|
||||||
<PackageReference Include="ExCSS" Version="4.1.0" />
|
<PackageReference Include="ExCSS" Version="4.1.0" />
|
||||||
<PackageReference Include="Flurl" Version="3.0.6" />
|
<PackageReference Include="Flurl" Version="3.0.6" />
|
||||||
@ -59,20 +59,19 @@
|
|||||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.2" />
|
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.2" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
|
||||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.9" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.10">
|
||||||
<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="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.2.1" />
|
||||||
<PackageReference Include="NetVips" Version="2.2.0" />
|
<PackageReference Include="NetVips" Version="2.2.0" />
|
||||||
<PackageReference Include="NetVips.Native" Version="8.13.1" />
|
<PackageReference Include="NetVips.Native" Version="8.13.1" />
|
||||||
<PackageReference Include="Ng.UserAgentService" Version="1.1.0" />
|
|
||||||
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
|
<PackageReference Include="NReco.Logging.File" Version="1.1.5" />
|
||||||
<PackageReference Include="Serilog" Version="2.12.0" />
|
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
|
<PackageReference Include="Serilog.AspNetCore" Version="6.0.1" />
|
||||||
@ -85,13 +84,13 @@
|
|||||||
<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.32.2" />
|
<PackageReference Include="SharpCompress" Version="0.32.2" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.45.0.54064">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.47.0.55603">
|
||||||
<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.4.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.23.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.24.0" />
|
||||||
<PackageReference Include="System.IO.Abstractions" Version="17.2.3" />
|
<PackageReference Include="System.IO.Abstractions" Version="17.2.3" />
|
||||||
<PackageReference Include="VersOne.Epub" Version="3.3.0-alpha1" />
|
<PackageReference Include="VersOne.Epub" Version="3.3.0-alpha1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -11,6 +11,7 @@ using API.DTOs.Search;
|
|||||||
using API.DTOs.System;
|
using API.DTOs.System;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.Entities.Metadata;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Services;
|
using API.Services;
|
||||||
using API.Services.Tasks.Scanner;
|
using API.Services.Tasks.Scanner;
|
||||||
@ -251,6 +252,14 @@ public class LibraryController : BaseApiController
|
|||||||
return BadRequest(
|
return BadRequest(
|
||||||
"You cannot delete a library while a scan is in progress. Please wait for scan to continue then try to delete");
|
"You cannot delete a library while a scan is in progress. Please wait for scan to continue then try to delete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Due to a bad schema that I can't figure out how to fix, we need to erase all RelatedSeries before we delete the library
|
||||||
|
foreach (var s in await _unitOfWork.SeriesRepository.GetSeriesForLibraryIdAsync(library.Id))
|
||||||
|
{
|
||||||
|
s.Relations = new List<SeriesRelation>();
|
||||||
|
_unitOfWork.SeriesRepository.Update(s);
|
||||||
|
}
|
||||||
|
|
||||||
_unitOfWork.LibraryRepository.Delete(library);
|
_unitOfWork.LibraryRepository.Delete(library);
|
||||||
await _unitOfWork.CommitAsync();
|
await _unitOfWork.CommitAsync();
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ using API.DTOs;
|
|||||||
using API.DTOs.Filtering;
|
using API.DTOs.Filtering;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using API.Extensions;
|
||||||
using Kavita.Common.Extensions;
|
using Kavita.Common.Extensions;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -31,15 +32,18 @@ public class MetadataController : BaseApiController
|
|||||||
[HttpGet("genres")]
|
[HttpGet("genres")]
|
||||||
public async Task<ActionResult<IList<GenreTagDto>>> GetAllGenres(string? libraryIds)
|
public async Task<ActionResult<IList<GenreTagDto>>> GetAllGenres(string? libraryIds)
|
||||||
{
|
{
|
||||||
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
||||||
if (ids != null && ids.Count > 0)
|
if (ids != null && ids.Count > 0)
|
||||||
{
|
{
|
||||||
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids));
|
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosAsync());
|
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosAsync(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches people from the instance
|
/// Fetches people from the instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,12 +52,13 @@ public class MetadataController : BaseApiController
|
|||||||
[HttpGet("people")]
|
[HttpGet("people")]
|
||||||
public async Task<ActionResult<IList<PersonDto>>> GetAllPeople(string? libraryIds)
|
public async Task<ActionResult<IList<PersonDto>>> GetAllPeople(string? libraryIds)
|
||||||
{
|
{
|
||||||
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
||||||
if (ids != null && ids.Count > 0)
|
if (ids != null && ids.Count > 0)
|
||||||
{
|
{
|
||||||
return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids));
|
return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids, userId));
|
||||||
}
|
}
|
||||||
return Ok(await _unitOfWork.PersonRepository.GetAllPeople());
|
return Ok(await _unitOfWork.PersonRepository.GetAllPersonDtosAsync(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -64,12 +69,13 @@ public class MetadataController : BaseApiController
|
|||||||
[HttpGet("tags")]
|
[HttpGet("tags")]
|
||||||
public async Task<ActionResult<IList<TagDto>>> GetAllTags(string? libraryIds)
|
public async Task<ActionResult<IList<TagDto>>> GetAllTags(string? libraryIds)
|
||||||
{
|
{
|
||||||
|
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||||
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
var ids = libraryIds?.Split(",").Select(int.Parse).ToList();
|
||||||
if (ids != null && ids.Count > 0)
|
if (ids != null && ids.Count > 0)
|
||||||
{
|
{
|
||||||
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids));
|
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids, userId));
|
||||||
}
|
}
|
||||||
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosAsync());
|
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosAsync(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -21,7 +21,6 @@ public class ReadingListController : BaseApiController
|
|||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly IEventHub _eventHub;
|
private readonly IEventHub _eventHub;
|
||||||
private readonly IReadingListService _readingListService;
|
private readonly IReadingListService _readingListService;
|
||||||
private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
|
|
||||||
|
|
||||||
public ReadingListController(IUnitOfWork unitOfWork, IEventHub eventHub, IReadingListService readingListService)
|
public ReadingListController(IUnitOfWork unitOfWork, IEventHub eventHub, IReadingListService readingListService)
|
||||||
{
|
{
|
||||||
@ -219,6 +218,10 @@ public class ReadingListController : BaseApiController
|
|||||||
|
|
||||||
dto.Title = dto.Title.Trim();
|
dto.Title = dto.Title.Trim();
|
||||||
if (!string.IsNullOrEmpty(dto.Title))
|
if (!string.IsNullOrEmpty(dto.Title))
|
||||||
|
{
|
||||||
|
readingList.Summary = dto.Summary;
|
||||||
|
|
||||||
|
if (!readingList.Title.Equals(dto.Title))
|
||||||
{
|
{
|
||||||
var hasExisting = user.ReadingLists.Any(l => l.Title.Equals(dto.Title));
|
var hasExisting = user.ReadingLists.Any(l => l.Title.Equals(dto.Title));
|
||||||
if (hasExisting)
|
if (hasExisting)
|
||||||
@ -228,13 +231,9 @@ public class ReadingListController : BaseApiController
|
|||||||
readingList.Title = dto.Title;
|
readingList.Title = dto.Title;
|
||||||
readingList.NormalizedTitle = Services.Tasks.Scanner.Parser.Parser.Normalize(readingList.Title);
|
readingList.NormalizedTitle = Services.Tasks.Scanner.Parser.Parser.Normalize(readingList.Title);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(dto.Title))
|
|
||||||
{
|
|
||||||
readingList.Summary = dto.Summary;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readingList.Promoted = dto.Promoted;
|
readingList.Promoted = dto.Promoted;
|
||||||
|
|
||||||
readingList.CoverImageLocked = dto.CoverImageLocked;
|
readingList.CoverImageLocked = dto.CoverImageLocked;
|
||||||
|
|
||||||
if (!dto.CoverImageLocked)
|
if (!dto.CoverImageLocked)
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Data.Misc;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.QueryableExtensions;
|
using AutoMapper.QueryableExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -15,9 +17,9 @@ public interface IGenreRepository
|
|||||||
void Remove(Genre genre);
|
void Remove(Genre genre);
|
||||||
Task<Genre> FindByNameAsync(string genreName);
|
Task<Genre> FindByNameAsync(string genreName);
|
||||||
Task<IList<Genre>> GetAllGenresAsync();
|
Task<IList<Genre>> GetAllGenresAsync();
|
||||||
Task<IList<GenreTagDto>> GetAllGenreDtosAsync();
|
Task<IList<GenreTagDto>> GetAllGenreDtosAsync(int userId);
|
||||||
Task RemoveAllGenreNoLongerAssociated(bool removeExternal = false);
|
Task RemoveAllGenreNoLongerAssociated(bool removeExternal = false);
|
||||||
Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(IList<int> libraryIds);
|
Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(IList<int> libraryIds, int userId);
|
||||||
Task<int> GetCountAsync();
|
Task<int> GetCountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,10 +65,18 @@ public class GenreRepository : IGenreRepository
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(IList<int> libraryIds)
|
/// <summary>
|
||||||
|
/// Returns a set of Genre tags for a set of library Ids. UserId will restrict returned Genres based on user's age restriction.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryIds"></param>
|
||||||
|
/// <param name="userId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IList<GenreTagDto>> GetAllGenreDtosForLibrariesAsync(IList<int> libraryIds, int userId)
|
||||||
{
|
{
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
return await _context.Series
|
return await _context.Series
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.SelectMany(s => s.Metadata.Genres)
|
.SelectMany(s => s.Metadata.Genres)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Distinct()
|
.Distinct()
|
||||||
@ -75,6 +85,7 @@ public class GenreRepository : IGenreRepository
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<int> GetCountAsync()
|
public async Task<int> GetCountAsync()
|
||||||
{
|
{
|
||||||
return await _context.Genre.CountAsync();
|
return await _context.Genre.CountAsync();
|
||||||
@ -85,9 +96,11 @@ public class GenreRepository : IGenreRepository
|
|||||||
return await _context.Genre.ToListAsync();
|
return await _context.Genre.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<GenreTagDto>> GetAllGenreDtosAsync()
|
public async Task<IList<GenreTagDto>> GetAllGenreDtosAsync(int userId)
|
||||||
{
|
{
|
||||||
|
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
return await _context.Genre
|
return await _context.Genre
|
||||||
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.QueryableExtensions;
|
using AutoMapper.QueryableExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -14,8 +15,9 @@ public interface IPersonRepository
|
|||||||
void Attach(Person person);
|
void Attach(Person person);
|
||||||
void Remove(Person person);
|
void Remove(Person person);
|
||||||
Task<IList<Person>> GetAllPeople();
|
Task<IList<Person>> GetAllPeople();
|
||||||
|
Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId);
|
||||||
Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false);
|
Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false);
|
||||||
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds);
|
Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds, int userId);
|
||||||
Task<int> GetCountAsync();
|
Task<int> GetCountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,14 +42,6 @@ public class PersonRepository : IPersonRepository
|
|||||||
_context.Person.Remove(person);
|
_context.Person.Remove(person);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Person> FindByNameAsync(string name)
|
|
||||||
{
|
|
||||||
var normalizedName = Services.Tasks.Scanner.Parser.Parser.Normalize(name);
|
|
||||||
return await _context.Person
|
|
||||||
.Where(p => normalizedName.Equals(p.NormalizedName))
|
|
||||||
.SingleOrDefaultAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false)
|
public async Task RemoveAllPeopleNoLongerAssociated(bool removeExternal = false)
|
||||||
{
|
{
|
||||||
var peopleWithNoConnections = await _context.Person
|
var peopleWithNoConnections = await _context.Person
|
||||||
@ -62,10 +56,12 @@ public class PersonRepository : IPersonRepository
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds)
|
public async Task<IList<PersonDto>> GetAllPeopleDtosForLibrariesAsync(List<int> libraryIds, int userId)
|
||||||
{
|
{
|
||||||
|
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
return await _context.Series
|
return await _context.Series
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
.SelectMany(s => s.Metadata.People)
|
.SelectMany(s => s.Metadata.People)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.OrderBy(p => p.Name)
|
.OrderBy(p => p.Name)
|
||||||
@ -87,4 +83,14 @@ public class PersonRepository : IPersonRepository
|
|||||||
.OrderBy(p => p.Name)
|
.OrderBy(p => p.Name)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IList<PersonDto>> GetAllPersonDtosAsync(int userId)
|
||||||
|
{
|
||||||
|
var ageRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
return await _context.Person
|
||||||
|
.OrderBy(p => p.Name)
|
||||||
|
.RestrictAgainstAgeRestriction(ageRating)
|
||||||
|
.ProjectTo<PersonDto>(_mapper.ConfigurationProvider)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,7 +291,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
const int maxRecords = 15;
|
const int maxRecords = 15;
|
||||||
var result = new SearchResultGroupDto();
|
var result = new SearchResultGroupDto();
|
||||||
var searchQueryNormalized = Services.Tasks.Scanner.Parser.Parser.Normalize(searchQuery);
|
var searchQueryNormalized = Services.Tasks.Scanner.Parser.Parser.Normalize(searchQuery);
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
var seriesIds = _context.Series
|
var seriesIds = _context.Series
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
@ -723,7 +723,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
private async Task<IQueryable<Series>> CreateFilteredSearchQueryable(int userId, int libraryId, FilterDto filter)
|
private async Task<IQueryable<Series>> CreateFilteredSearchQueryable(int userId, int libraryId, FilterDto filter)
|
||||||
{
|
{
|
||||||
var userLibraries = await GetUserLibraries(libraryId, userId);
|
var userLibraries = await GetUserLibraries(libraryId, userId);
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
var formats = ExtractFilters(libraryId, userId, filter, ref userLibraries,
|
var formats = ExtractFilters(libraryId, userId, filter, ref userLibraries,
|
||||||
out var allPeopleIds, out var hasPeopleFilter, out var hasGenresFilter,
|
out var allPeopleIds, out var hasPeopleFilter, out var hasGenresFilter,
|
||||||
@ -1028,7 +1028,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
{
|
{
|
||||||
var seriesMap = new Dictionary<string, GroupedSeriesDto>();
|
var seriesMap = new Dictionary<string, GroupedSeriesDto>();
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
var items = (await GetRecentlyAddedChaptersQuery(userId));
|
var items = (await GetRecentlyAddedChaptersQuery(userId));
|
||||||
if (userRating.AgeRating != AgeRating.NotApplicable)
|
if (userRating.AgeRating != AgeRating.NotApplicable)
|
||||||
@ -1063,23 +1063,10 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
return seriesMap.Values.AsEnumerable();
|
return seriesMap.Values.AsEnumerable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<AgeRestriction> GetUserAgeRestriction(int userId)
|
|
||||||
{
|
|
||||||
return await _context.AppUser
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(u => u.Id == userId)
|
|
||||||
.Select(u =>
|
|
||||||
new AgeRestriction(){
|
|
||||||
AgeRating = u.AgeRestriction,
|
|
||||||
IncludeUnknowns = u.AgeRestrictionIncludeUnknowns
|
|
||||||
})
|
|
||||||
.SingleAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<SeriesDto>> GetSeriesForRelationKind(int userId, int seriesId, RelationKind kind)
|
public async Task<IEnumerable<SeriesDto>> GetSeriesForRelationKind(int userId, int seriesId, RelationKind kind)
|
||||||
{
|
{
|
||||||
var libraryIds = GetLibraryIdsForUser(userId);
|
var libraryIds = GetLibraryIdsForUser(userId);
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
var usersSeriesIds = _context.Series
|
var usersSeriesIds = _context.Series
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
@ -1108,9 +1095,14 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
var libraryIds = GetLibraryIdsForUser(userId, libraryId);
|
var libraryIds = GetLibraryIdsForUser(userId, libraryId);
|
||||||
var usersSeriesIds = GetSeriesIdsForLibraryIds(libraryIds);
|
var usersSeriesIds = GetSeriesIdsForLibraryIds(libraryIds);
|
||||||
|
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
// Because this can be called from an API, we need to provide an additional check if the genre has anything the
|
||||||
|
// user with age restrictions can access
|
||||||
|
|
||||||
var query = _context.Series
|
var query = _context.Series
|
||||||
.Where(s => s.Metadata.Genres.Select(g => g.Id).Contains(genreId))
|
.Where(s => s.Metadata.Genres.Select(g => g.Id).Contains(genreId))
|
||||||
.Where(s => usersSeriesIds.Contains(s.Id))
|
.Where(s => usersSeriesIds.Contains(s.Id))
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
||||||
|
|
||||||
@ -1147,7 +1139,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
public async Task<SeriesDto> GetSeriesForMangaFile(int mangaFileId, int userId)
|
public async Task<SeriesDto> GetSeriesForMangaFile(int mangaFileId, int userId)
|
||||||
{
|
{
|
||||||
var libraryIds = GetLibraryIdsForUser(userId);
|
var libraryIds = GetLibraryIdsForUser(userId);
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
return await _context.MangaFile
|
return await _context.MangaFile
|
||||||
.Where(m => m.Id == mangaFileId)
|
.Where(m => m.Id == mangaFileId)
|
||||||
@ -1164,7 +1156,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
public async Task<SeriesDto> GetSeriesForChapter(int chapterId, int userId)
|
public async Task<SeriesDto> GetSeriesForChapter(int chapterId, int userId)
|
||||||
{
|
{
|
||||||
var libraryIds = GetLibraryIdsForUser(userId);
|
var libraryIds = GetLibraryIdsForUser(userId);
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
return await _context.Chapter
|
return await _context.Chapter
|
||||||
.Where(m => m.Id == chapterId)
|
.Where(m => m.Id == chapterId)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
@ -1321,9 +1313,11 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.Where(s => usersSeriesIds.Contains(s.SeriesId) && s.Rating > 4)
|
.Where(s => usersSeriesIds.Contains(s.SeriesId) && s.Rating > 4)
|
||||||
.Select(p => p.SeriesId)
|
.Select(p => p.SeriesId)
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
var query = _context.Series
|
var query = _context.Series
|
||||||
.Where(s => distinctSeriesIdsWithHighRating.Contains(s.Id))
|
.Where(s => distinctSeriesIdsWithHighRating.Contains(s.Id))
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.OrderByDescending(s => _context.AppUserRating.Where(r => r.SeriesId == s.Id).Select(r => r.Rating).Average())
|
.OrderByDescending(s => _context.AppUserRating.Where(r => r.SeriesId == s.Id).Select(r => r.Rating).Average())
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
||||||
@ -1340,6 +1334,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.Where(s => usersSeriesIds.Contains(s.SeriesId))
|
.Where(s => usersSeriesIds.Contains(s.SeriesId))
|
||||||
.Select(p => p.SeriesId)
|
.Select(p => p.SeriesId)
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
|
|
||||||
var query = _context.Series
|
var query = _context.Series
|
||||||
@ -1349,6 +1344,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
&& !distinctSeriesIdsWithProgress.Contains(s.Id) &&
|
&& !distinctSeriesIdsWithProgress.Contains(s.Id) &&
|
||||||
usersSeriesIds.Contains(s.Id))
|
usersSeriesIds.Contains(s.Id))
|
||||||
.Where(s => s.Metadata.PublicationStatus != PublicationStatus.OnGoing)
|
.Where(s => s.Metadata.PublicationStatus != PublicationStatus.OnGoing)
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
||||||
|
|
||||||
@ -1365,6 +1361,8 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.Select(p => p.SeriesId)
|
.Select(p => p.SeriesId)
|
||||||
.Distinct();
|
.Distinct();
|
||||||
|
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
|
|
||||||
var query = _context.Series
|
var query = _context.Series
|
||||||
.Where(s => (
|
.Where(s => (
|
||||||
@ -1373,6 +1371,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
&& !distinctSeriesIdsWithProgress.Contains(s.Id) &&
|
&& !distinctSeriesIdsWithProgress.Contains(s.Id) &&
|
||||||
usersSeriesIds.Contains(s.Id))
|
usersSeriesIds.Contains(s.Id))
|
||||||
.Where(s => s.Metadata.PublicationStatus == PublicationStatus.OnGoing)
|
.Where(s => s.Metadata.PublicationStatus == PublicationStatus.OnGoing)
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider);
|
||||||
|
|
||||||
@ -1406,7 +1405,7 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
{
|
{
|
||||||
var libraryIds = GetLibraryIdsForUser(userId);
|
var libraryIds = GetLibraryIdsForUser(userId);
|
||||||
var usersSeriesIds = GetSeriesIdsForLibraryIds(libraryIds);
|
var usersSeriesIds = GetSeriesIdsForLibraryIds(libraryIds);
|
||||||
var userRating = await GetUserAgeRestriction(userId);
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
|
|
||||||
return new RelatedSeriesDto()
|
return new RelatedSeriesDto()
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs.Metadata;
|
using API.DTOs.Metadata;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.QueryableExtensions;
|
using AutoMapper.QueryableExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -13,11 +14,10 @@ public interface ITagRepository
|
|||||||
{
|
{
|
||||||
void Attach(Tag tag);
|
void Attach(Tag tag);
|
||||||
void Remove(Tag tag);
|
void Remove(Tag tag);
|
||||||
Task<Tag> FindByNameAsync(string tagName);
|
|
||||||
Task<IList<Tag>> GetAllTagsAsync();
|
Task<IList<Tag>> GetAllTagsAsync();
|
||||||
Task<IList<TagDto>> GetAllTagDtosAsync();
|
Task<IList<TagDto>> GetAllTagDtosAsync(int userId);
|
||||||
Task RemoveAllTagNoLongerAssociated(bool removeExternal = false);
|
Task RemoveAllTagNoLongerAssociated(bool removeExternal = false);
|
||||||
Task<IList<TagDto>> GetAllTagDtosForLibrariesAsync(IList<int> libraryIds);
|
Task<IList<TagDto>> GetAllTagDtosForLibrariesAsync(IList<int> libraryIds, int userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TagRepository : ITagRepository
|
public class TagRepository : ITagRepository
|
||||||
@ -41,13 +41,6 @@ public class TagRepository : ITagRepository
|
|||||||
_context.Tag.Remove(tag);
|
_context.Tag.Remove(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tag> FindByNameAsync(string tagName)
|
|
||||||
{
|
|
||||||
var normalizedName = Services.Tasks.Scanner.Parser.Parser.Normalize(tagName);
|
|
||||||
return await _context.Tag
|
|
||||||
.FirstOrDefaultAsync(g => g.NormalizedTitle.Equals(normalizedName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RemoveAllTagNoLongerAssociated(bool removeExternal = false)
|
public async Task RemoveAllTagNoLongerAssociated(bool removeExternal = false)
|
||||||
{
|
{
|
||||||
var tagsWithNoConnections = await _context.Tag
|
var tagsWithNoConnections = await _context.Tag
|
||||||
@ -62,10 +55,12 @@ public class TagRepository : ITagRepository
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<TagDto>> GetAllTagDtosForLibrariesAsync(IList<int> libraryIds)
|
public async Task<IList<TagDto>> GetAllTagDtosForLibrariesAsync(IList<int> libraryIds, int userId)
|
||||||
{
|
{
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
return await _context.Series
|
return await _context.Series
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.SelectMany(s => s.Metadata.Tags)
|
.SelectMany(s => s.Metadata.Tags)
|
||||||
.AsSplitQuery()
|
.AsSplitQuery()
|
||||||
.Distinct()
|
.Distinct()
|
||||||
@ -80,10 +75,12 @@ public class TagRepository : ITagRepository
|
|||||||
return await _context.Tag.ToListAsync();
|
return await _context.Tag.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<TagDto>> GetAllTagDtosAsync()
|
public async Task<IList<TagDto>> GetAllTagDtosAsync(int userId)
|
||||||
{
|
{
|
||||||
|
var userRating = await _context.AppUser.GetUserAgeRestriction(userId);
|
||||||
return await _context.Tag
|
return await _context.Tag
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.RestrictAgainstAgeRestriction(userRating)
|
||||||
.OrderBy(t => t.Title)
|
.OrderBy(t => t.Title)
|
||||||
.ProjectTo<TagDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<TagDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using API.Data.Misc;
|
using API.Data.Misc;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Extensions;
|
namespace API.Extensions;
|
||||||
|
|
||||||
@ -33,6 +35,48 @@ public static class QueryableExtensions
|
|||||||
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
|
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IQueryable<Genre> RestrictAgainstAgeRestriction(this IQueryable<Genre> queryable, AgeRestriction restriction)
|
||||||
|
{
|
||||||
|
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
|
||||||
|
|
||||||
|
if (restriction.IncludeUnknowns)
|
||||||
|
{
|
||||||
|
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
|
||||||
|
sm.AgeRating <= restriction.AgeRating));
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
|
||||||
|
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IQueryable<Tag> RestrictAgainstAgeRestriction(this IQueryable<Tag> queryable, AgeRestriction restriction)
|
||||||
|
{
|
||||||
|
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
|
||||||
|
|
||||||
|
if (restriction.IncludeUnknowns)
|
||||||
|
{
|
||||||
|
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
|
||||||
|
sm.AgeRating <= restriction.AgeRating));
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
|
||||||
|
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IQueryable<Person> RestrictAgainstAgeRestriction(this IQueryable<Person> queryable, AgeRestriction restriction)
|
||||||
|
{
|
||||||
|
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
|
||||||
|
|
||||||
|
if (restriction.IncludeUnknowns)
|
||||||
|
{
|
||||||
|
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
|
||||||
|
sm.AgeRating <= restriction.AgeRating));
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryable.Where(c => c.SeriesMetadatas.All(sm =>
|
||||||
|
sm.AgeRating <= restriction.AgeRating && sm.AgeRating > AgeRating.Unknown));
|
||||||
|
}
|
||||||
|
|
||||||
public static IQueryable<ReadingList> RestrictAgainstAgeRestriction(this IQueryable<ReadingList> queryable, AgeRestriction restriction)
|
public static IQueryable<ReadingList> RestrictAgainstAgeRestriction(this IQueryable<ReadingList> queryable, AgeRestriction restriction)
|
||||||
{
|
{
|
||||||
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
|
if (restriction.AgeRating == AgeRating.NotApplicable) return queryable;
|
||||||
@ -45,4 +89,25 @@ public static class QueryableExtensions
|
|||||||
|
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task<AgeRestriction> GetUserAgeRestriction(this DbSet<AppUser> queryable, int userId)
|
||||||
|
{
|
||||||
|
if (userId < 1)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new AgeRestriction()
|
||||||
|
{
|
||||||
|
AgeRating = AgeRating.NotApplicable,
|
||||||
|
IncludeUnknowns = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return queryable
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(u => u.Id == userId)
|
||||||
|
.Select(u =>
|
||||||
|
new AgeRestriction(){
|
||||||
|
AgeRating = u.AgeRestriction,
|
||||||
|
IncludeUnknowns = u.AgeRestrictionIncludeUnknowns
|
||||||
|
})
|
||||||
|
.SingleAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,14 +63,4 @@ public static class GenreHelper
|
|||||||
metadataGenres.Add(genre);
|
metadataGenres.Add(genre);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddGenreIfNotExists(BlockingCollection<Genre> metadataGenres, Genre genre)
|
|
||||||
{
|
|
||||||
var existingGenre = metadataGenres.FirstOrDefault(p =>
|
|
||||||
p.NormalizedTitle == Services.Tasks.Scanner.Parser.Parser.Normalize(genre.Title));
|
|
||||||
if (existingGenre == null)
|
|
||||||
{
|
|
||||||
metadataGenres.Add(genre);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using API.Data;
|
|||||||
using API.Data.Metadata;
|
using API.Data.Metadata;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.Data.Scanner;
|
using API.Data.Scanner;
|
||||||
|
using API.DTOs.Metadata;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
|
@ -68,6 +68,7 @@ public class ParseScannedFiles
|
|||||||
/// This will Scan all files in a folder path. For each folder within the folderPath, FolderAction will be invoked for all files contained
|
/// This will Scan all files in a folder path. For each folder within the folderPath, FolderAction will be invoked for all files contained
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scanDirectoryByDirectory">Scan directory by directory and for each, call folderAction</param>
|
/// <param name="scanDirectoryByDirectory">Scan directory by directory and for each, call folderAction</param>
|
||||||
|
/// <param name="seriesPaths">A dictionary mapping a normalized path to a list of <see cref="SeriesModified"/> to help scanner skip I/O</param>
|
||||||
/// <param name="folderPath">A library folder or series folder</param>
|
/// <param name="folderPath">A library folder or series folder</param>
|
||||||
/// <param name="folderAction">A callback async Task to be called once all files for each folder path are found</param>
|
/// <param name="folderAction">A callback async Task to be called once all files for each folder path are found</param>
|
||||||
/// <param name="forceCheck">If we should bypass any folder last write time checks on the scan and force I/O</param>
|
/// <param name="forceCheck">If we should bypass any folder last write time checks on the scan and force I/O</param>
|
||||||
@ -215,6 +216,7 @@ public class ParseScannedFiles
|
|||||||
/// Using a normalized name from the passed ParserInfo, this checks against all found series so far and if an existing one exists with
|
/// Using a normalized name from the passed ParserInfo, this checks against all found series so far and if an existing one exists with
|
||||||
/// same normalized name, it merges into the existing one. This is important as some manga may have a slight difference with punctuation or capitalization.
|
/// same normalized name, it merges into the existing one. This is important as some manga may have a slight difference with punctuation or capitalization.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="scannedSeries"></param>
|
||||||
/// <param name="info"></param>
|
/// <param name="info"></param>
|
||||||
/// <returns>Series Name to group this info into</returns>
|
/// <returns>Series Name to group this info into</returns>
|
||||||
private string MergeName(ConcurrentDictionary<ParsedSeries, List<ParserInfo>> scannedSeries, ParserInfo info)
|
private string MergeName(ConcurrentDictionary<ParsedSeries, List<ParserInfo>> scannedSeries, ParserInfo info)
|
||||||
|
@ -749,12 +749,12 @@ public static class Parser
|
|||||||
foreach (var regex in MangaChapterRegex)
|
foreach (var regex in MangaChapterRegex)
|
||||||
{
|
{
|
||||||
var matches = regex.Matches(filename);
|
var matches = regex.Matches(filename);
|
||||||
foreach (Match match in matches)
|
foreach (var groups in matches.Select(match => match.Groups))
|
||||||
{
|
{
|
||||||
if (!match.Groups["Chapter"].Success || match.Groups["Chapter"] == Match.Empty) continue;
|
if (!groups["Chapter"].Success || groups["Chapter"] == Match.Empty) continue;
|
||||||
|
|
||||||
var value = match.Groups["Chapter"].Value;
|
var value = groups["Chapter"].Value;
|
||||||
var hasPart = match.Groups["Part"].Success;
|
var hasPart = groups["Part"].Success;
|
||||||
|
|
||||||
return FormatValue(value, hasPart);
|
return FormatValue(value, hasPart);
|
||||||
}
|
}
|
||||||
@ -778,11 +778,11 @@ public static class Parser
|
|||||||
foreach (var regex in ComicChapterRegex)
|
foreach (var regex in ComicChapterRegex)
|
||||||
{
|
{
|
||||||
var matches = regex.Matches(filename);
|
var matches = regex.Matches(filename);
|
||||||
foreach (Match match in matches)
|
foreach (var groups in matches.Select(match => match.Groups))
|
||||||
{
|
{
|
||||||
if (!match.Groups["Chapter"].Success || match.Groups["Chapter"] == Match.Empty) continue;
|
if (!groups["Chapter"].Success || groups["Chapter"] == Match.Empty) continue;
|
||||||
var value = match.Groups["Chapter"].Value;
|
var value = groups["Chapter"].Value;
|
||||||
var hasPart = match.Groups["Part"].Success;
|
var hasPart = groups["Part"].Success;
|
||||||
return FormatValue(value, hasPart);
|
return FormatValue(value, hasPart);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -428,7 +428,7 @@ public class ScannerService : IScannerService
|
|||||||
/// <param name="forceUpdate">Defaults to false</param>
|
/// <param name="forceUpdate">Defaults to false</param>
|
||||||
[Queue(TaskScheduler.ScanQueue)]
|
[Queue(TaskScheduler.ScanQueue)]
|
||||||
[DisableConcurrentExecution(60 * 60 * 60)]
|
[DisableConcurrentExecution(60 * 60 * 60)]
|
||||||
[AutomaticRetry(Attempts = 0, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||||
public async Task ScanLibrary(int libraryId, bool forceUpdate = false)
|
public async Task ScanLibrary(int libraryId, bool forceUpdate = false)
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
|
@ -75,7 +75,7 @@ public class TokenService : ITokenService
|
|||||||
var username = tokenContent.Claims.FirstOrDefault(q => q.Type == JwtRegisteredClaimNames.NameId)?.Value;
|
var username = tokenContent.Claims.FirstOrDefault(q => q.Type == JwtRegisteredClaimNames.NameId)?.Value;
|
||||||
var user = await _userManager.FindByNameAsync(username);
|
var user = await _userManager.FindByNameAsync(username);
|
||||||
if (user == null) return null; // This forces a logout
|
if (user == null) return null; // This forces a logout
|
||||||
var isValid = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "RefreshToken", request.RefreshToken);
|
await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "RefreshToken", request.RefreshToken);
|
||||||
|
|
||||||
await _userManager.UpdateSecurityStampAsync(user);
|
await _userManager.UpdateSecurityStampAsync(user);
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@ public class PresenceTracker : IPresenceTracker
|
|||||||
|
|
||||||
public Task<string[]> GetOnlineAdmins()
|
public Task<string[]> GetOnlineAdmins()
|
||||||
{
|
{
|
||||||
// TODO: This might end in stale data, we want to get the online users, query against DB to check if they are admins then return
|
|
||||||
string[] onlineUsers;
|
string[] onlineUsers;
|
||||||
lock (OnlineUsers)
|
lock (OnlineUsers)
|
||||||
{
|
{
|
||||||
|
@ -74,21 +74,21 @@ public class Startup
|
|||||||
new CacheProfile()
|
new CacheProfile()
|
||||||
{
|
{
|
||||||
Duration = 60 * 10,
|
Duration = 60 * 10,
|
||||||
Location = ResponseCacheLocation.Any,
|
Location = ResponseCacheLocation.None,
|
||||||
NoStore = false
|
NoStore = false
|
||||||
});
|
});
|
||||||
options.CacheProfiles.Add("5Minute",
|
options.CacheProfiles.Add("5Minute",
|
||||||
new CacheProfile()
|
new CacheProfile()
|
||||||
{
|
{
|
||||||
Duration = 60 * 5,
|
Duration = 60 * 5,
|
||||||
Location = ResponseCacheLocation.Any,
|
Location = ResponseCacheLocation.None,
|
||||||
});
|
});
|
||||||
// Instant is a very quick cache, because we can't bust based on the query params, but rather body
|
// Instant is a very quick cache, because we can't bust based on the query params, but rather body
|
||||||
options.CacheProfiles.Add("Instant",
|
options.CacheProfiles.Add("Instant",
|
||||||
new CacheProfile()
|
new CacheProfile()
|
||||||
{
|
{
|
||||||
Duration = 30,
|
Duration = 30,
|
||||||
Location = ResponseCacheLocation.Any,
|
Location = ResponseCacheLocation.None,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
services.Configure<ForwardedHeadersOptions>(options =>
|
services.Configure<ForwardedHeadersOptions>(options =>
|
||||||
@ -300,13 +300,6 @@ public class Startup
|
|||||||
|
|
||||||
app.Use(async (context, next) =>
|
app.Use(async (context, next) =>
|
||||||
{
|
{
|
||||||
// Note: I removed this as I caught Chrome caching api responses when it shouldn't have
|
|
||||||
// context.Response.GetTypedHeaders().CacheControl =
|
|
||||||
// new CacheControlHeaderValue()
|
|
||||||
// {
|
|
||||||
// Public = false,
|
|
||||||
// MaxAge = TimeSpan.FromSeconds(10),
|
|
||||||
// };
|
|
||||||
context.Response.Headers[HeaderNames.Vary] =
|
context.Response.Headers[HeaderNames.Vary] =
|
||||||
new[] { "Accept-Encoding" };
|
new[] { "Accept-Encoding" };
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ public class OsInfo : IOsInfo
|
|||||||
public static Os Os { get; }
|
public static Os Os { get; }
|
||||||
|
|
||||||
public static bool IsNotWindows => !IsWindows;
|
public static bool IsNotWindows => !IsWindows;
|
||||||
public static bool IsLinux => Os == Os.Linux || Os == Os.LinuxMusl || Os == Os.Bsd;
|
public static bool IsLinux => Os is Os.Linux or Os.LinuxMusl or Os.Bsd;
|
||||||
public static bool IsOsx => Os == Os.Osx;
|
public static bool IsOsx => Os == Os.Osx;
|
||||||
public static bool IsWindows => Os == Os.Windows;
|
public static bool IsWindows => Os == Os.Windows;
|
||||||
|
|
||||||
|
@ -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="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.45.0.54064">
|
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.47.0.55603">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -86,8 +86,6 @@ export class MetadataService {
|
|||||||
return of(this.validLanguages);
|
return of(this.validLanguages);
|
||||||
}
|
}
|
||||||
return this.httpClient.get<Array<Language>>(this.baseUrl + 'metadata/all-languages').pipe(map(l => this.validLanguages = l));
|
return this.httpClient.get<Array<Language>>(this.baseUrl + 'metadata/all-languages').pipe(map(l => this.validLanguages = l));
|
||||||
|
|
||||||
//return this.httpClient.get<Array<Language>>(this.baseUrl + 'metadata/all-languages').pipe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllPeople(libraries?: Array<number>) {
|
getAllPeople(libraries?: Array<number>) {
|
||||||
|
@ -190,9 +190,12 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.imageUrls.push(e.target.result); // This is base64 already
|
this.imageUrls.push(e.target.result); // This is base64 already
|
||||||
this.imageUrlsChange.emit(this.imageUrls);
|
this.imageUrlsChange.emit(this.imageUrls);
|
||||||
this.selectedIndex += 1;
|
this.selectedIndex = this.imageUrls.length - 1;
|
||||||
this.imageSelected.emit(this.selectedIndex); // Auto select newly uploaded image
|
this.imageSelected.emit(this.selectedIndex); // Auto select newly uploaded image
|
||||||
this.selectedBase64Url.emit(e.target.result);
|
this.selectedBase64Url.emit(e.target.result);
|
||||||
|
setTimeout(() => {
|
||||||
|
(this.document.querySelector('div.image-card[aria-label="Image ' + this.selectedIndex + '"]') as HTMLElement).focus();
|
||||||
|
})
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +212,7 @@ export class CoverImageChooserComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Auto select newly uploaded image and tell parent of new base64 url
|
// Auto select newly uploaded image and tell parent of new base64 url
|
||||||
this.selectImage(this.selectedIndex + 1);
|
this.selectImage(index >= 0 ? index : this.imageUrls.length - 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
import { NavigationStart, Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ToastrService } from 'ngx-toastr';
|
import { ToastrService } from 'ngx-toastr';
|
||||||
import { filter, take } from 'rxjs/operators';
|
|
||||||
import { Series } from 'src/app/_models/series';
|
import { Series } from 'src/app/_models/series';
|
||||||
import { AccountService } from 'src/app/_services/account.service';
|
|
||||||
import { ImageService } from 'src/app/_services/image.service';
|
import { ImageService } from 'src/app/_services/image.service';
|
||||||
import { ActionFactoryService, Action, ActionItem } from 'src/app/_services/action-factory.service';
|
import { ActionFactoryService, Action, ActionItem } from 'src/app/_services/action-factory.service';
|
||||||
import { SeriesService } from 'src/app/_services/series.service';
|
import { SeriesService } from 'src/app/_services/series.service';
|
||||||
@ -37,7 +35,10 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input() relation: RelationKind | undefined = undefined;
|
@Input() relation: RelationKind | undefined = undefined;
|
||||||
|
|
||||||
@Output() clicked = new EventEmitter<Series>();
|
@Output() clicked = new EventEmitter<Series>();
|
||||||
@Output() reload = new EventEmitter<boolean>();
|
/**
|
||||||
|
* Emits when a reload needs to occur and the id of the entity
|
||||||
|
*/
|
||||||
|
@Output() reload = new EventEmitter<number>();
|
||||||
@Output() dataChanged = new EventEmitter<Series>();
|
@Output() dataChanged = new EventEmitter<Series>();
|
||||||
/**
|
/**
|
||||||
* When the card is selected.
|
* When the card is selected.
|
||||||
@ -103,7 +104,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
case Action.RemoveFromWantToReadList:
|
case Action.RemoveFromWantToReadList:
|
||||||
this.actionService.removeMultipleSeriesFromWantToReadList([series.id]);
|
this.actionService.removeMultipleSeriesFromWantToReadList([series.id]);
|
||||||
if (this.router.url.startsWith('/want-to-read')) {
|
if (this.router.url.startsWith('/want-to-read')) {
|
||||||
this.reload.emit(true);
|
this.reload.emit(series.id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case(Action.AddToCollection):
|
case(Action.AddToCollection):
|
||||||
@ -125,7 +126,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.seriesService.getSeries(data.id).subscribe(series => {
|
this.seriesService.getSeries(data.id).subscribe(series => {
|
||||||
this.data = series;
|
this.data = series;
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
this.reload.emit(true);
|
this.reload.emit(series.id);
|
||||||
this.dataChanged.emit(series);
|
this.dataChanged.emit(series);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -145,7 +146,7 @@ export class SeriesCardComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
async deleteSeries(series: Series) {
|
async deleteSeries(series: Series) {
|
||||||
this.actionService.deleteSeries(series, (result: boolean) => {
|
this.actionService.deleteSeries(series, (result: boolean) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.reload.emit(true);
|
this.reload.emit(series.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
<app-carousel-reel [items]="inProgress" title="On Deck" (sectionClick)="handleSectionClick($event)">
|
<app-carousel-reel [items]="inProgress" title="On Deck" (sectionClick)="handleSectionClick($event)">
|
||||||
<ng-template #carouselItem let-item let-position="idx">
|
<ng-template #carouselItem let-item let-position="idx">
|
||||||
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress($event)" (dataChanged)="reloadInProgress($event)"></app-series-card>
|
<app-series-card [data]="item" [libraryId]="item.libraryId" [suppressLibraryLink]="libraryId !== 0" (reload)="reloadInProgress(item)" (dataChanged)="reloadInProgress($event)"></app-series-card>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-carousel-reel>
|
</app-carousel-reel>
|
||||||
|
|
||||||
|
@ -66,8 +66,8 @@ export class LibraryRecommendedComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
reloadInProgress(series: Series | boolean) {
|
reloadInProgress(series: Series | number) {
|
||||||
if (series === true || series === false) {
|
if (Number.isInteger(series)) {
|
||||||
if (!series) {return;}
|
if (!series) {return;}
|
||||||
}
|
}
|
||||||
// If the update to Series doesn't affect the requirement to be in this stream, then ignore update request
|
// If the update to Series doesn't affect the requirement to be in this stream, then ignore update request
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
[refresh]="refresh"
|
[refresh]="refresh"
|
||||||
(applyFilter)="updateFilter($event)">
|
(applyFilter)="updateFilter($event)">
|
||||||
<ng-template #cardItem let-item let-position="idx">
|
<ng-template #cardItem let-item let-position="idx">
|
||||||
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="removeSeries(item.id)"
|
<app-series-card [data]="item" [libraryId]="item.libraryId" (reload)="removeSeries($event)"
|
||||||
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)" [selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
|
(selection)="bulkSelectionService.handleCardSelection('series', position, series.length, $event)"
|
||||||
|
[selected]="bulkSelectionService.isCardSelected('series', position)" [allowSelection]="true"
|
||||||
></app-series-card>
|
></app-series-card>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ export class WantToReadComponent implements OnInit, OnDestroy {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionTag: any;
|
collectionTag: any;
|
||||||
tagImage: any;
|
tagImage: any;
|
||||||
|
|
||||||
@ -164,6 +165,23 @@ export class WantToReadComponent implements OnInit, OnDestroy {
|
|||||||
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.seriesPagination, this.filter);
|
if (!data.isFirst) this.filterUtilityService.updateUrlFromFilter(this.seriesPagination, this.filter);
|
||||||
this.loadPage();
|
this.loadPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleAction(action: ActionItem<Series>, series: Series) {
|
||||||
|
// let lib: Partial<Library> = library;
|
||||||
|
// if (library === undefined) {
|
||||||
|
// lib = {id: this.libraryId, name: this.libraryName};
|
||||||
|
// }
|
||||||
|
// switch (action.action) {
|
||||||
|
// case(Action.Scan):
|
||||||
|
// this.actionService.scanLibrary(lib);
|
||||||
|
// break;
|
||||||
|
// case(Action.RefreshMetadata):
|
||||||
|
// this.actionService.refreshMetadata(lib);
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user