Merged develop in
This commit is contained in:
commit
7c692a1b46
580 changed files with 21233 additions and 9031 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -137,6 +138,12 @@ public class AccountController : BaseApiController
|
|||
return BadRequest(usernameValidation);
|
||||
}
|
||||
|
||||
// If Email is empty, default to the username
|
||||
if (string.IsNullOrEmpty(registerDto.Email))
|
||||
{
|
||||
registerDto.Email = registerDto.Username;
|
||||
}
|
||||
|
||||
var user = new AppUserBuilder(registerDto.Username, registerDto.Email,
|
||||
await _unitOfWork.SiteThemeRepository.GetDefaultTheme()).Build();
|
||||
|
||||
|
|
@ -351,10 +358,11 @@ public class AccountController : BaseApiController
|
|||
/// <param name="dto"></param>
|
||||
/// <returns>Returns just if the email was sent or server isn't reachable</returns>
|
||||
[HttpPost("update/email")]
|
||||
public async Task<ActionResult> UpdateEmail(UpdateEmailDto? dto)
|
||||
public async Task<ActionResult<InviteUserResponse>> UpdateEmail(UpdateEmailDto? dto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
if (user == null || User.IsInRole(PolicyConstants.ReadOnlyRole)) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||
if (user == null || User.IsInRole(PolicyConstants.ReadOnlyRole))
|
||||
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||
|
||||
if (dto == null || string.IsNullOrEmpty(dto.Email) || string.IsNullOrEmpty(dto.Password))
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-payload"));
|
||||
|
|
@ -363,12 +371,13 @@ public class AccountController : BaseApiController
|
|||
// Validate this user's password
|
||||
if (! await _userManager.CheckPasswordAsync(user, dto.Password))
|
||||
{
|
||||
_logger.LogCritical("A user tried to change {UserName}'s email, but password didn't validate", user.UserName);
|
||||
_logger.LogWarning("A user tried to change {UserName}'s email, but password didn't validate", user.UserName);
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||
}
|
||||
|
||||
// Validate no other users exist with this email
|
||||
if (user.Email!.Equals(dto.Email)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
|
||||
if (user.Email!.Equals(dto.Email))
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
|
||||
|
||||
// Check if email is used by another user
|
||||
var existingUserEmail = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
|
||||
|
|
@ -385,8 +394,10 @@ public class AccountController : BaseApiController
|
|||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generate-token"));
|
||||
}
|
||||
|
||||
var isValidEmailAddress = _emailService.IsValidEmail(user.Email);
|
||||
var serverSettings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
var shouldEmailUser = serverSettings.IsEmailSetup() || !_emailService.IsValidEmail(user.Email);
|
||||
var shouldEmailUser = serverSettings.IsEmailSetup() || !isValidEmailAddress;
|
||||
|
||||
user.EmailConfirmed = !shouldEmailUser;
|
||||
user.ConfirmationToken = token;
|
||||
await _userManager.UpdateAsync(user);
|
||||
|
|
@ -400,7 +411,8 @@ public class AccountController : BaseApiController
|
|||
return Ok(new InviteUserResponse
|
||||
{
|
||||
EmailLink = string.Empty,
|
||||
EmailSent = false
|
||||
EmailSent = false,
|
||||
InvalidEmail = !isValidEmailAddress
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -408,7 +420,7 @@ public class AccountController : BaseApiController
|
|||
// Send a confirmation email
|
||||
try
|
||||
{
|
||||
if (!_emailService.IsValidEmail(user.Email))
|
||||
if (!isValidEmailAddress)
|
||||
{
|
||||
_logger.LogCritical("[Update Email]: User is trying to update their email, but their existing email ({Email}) isn't valid. No email will be send", user.Email);
|
||||
return Ok(new InviteUserResponse
|
||||
|
|
@ -440,7 +452,8 @@ public class AccountController : BaseApiController
|
|||
return Ok(new InviteUserResponse
|
||||
{
|
||||
EmailLink = string.Empty,
|
||||
EmailSent = true
|
||||
EmailSent = true,
|
||||
InvalidEmail = !isValidEmailAddress
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.Data.ManualMigrations;
|
||||
using API.DTOs;
|
||||
|
|
@ -16,12 +17,10 @@ namespace API.Controllers;
|
|||
public class AdminController : BaseApiController
|
||||
{
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public AdminController(UserManager<AppUser> userManager, IUnitOfWork unitOfWork)
|
||||
public AdminController(UserManager<AppUser> userManager)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,18 +31,7 @@ public class AdminController : BaseApiController
|
|||
[HttpGet("exists")]
|
||||
public async Task<ActionResult<bool>> AdminExists()
|
||||
{
|
||||
var users = await _userManager.GetUsersInRoleAsync("Admin");
|
||||
var users = await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole);
|
||||
return users.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the progress information for a particular user
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Authorize("RequireAdminRole")]
|
||||
[HttpPost("update-chapter-progress")]
|
||||
public async Task<ActionResult<bool>> UpdateChapterProgress(UpdateUserProgressDto dto)
|
||||
{
|
||||
return Ok(await Task.FromResult(false));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class BookController : BaseApiController
|
|||
case MangaFormat.Epub:
|
||||
{
|
||||
var mangaFile = (await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId))[0];
|
||||
using var book = await EpubReader.OpenBookAsync(mangaFile.FilePath, BookService.BookReaderOptions);
|
||||
using var book = await EpubReader.OpenBookAsync(mangaFile.FilePath, BookService.LenientBookReaderOptions);
|
||||
bookTitle = book.Title;
|
||||
break;
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ public class BookController : BaseApiController
|
|||
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
|
||||
if (chapter == null) return BadRequest(await _localizationService.Get("en", "chapter-doesnt-exist"));
|
||||
|
||||
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath, BookService.BookReaderOptions);
|
||||
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath, BookService.LenientBookReaderOptions);
|
||||
var key = BookService.CoalesceKeyForAnyFile(book, file);
|
||||
|
||||
if (!book.Content.AllFiles.ContainsLocalFileRefWithKey(key)) return BadRequest(await _localizationService.Get("en", "file-missing"));
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using API.Data.Repositories;
|
|||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Person;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Services;
|
||||
|
|
@ -234,131 +235,121 @@ public class ChapterController : BaseApiController
|
|||
|
||||
|
||||
#region Genres
|
||||
if (dto.Genres is {Count: > 0})
|
||||
{
|
||||
chapter.Genres ??= new List<Genre>();
|
||||
await GenreHelper.UpdateChapterGenres(chapter, dto.Genres.Select(t => t.Title), _unitOfWork);
|
||||
}
|
||||
chapter.Genres ??= [];
|
||||
await GenreHelper.UpdateChapterGenres(chapter, dto.Genres.Select(t => t.Title), _unitOfWork);
|
||||
#endregion
|
||||
|
||||
#region Tags
|
||||
if (dto.Tags is {Count: > 0})
|
||||
{
|
||||
chapter.Tags ??= new List<Tag>();
|
||||
await TagHelper.UpdateChapterTags(chapter, dto.Tags.Select(t => t.Title), _unitOfWork);
|
||||
}
|
||||
chapter.Tags ??= [];
|
||||
await TagHelper.UpdateChapterTags(chapter, dto.Tags.Select(t => t.Title), _unitOfWork);
|
||||
#endregion
|
||||
|
||||
#region People
|
||||
if (PersonHelper.HasAnyPeople(dto))
|
||||
{
|
||||
chapter.People ??= new List<ChapterPeople>();
|
||||
chapter.People ??= [];
|
||||
|
||||
// Update writers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Writers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Writer,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update writers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Writers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Writer,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update characters
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Characters.Select(p => p.Name).ToList(),
|
||||
PersonRole.Character,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update characters
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Characters.Select(p => p.Name).ToList(),
|
||||
PersonRole.Character,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update pencillers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Pencillers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Penciller,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update pencillers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Pencillers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Penciller,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update inkers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Inkers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Inker,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update inkers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Inkers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Inker,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update colorists
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Colorists.Select(p => p.Name).ToList(),
|
||||
PersonRole.Colorist,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update colorists
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Colorists.Select(p => p.Name).ToList(),
|
||||
PersonRole.Colorist,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update letterers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Letterers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Letterer,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update letterers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Letterers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Letterer,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update cover artists
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.CoverArtists.Select(p => p.Name).ToList(),
|
||||
PersonRole.CoverArtist,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update cover artists
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.CoverArtists.Select(p => p.Name).ToList(),
|
||||
PersonRole.CoverArtist,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update editors
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Editors.Select(p => p.Name).ToList(),
|
||||
PersonRole.Editor,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update editors
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Editors.Select(p => p.Name).ToList(),
|
||||
PersonRole.Editor,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update publishers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Publishers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Publisher,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update publishers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Publishers.Select(p => p.Name).ToList(),
|
||||
PersonRole.Publisher,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update translators
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Translators.Select(p => p.Name).ToList(),
|
||||
PersonRole.Translator,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update translators
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Translators.Select(p => p.Name).ToList(),
|
||||
PersonRole.Translator,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update imprints
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Imprints.Select(p => p.Name).ToList(),
|
||||
PersonRole.Imprint,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update imprints
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Imprints.Select(p => p.Name).ToList(),
|
||||
PersonRole.Imprint,
|
||||
_unitOfWork
|
||||
);
|
||||
// Update teams
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Teams.Select(p => p.Name).ToList(),
|
||||
PersonRole.Team,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update teams
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Teams.Select(p => p.Name).ToList(),
|
||||
PersonRole.Team,
|
||||
_unitOfWork
|
||||
);
|
||||
|
||||
// Update locations
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Locations.Select(p => p.Name).ToList(),
|
||||
PersonRole.Location,
|
||||
_unitOfWork
|
||||
);
|
||||
}
|
||||
// Update locations
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
dto.Locations.Select(p => p.Name).ToList(),
|
||||
PersonRole.Location,
|
||||
_unitOfWork
|
||||
);
|
||||
#endregion
|
||||
|
||||
#region Locks
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ public class DownloadController : BaseApiController
|
|||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.DownloadProgressEvent(username,
|
||||
filename, $"Downloading {filename}", 0F, "started"));
|
||||
|
||||
if (files.Count == 1 && files.First().Format != MangaFormat.Image)
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
|
|
@ -167,15 +168,17 @@ public class DownloadController : BaseApiController
|
|||
}
|
||||
|
||||
var filePath = _archiveService.CreateZipFromFoldersForDownload(files.Select(c => c.FilePath).ToList(), tempFolder, ProgressCallback);
|
||||
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.DownloadProgressEvent(username,
|
||||
filename, "Download Complete", 1F, "ended"));
|
||||
|
||||
return PhysicalFile(filePath, DefaultContentType, Uri.EscapeDataString(downloadName), true);
|
||||
|
||||
async Task ProgressCallback(Tuple<string, float> progressInfo)
|
||||
{
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.DownloadProgressEvent(username, filename, $"Extracting {Path.GetFileNameWithoutExtension(progressInfo.Item1)}",
|
||||
MessageFactory.DownloadProgressEvent(username, filename, $"Processing {Path.GetFileNameWithoutExtension(progressInfo.Item1)}",
|
||||
Math.Clamp(progressInfo.Item2, 0F, 1F)));
|
||||
}
|
||||
}
|
||||
|
|
@ -193,8 +196,10 @@ public class DownloadController : BaseApiController
|
|||
public async Task<ActionResult> DownloadSeries(int seriesId)
|
||||
{
|
||||
if (!await HasDownloadPermission()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
|
||||
if (series == null) return BadRequest("Invalid Series");
|
||||
|
||||
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using API.Extensions;
|
|||
using API.Helpers;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
|
|
@ -24,11 +25,16 @@ public class FilterController : BaseApiController
|
|||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IStreamService _streamService;
|
||||
private readonly ILogger<FilterController> _logger;
|
||||
|
||||
public FilterController(IUnitOfWork unitOfWork, ILocalizationService localizationService)
|
||||
public FilterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IStreamService streamService,
|
||||
ILogger<FilterController> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_localizationService = localizationService;
|
||||
_streamService = streamService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,4 +126,57 @@ public class FilterController : BaseApiController
|
|||
{
|
||||
return Ok(SmartFilterHelper.Decode(dto.EncodedFilter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rename a Smart Filter given the filterId and new name
|
||||
/// </summary>
|
||||
/// <param name="filterId"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("rename")]
|
||||
public async Task<ActionResult> RenameFilter([FromQuery] int filterId, [FromQuery] string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(),
|
||||
AppUserIncludes.SmartFilters);
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
name = name.Trim();
|
||||
|
||||
if (User.IsInRole(PolicyConstants.ReadOnlyRole))
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(user.Id, "permission-denied"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(user.Id, "smart-filter-name-required"));
|
||||
}
|
||||
|
||||
if (Seed.DefaultStreams.Any(s => s.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(user.Id, "smart-filter-system-name"));
|
||||
}
|
||||
|
||||
var filter = user.SmartFilters.FirstOrDefault(f => f.Id == filterId);
|
||||
if (filter == null)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(user.Id, "filter-not-found"));
|
||||
}
|
||||
|
||||
filter.Name = name;
|
||||
_unitOfWork.AppUserSmartFilterRepository.Update(filter);
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
await _streamService.RenameSmartFilterStreams(filter);
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception when renaming smart filter: {FilterId}", filterId);
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,6 @@ public class LibraryController : BaseApiController
|
|||
|
||||
var ret = _unitOfWork.LibraryRepository.GetLibraryDtosForUsernameAsync(username);
|
||||
await _libraryCacheProvider.SetAsync(CacheKey, ret, TimeSpan.FromHours(24));
|
||||
_logger.LogDebug("Caching libraries for {Key}", cacheKey);
|
||||
|
||||
return Ok(ret);
|
||||
}
|
||||
|
|
@ -351,27 +350,6 @@ public class LibraryController : BaseApiController
|
|||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("analyze")]
|
||||
public ActionResult Analyze(int libraryId)
|
||||
{
|
||||
_taskScheduler.AnalyzeFilesForLibrary(libraryId, true);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("analyze-multiple")]
|
||||
public ActionResult AnalyzeMultiple(BulkActionDto dto)
|
||||
{
|
||||
foreach (var libraryId in dto.Ids)
|
||||
{
|
||||
_taskScheduler.AnalyzeFilesForLibrary(libraryId, dto.Force ?? false);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Copy the library settings (adv tab + optional type) to a set of other libraries.
|
||||
/// </summary>
|
||||
|
|
@ -440,8 +418,7 @@ public class LibraryController : BaseApiController
|
|||
.Distinct()
|
||||
.Select(Services.Tasks.Scanner.Parser.Parser.NormalizePath);
|
||||
|
||||
var seriesFolder = _directoryService.FindHighestDirectoriesFromFiles(libraryFolder,
|
||||
new List<string>() {dto.FolderPath});
|
||||
var seriesFolder = _directoryService.FindHighestDirectoriesFromFiles(libraryFolder, [dto.FolderPath]);
|
||||
|
||||
_taskScheduler.ScanFolder(seriesFolder.Keys.Count == 1 ? seriesFolder.Keys.First() : dto.FolderPath);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Services;
|
||||
using EasyCaching.Core;
|
||||
|
|
@ -45,8 +46,8 @@ public class LocaleController : BaseApiController
|
|||
}
|
||||
|
||||
var ret = _localizationService.GetLocales().Where(l => l.TranslationCompletion > 0f);
|
||||
await _localeCacheProvider.SetAsync(CacheKey, ret, TimeSpan.FromDays(7));
|
||||
await _localeCacheProvider.SetAsync(CacheKey, ret, TimeSpan.FromDays(1));
|
||||
|
||||
return Ok();
|
||||
return Ok(ret);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using API.DTOs.Recommendation;
|
|||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Services;
|
||||
using API.Services.Plus;
|
||||
using Kavita.Common.Extensions;
|
||||
|
|
@ -225,7 +226,7 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc
|
|||
var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
|
||||
var user = await unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId())!;
|
||||
|
||||
userReviews.AddRange(ReviewService.SelectSpectrumOfReviews(ret.Reviews.ToList()));
|
||||
userReviews.AddRange(ReviewHelper.SelectSpectrumOfReviews(ret.Reviews.ToList()));
|
||||
ret.Reviews = userReviews;
|
||||
|
||||
if (!isAdmin && ret.Recommendations != null && user != null)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ using AutoMapper;
|
|||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeTypes;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
|
@ -36,6 +37,7 @@ namespace API.Controllers;
|
|||
[AllowAnonymous]
|
||||
public class OpdsController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<OpdsController> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IDownloadService _downloadService;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
|
|
@ -82,7 +84,7 @@ public class OpdsController : BaseApiController
|
|||
IDirectoryService directoryService, ICacheService cacheService,
|
||||
IReaderService readerService, ISeriesService seriesService,
|
||||
IAccountService accountService, ILocalizationService localizationService,
|
||||
IMapper mapper)
|
||||
IMapper mapper, ILogger<OpdsController> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_downloadService = downloadService;
|
||||
|
|
@ -93,6 +95,7 @@ public class OpdsController : BaseApiController
|
|||
_accountService = accountService;
|
||||
_localizationService = localizationService;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
|
||||
_xmlSerializer = new XmlSerializer(typeof(Feed));
|
||||
_xmlOpenSearchSerializer = new XmlSerializer(typeof(OpenSearchDescription));
|
||||
|
|
@ -580,19 +583,25 @@ public class OpdsController : BaseApiController
|
|||
public async Task<IActionResult> GetReadingListItems(int readingListId, string apiKey, [FromQuery] int pageNumber = 0)
|
||||
{
|
||||
var userId = await GetUser(apiKey);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
|
||||
var userWithLists = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user!.UserName!, AppUserIncludes.ReadingListsWithItems);
|
||||
if (userWithLists == null) return Unauthorized();
|
||||
var readingList = userWithLists.ReadingLists.SingleOrDefault(t => t.Id == readingListId);
|
||||
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
|
||||
}
|
||||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, user.Id);
|
||||
if (readingList == null)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(userId, "reading-list-restricted"));
|
||||
}
|
||||
|
||||
var (baseUrl, prefix) = await GetPrefix();
|
||||
var feed = CreateFeed(readingList.Title + " " + await _localizationService.Translate(userId, "reading-list"), $"{apiKey}/reading-list/{readingListId}", apiKey, prefix);
|
||||
SetFeedId(feed, $"reading-list-{readingListId}");
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public class PersonController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of authors & artists for browsing
|
||||
/// Returns a list of authors and artists for browsing
|
||||
/// </summary>
|
||||
/// <param name="userParams"></param>
|
||||
/// <returns></returns>
|
||||
|
|
|
|||
|
|
@ -803,7 +803,7 @@ public class ReaderController : BaseApiController
|
|||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("time-left")]
|
||||
[ResponseCache(CacheProfileName = "Hour", VaryByQueryKeys = ["seriesId"])]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["seriesId"])]
|
||||
public async Task<ActionResult<HourEstimateRangeDto>> GetEstimateToCompletion(int seriesId)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ using API.Data;
|
|||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.ReadingLists;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Services;
|
||||
using API.SignalR;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -24,13 +24,15 @@ public class ReadingListController : BaseApiController
|
|||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IReadingListService _readingListService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IReaderService _readerService;
|
||||
|
||||
public ReadingListController(IUnitOfWork unitOfWork, IReadingListService readingListService,
|
||||
ILocalizationService localizationService)
|
||||
ILocalizationService localizationService, IReaderService readerService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_readingListService = readingListService;
|
||||
_localizationService = localizationService;
|
||||
_readerService = readerService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -39,9 +41,15 @@ public class ReadingListController : BaseApiController
|
|||
/// <param name="readingListId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<ReadingListDto>>> GetList(int readingListId)
|
||||
public async Task<ActionResult<ReadingListDto>> GetList(int readingListId)
|
||||
{
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, User.GetUserId()));
|
||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, User.GetUserId());
|
||||
if (readingList == null)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-restricted"));
|
||||
}
|
||||
|
||||
return Ok(readingList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -123,7 +131,7 @@ public class ReadingListController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a list item from the list. Will reorder all item positions afterwards
|
||||
/// Deletes a list item from the list. Item orders will update as a result.
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -262,7 +270,7 @@ public class ReadingListController : BaseApiController
|
|||
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
|
||||
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
|
||||
var chapterIdsForSeries =
|
||||
await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new [] {dto.SeriesId});
|
||||
await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync([dto.SeriesId]);
|
||||
|
||||
// If there are adds, tell tracking this has been modified
|
||||
if (await _readingListService.AddChaptersToReadingList(dto.SeriesId, chapterIdsForSeries, readingList))
|
||||
|
|
@ -447,26 +455,38 @@ public class ReadingListController : BaseApiController
|
|||
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of characters associated with the reading list
|
||||
/// Returns a list of a given role associated with the reading list
|
||||
/// </summary>
|
||||
/// <param name="readingListId"></param>
|
||||
/// <param name="role">PersonRole</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("people")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.TenMinute, VaryByQueryKeys = ["readingListId", "role"])]
|
||||
public ActionResult<IEnumerable<PersonDto>> GetPeopleByRoleForList(int readingListId, PersonRole role)
|
||||
{
|
||||
return Ok(_unitOfWork.ReadingListRepository.GetReadingListPeopleAsync(readingListId, role));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all people in given roles for a reading list
|
||||
/// </summary>
|
||||
/// <param name="readingListId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("characters")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.TenMinute)]
|
||||
public ActionResult<IEnumerable<PersonDto>> GetCharactersForList(int readingListId)
|
||||
[HttpGet("all-people")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.TenMinute, VaryByQueryKeys = ["readingListId"])]
|
||||
public async Task<ActionResult<IEnumerable<PersonDto>>> GetAllPeopleForList(int readingListId)
|
||||
{
|
||||
return Ok(_unitOfWork.ReadingListRepository.GetReadingListCharactersAsync(readingListId));
|
||||
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListAllPeopleAsync(readingListId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next chapter within the reading list
|
||||
/// </summary>
|
||||
/// <param name="currentChapterId"></param>
|
||||
/// <param name="readingListId"></param>
|
||||
/// <returns>Chapter Id for next item, -1 if nothing exists</returns>
|
||||
/// <returns>Chapter ID for next item, -1 if nothing exists</returns>
|
||||
[HttpGet("next-chapter")]
|
||||
public async Task<ActionResult<int>> GetNextChapter(int currentChapterId, int readingListId)
|
||||
{
|
||||
|
|
@ -572,4 +592,26 @@ public class ReadingListController : BaseApiController
|
|||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns random information about a Reading List
|
||||
/// </summary>
|
||||
/// <param name="readingListId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("info")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = ["readingListId"])]
|
||||
public async Task<ActionResult<ReadingListInfoDto?>> GetReadingListInfo(int readingListId)
|
||||
{
|
||||
var result = await _unitOfWork.ReadingListRepository.GetReadingListInfoAsync(readingListId);
|
||||
|
||||
if (result == null) return Ok(null);
|
||||
|
||||
var timeEstimate = _readerService.GetTimeEstimate(result.WordCount, result.Pages, result.IsAllEpub);
|
||||
|
||||
result.MinHoursToRead = timeEstimate.MinHours;
|
||||
result.AvgHoursToRead = timeEstimate.AvgHours;
|
||||
result.MaxHoursToRead = timeEstimate.MaxHours;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public class ScrobblingController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the current user's MAL token & username
|
||||
/// Get the current user's MAL token and username
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("mal-token")]
|
||||
|
|
@ -270,4 +270,15 @@ public class ScrobblingController : BaseApiController
|
|||
await _unitOfWork.CommitAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has the logged in user ran scrobble generation
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("has-ran-scrobble-gen")]
|
||||
public async Task<ActionResult<bool>> HasRanScrobbleGen()
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId());
|
||||
return Ok(user is {HasRunScrobbleEventGeneration: true});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,8 @@ public class SeriesController : BaseApiController
|
|||
// Trigger a refresh when we are moving from a locked image to a non-locked
|
||||
needsRefreshMetadata = true;
|
||||
series.CoverImage = null;
|
||||
series.CoverImageLocked = updateSeries.CoverImageLocked;
|
||||
series.CoverImageLocked = false;
|
||||
_logger.LogDebug("[SeriesCoverImageBug] Setting Series Cover Image to null: {SeriesId}", series.Id);
|
||||
series.ResetColorScape();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,10 +203,11 @@ public class ServerController : BaseApiController
|
|||
/// <summary>
|
||||
/// Returns how many versions out of date this install is
|
||||
/// </summary>
|
||||
/// <param name="stableOnly">Only count Stable releases</param>
|
||||
[HttpGet("check-out-of-date")]
|
||||
public async Task<ActionResult<int>> CheckHowOutOfDate()
|
||||
public async Task<ActionResult<int>> CheckHowOutOfDate(bool stableOnly = true)
|
||||
{
|
||||
return Ok(await _versionUpdaterService.GetNumberOfReleasesBehind());
|
||||
return Ok(await _versionUpdaterService.GetNumberOfReleasesBehind(stableOnly));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,27 +34,26 @@ public class SettingsController : BaseApiController
|
|||
{
|
||||
private readonly ILogger<SettingsController> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly ILibraryWatcher _libraryWatcher;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler,
|
||||
IDirectoryService directoryService, IMapper mapper, IEmailService emailService, ILibraryWatcher libraryWatcher,
|
||||
ILocalizationService localizationService)
|
||||
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, IMapper mapper,
|
||||
IEmailService emailService, ILocalizationService localizationService, ISettingsService settingsService)
|
||||
{
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
_taskScheduler = taskScheduler;
|
||||
_directoryService = directoryService;
|
||||
_mapper = mapper;
|
||||
_emailService = emailService;
|
||||
_libraryWatcher = libraryWatcher;
|
||||
_localizationService = localizationService;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the base url for this instance (if set)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("base-url")]
|
||||
public async Task<ActionResult<string>> GetBaseUrl()
|
||||
{
|
||||
|
|
@ -139,346 +138,33 @@ public class SettingsController : BaseApiController
|
|||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update Server settings
|
||||
/// </summary>
|
||||
/// <param name="updateSettingsDto"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ServerSettingDto>> UpdateSettings(ServerSettingDto updateSettingsDto)
|
||||
{
|
||||
_logger.LogInformation("{UserName} is updating Server Settings", User.GetUsername());
|
||||
|
||||
// We do not allow CacheDirectory changes, so we will ignore.
|
||||
var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync();
|
||||
var updateBookmarks = false;
|
||||
var originalBookmarkDirectory = _directoryService.BookmarkDirectory;
|
||||
|
||||
var bookmarkDirectory = updateSettingsDto.BookmarksDirectory;
|
||||
if (!updateSettingsDto.BookmarksDirectory.EndsWith("bookmarks") &&
|
||||
!updateSettingsDto.BookmarksDirectory.EndsWith("bookmarks/"))
|
||||
{
|
||||
bookmarkDirectory =
|
||||
_directoryService.FileSystem.Path.Join(updateSettingsDto.BookmarksDirectory, "bookmarks");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(updateSettingsDto.BookmarksDirectory))
|
||||
{
|
||||
bookmarkDirectory = _directoryService.BookmarkDirectory;
|
||||
}
|
||||
|
||||
var updateTask = false;
|
||||
foreach (var setting in currentSettings)
|
||||
{
|
||||
if (setting.Key == ServerSettingKey.OnDeckProgressDays &&
|
||||
updateSettingsDto.OnDeckProgressDays + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.OnDeckProgressDays + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.OnDeckUpdateDays &&
|
||||
updateSettingsDto.OnDeckUpdateDays + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.OnDeckUpdateDays + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.Port && updateSettingsDto.Port + string.Empty != setting.Value)
|
||||
{
|
||||
if (OsInfo.IsDocker) continue;
|
||||
setting.Value = updateSettingsDto.Port + string.Empty;
|
||||
// Port is managed in appSetting.json
|
||||
Configuration.Port = updateSettingsDto.Port;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.CacheSize &&
|
||||
updateSettingsDto.CacheSize + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.CacheSize + string.Empty;
|
||||
// CacheSize is managed in appSetting.json
|
||||
Configuration.CacheSize = updateSettingsDto.CacheSize;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
updateTask = updateTask || UpdateSchedulingSettings(setting, updateSettingsDto);
|
||||
|
||||
UpdateEmailSettings(setting, updateSettingsDto);
|
||||
|
||||
|
||||
|
||||
if (setting.Key == ServerSettingKey.IpAddresses && updateSettingsDto.IpAddresses != setting.Value)
|
||||
{
|
||||
if (OsInfo.IsDocker) continue;
|
||||
// Validate IP addresses
|
||||
foreach (var ipAddress in updateSettingsDto.IpAddresses.Split(',',
|
||||
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!IPAddress.TryParse(ipAddress.Trim(), out _))
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "ip-address-invalid",
|
||||
ipAddress));
|
||||
}
|
||||
}
|
||||
|
||||
setting.Value = updateSettingsDto.IpAddresses;
|
||||
// IpAddresses is managed in appSetting.json
|
||||
Configuration.IpAddresses = updateSettingsDto.IpAddresses;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.BaseUrl && updateSettingsDto.BaseUrl + string.Empty != setting.Value)
|
||||
{
|
||||
var path = !updateSettingsDto.BaseUrl.StartsWith('/')
|
||||
? $"/{updateSettingsDto.BaseUrl}"
|
||||
: updateSettingsDto.BaseUrl;
|
||||
path = !path.EndsWith('/')
|
||||
? $"{path}/"
|
||||
: path;
|
||||
setting.Value = path;
|
||||
Configuration.BaseUrl = updateSettingsDto.BaseUrl;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.LoggingLevel &&
|
||||
updateSettingsDto.LoggingLevel + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.LoggingLevel + string.Empty;
|
||||
LogLevelOptions.SwitchLogLevel(updateSettingsDto.LoggingLevel);
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EnableOpds &&
|
||||
updateSettingsDto.EnableOpds + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.EnableOpds + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EncodeMediaAs &&
|
||||
((int)updateSettingsDto.EncodeMediaAs).ToString() != setting.Value)
|
||||
{
|
||||
setting.Value = ((int)updateSettingsDto.EncodeMediaAs).ToString();
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.CoverImageSize &&
|
||||
((int)updateSettingsDto.CoverImageSize).ToString() != setting.Value)
|
||||
{
|
||||
setting.Value = ((int)updateSettingsDto.CoverImageSize).ToString();
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.HostName && updateSettingsDto.HostName + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = (updateSettingsDto.HostName + string.Empty).Trim();
|
||||
setting.Value = UrlHelper.RemoveEndingSlash(setting.Value);
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.BookmarkDirectory && bookmarkDirectory != setting.Value)
|
||||
{
|
||||
// Validate new directory can be used
|
||||
if (!await _directoryService.CheckWriteAccess(bookmarkDirectory))
|
||||
{
|
||||
return BadRequest(
|
||||
await _localizationService.Translate(User.GetUserId(), "bookmark-dir-permissions"));
|
||||
}
|
||||
|
||||
originalBookmarkDirectory = setting.Value;
|
||||
// Normalize the path deliminators. Just to look nice in DB, no functionality
|
||||
setting.Value = _directoryService.FileSystem.Path.GetFullPath(bookmarkDirectory);
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
updateBookmarks = true;
|
||||
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.AllowStatCollection &&
|
||||
updateSettingsDto.AllowStatCollection + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.AllowStatCollection + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.TotalBackups &&
|
||||
updateSettingsDto.TotalBackups + string.Empty != setting.Value)
|
||||
{
|
||||
if (updateSettingsDto.TotalBackups > 30 || updateSettingsDto.TotalBackups < 1)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "total-backups"));
|
||||
}
|
||||
|
||||
setting.Value = updateSettingsDto.TotalBackups + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.TotalLogs &&
|
||||
updateSettingsDto.TotalLogs + string.Empty != setting.Value)
|
||||
{
|
||||
if (updateSettingsDto.TotalLogs > 30 || updateSettingsDto.TotalLogs < 1)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "total-logs"));
|
||||
}
|
||||
|
||||
setting.Value = updateSettingsDto.TotalLogs + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EnableFolderWatching &&
|
||||
updateSettingsDto.EnableFolderWatching + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.EnableFolderWatching + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok(updateSettingsDto);
|
||||
|
||||
try
|
||||
{
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
if (!updateSettingsDto.AllowStatCollection)
|
||||
{
|
||||
_taskScheduler.CancelStatsTasks();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _taskScheduler.ScheduleStatsTasks();
|
||||
}
|
||||
|
||||
if (updateBookmarks)
|
||||
{
|
||||
UpdateBookmarkDirectory(originalBookmarkDirectory, bookmarkDirectory);
|
||||
}
|
||||
|
||||
if (updateTask)
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _taskScheduler.ScheduleTasks());
|
||||
}
|
||||
|
||||
if (updateSettingsDto.EnableFolderWatching)
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _libraryWatcher.StartWatching());
|
||||
}
|
||||
else
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _libraryWatcher.StopWatching());
|
||||
}
|
||||
var d = await _settingsService.UpdateSettings(updateSettingsDto);
|
||||
return Ok(d);
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an exception when updating server settings");
|
||||
await _unitOfWork.RollbackAsync();
|
||||
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
|
||||
}
|
||||
|
||||
|
||||
_logger.LogInformation("Server Settings updated");
|
||||
BackgroundJob.Enqueue(() => _taskScheduler.ScheduleTasks());
|
||||
|
||||
return Ok(updateSettingsDto);
|
||||
}
|
||||
|
||||
|
||||
private void UpdateBookmarkDirectory(string originalBookmarkDirectory, string bookmarkDirectory)
|
||||
{
|
||||
_directoryService.ExistOrCreate(bookmarkDirectory);
|
||||
_directoryService.CopyDirectoryToDirectory(originalBookmarkDirectory, bookmarkDirectory);
|
||||
_directoryService.ClearAndDeleteDirectory(originalBookmarkDirectory);
|
||||
}
|
||||
|
||||
private bool UpdateSchedulingSettings(ServerSetting setting, ServerSettingDto updateSettingsDto)
|
||||
{
|
||||
if (setting.Key == ServerSettingKey.TaskBackup && updateSettingsDto.TaskBackup != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.TaskBackup;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.TaskScan && updateSettingsDto.TaskScan != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.TaskScan;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.TaskCleanup && updateSettingsDto.TaskCleanup != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.TaskCleanup;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateEmailSettings(ServerSetting setting, ServerSettingDto updateSettingsDto)
|
||||
{
|
||||
if (setting.Key == ServerSettingKey.EmailHost &&
|
||||
updateSettingsDto.SmtpConfig.Host + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.Host + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailPort &&
|
||||
updateSettingsDto.SmtpConfig.Port + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.Port + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailAuthPassword &&
|
||||
updateSettingsDto.SmtpConfig.Password + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.Password + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailAuthUserName &&
|
||||
updateSettingsDto.SmtpConfig.UserName + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.UserName + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailSenderAddress &&
|
||||
updateSettingsDto.SmtpConfig.SenderAddress + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.SenderAddress + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailSenderDisplayName &&
|
||||
updateSettingsDto.SmtpConfig.SenderDisplayName + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.SenderDisplayName + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailSizeLimit &&
|
||||
updateSettingsDto.SmtpConfig.SizeLimit + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.SizeLimit + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailEnableSsl &&
|
||||
updateSettingsDto.SmtpConfig.EnableSsl + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.EnableSsl + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.EmailCustomizedTemplates &&
|
||||
updateSettingsDto.SmtpConfig.CustomizedTemplates + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.SmtpConfig.CustomizedTemplates + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// All values allowed for Task Scheduling APIs. A custom cron job is not included. Disabled is not applicable for Cleanup.
|
||||
/// </summary>
|
||||
|
|
@ -549,7 +235,7 @@ public class SettingsController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the metadata settings for Kavita+ users
|
||||
/// Update the metadata settings for Kavita+ Metadata feature
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -557,54 +243,14 @@ public class SettingsController : BaseApiController
|
|||
[HttpPost("metadata-settings")]
|
||||
public async Task<ActionResult<MetadataSettingsDto>> UpdateMetadataSettings(MetadataSettingsDto dto)
|
||||
{
|
||||
var existingMetadataSetting = await _unitOfWork.SettingsRepository.GetMetadataSettings();
|
||||
existingMetadataSetting.Enabled = dto.Enabled;
|
||||
existingMetadataSetting.EnableSummary = dto.EnableSummary;
|
||||
existingMetadataSetting.EnableLocalizedName = dto.EnableLocalizedName;
|
||||
existingMetadataSetting.EnablePublicationStatus = dto.EnablePublicationStatus;
|
||||
existingMetadataSetting.EnableRelationships = dto.EnableRelationships;
|
||||
existingMetadataSetting.EnablePeople = dto.EnablePeople;
|
||||
existingMetadataSetting.EnableStartDate = dto.EnableStartDate;
|
||||
existingMetadataSetting.EnableGenres = dto.EnableGenres;
|
||||
existingMetadataSetting.EnableTags = dto.EnableTags;
|
||||
existingMetadataSetting.FirstLastPeopleNaming = dto.FirstLastPeopleNaming;
|
||||
existingMetadataSetting.EnableCoverImage = dto.EnableCoverImage;
|
||||
|
||||
existingMetadataSetting.AgeRatingMappings = dto.AgeRatingMappings ?? [];
|
||||
|
||||
existingMetadataSetting.Blacklist = dto.Blacklist.Where(s => !string.IsNullOrWhiteSpace(s)).DistinctBy(d => d.ToNormalized()).ToList() ?? [];
|
||||
existingMetadataSetting.Whitelist = dto.Whitelist.Where(s => !string.IsNullOrWhiteSpace(s)).DistinctBy(d => d.ToNormalized()).ToList() ?? [];
|
||||
existingMetadataSetting.Overrides = dto.Overrides.ToList() ?? [];
|
||||
existingMetadataSetting.PersonRoles = dto.PersonRoles ?? [];
|
||||
|
||||
// Handle Field Mappings
|
||||
if (dto.FieldMappings != null)
|
||||
try
|
||||
{
|
||||
// Clear existing mappings
|
||||
existingMetadataSetting.FieldMappings ??= [];
|
||||
_unitOfWork.SettingsRepository.RemoveRange(existingMetadataSetting.FieldMappings);
|
||||
|
||||
existingMetadataSetting.FieldMappings.Clear();
|
||||
|
||||
|
||||
// Add new mappings
|
||||
foreach (var mappingDto in dto.FieldMappings)
|
||||
{
|
||||
existingMetadataSetting.FieldMappings.Add(new MetadataFieldMapping
|
||||
{
|
||||
SourceType = mappingDto.SourceType,
|
||||
DestinationType = mappingDto.DestinationType,
|
||||
SourceValue = mappingDto.SourceValue,
|
||||
DestinationValue = mappingDto.DestinationValue,
|
||||
ExcludeFromSource = mappingDto.ExcludeFromSource
|
||||
});
|
||||
}
|
||||
return Ok(await _settingsService.UpdateMetadataSettings(dto));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an issue when updating metadata settings");
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
|
||||
// Save changes
|
||||
await _unitOfWork.CommitAsync();
|
||||
|
||||
// Return updated settings
|
||||
return Ok(await _unitOfWork.SettingsRepository.GetMetadataSettingDto());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,4 +204,30 @@ public class StreamController : BaseApiController
|
|||
await _streamService.UpdateSideNavStreamBulk(User.GetUserId(), dto);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a Smart Filter from a user's SideNav Streams
|
||||
/// </summary>
|
||||
/// <param name="sideNavStreamId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("smart-filter-side-nav-stream")]
|
||||
public async Task<ActionResult> DeleteSmartFilterSideNavStream([FromQuery] int sideNavStreamId)
|
||||
{
|
||||
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||
await _streamService.DeleteSideNavSmartFilterStream(User.GetUserId(), sideNavStreamId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a Smart Filter from a user's Dashboard Streams
|
||||
/// </summary>
|
||||
/// <param name="dashboardStreamId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("smart-filter-dashboard-stream")]
|
||||
public async Task<ActionResult> DeleteSmartFilterDashboardStream([FromQuery] int dashboardStreamId)
|
||||
{
|
||||
if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
|
||||
await _streamService.DeleteDashboardSmartFilterStream(User.GetUserId(), dashboardStreamId);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
#nullable enable
|
||||
|
||||
public class VolumeController : BaseApiController
|
||||
{
|
||||
|
|
@ -23,13 +24,15 @@ public class VolumeController : BaseApiController
|
|||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the appropriate Volume
|
||||
/// </summary>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<VolumeDto>> GetVolume(int volumeId)
|
||||
public async Task<ActionResult<VolumeDto?>> GetVolume(int volumeId)
|
||||
{
|
||||
var volume =
|
||||
await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, User.GetUserId());
|
||||
|
||||
return Ok(volume);
|
||||
return Ok(await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, User.GetUserId()));
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
|
|
@ -39,7 +42,7 @@ public class VolumeController : BaseApiController
|
|||
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId,
|
||||
VolumeIncludes.Chapters | VolumeIncludes.People | VolumeIncludes.Tags);
|
||||
if (volume == null)
|
||||
return BadRequest(_localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
|
||||
return BadRequest(_localizationService.Translate(User.GetUserId(), "volume-doesnt-exist"));
|
||||
|
||||
_unitOfWork.VolumeRepository.Remove(volume);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue