Merge branch 'develop' into feature/oidc
This commit is contained in:
commit
465723fedf
358 changed files with 34968 additions and 5203 deletions
|
|
@ -165,6 +165,9 @@ public class AccountController : BaseApiController
|
|||
// Assign default streams
|
||||
AddDefaultStreamsToUser(user);
|
||||
|
||||
// Assign default reading profile
|
||||
await AddDefaultReadingProfileToUser(user);
|
||||
|
||||
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
if (string.IsNullOrEmpty(token)) return BadRequest(await _localizationService.Get("en", "confirm-token-gen"));
|
||||
if (!await ConfirmEmailToken(token, user)) return BadRequest(await _localizationService.Get("en", "validate-email", token));
|
||||
|
|
@ -625,7 +628,7 @@ public class AccountController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Requests the Invite Url for the UserId. Will return error if user is already validated.
|
||||
/// Requests the Invite Url for the AppUserId. Will return error if user is already validated.
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="withBaseUrl">Include the "https://ip:port/" in the generated link</param>
|
||||
|
|
@ -685,6 +688,9 @@ public class AccountController : BaseApiController
|
|||
// Assign default streams
|
||||
AddDefaultStreamsToUser(user);
|
||||
|
||||
// Assign default reading profile
|
||||
await AddDefaultReadingProfileToUser(user);
|
||||
|
||||
// Assign Roles
|
||||
var roles = dto.Roles;
|
||||
var hasAdminRole = dto.Roles.Contains(PolicyConstants.AdminRole);
|
||||
|
|
@ -795,6 +801,16 @@ public class AccountController : BaseApiController
|
|||
}
|
||||
}
|
||||
|
||||
private async Task AddDefaultReadingProfileToUser(AppUser user)
|
||||
{
|
||||
var profile = new AppUserReadingProfileBuilder(user.Id)
|
||||
.WithName("Default Profile")
|
||||
.WithKind(ReadingProfileKind.Default)
|
||||
.Build();
|
||||
_unitOfWork.AppUserReadingProfileRepository.Add(profile);
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Last step in authentication flow, confirms the email token for email
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using API.DTOs;
|
|||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.MetadataMatching;
|
||||
using API.Entities.Person;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
|
|
@ -208,6 +209,7 @@ public class ChapterController : BaseApiController
|
|||
if (chapter.AgeRating != dto.AgeRating)
|
||||
{
|
||||
chapter.AgeRating = dto.AgeRating;
|
||||
chapter.KPlusOverrides.Remove(MetadataSettingField.AgeRating);
|
||||
}
|
||||
|
||||
dto.Summary ??= string.Empty;
|
||||
|
|
@ -215,6 +217,7 @@ public class ChapterController : BaseApiController
|
|||
if (chapter.Summary != dto.Summary.Trim())
|
||||
{
|
||||
chapter.Summary = dto.Summary.Trim();
|
||||
chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterSummary);
|
||||
}
|
||||
|
||||
if (chapter.Language != dto.Language)
|
||||
|
|
@ -230,11 +233,13 @@ public class ChapterController : BaseApiController
|
|||
if (chapter.TitleName != dto.TitleName)
|
||||
{
|
||||
chapter.TitleName = dto.TitleName;
|
||||
chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterTitle);
|
||||
}
|
||||
|
||||
if (chapter.ReleaseDate != dto.ReleaseDate)
|
||||
{
|
||||
chapter.ReleaseDate = dto.ReleaseDate;
|
||||
chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterReleaseDate);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(dto.ISBN) && ArticleNumberHelper.IsValidIsbn10(dto.ISBN) ||
|
||||
|
|
@ -333,6 +338,8 @@ public class ChapterController : BaseApiController
|
|||
_unitOfWork
|
||||
);
|
||||
|
||||
// TODO: Only remove field if changes were made
|
||||
chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterPublisher);
|
||||
// Update publishers
|
||||
await PersonHelper.UpdateChapterPeopleAsync(
|
||||
chapter,
|
||||
|
|
|
|||
119
API/Controllers/KoreaderController.cs
Normal file
119
API/Controllers/KoreaderController.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs.Koreader;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static System.Net.WebRequestMethods;
|
||||
|
||||
namespace API.Controllers;
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// The endpoint to interface with Koreader's Progress Sync plugin.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Koreader uses a different form of authentication. It stores the username and password in headers.
|
||||
/// https://github.com/koreader/koreader/blob/master/plugins/kosync.koplugin/KOSyncClient.lua
|
||||
/// </remarks>
|
||||
[AllowAnonymous]
|
||||
public class KoreaderController : BaseApiController
|
||||
{
|
||||
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IKoreaderService _koreaderService;
|
||||
private readonly ILogger<KoreaderController> _logger;
|
||||
|
||||
public KoreaderController(IUnitOfWork unitOfWork, ILocalizationService localizationService,
|
||||
IKoreaderService koreaderService, ILogger<KoreaderController> logger)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_localizationService = localizationService;
|
||||
_koreaderService = koreaderService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// We won't allow users to be created from Koreader. Rather, they
|
||||
// must already have an account.
|
||||
/*
|
||||
[HttpPost("/users/create")]
|
||||
public IActionResult CreateUser(CreateUserRequest request)
|
||||
{
|
||||
}
|
||||
*/
|
||||
|
||||
[HttpGet("{apiKey}/users/auth")]
|
||||
public async Task<IActionResult> Authenticate(string apiKey)
|
||||
{
|
||||
var userId = await GetUserId(apiKey);
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
if (user == null) return Unauthorized();
|
||||
|
||||
return Ok(new { username = user.UserName });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Syncs book progress with Kavita. Will attempt to save the underlying reader position if possible.
|
||||
/// </summary>
|
||||
/// <param name="apiKey"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut("{apiKey}/syncs/progress")]
|
||||
public async Task<ActionResult<KoreaderProgressUpdateDto>> UpdateProgress(string apiKey, KoreaderBookDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = await GetUserId(apiKey);
|
||||
await _koreaderService.SaveProgress(request, userId);
|
||||
|
||||
return Ok(new KoreaderProgressUpdateDto{ Document = request.Document, Timestamp = DateTime.UtcNow });
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets book progress from Kavita, if not found will return a 400
|
||||
/// </summary>
|
||||
/// <param name="apiKey"></param>
|
||||
/// <param name="ebookHash"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{apiKey}/syncs/progress/{ebookHash}")]
|
||||
public async Task<ActionResult<KoreaderBookDto>> GetProgress(string apiKey, string ebookHash)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = await GetUserId(apiKey);
|
||||
var response = await _koreaderService.GetProgress(ebookHash, userId);
|
||||
_logger.LogDebug("Koreader response progress for User ({UserId}): {Progress}", userId, response.Progress.Sanitize());
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (KavitaException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<int> GetUserId(string apiKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new KavitaException(await _localizationService.Get("en", "user-doesnt-exist"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -623,6 +623,7 @@ public class LibraryController : BaseApiController
|
|||
library.ManageReadingLists = dto.ManageReadingLists;
|
||||
library.AllowScrobbling = dto.AllowScrobbling;
|
||||
library.AllowMetadataMatching = dto.AllowMetadataMatching;
|
||||
library.EnableMetadata = dto.EnableMetadata;
|
||||
library.LibraryFileTypes = dto.FileGroupTypes
|
||||
.Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id})
|
||||
.Distinct()
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ using System.Threading.Tasks;
|
|||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Metadata.Browse;
|
||||
using API.DTOs.Person;
|
||||
using API.DTOs.Recommendation;
|
||||
using API.DTOs.SeriesDetail;
|
||||
|
|
@ -46,6 +48,22 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc
|
|||
return Ok(await unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(User.GetUserId(), ids, context));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of Genres with counts for counts when Genre is on Series/Chapter
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("genres-with-counts")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute)]
|
||||
public async Task<ActionResult<PagedList<BrowseGenreDto>>> GetBrowseGenres(UserParams? userParams = null)
|
||||
{
|
||||
userParams ??= UserParams.Default;
|
||||
|
||||
var list = await unitOfWork.GenreRepository.GetBrowseableGenre(User.GetUserId(), userParams);
|
||||
Response.AddPaginationHeader(list.CurrentPage, list.PageSize, list.TotalCount, list.TotalPages);
|
||||
|
||||
return Ok(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches people from the instance by role
|
||||
/// </summary>
|
||||
|
|
@ -95,6 +113,22 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc
|
|||
return Ok(await unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of Tags with counts for counts when Tag is on Series/Chapter
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("tags-with-counts")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.FiveMinute)]
|
||||
public async Task<ActionResult<PagedList<BrowseTagDto>>> GetBrowseTags(UserParams? userParams = null)
|
||||
{
|
||||
userParams ??= UserParams.Default;
|
||||
|
||||
var list = await unitOfWork.TagRepository.GetBrowseableTag(User.GetUserId(), userParams);
|
||||
Response.AddPaginationHeader(list.CurrentPage, list.PageSize, list.TotalCount, list.TotalPages);
|
||||
|
||||
return Ok(list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches all age ratings from the instance
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ using System.Threading.Tasks;
|
|||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.DTOs.Metadata.Browse;
|
||||
using API.DTOs.Metadata.Browse.Requests;
|
||||
using API.DTOs.Person;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
|
|
@ -77,11 +80,13 @@ public class PersonController : BaseApiController
|
|||
/// <param name="userParams"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("all")]
|
||||
public async Task<ActionResult<PagedList<BrowsePersonDto>>> GetAuthorsForBrowse([FromQuery] UserParams? userParams)
|
||||
public async Task<ActionResult<PagedList<BrowsePersonDto>>> GetPeopleForBrowse(BrowsePersonFilterDto filter, [FromQuery] UserParams? userParams)
|
||||
{
|
||||
userParams ??= UserParams.Default;
|
||||
var list = await _unitOfWork.PersonRepository.GetAllWritersAndSeriesCount(User.GetUserId(), userParams);
|
||||
|
||||
var list = await _unitOfWork.PersonRepository.GetBrowsePersonDtos(User.GetUserId(), filter, userParams);
|
||||
Response.AddPaginationHeader(list.CurrentPage, list.PageSize, list.TotalCount, list.TotalPages);
|
||||
|
||||
return Ok(list);
|
||||
}
|
||||
|
||||
|
|
@ -112,6 +117,7 @@ public class PersonController : BaseApiController
|
|||
|
||||
|
||||
person.Name = dto.Name?.Trim();
|
||||
person.NormalizedName = person.Name.ToNormalized();
|
||||
person.Description = dto.Description ?? string.Empty;
|
||||
person.CoverImageLocked = dto.CoverImageLocked;
|
||||
|
||||
|
|
@ -179,7 +185,7 @@ public class PersonController : BaseApiController
|
|||
[HttpGet("series-known-for")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetKnownSeries(int personId)
|
||||
{
|
||||
return Ok(await _unitOfWork.PersonRepository.GetSeriesKnownFor(personId));
|
||||
return Ok(await _unitOfWork.PersonRepository.GetSeriesKnownFor(personId, User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -200,6 +206,7 @@ public class PersonController : BaseApiController
|
|||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("merge")]
|
||||
[Authorize("RequireAdminRole")]
|
||||
public async Task<ActionResult<PersonDto>> MergePeople(PersonMergeDto dto)
|
||||
{
|
||||
var dst = await _unitOfWork.PersonRepository.GetPersonById(dto.DestId, PersonIncludes.All);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class PluginController(IUnitOfWork unitOfWork, ITokenService tokenService
|
|||
throw new KavitaUnauthenticatedUserException();
|
||||
}
|
||||
var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({UserId})'s API Key", pluginName.Replace(Environment.NewLine, string.Empty), user!.UserName, userId);
|
||||
logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({AppUserId})'s API Key", pluginName.Replace(Environment.NewLine, string.Empty), user!.UserName, userId);
|
||||
|
||||
return new UserDto
|
||||
{
|
||||
|
|
|
|||
198
API/Controllers/ReadingProfileController.cs
Normal file
198
API/Controllers/ReadingProfileController.cs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using AutoMapper;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
[Route("api/reading-profile")]
|
||||
public class ReadingProfileController(ILogger<ReadingProfileController> logger, IUnitOfWork unitOfWork,
|
||||
IReadingProfileService readingProfileService): BaseApiController
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets all non-implicit reading profiles for a user
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("all")]
|
||||
public async Task<ActionResult<IList<UserReadingProfileDto>>> GetAllReadingProfiles()
|
||||
{
|
||||
return Ok(await unitOfWork.AppUserReadingProfileRepository.GetProfilesDtoForUser(User.GetUserId(), true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the ReadingProfile that should be applied to the given series, walks up the tree.
|
||||
/// Series -> Library -> Default
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="skipImplicit"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{seriesId:int}")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> GetProfileForSeries(int seriesId, [FromQuery] bool skipImplicit)
|
||||
{
|
||||
return Ok(await readingProfileService.GetReadingProfileDtoForSeries(User.GetUserId(), seriesId, skipImplicit));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the (potential) Reading Profile bound to the library
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("library")]
|
||||
public async Task<ActionResult<UserReadingProfileDto?>> GetProfileForLibrary(int libraryId)
|
||||
{
|
||||
return Ok(await readingProfileService.GetReadingProfileDtoForLibrary(User.GetUserId(), libraryId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new reading profile for the current user
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("create")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> CreateReadingProfile([FromBody] UserReadingProfileDto dto)
|
||||
{
|
||||
return Ok(await readingProfileService.CreateReadingProfile(User.GetUserId(), dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Promotes the implicit profile to a user profile. Removes the series from other profiles
|
||||
/// </summary>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("promote")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> PromoteImplicitReadingProfile([FromQuery] int profileId)
|
||||
{
|
||||
return Ok(await readingProfileService.PromoteImplicitProfile(User.GetUserId(), profileId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the implicit reading profile for a series, creates one if none exists
|
||||
/// </summary>
|
||||
/// <remarks>Any modification to the reader settings during reading will create an implicit profile. Use "update-parent" to save to the bound series profile.</remarks>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("series")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> UpdateReadingProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId)
|
||||
{
|
||||
var updatedProfile = await readingProfileService.UpdateImplicitReadingProfile(User.GetUserId(), seriesId, dto);
|
||||
return Ok(updatedProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the non-implicit reading profile for the given series, and removes implicit profiles
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("update-parent")]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> UpdateParentProfileForSeries([FromBody] UserReadingProfileDto dto, [FromQuery] int seriesId)
|
||||
{
|
||||
var newParentProfile = await readingProfileService.UpdateParent(User.GetUserId(), seriesId, dto);
|
||||
return Ok(newParentProfile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the given reading profile, must belong to the current user
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns>The updated reading profile</returns>
|
||||
/// <remarks>
|
||||
/// This does not update connected series and libraries.
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<UserReadingProfileDto>> UpdateReadingProfile(UserReadingProfileDto dto)
|
||||
{
|
||||
return Ok(await readingProfileService.UpdateReadingProfile(User.GetUserId(), dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the given profile, requires the profile to belong to the logged-in user
|
||||
/// </summary>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="KavitaException"></exception>
|
||||
/// <exception cref="UnauthorizedAccessException"></exception>
|
||||
[HttpDelete]
|
||||
public async Task<IActionResult> DeleteReadingProfile([FromQuery] int profileId)
|
||||
{
|
||||
await readingProfileService.DeleteReadingProfile(User.GetUserId(), profileId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the reading profile for a given series, removes the old one
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("series/{seriesId:int}")]
|
||||
public async Task<IActionResult> AddProfileToSeries(int seriesId, [FromQuery] int profileId)
|
||||
{
|
||||
await readingProfileService.AddProfileToSeries(User.GetUserId(), profileId, seriesId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the reading profile for the given series for the currently logged-in user
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("series/{seriesId:int}")]
|
||||
public async Task<IActionResult> ClearSeriesProfile(int seriesId)
|
||||
{
|
||||
await readingProfileService.ClearSeriesProfile(User.GetUserId(), seriesId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the reading profile for a given library, removes the old one
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("library/{libraryId:int}")]
|
||||
public async Task<IActionResult> AddProfileToLibrary(int libraryId, [FromQuery] int profileId)
|
||||
{
|
||||
await readingProfileService.AddProfileToLibrary(User.GetUserId(), profileId, libraryId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the reading profile for the given library for the currently logged-in user
|
||||
/// </summary>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="profileId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpDelete("library/{libraryId:int}")]
|
||||
public async Task<IActionResult> ClearLibraryProfile(int libraryId)
|
||||
{
|
||||
await readingProfileService.ClearLibraryProfile(User.GetUserId(), libraryId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns the reading profile to all passes series, and deletes their implicit profiles
|
||||
/// </summary>
|
||||
/// <param name="profileId"></param>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("bulk")]
|
||||
public async Task<IActionResult> BulkAddReadingProfile([FromQuery] int profileId, [FromBody] IList<int> seriesIds)
|
||||
{
|
||||
await readingProfileService.BulkAddProfileToSeries(User.GetUserId(), profileId, seriesIds);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ public class ScrobblingController : BaseApiController
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a hold against the Series for user's scrobbling
|
||||
/// Remove a hold against the Series for user's scrobbling
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
|
|
@ -281,4 +281,18 @@ public class ScrobblingController : BaseApiController
|
|||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId());
|
||||
return Ok(user is {HasRunScrobbleEventGeneration: true});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the given scrobble events if they belong to that user
|
||||
/// </summary>
|
||||
/// <param name="eventIds"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("bulk-remove-events")]
|
||||
public async Task<ActionResult> BulkRemoveScrobbleEvents(IList<long> eventIds)
|
||||
{
|
||||
var events = await _unitOfWork.ScrobbleRepository.GetUserEvents(User.GetUserId(), eventIds);
|
||||
_unitOfWork.ScrobbleRepository.Remove(events);
|
||||
await _unitOfWork.CommitAsync();
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using API.DTOs.Recommendation;
|
|||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.MetadataMatching;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Services;
|
||||
|
|
@ -224,6 +225,7 @@ public class SeriesController : BaseApiController
|
|||
needsRefreshMetadata = true;
|
||||
series.CoverImage = null;
|
||||
series.CoverImageLocked = false;
|
||||
series.Metadata.KPlusOverrides.Remove(MetadataSettingField.Covers);
|
||||
_logger.LogDebug("[SeriesCoverImageBug] Setting Series Cover Image to null: {SeriesId}", series.Id);
|
||||
series.ResetColorScape();
|
||||
|
||||
|
|
@ -310,7 +312,7 @@ public class SeriesController : BaseApiController
|
|||
/// </summary>
|
||||
/// <param name="filterDto"></param>
|
||||
/// <param name="userParams"></param>
|
||||
/// <param name="libraryId"></param>
|
||||
/// <param name="libraryId">This is not in use</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("all-v2")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeriesV2(FilterV2Dto filterDto, [FromQuery] UserParams userParams,
|
||||
|
|
@ -321,8 +323,6 @@ public class SeriesController : BaseApiController
|
|||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdV2Async(userId, userParams, filterDto, context);
|
||||
|
||||
// Apply progress/rating information (I can't work out how to do this in initial query)
|
||||
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series"));
|
||||
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using API.Data;
|
|||
using API.Data.Repositories;
|
||||
using API.DTOs.Uploads;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.MetadataMatching;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Tasks.Metadata;
|
||||
|
|
@ -112,8 +113,10 @@ public class UploadController : BaseApiController
|
|||
|
||||
series.CoverImage = filePath;
|
||||
series.CoverImageLocked = lockState;
|
||||
series.Metadata.KPlusOverrides.Remove(MetadataSettingField.Covers);
|
||||
_imageService.UpdateColorScape(series);
|
||||
_unitOfWork.SeriesRepository.Update(series);
|
||||
_unitOfWork.SeriesRepository.Update(series.Metadata);
|
||||
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
|
|
@ -277,6 +280,7 @@ public class UploadController : BaseApiController
|
|||
|
||||
chapter.CoverImage = filePath;
|
||||
chapter.CoverImageLocked = lockState;
|
||||
chapter.KPlusOverrides.Remove(MetadataSettingField.ChapterCovers);
|
||||
_unitOfWork.ChapterRepository.Update(chapter);
|
||||
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(chapter.VolumeId);
|
||||
if (volume != null)
|
||||
|
|
|
|||
|
|
@ -103,38 +103,13 @@ public class UsersController : BaseApiController
|
|||
|
||||
var existingPreferences = user!.UserPreferences;
|
||||
|
||||
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
|
||||
existingPreferences.ScalingOption = preferencesDto.ScalingOption;
|
||||
existingPreferences.PageSplitOption = preferencesDto.PageSplitOption;
|
||||
existingPreferences.AutoCloseMenu = preferencesDto.AutoCloseMenu;
|
||||
existingPreferences.ShowScreenHints = preferencesDto.ShowScreenHints;
|
||||
existingPreferences.EmulateBook = preferencesDto.EmulateBook;
|
||||
existingPreferences.ReaderMode = preferencesDto.ReaderMode;
|
||||
existingPreferences.LayoutMode = preferencesDto.LayoutMode;
|
||||
existingPreferences.BackgroundColor = string.IsNullOrEmpty(preferencesDto.BackgroundColor) ? "#000000" : preferencesDto.BackgroundColor;
|
||||
existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin;
|
||||
existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing;
|
||||
existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily;
|
||||
existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize;
|
||||
existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate;
|
||||
existingPreferences.BookReaderReadingDirection = preferencesDto.BookReaderReadingDirection;
|
||||
existingPreferences.BookReaderWritingStyle = preferencesDto.BookReaderWritingStyle;
|
||||
existingPreferences.BookThemeName = preferencesDto.BookReaderThemeName;
|
||||
existingPreferences.BookReaderLayoutMode = preferencesDto.BookReaderLayoutMode;
|
||||
existingPreferences.BookReaderImmersiveMode = preferencesDto.BookReaderImmersiveMode;
|
||||
existingPreferences.GlobalPageLayoutMode = preferencesDto.GlobalPageLayoutMode;
|
||||
existingPreferences.BlurUnreadSummaries = preferencesDto.BlurUnreadSummaries;
|
||||
existingPreferences.LayoutMode = preferencesDto.LayoutMode;
|
||||
existingPreferences.PromptForDownloadSize = preferencesDto.PromptForDownloadSize;
|
||||
existingPreferences.NoTransitions = preferencesDto.NoTransitions;
|
||||
existingPreferences.SwipeToPaginate = preferencesDto.SwipeToPaginate;
|
||||
existingPreferences.CollapseSeriesRelationships = preferencesDto.CollapseSeriesRelationships;
|
||||
existingPreferences.ShareReviews = preferencesDto.ShareReviews;
|
||||
|
||||
existingPreferences.PdfTheme = preferencesDto.PdfTheme;
|
||||
existingPreferences.PdfScrollMode = preferencesDto.PdfScrollMode;
|
||||
existingPreferences.PdfSpreadMode = preferencesDto.PdfSpreadMode;
|
||||
|
||||
if (await _licenseService.HasActiveLicense())
|
||||
{
|
||||
existingPreferences.AniListScrobblingEnabled = preferencesDto.AniListScrobblingEnabled;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue