mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-23 15:30:34 -04:00
Basic Metadata Polish (#3548)
This commit is contained in:
parent
c0b59d87a4
commit
4c44dbf3e2
@ -26,6 +26,7 @@ public abstract class AbstractDbTest : IDisposable
|
|||||||
protected readonly DbConnection _connection;
|
protected readonly DbConnection _connection;
|
||||||
protected readonly DataContext _context;
|
protected readonly DataContext _context;
|
||||||
protected readonly IUnitOfWork _unitOfWork;
|
protected readonly IUnitOfWork _unitOfWork;
|
||||||
|
protected readonly IMapper _mapper;
|
||||||
|
|
||||||
|
|
||||||
protected const string CacheDirectory = "C:/kavita/config/cache/";
|
protected const string CacheDirectory = "C:/kavita/config/cache/";
|
||||||
@ -42,6 +43,7 @@ public abstract class AbstractDbTest : IDisposable
|
|||||||
{
|
{
|
||||||
var contextOptions = new DbContextOptionsBuilder<DataContext>()
|
var contextOptions = new DbContextOptionsBuilder<DataContext>()
|
||||||
.UseSqlite(CreateInMemoryDatabase())
|
.UseSqlite(CreateInMemoryDatabase())
|
||||||
|
.EnableSensitiveDataLogging()
|
||||||
.Options;
|
.Options;
|
||||||
|
|
||||||
_connection = RelationalOptionsExtension.Extract(contextOptions).Connection;
|
_connection = RelationalOptionsExtension.Extract(contextOptions).Connection;
|
||||||
@ -53,10 +55,10 @@ public abstract class AbstractDbTest : IDisposable
|
|||||||
Task.Run(SeedDb).GetAwaiter().GetResult();
|
Task.Run(SeedDb).GetAwaiter().GetResult();
|
||||||
|
|
||||||
var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperProfiles>());
|
var config = new MapperConfiguration(cfg => cfg.AddProfile<AutoMapperProfiles>());
|
||||||
var mapper = config.CreateMapper();
|
_mapper = config.CreateMapper();
|
||||||
|
|
||||||
GlobalConfiguration.Configuration.UseInMemoryStorage();
|
GlobalConfiguration.Configuration.UseInMemoryStorage();
|
||||||
_unitOfWork = new UnitOfWork(_context, mapper, null);
|
_unitOfWork = new UnitOfWork(_context, _mapper, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DbConnection CreateInMemoryDatabase()
|
private static DbConnection CreateInMemoryDatabase()
|
||||||
@ -92,6 +94,7 @@ public abstract class AbstractDbTest : IDisposable
|
|||||||
|
|
||||||
|
|
||||||
_context.Library.Add(new LibraryBuilder("Manga")
|
_context.Library.Add(new LibraryBuilder("Manga")
|
||||||
|
.WithAllowMetadataMatching(true)
|
||||||
.WithFolderPath(new FolderPathBuilder(DataDirectory).Build())
|
.WithFolderPath(new FolderPathBuilder(DataDirectory).Build())
|
||||||
.Build());
|
.Build());
|
||||||
|
|
||||||
|
@ -932,11 +932,11 @@ public class SeriesFilterTests : AbstractDbTest
|
|||||||
|
|
||||||
var seriesService = new SeriesService(_unitOfWork, Substitute.For<IEventHub>(),
|
var seriesService = new SeriesService(_unitOfWork, Substitute.For<IEventHub>(),
|
||||||
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
|
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
|
||||||
Substitute.For<IScrobblingService>(), Substitute.For<ILocalizationService>()
|
Substitute.For<IScrobblingService>(), Substitute.For<ILocalizationService>());
|
||||||
, Substitute.For<IImageService>());
|
|
||||||
|
|
||||||
// Select 0 Rating
|
// Select 0 Rating
|
||||||
var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2);
|
var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2);
|
||||||
|
Assert.NotNull(zeroRating);
|
||||||
|
|
||||||
Assert.True(await seriesService.UpdateRating(user, new UpdateSeriesRatingDto()
|
Assert.True(await seriesService.UpdateRating(user, new UpdateSeriesRatingDto()
|
||||||
{
|
{
|
||||||
|
18
API.Tests/Helpers/StringHelperTests.cs
Normal file
18
API.Tests/Helpers/StringHelperTests.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using API.Helpers;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace API.Tests.Helpers;
|
||||||
|
|
||||||
|
public class StringHelperTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(
|
||||||
|
"<p>A Perfect Marriage Becomes a Perfect Affair!<br /> <br><br><br /> Every woman wishes for that happily ever after, but when time flies by and you've become a neglected housewife, what's a woman to do?</p>",
|
||||||
|
"<p>A Perfect Marriage Becomes a Perfect Affair!<br /> Every woman wishes for that happily ever after, but when time flies by and you've become a neglected housewife, what's a woman to do?</p>"
|
||||||
|
)]
|
||||||
|
public void Test(string input, string expected)
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, StringHelper.SquashBreaklines(input));
|
||||||
|
}
|
||||||
|
}
|
2798
API.Tests/Services/ExternalMetadataServiceTests.cs
Normal file
2798
API.Tests/Services/ExternalMetadataServiceTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,7 +17,7 @@ namespace API.Tests.Services;
|
|||||||
|
|
||||||
public class ProcessSeriesTests
|
public class ProcessSeriesTests
|
||||||
{
|
{
|
||||||
|
// TODO: Implement
|
||||||
|
|
||||||
#region UpdateSeriesMetadata
|
#region UpdateSeriesMetadata
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ public class SeriesServiceTests : AbstractDbTest
|
|||||||
|
|
||||||
_seriesService = new SeriesService(_unitOfWork, Substitute.For<IEventHub>(),
|
_seriesService = new SeriesService(_unitOfWork, Substitute.For<IEventHub>(),
|
||||||
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
|
Substitute.For<ITaskScheduler>(), Substitute.For<ILogger<SeriesService>>(),
|
||||||
Substitute.For<IScrobblingService>(), locService, Substitute.For<IImageService>());
|
Substitute.For<IScrobblingService>(), locService);
|
||||||
}
|
}
|
||||||
#region Setup
|
#region Setup
|
||||||
|
|
||||||
|
@ -63,9 +63,16 @@ public class LicenseController(
|
|||||||
[HttpGet("info")]
|
[HttpGet("info")]
|
||||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
[ResponseCache(CacheProfileName = ResponseCacheProfiles.LicenseCache)]
|
||||||
public async Task<ActionResult<LicenseInfoDto?>> GetLicenseInfo(bool forceCheck = false)
|
public async Task<ActionResult<LicenseInfoDto?>> GetLicenseInfo(bool forceCheck = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return Ok(await licenseService.GetLicenseInfo(forceCheck));
|
return Ok(await licenseService.GetLicenseInfo(forceCheck));
|
||||||
}
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return Ok(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Authorize("RequireAdminRole")]
|
[Authorize("RequireAdminRole")]
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
|
@ -15,7 +15,7 @@ public class ExternalSeriesDetailDto
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public int? AniListId { get; set; }
|
public int? AniListId { get; set; }
|
||||||
public long? MALId { get; set; }
|
public long? MALId { get; set; }
|
||||||
public IList<string> Synonyms { get; set; }
|
public IList<string> Synonyms { get; set; } = [];
|
||||||
public PlusMediaFormat PlusMediaFormat { get; set; }
|
public PlusMediaFormat PlusMediaFormat { get; set; }
|
||||||
public string? SiteUrl { get; set; }
|
public string? SiteUrl { get; set; }
|
||||||
public string? CoverUrl { get; set; }
|
public string? CoverUrl { get; set; }
|
||||||
@ -30,8 +30,8 @@ public class ExternalSeriesDetailDto
|
|||||||
public int AverageScore { get; set; }
|
public int AverageScore { get; set; }
|
||||||
public int Chapters { get; set; }
|
public int Chapters { get; set; }
|
||||||
public int Volumes { get; set; }
|
public int Volumes { get; set; }
|
||||||
public IList<SeriesRelationship>? Relations { get; set; }
|
public IList<SeriesRelationship>? Relations { get; set; } = [];
|
||||||
public IList<SeriesCharacter>? Characters { get; set; }
|
public IList<SeriesCharacter>? Characters { get; set; } = [];
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -230,9 +230,10 @@ public class PersonRepository : IPersonRepository
|
|||||||
|
|
||||||
public async Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId)
|
public async Task<IEnumerable<SeriesDto>> GetSeriesKnownFor(int personId)
|
||||||
{
|
{
|
||||||
|
List<PersonRole> notValidRoles = [PersonRole.Location, PersonRole.Team, PersonRole.Other, PersonRole.Publisher, PersonRole.Translator];
|
||||||
return await _context.Person
|
return await _context.Person
|
||||||
.Where(p => p.Id == personId)
|
.Where(p => p.Id == personId)
|
||||||
.SelectMany(p => p.SeriesMetadataPeople)
|
.SelectMany(p => p.SeriesMetadataPeople.Where(smp => !notValidRoles.Contains(smp.Role)))
|
||||||
.Select(smp => smp.SeriesMetadata)
|
.Select(smp => smp.SeriesMetadata)
|
||||||
.Select(sm => sm.Series)
|
.Select(sm => sm.Series)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
|
@ -75,6 +75,7 @@ public interface ISeriesRepository
|
|||||||
{
|
{
|
||||||
void Add(Series series);
|
void Add(Series series);
|
||||||
void Attach(Series series);
|
void Attach(Series series);
|
||||||
|
void Attach(SeriesRelation relation);
|
||||||
void Update(Series series);
|
void Update(Series series);
|
||||||
void Remove(Series series);
|
void Remove(Series series);
|
||||||
void Remove(IEnumerable<Series> series);
|
void Remove(IEnumerable<Series> series);
|
||||||
@ -146,6 +147,9 @@ public interface ISeriesRepository
|
|||||||
Task<IEnumerable<Series>> GetAllSeriesByNameAsync(IList<string> normalizedNames,
|
Task<IEnumerable<Series>> GetAllSeriesByNameAsync(IList<string> normalizedNames,
|
||||||
int userId, SeriesIncludes includes = SeriesIncludes.None);
|
int userId, SeriesIncludes includes = SeriesIncludes.None);
|
||||||
Task<Series?> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true);
|
Task<Series?> GetFullSeriesByAnyName(string seriesName, string localizedName, int libraryId, MangaFormat format, bool withFullIncludes = true);
|
||||||
|
|
||||||
|
Task<Series?> GetSeriesByAnyName(IList<string> names, IList<MangaFormat> formats,
|
||||||
|
int userId, int? aniListId = null, SeriesIncludes includes = SeriesIncludes.None);
|
||||||
Task<Series?> GetSeriesByAnyName(string seriesName, string localizedName, IList<MangaFormat> formats, int userId, int? aniListId = null, SeriesIncludes includes = SeriesIncludes.None);
|
Task<Series?> GetSeriesByAnyName(string seriesName, string localizedName, IList<MangaFormat> formats, int userId, int? aniListId = null, SeriesIncludes includes = SeriesIncludes.None);
|
||||||
public Task<IList<Series>> GetAllSeriesByAnyName(string seriesName, string localizedName, int libraryId,
|
public Task<IList<Series>> GetAllSeriesByAnyName(string seriesName, string localizedName, int libraryId,
|
||||||
MangaFormat format);
|
MangaFormat format);
|
||||||
@ -195,6 +199,11 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
_context.Series.Attach(series);
|
_context.Series.Attach(series);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Attach(SeriesRelation relation)
|
||||||
|
{
|
||||||
|
_context.SeriesRelation.Attach(relation);
|
||||||
|
}
|
||||||
|
|
||||||
public void Attach(ExternalSeriesMetadata metadata)
|
public void Attach(ExternalSeriesMetadata metadata)
|
||||||
{
|
{
|
||||||
_context.ExternalSeriesMetadata.Attach(metadata);
|
_context.ExternalSeriesMetadata.Attach(metadata);
|
||||||
@ -1757,6 +1766,41 @@ public class SeriesRepository : ISeriesRepository
|
|||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<Series?> GetSeriesByAnyName(IList<string> names, IList<MangaFormat> formats,
|
||||||
|
int userId, int? aniListId = null, SeriesIncludes includes = SeriesIncludes.None)
|
||||||
|
{
|
||||||
|
var libraryIds = GetLibraryIdsForUser(userId);
|
||||||
|
names = names.Where(s => !string.IsNullOrEmpty(s)).Distinct().ToList();
|
||||||
|
var normalizedNames = names.Select(s => s.ToNormalized()).ToList();
|
||||||
|
|
||||||
|
|
||||||
|
var query = _context.Series
|
||||||
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
|
.Where(s => formats.Contains(s.Format));
|
||||||
|
|
||||||
|
if (aniListId.HasValue && aniListId.Value > 0)
|
||||||
|
{
|
||||||
|
// If AniList ID is provided, override name checks
|
||||||
|
query = query.Where(s => s.ExternalSeriesMetadata.AniListId == aniListId.Value ||
|
||||||
|
normalizedNames.Contains(s.NormalizedName)
|
||||||
|
|| normalizedNames.Contains(s.NormalizedLocalizedName)
|
||||||
|
|| names.Contains(s.OriginalName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Otherwise, use name checks
|
||||||
|
query = query.Where(s =>
|
||||||
|
normalizedNames.Contains(s.NormalizedName)
|
||||||
|
|| normalizedNames.Contains(s.NormalizedLocalizedName)
|
||||||
|
|| names.Contains(s.OriginalName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.Includes(includes)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IList<Series>> GetAllSeriesByAnyName(string seriesName, string localizedName, int libraryId,
|
public async Task<IList<Series>> GetAllSeriesByAnyName(string seriesName, string localizedName, int libraryId,
|
||||||
MangaFormat format)
|
MangaFormat format)
|
||||||
{
|
{
|
||||||
|
@ -26,9 +26,10 @@ public static class PlusMediaFormatExtensions
|
|||||||
{
|
{
|
||||||
return plusMediaFormat switch
|
return plusMediaFormat switch
|
||||||
{
|
{
|
||||||
PlusMediaFormat.Manga => new[] { LibraryType.Manga, LibraryType.Image },
|
PlusMediaFormat.Manga => [LibraryType.Manga, LibraryType.Image],
|
||||||
PlusMediaFormat.Comic => new[] { LibraryType.Comic, LibraryType.ComicVine },
|
PlusMediaFormat.Comic => [LibraryType.Comic, LibraryType.ComicVine],
|
||||||
PlusMediaFormat.LightNovel => new[] { LibraryType.LightNovel, LibraryType.Book, LibraryType.Manga },
|
PlusMediaFormat.LightNovel => [LibraryType.LightNovel, LibraryType.Book, LibraryType.Manga],
|
||||||
|
PlusMediaFormat.Book => [LibraryType.LightNovel, LibraryType.Book],
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(plusMediaFormat), plusMediaFormat, null)
|
_ => throw new ArgumentOutOfRangeException(nameof(plusMediaFormat), plusMediaFormat, null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -61,4 +61,10 @@ public class AppUserBuilder : IEntityBuilder<AppUser>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AppUserBuilder WithRole(string role)
|
||||||
|
{
|
||||||
|
_appUser.UserRoles ??= new List<AppUserRole>();
|
||||||
|
_appUser.UserRoles.Add(new AppUserRole() {Role = new AppRole() {Name = role}});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,11 +21,13 @@ public class SeriesBuilder : IEntityBuilder<Series>
|
|||||||
_series = new Series()
|
_series = new Series()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
|
|
||||||
LocalizedName = name.ToNormalized(),
|
LocalizedName = name.ToNormalized(),
|
||||||
|
NormalizedLocalizedName = name.ToNormalized(),
|
||||||
|
|
||||||
OriginalName = name,
|
OriginalName = name,
|
||||||
SortName = name,
|
SortName = name,
|
||||||
NormalizedName = name.ToNormalized(),
|
NormalizedName = name.ToNormalized(),
|
||||||
NormalizedLocalizedName = name.ToNormalized(),
|
|
||||||
Metadata = new SeriesMetadataBuilder()
|
Metadata = new SeriesMetadataBuilder()
|
||||||
.WithPublicationStatus(PublicationStatus.OnGoing)
|
.WithPublicationStatus(PublicationStatus.OnGoing)
|
||||||
.Build(),
|
.Build(),
|
||||||
@ -39,14 +41,25 @@ public class SeriesBuilder : IEntityBuilder<Series>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="localizedName"></param>
|
/// <param name="localizedName"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public SeriesBuilder WithLocalizedName(string localizedName)
|
public SeriesBuilder WithLocalizedName(string localizedName, bool lockStatus = false)
|
||||||
{
|
{
|
||||||
|
// Why is this here?
|
||||||
if (string.IsNullOrEmpty(localizedName))
|
if (string.IsNullOrEmpty(localizedName))
|
||||||
{
|
{
|
||||||
localizedName = _series.Name;
|
localizedName = _series.Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
_series.LocalizedName = localizedName;
|
_series.LocalizedName = localizedName;
|
||||||
_series.NormalizedLocalizedName = localizedName.ToNormalized();
|
_series.NormalizedLocalizedName = localizedName.ToNormalized();
|
||||||
|
_series.LocalizedNameLocked = lockStatus;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeriesBuilder WithLocalizedNameAllowEmpty(string localizedName, bool lockStatus = false)
|
||||||
|
{
|
||||||
|
_series.LocalizedName = localizedName;
|
||||||
|
_series.NormalizedLocalizedName = localizedName.ToNormalized();
|
||||||
|
_series.LocalizedNameLocked = lockStatus;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,4 +119,15 @@ public class SeriesBuilder : IEntityBuilder<Series>
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SeriesBuilder WithRelationship(int targetSeriesId, RelationKind kind)
|
||||||
|
{
|
||||||
|
_series.Relations ??= [];
|
||||||
|
_series.Relations.Add(new SeriesRelation()
|
||||||
|
{
|
||||||
|
RelationKind = kind,
|
||||||
|
TargetSeriesId = targetSeriesId
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,15 +39,17 @@ public class SeriesMetadataBuilder : IEntityBuilder<SeriesMetadata>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeriesMetadataBuilder WithPublicationStatus(PublicationStatus status)
|
public SeriesMetadataBuilder WithPublicationStatus(PublicationStatus status, bool lockState = false)
|
||||||
{
|
{
|
||||||
_seriesMetadata.PublicationStatus = status;
|
_seriesMetadata.PublicationStatus = status;
|
||||||
|
_seriesMetadata.PublicationStatusLocked = lockState;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeriesMetadataBuilder WithAgeRating(AgeRating rating)
|
public SeriesMetadataBuilder WithAgeRating(AgeRating rating, bool lockState = false)
|
||||||
{
|
{
|
||||||
_seriesMetadata.AgeRating = rating;
|
_seriesMetadata.AgeRating = rating;
|
||||||
|
_seriesMetadata.AgeRatingLocked = lockState;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +62,6 @@ public class SeriesMetadataBuilder : IEntityBuilder<SeriesMetadata>
|
|||||||
Person = person,
|
Person = person,
|
||||||
SeriesMetadata = _seriesMetadata,
|
SeriesMetadata = _seriesMetadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,15 +71,40 @@ public class SeriesMetadataBuilder : IEntityBuilder<SeriesMetadata>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeriesMetadataBuilder WithReleaseYear(int year)
|
public SeriesMetadataBuilder WithReleaseYear(int year, bool lockStatus = false)
|
||||||
{
|
{
|
||||||
_seriesMetadata.ReleaseYear = year;
|
_seriesMetadata.ReleaseYear = year;
|
||||||
|
_seriesMetadata.ReleaseYearLocked = lockStatus;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SeriesMetadataBuilder WithSummary(string summary)
|
public SeriesMetadataBuilder WithSummary(string summary, bool lockStatus = false)
|
||||||
{
|
{
|
||||||
_seriesMetadata.Summary = summary;
|
_seriesMetadata.Summary = summary;
|
||||||
|
_seriesMetadata.SummaryLocked = lockStatus;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeriesMetadataBuilder WithGenre(Genre genre, bool lockStatus = false)
|
||||||
|
{
|
||||||
|
_seriesMetadata.Genres ??= [];
|
||||||
|
_seriesMetadata.Genres.Add(genre);
|
||||||
|
_seriesMetadata.GenresLocked = lockStatus;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeriesMetadataBuilder WithGenres(List<Genre> genres, bool lockStatus = false)
|
||||||
|
{
|
||||||
|
_seriesMetadata.Genres = genres;
|
||||||
|
_seriesMetadata.GenresLocked = lockStatus;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SeriesMetadataBuilder WithTag(Tag tag, bool lockStatus = false)
|
||||||
|
{
|
||||||
|
_seriesMetadata.Tags ??= [];
|
||||||
|
_seriesMetadata.Tags.Add(tag);
|
||||||
|
_seriesMetadata.TagsLocked = lockStatus;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
API/Helpers/StringHelper.cs
Normal file
42
API/Helpers/StringHelper.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace API.Helpers;
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
public static class StringHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to squash duplicate break and new lines with a single new line.
|
||||||
|
/// </summary>
|
||||||
|
/// <example>Test br br Test -> Test br Test</example>
|
||||||
|
/// <param name="summary"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string? SquashBreaklines(string? summary)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(summary))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First standardize all br tags to <br /> format
|
||||||
|
summary = Regex.Replace(summary, @"<br\s*/?>", "<br />", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
// Replace multiple consecutive br tags with a single br tag
|
||||||
|
summary = Regex.Replace(summary, @"(?:<br />\s*)+", "<br /> ", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
// Normalize remaining whitespace (replace multiple spaces with a single space)
|
||||||
|
summary = Regex.Replace(summary, @"\s+", " ").Trim();
|
||||||
|
|
||||||
|
return summary.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the (Source: MangaDex) type of tags at the end of descriptions from AL
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string? RemoveSourceInDescription(string? description)
|
||||||
|
{
|
||||||
|
return description?.Trim();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,20 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Comparators;
|
using API.Comparators;
|
||||||
using API.Constants;
|
|
||||||
using API.Controllers;
|
|
||||||
using API.Data;
|
using API.Data;
|
||||||
using API.Data.Repositories;
|
using API.Data.Repositories;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.DTOs.CollectionTags;
|
|
||||||
using API.DTOs.SeriesDetail;
|
using API.DTOs.SeriesDetail;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Entities.Interfaces;
|
|
||||||
using API.Entities.Metadata;
|
using API.Entities.Metadata;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
@ -22,7 +17,6 @@ using API.Helpers.Builders;
|
|||||||
using API.Services.Plus;
|
using API.Services.Plus;
|
||||||
using API.Services.Tasks.Scanner.Parser;
|
using API.Services.Tasks.Scanner.Parser;
|
||||||
using API.SignalR;
|
using API.SignalR;
|
||||||
using EasyCaching.Core;
|
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Kavita.Common;
|
using Kavita.Common;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -56,7 +50,6 @@ public class SeriesService : ISeriesService
|
|||||||
private readonly ILogger<SeriesService> _logger;
|
private readonly ILogger<SeriesService> _logger;
|
||||||
private readonly IScrobblingService _scrobblingService;
|
private readonly IScrobblingService _scrobblingService;
|
||||||
private readonly ILocalizationService _localizationService;
|
private readonly ILocalizationService _localizationService;
|
||||||
private readonly IImageService _imageService;
|
|
||||||
|
|
||||||
private readonly NextExpectedChapterDto _emptyExpectedChapter = new NextExpectedChapterDto
|
private readonly NextExpectedChapterDto _emptyExpectedChapter = new NextExpectedChapterDto
|
||||||
{
|
{
|
||||||
@ -66,7 +59,7 @@ public class SeriesService : ISeriesService
|
|||||||
};
|
};
|
||||||
|
|
||||||
public SeriesService(IUnitOfWork unitOfWork, IEventHub eventHub, ITaskScheduler taskScheduler,
|
public SeriesService(IUnitOfWork unitOfWork, IEventHub eventHub, ITaskScheduler taskScheduler,
|
||||||
ILogger<SeriesService> logger, IScrobblingService scrobblingService, ILocalizationService localizationService, IImageService imageService)
|
ILogger<SeriesService> logger, IScrobblingService scrobblingService, ILocalizationService localizationService)
|
||||||
{
|
{
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_eventHub = eventHub;
|
_eventHub = eventHub;
|
||||||
@ -74,7 +67,6 @@ public class SeriesService : ISeriesService
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_scrobblingService = scrobblingService;
|
_scrobblingService = scrobblingService;
|
||||||
_localizationService = localizationService;
|
_localizationService = localizationService;
|
||||||
_imageService = imageService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -124,6 +124,8 @@ export class AppComponent implements OnInit {
|
|||||||
// Bootstrap anything that's needed
|
// Bootstrap anything that's needed
|
||||||
this.themeService.getThemes().subscribe();
|
this.themeService.getThemes().subscribe();
|
||||||
this.libraryService.getLibraryNames().pipe(take(1), shareReplay({refCount: true, bufferSize: 1})).subscribe();
|
this.libraryService.getLibraryNames().pipe(take(1), shareReplay({refCount: true, bufferSize: 1})).subscribe();
|
||||||
|
if (this.accountService.hasAdminRole(user)) {
|
||||||
this.licenseService.licenseInfo().subscribe();
|
this.licenseService.licenseInfo().subscribe();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -429,7 +429,7 @@
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<app-setting-item [title]="t('team-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
<app-setting-item [title]="t('team-label')" [toggleOnViewClick]="false" [showEdit]="false">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Character);metadata.teamLocked = true" [settings]="getPersonsSettings(PersonRole.Team)"
|
<app-typeahead (selectedData)="updatePerson($event, PersonRole.Team);metadata.teamLocked = true" [settings]="getPersonsSettings(PersonRole.Team)"
|
||||||
[(locked)]="metadata.teamLocked" (onUnlock)="metadata.teamLocked = false"
|
[(locked)]="metadata.teamLocked" (onUnlock)="metadata.teamLocked = false"
|
||||||
(newItemAdded)="metadata.teamLocked = true">
|
(newItemAdded)="metadata.teamLocked = true">
|
||||||
<ng-template #badgeItem let-item let-position="idx">
|
<ng-template #badgeItem let-item let-position="idx">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.0.1",
|
"openapi": "3.0.1",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Kavita",
|
"title": "Kavita",
|
||||||
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.4.12",
|
"description": "Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v0.8.4.13",
|
||||||
"license": {
|
"license": {
|
||||||
"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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user