Localization - First Pass (#2174)

* Started designing the backend localization service

* Worked in Transloco for initial PoC

* Worked in Transloco for initial PoC

* Translated the login screen

* translated dashboard screen

* Started work on the backend

* Fixed a logic bug

* translated edit-user screen

* Hooked up the backend for having a locale property.

* Hooked up the ability to view the available locales and switch to them.

* Made the localization service languages be derived from what's in langs/ directory.

* Fixed up localization switching

* Switched when we check for a license on UI bootstrap

* Tweaked some code

* Fixed the bug where dashboard wasn't loading and made it so language switching is working.

* Fixed a bug on dashboard with languagePath

* Converted user-scrobble-history.component.html

* Converted spoiler.component.html

* Converted review-series-modal.component.html

* Converted review-card-modal.component.html

* Updated the readme

* Translated using Weblate (English)

Currently translated at 100.0% (54 of 54 strings)

Translation: Kavita/ui
Translate-URL: https://hosted.weblate.org/projects/kavita/ui/en/

* Converted review-card.component.html

* Deleted dead component

* Converted want-to-read.component.html

* Added translation using Weblate (Korean)

* Translated using Weblate (Spanish)

Currently translated at 40.7% (22 of 54 strings)

Translation: Kavita/ui
Translate-URL: https://hosted.weblate.org/projects/kavita/ui/es/

* Translated using Weblate (Korean)

Currently translated at 62.9% (34 of 54 strings)

Translation: Kavita/ui
Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/

* Converted user-preferences.component.html

* Translated using Weblate (Korean)

Currently translated at 92.5% (50 of 54 strings)

Translation: Kavita/ui
Translate-URL: https://hosted.weblate.org/projects/kavita/ui/ko/

* Converted user-holds.component.html

* Converted theme-manager.component.html

* Converted restriction-selector.component.html

* Converted manage-devices.component.html

* Converted edit-device.component.html

* Converted change-password.component.html

* Converted change-email.component.html

* Converted change-age-restriction.component.html

* Converted api-key.component.html

* Converted anilist-key.component.html

* Converted typeahead.component.html

* Converted user-stats-info-cards.component.html

* Converted user-stats.component.html

* Converted top-readers.component.html

* Converted some pipes and ensure translation is loaded before the app.

* Finished all but one pipe for localization

* Converted directory-picker.component.html

* Converted library-access-modal.component.html

* Converted a few components

* Converted a few components

* Converted a few components

* Converted a few components

* Converted a few components

* Merged weblate in

* ... -> … update

* Updated the readme

* Updateded all fonts to be woff2

* Cleaned up some strings to increase re-use

* Removed an old flow (that doesn't exist in backend any longer) from when we introduced emails on Kavita.

* Converted Series detail

* Lots more converted

* Lots more converted & hooked up the ability to flatten during prod build the language files.

* Lots more converted

* Lots more converted & fixed a bunch of broken pipes due to inject()

* Lots more converted

* Lots more converted

* Lots more converted & fixed some bad keys

* Lots more converted

* Fixed some bugs with admin dasbhoard nested tabs not rendering on first load due to not using onpush change detection

* Fixed up some localization errors and fixed forgot password error when the user doesn't have change password permission

* Fixed a stupid build issue again

* Started adding errors for interceptor and backend.

* Finished off manga-reader

* More translations

* Few fixes

* Fixed a bug where character tag badges weren't showing the name on chapter info

* All components are translated

* All toasts are translated

* All confirm/alerts are translated

* Trying something new for the backend

* Migrated the localization strings for the backend into a new file.

* Updated the localization service to be able to do backend localization with fallback to english.

* Cleaned up some external reviews code to reduce looping

* Localized AccountController.cs

* 60% done with controllers

* All controllers are done

* All KavitaExceptions are covered

* Some shakeout fixes

* Prep for initial merge

* Everything is done except options and basic shakeout proves response times are good. Unit tests are broken.

* Fixed up the unit tests

* All unit tests are now working

* Removed some quantifier

* I'm not sure I can support localization for some Volume/Chapter/Book strings within the codebase.

---------

Co-authored-by: Robbie Davis <robbie@therobbiedavis.com>
Co-authored-by: majora2007 <kavitareader@gmail.com>
Co-authored-by: expertjun <jtrobin@naver.com>
Co-authored-by: ThePromidius <thepromidiusyt@gmail.com>
This commit is contained in:
Joe Milazzo 2023-08-03 10:33:51 -05:00 committed by GitHub
parent 670bf82c38
commit 3b23d63234
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
389 changed files with 13652 additions and 7925 deletions

View file

@ -14,12 +14,9 @@ using API.Entities.Enums;
using API.Errors;
using API.Extensions;
using API.Helpers.Builders;
using API.Middleware.RateLimit;
using API.Services;
using API.Services.Plus;
using API.SignalR;
using AutoMapper;
using EasyCaching.Core;
using Hangfire;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
@ -46,6 +43,7 @@ public class AccountController : BaseApiController
private readonly IAccountService _accountService;
private readonly IEmailService _emailService;
private readonly IEventHub _eventHub;
private readonly ILocalizationService _localizationService;
/// <inheritdoc />
public AccountController(UserManager<AppUser> userManager,
@ -53,7 +51,8 @@ public class AccountController : BaseApiController
ITokenService tokenService, IUnitOfWork unitOfWork,
ILogger<AccountController> logger,
IMapper mapper, IAccountService accountService,
IEmailService emailService, IEventHub eventHub)
IEmailService emailService, IEventHub eventHub,
ILocalizationService localizationService)
{
_userManager = userManager;
_signInManager = signInManager;
@ -64,6 +63,7 @@ public class AccountController : BaseApiController
_accountService = accountService;
_emailService = emailService;
_eventHub = eventHub;
_localizationService = localizationService;
}
/// <summary>
@ -82,19 +82,21 @@ public class AccountController : BaseApiController
var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
if (resetPasswordDto.UserName == User.GetUsername() && !(User.IsInRole(PolicyConstants.ChangePasswordRole) || isAdmin))
return Unauthorized("You are not permitted to this operation.");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
if (resetPasswordDto.UserName != User.GetUsername() && !isAdmin)
return Unauthorized("You are not permitted to this operation.");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
if (string.IsNullOrEmpty(resetPasswordDto.OldPassword) && !isAdmin)
return BadRequest(new ApiException(400, "You must enter your existing password to change your account unless you're an admin"));
return BadRequest(
new ApiException(400,
await _localizationService.Translate(User.GetUserId(), "password-required")));
// If you're an admin and the username isn't yours, you don't need to validate the password
var isResettingOtherUser = (resetPasswordDto.UserName != User.GetUsername() && isAdmin);
if (!isResettingOtherUser && !await _userManager.CheckPasswordAsync(user, resetPasswordDto.OldPassword))
{
return BadRequest("Invalid Password");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-password"));
}
var errors = await _accountService.ChangeUserPassword(user, resetPasswordDto.Password);
@ -117,7 +119,7 @@ public class AccountController : BaseApiController
public async Task<ActionResult<UserDto>> RegisterFirstUser(RegisterDto registerDto)
{
var admins = await _userManager.GetUsersInRoleAsync("Admin");
if (admins.Count > 0) return BadRequest("Not allowed");
if (admins.Count > 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "denied"));
try
{
@ -135,8 +137,8 @@ public class AccountController : BaseApiController
if (!result.Succeeded) return BadRequest(result.Errors);
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
if (string.IsNullOrEmpty(token)) return BadRequest("There was an issue generating a confirmation token.");
if (!await ConfirmEmailToken(token, user)) return BadRequest($"There was an issue validating your email: {token}");
if (string.IsNullOrEmpty(token)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "confirm-token-gen"));
if (!await ConfirmEmailToken(token, user)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "validate-email", token));
var roleResult = await _userManager.AddToRoleAsync(user, PolicyConstants.AdminRole);
@ -163,7 +165,7 @@ public class AccountController : BaseApiController
await _unitOfWork.CommitAsync();
}
return BadRequest("Something went wrong when registering user");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "register-user"));
}
@ -180,9 +182,9 @@ public class AccountController : BaseApiController
.Include(u => u.UserPreferences)
.SingleOrDefaultAsync(x => x.NormalizedUserName == loginDto.Username.ToUpper());
if (user == null) return Unauthorized("Your credentials are not correct");
if (user == null) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "bad-credentials"));
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Contains(PolicyConstants.LoginRole)) return Unauthorized("Your account is disabled. Contact the server admin.");
if (!roles.Contains(PolicyConstants.LoginRole)) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "disabled-account"));
var result = await _signInManager
.CheckPasswordSignInAsync(user, loginDto.Password, true);
@ -190,12 +192,12 @@ public class AccountController : BaseApiController
if (result.IsLockedOut)
{
await _userManager.UpdateSecurityStampAsync(user);
return Unauthorized("You've been locked out from too many authorization attempts. Please wait 10 minutes.");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "locked-out"));
}
if (!result.Succeeded)
{
return Unauthorized(result.IsNotAllowed ? "You must confirm your email first" : "Your credentials are not correct");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), result.IsNotAllowed ? "confirm-email" : "bad-credentials"));
}
// Update LastActive on account
@ -256,7 +258,7 @@ public class AccountController : BaseApiController
var token = await _tokenService.ValidateRefreshToken(tokenRequestDto);
if (token == null)
{
return Unauthorized(new { message = "Invalid token" });
return Unauthorized(new { message = await _localizationService.Translate(User.GetUserId(), "invalid-token") });
}
return Ok(token);
@ -295,7 +297,7 @@ public class AccountController : BaseApiController
}
await _unitOfWork.RollbackAsync();
return BadRequest("Something went wrong, unable to reset key");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "unable-to-reset-key"));
}
@ -310,26 +312,27 @@ public class AccountController : BaseApiController
public async Task<ActionResult> UpdateEmail(UpdateEmailDto? dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized("You do not have permission");
if (user == null) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
if (dto == null || string.IsNullOrEmpty(dto.Email) || string.IsNullOrEmpty(dto.Password)) return BadRequest("Invalid payload");
if (dto == null || string.IsNullOrEmpty(dto.Email) || string.IsNullOrEmpty(dto.Password))
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-payload"));
// 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);
return BadRequest("You do not have permission");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
}
// Validate no other users exist with this email
if (user.Email!.Equals(dto.Email)) return Ok("Nothing to do");
if (user.Email!.Equals(dto.Email)) return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
// Check if email is used by another user
var existingUserEmail = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (existingUserEmail != null)
{
return BadRequest("You cannot share emails across multiple accounts");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "share-multiple-emails"));
}
// All validations complete, generate a new token and email it to the user at the new address. Confirm email link will update the email
@ -337,7 +340,7 @@ public class AccountController : BaseApiController
if (string.IsNullOrEmpty(token))
{
_logger.LogError("There was an issue generating a token for the email");
return BadRequest("There was an issue creating a confirmation email token. See logs.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generate-token"));
}
user.EmailConfirmed = false;
@ -392,10 +395,10 @@ public class AccountController : BaseApiController
public async Task<ActionResult> UpdateAgeRestriction(UpdateAgeRestrictionDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized("You do not have permission");
if (user == null) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
if (!await _accountService.HasChangeRestrictionRole(user)) return BadRequest("You do not have permission");
if (!await _accountService.HasChangeRestrictionRole(user)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
user.AgeRestriction = isAdmin ? AgeRating.NotApplicable : dto.AgeRating;
user.AgeRestrictionIncludeUnknowns = isAdmin || dto.IncludeUnknowns;
@ -410,7 +413,7 @@ public class AccountController : BaseApiController
catch (Exception ex)
{
_logger.LogError(ex, "There was an error updating the age restriction");
return BadRequest("There was an error updating the age restriction");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "age-restriction-update"));
}
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
@ -429,17 +432,17 @@ public class AccountController : BaseApiController
{
var adminUser = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (adminUser == null) return Unauthorized();
if (!await _unitOfWork.UserRepository.IsUserAdminAsync(adminUser)) return Unauthorized("You do not have permission");
if (!await _unitOfWork.UserRepository.IsUserAdminAsync(adminUser)) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(dto.UserId);
if (user == null) return BadRequest("User does not exist");
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-user"));
// Check if username is changing
if (!user.UserName!.Equals(dto.Username))
{
// Validate username change
var errors = await _accountService.ValidateUsername(dto.Username);
if (errors.Any()) return BadRequest("Username already taken");
if (errors.Any()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "username-taken"));
user.UserName = dto.Username;
_unitOfWork.UserRepository.Update(user);
}
@ -504,7 +507,7 @@ public class AccountController : BaseApiController
}
await _unitOfWork.RollbackAsync();
return BadRequest("There was an exception when updating the user");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-user-update"));
}
/// <summary>
@ -520,9 +523,9 @@ public class AccountController : BaseApiController
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
if (user.EmailConfirmed)
return BadRequest("User is already confirmed");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-confirmed"));
if (string.IsNullOrEmpty(user.ConfirmationToken))
return BadRequest("Manual setup is unable to be completed. Please cancel and recreate the invite.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "manual-setup-fail"));
return await _accountService.GenerateEmailLink(Request, user.ConfirmationToken, "confirm-email", user.Email!, withBaseUrl);
}
@ -539,7 +542,7 @@ public class AccountController : BaseApiController
public async Task<ActionResult<string>> InviteUser(InviteUserDto dto)
{
var adminUser = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (adminUser == null) return Unauthorized("You are not permitted");
if (adminUser == null) return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
_logger.LogInformation("{User} is inviting {Email} to the server", adminUser.UserName, dto.Email);
@ -552,8 +555,8 @@ public class AccountController : BaseApiController
{
var invitedUser = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (await _userManager.IsEmailConfirmedAsync(invitedUser!))
return BadRequest($"User is already registered as {invitedUser!.UserName}");
return BadRequest("User is already invited under this email and has yet to accepted invite.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-registered", invitedUser!.UserName));
return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-invited"));
}
}
@ -608,7 +611,7 @@ public class AccountController : BaseApiController
if (string.IsNullOrEmpty(token))
{
_logger.LogError("There was an issue generating a token for the email");
return BadRequest("There was an creating the invite user");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-invite-user"));
}
user.ConfirmationToken = token;
@ -650,7 +653,7 @@ public class AccountController : BaseApiController
_logger.LogError(ex, "There was an error during invite user flow, unable to send an email");
}
return BadRequest("There was an error setting up your account. Please check the logs");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-invite-user"));
}
/// <summary>
@ -667,7 +670,7 @@ public class AccountController : BaseApiController
if (user == null)
{
_logger.LogInformation("confirm-email failed from invalid registered email: {Email}", dto.Email);
return BadRequest("Invalid email confirmation");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-email-confirmation"));
}
// Validate Password and Username
@ -688,7 +691,7 @@ public class AccountController : BaseApiController
if (!await ConfirmEmailToken(dto.Token, user))
{
_logger.LogInformation("confirm-email failed from invalid token: {Token}", dto.Token);
return BadRequest("Invalid email confirmation");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-email-confirmation"));
}
user.UserName = dto.Username;
@ -731,13 +734,13 @@ public class AccountController : BaseApiController
if (user == null)
{
_logger.LogInformation("confirm-email failed from invalid registered email: {Email}", dto.Email);
return BadRequest("Invalid email confirmation");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-email-confirmation"));
}
if (!await ConfirmEmailToken(dto.Token, user))
{
_logger.LogInformation("confirm-email failed from invalid token: {Token}", dto.Token);
return BadRequest("Invalid email confirmation");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-email-confirmation"));
}
_logger.LogInformation("User is updating email from {OldEmail} to {NewEmail}", user.Email, dto.Email);
@ -745,7 +748,7 @@ public class AccountController : BaseApiController
if (!result.Succeeded)
{
_logger.LogError("Unable to update email for users: {Errors}", result.Errors.Select(e => e.Description));
return BadRequest("Unable to update email for user. Check logs");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-user-email-update"));
}
user.ConfirmationToken = null;
await _unitOfWork.CommitAsync();
@ -768,7 +771,7 @@ public class AccountController : BaseApiController
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (user == null)
{
return BadRequest("Invalid credentials");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bad-credentials"));
}
var result = await _userManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider,
@ -776,16 +779,16 @@ public class AccountController : BaseApiController
if (!result)
{
_logger.LogInformation("Unable to reset password, your email token is not correct: {@Dto}", dto);
return BadRequest("Invalid credentials");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bad-credentials"));
}
var errors = await _accountService.ChangeUserPassword(user, dto.Password);
return errors.Any() ? BadRequest(errors) : Ok("Password updated");
return errors.Any() ? BadRequest(errors) : Ok(await _localizationService.Translate(User.GetUserId(), "password-updated"));
}
catch (Exception ex)
{
_logger.LogError(ex, "There was an unexpected error when confirming new password");
return BadRequest("There was an unexpected error when confirming new password");
return BadRequest("generic-password-update");
}
}
@ -804,15 +807,15 @@ public class AccountController : BaseApiController
if (user == null)
{
_logger.LogError("There are no users with email: {Email} but user is requesting password reset", email);
return Ok("An email will be sent to the email if it exists in our database");
return Ok(await _localizationService.Translate(User.GetUserId(), "forgot-password-generic"));
}
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Any(r => r is PolicyConstants.AdminRole or PolicyConstants.ChangePasswordRole))
return Unauthorized("You are not permitted to this operation.");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
if (string.IsNullOrEmpty(user.Email) || !user.EmailConfirmed)
return BadRequest("You do not have an email on account or it has not been confirmed");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "confirm-email"));
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var emailLink = await _accountService.GenerateEmailLink(Request, token, "confirm-reset-password", user.Email);
@ -825,10 +828,10 @@ public class AccountController : BaseApiController
ServerConfirmationLink = emailLink,
InstallId = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallId)).Value
});
return Ok("Email sent");
return Ok(await _localizationService.Translate(User.GetUserId(), "email-sent"));
}
return Ok("Your server is not accessible. The Link to reset your password is in the logs.");
return Ok(await _localizationService.Translate(User.GetUserId(), "not-accessible-password"));
}
[HttpGet("email-confirmed")]
@ -845,12 +848,12 @@ public class AccountController : BaseApiController
public async Task<ActionResult<UserDto>> ConfirmMigrationEmail(ConfirmMigrationEmailDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (user == null) return BadRequest("Invalid credentials");
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "bad-credentials"));
if (!await ConfirmEmailToken(dto.Token, user))
{
_logger.LogInformation("confirm-migration-email email token is invalid");
return BadRequest("Invalid credentials");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bad-credentials"));
}
await _unitOfWork.CommitAsync();
@ -881,12 +884,12 @@ public class AccountController : BaseApiController
public async Task<ActionResult<string>> ResendConfirmationSendEmail([FromQuery] int userId)
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return BadRequest("User does not exist");
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-user"));
if (string.IsNullOrEmpty(user.Email))
return BadRequest(
"This user needs to migrate. Have them log out and login to trigger a migration flow");
if (user.EmailConfirmed) return BadRequest("User already confirmed");
await _localizationService.Translate(User.GetUserId(), "user-migration-needed"));
if (user.EmailConfirmed) return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-confirmed"));
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var emailLink = await _accountService.GenerateEmailLink(Request, token, "confirm-email", user.Email);
@ -907,12 +910,12 @@ public class AccountController : BaseApiController
catch (Exception ex)
{
_logger.LogError(ex, "There was an issue resending invite email");
return BadRequest("There was an issue resending invite email");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-invite-email"));
}
return Ok(emailLink);
}
return Ok("The server is not accessible externally");
return Ok(await _localizationService.Translate(User.GetUserId(), "not-accessible"));
}
/// <summary>
@ -926,7 +929,7 @@ public class AccountController : BaseApiController
{
// If there is an admin account already, return
var users = await _unitOfWork.UserRepository.GetAdminUsersAsync();
if (users.Any()) return BadRequest("Admin already exists");
if (users.Any()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "admin-already-exists"));
// Check if there is an existing invite
var emailValidationErrors = await _accountService.ValidateEmail(dto.Email);
@ -934,27 +937,27 @@ public class AccountController : BaseApiController
{
var invitedUser = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (await _userManager.IsEmailConfirmedAsync(invitedUser!))
return BadRequest($"User is already registered as {invitedUser!.UserName}");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-registered", invitedUser!.UserName));
_logger.LogInformation("A user is attempting to login, but hasn't accepted email invite");
return BadRequest("User is already invited under this email and has yet to accepted invite.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-already-invited"));
}
var user = await _userManager.Users
.Include(u => u.UserPreferences)
.SingleOrDefaultAsync(x => x.NormalizedUserName == dto.Username.ToUpper());
if (user == null) return BadRequest("Invalid username");
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-username"));
var validPassword = await _signInManager.UserManager.CheckPasswordAsync(user, dto.Password);
if (!validPassword) return BadRequest("Your credentials are not correct");
if (!validPassword) return BadRequest(await _localizationService.Translate(User.GetUserId(), "bad-credentials"));
try
{
var token = await _userManager.GenerateEmailConfirmationTokenAsync(user);
user.Email = dto.Email;
if (!await ConfirmEmailToken(token, user)) return BadRequest("There was a critical error during migration");
if (!await ConfirmEmailToken(token, user)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "critical-email-migration"));
_unitOfWork.UserRepository.Update(user);
await _unitOfWork.CommitAsync();
@ -968,7 +971,7 @@ public class AccountController : BaseApiController
await _unitOfWork.CommitAsync();
}
return BadRequest("There was an error setting up your account. Please check the logs");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "critical-email-migration"));
}

View file

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using API.Data;
using API.DTOs.Reader;
using API.Entities.Enums;
using API.Extensions;
using API.Services;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
@ -18,13 +19,16 @@ public class BookController : BaseApiController
private readonly IBookService _bookService;
private readonly IUnitOfWork _unitOfWork;
private readonly ICacheService _cacheService;
private readonly ILocalizationService _localizationService;
public BookController(IBookService bookService,
IUnitOfWork unitOfWork, ICacheService cacheService)
IUnitOfWork unitOfWork, ICacheService cacheService,
ILocalizationService localizationService)
{
_bookService = bookService;
_unitOfWork = unitOfWork;
_cacheService = cacheService;
_localizationService = localizationService;
}
/// <summary>
@ -37,7 +41,7 @@ public class BookController : BaseApiController
public async Task<ActionResult<BookInfoDto>> GetBookInfo(int chapterId)
{
var dto = await _unitOfWork.ChapterRepository.GetChapterInfoDtoAsync(chapterId);
if (dto == null) return BadRequest("Chapter does not exist");
if (dto == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var bookTitle = string.Empty;
switch (dto.SeriesFormat)
{
@ -92,14 +96,14 @@ public class BookController : BaseApiController
[AllowAnonymous]
public async Task<ActionResult> GetBookPageResources(int chapterId, [FromQuery] string file)
{
if (chapterId <= 0) return BadRequest("Chapter is not valid");
if (chapterId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Chapter is not valid");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath, BookService.BookReaderOptions);
var key = BookService.CoalesceKeyForAnyFile(book, file);
if (!book.Content.AllFiles.ContainsLocalFileRefWithKey(key)) return BadRequest("File was not found in book");
if (!book.Content.AllFiles.ContainsLocalFileRefWithKey(key)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "file-missing"));
var bookFile = book.Content.AllFiles.GetLocalFileRefByKey(key);
var content = await bookFile.ReadContentAsBytesAsync();
@ -118,9 +122,9 @@ public class BookController : BaseApiController
[HttpGet("{chapterId}/chapters")]
public async Task<ActionResult<ICollection<BookChapterItem>>> GetBookChapters(int chapterId)
{
if (chapterId <= 0) return BadRequest("Chapter is not valid");
if (chapterId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Chapter is not valid");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
try
{
@ -144,7 +148,7 @@ public class BookController : BaseApiController
public async Task<ActionResult<string>> GetBookPage(int chapterId, [FromQuery] int page)
{
var chapter = await _cacheService.Ensure(chapterId);
if (chapter == null) return BadRequest("Could not find Chapter");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var path = _cacheService.GetCachedFile(chapter);
var baseUrl = "//" + Request.Host + Request.PathBase + "/api/";
@ -155,7 +159,7 @@ public class BookController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
}
}

View file

@ -20,12 +20,15 @@ public class CollectionController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly ICollectionTagService _collectionService;
private readonly ILocalizationService _localizationService;
/// <inheritdoc />
public CollectionController(IUnitOfWork unitOfWork, ICollectionTagService collectionService)
public CollectionController(IUnitOfWork unitOfWork, ICollectionTagService collectionService,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_collectionService = collectionService;
_localizationService = localizationService;
}
/// <summary>
@ -87,14 +90,14 @@ public class CollectionController : BaseApiController
{
try
{
if (await _collectionService.UpdateTag(updatedTag)) return Ok("Tag updated successfully");
if (await _collectionService.UpdateTag(updatedTag)) return Ok(await _localizationService.Translate(User.GetUserId(), "collection-updated-successfully"));
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
return BadRequest("Something went wrong, please try again");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
}
/// <summary>
@ -111,7 +114,7 @@ public class CollectionController : BaseApiController
if (await _collectionService.AddTagToSeries(tag, dto.SeriesIds)) return Ok();
return BadRequest("There was an issue updating series with collection tag");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
}
/// <summary>
@ -126,18 +129,17 @@ public class CollectionController : BaseApiController
try
{
var tag = await _unitOfWork.CollectionTagRepository.GetTagAsync(updateSeriesForTagDto.Tag.Id, CollectionTagIncludes.SeriesMetadata);
if (tag == null) return BadRequest("Not a valid Tag");
if (tag == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "collection-doesnt-exist"));
tag.SeriesMetadatas ??= new List<SeriesMetadata>();
if (await _collectionService.RemoveTagFromSeries(tag, updateSeriesForTagDto.SeriesIdsToRemove))
return Ok("Tag updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "collection-updated"));
}
catch (Exception)
{
await _unitOfWork.RollbackAsync();
}
return BadRequest("Something went wrong. Please try again.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
}
}

View file

@ -21,13 +21,16 @@ public class DeviceController : BaseApiController
private readonly IDeviceService _deviceService;
private readonly IEmailService _emailService;
private readonly IEventHub _eventHub;
private readonly ILocalizationService _localizationService;
public DeviceController(IUnitOfWork unitOfWork, IDeviceService deviceService, IEmailService emailService, IEventHub eventHub)
public DeviceController(IUnitOfWork unitOfWork, IDeviceService deviceService,
IEmailService emailService, IEventHub eventHub, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_deviceService = deviceService;
_emailService = emailService;
_eventHub = eventHub;
_localizationService = localizationService;
}
@ -36,9 +39,19 @@ public class DeviceController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
if (user == null) return Unauthorized();
var device = await _deviceService.Create(dto, user);
try
{
var device = await _deviceService.Create(dto, user);
if (device == null)
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-create"));
}
catch (KavitaException ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
if (device == null) return BadRequest("There was an error when creating the device");
return Ok();
}
@ -50,7 +63,7 @@ public class DeviceController : BaseApiController
if (user == null) return Unauthorized();
var device = await _deviceService.Update(dto, user);
if (device == null) return BadRequest("There was an error when updating the device");
if (device == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-update"));
return Ok();
}
@ -63,12 +76,12 @@ public class DeviceController : BaseApiController
[HttpDelete]
public async Task<ActionResult> DeleteDevice(int deviceId)
{
if (deviceId <= 0) return BadRequest("Not a valid deviceId");
if (deviceId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "device-doesnt-exist"));
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
if (user == null) return Unauthorized();
if (await _deviceService.Delete(user, deviceId)) return Ok();
return BadRequest("Could not delete device");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-device-delete"));
}
[HttpGet]
@ -81,15 +94,16 @@ public class DeviceController : BaseApiController
[HttpPost("send-to")]
public async Task<ActionResult> SendToDevice(SendToDeviceDto dto)
{
if (dto.ChapterIds.Any(i => i < 0)) return BadRequest("ChapterIds must be greater than 0");
if (dto.DeviceId < 0) return BadRequest("DeviceId must be greater than 0");
if (dto.ChapterIds.Any(i => i < 0)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "ChapterIds"));
if (dto.DeviceId < 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "DeviceId"));
if (await _emailService.IsDefaultEmailService())
return BadRequest("Send to device cannot be used with Kavita's email service. Please configure your own.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email"));
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress,
MessageFactory.SendingToDeviceEvent($"Transferring files to your device", "started"), userId);
MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"),
"started"), userId);
try
{
var success = await _deviceService.SendTo(dto.ChapterIds, dto.DeviceId);
@ -97,15 +111,16 @@ public class DeviceController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
finally
{
await _eventHub.SendMessageToAsync(MessageFactory.SendingToDevice,
MessageFactory.SendingToDeviceEvent($"Transferring files to your device", "ended"), userId);
MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"),
"ended"), userId);
}
return BadRequest("There was an error sending the file to the device");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-send-to"));
}
@ -113,19 +128,21 @@ public class DeviceController : BaseApiController
[HttpPost("send-series-to")]
public async Task<ActionResult> SendSeriesToDevice(SendSeriesToDeviceDto dto)
{
if (dto.SeriesId <= 0) return BadRequest("SeriesId must be greater than 0");
if (dto.DeviceId < 0) return BadRequest("DeviceId must be greater than 0");
if (dto.SeriesId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "SeriesId"));
if (dto.DeviceId < 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "DeviceId"));
if (await _emailService.IsDefaultEmailService())
return BadRequest("Send to device cannot be used with Kavita's email service. Please configure your own.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "send-to-kavita-email"));
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress, MessageFactory.SendingToDeviceEvent($"Transferring files to your device", "started"), userId);
await _eventHub.SendMessageToAsync(MessageFactory.NotificationProgress,
MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"),
"started"), userId);
var series =
await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId,
SeriesIncludes.Volumes | SeriesIncludes.Chapters);
if (series == null) return BadRequest("Series doesn't Exist");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "series-doesnt-exist"));
var chapterIds = series.Volumes.SelectMany(v => v.Chapters.Select(c => c.Id)).ToList();
try
{
@ -134,14 +151,16 @@ public class DeviceController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
finally
{
await _eventHub.SendMessageToAsync(MessageFactory.SendingToDevice, MessageFactory.SendingToDeviceEvent($"Transferring files to your device", "ended"), userId);
await _eventHub.SendMessageToAsync(MessageFactory.SendingToDevice,
MessageFactory.SendingToDeviceEvent(await _localizationService.Translate(User.GetUserId(), "send-to-device-status"),
"ended"), userId);
}
return BadRequest("There was an error sending the file(s) to the device");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-send-to"));
}

View file

@ -30,11 +30,12 @@ public class DownloadController : BaseApiController
private readonly ILogger<DownloadController> _logger;
private readonly IBookmarkService _bookmarkService;
private readonly IAccountService _accountService;
private readonly ILocalizationService _localizationService;
private const string DefaultContentType = "application/octet-stream";
public DownloadController(IUnitOfWork unitOfWork, IArchiveService archiveService, IDirectoryService directoryService,
IDownloadService downloadService, IEventHub eventHub, ILogger<DownloadController> logger, IBookmarkService bookmarkService,
IAccountService accountService)
IAccountService accountService, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_archiveService = archiveService;
@ -44,6 +45,7 @@ public class DownloadController : BaseApiController
_logger = logger;
_bookmarkService = bookmarkService;
_accountService = accountService;
_localizationService = localizationService;
}
/// <summary>
@ -92,9 +94,9 @@ public class DownloadController : BaseApiController
[HttpGet("volume")]
public async Task<ActionResult> DownloadVolume(int volumeId)
{
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
if (!await HasDownloadPermission()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
var volume = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(volumeId);
if (volume == null) return BadRequest("Volume doesn't exist");
if (volume == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "volume-doesnt-exist"));
var files = await _unitOfWork.VolumeRepository.GetFilesForVolume(volumeId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId);
try
@ -128,10 +130,10 @@ public class DownloadController : BaseApiController
[HttpGet("chapter")]
public async Task<ActionResult> DownloadChapter(int chapterId)
{
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
if (!await HasDownloadPermission()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Invalid chapter");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var volume = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapter.VolumeId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume!.SeriesId);
try
@ -178,7 +180,7 @@ public class DownloadController : BaseApiController
[HttpGet("series")]
public async Task<ActionResult> DownloadSeries(int seriesId)
{
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
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);
@ -200,8 +202,8 @@ public class DownloadController : BaseApiController
[HttpPost("bookmarks")]
public async Task<ActionResult> DownloadBookmarkPages(DownloadBookmarkDto downloadBookmarkDto)
{
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
if (!downloadBookmarkDto.Bookmarks.Any()) return BadRequest("Bookmarks cannot be empty");
if (!await HasDownloadPermission()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied"));
if (!downloadBookmarkDto.Bookmarks.Any()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmarks-empty"));
// We know that all bookmarks will be for one single seriesId
var userId = User.GetUserId()!;

View file

@ -22,13 +22,16 @@ public class ImageController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly IDirectoryService _directoryService;
private readonly IImageService _imageService;
private readonly ILocalizationService _localizationService;
/// <inheritdoc />
public ImageController(IUnitOfWork unitOfWork, IDirectoryService directoryService, IImageService imageService)
public ImageController(IUnitOfWork unitOfWork, IDirectoryService directoryService,
IImageService imageService, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_directoryService = directoryService;
_imageService = imageService;
_localizationService = localizationService;
}
/// <summary>
@ -42,7 +45,7 @@ public class ImageController : BaseApiController
{
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-cover-image"));
var format = _directoryService.FileSystem.Path.GetExtension(path);
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
@ -59,7 +62,7 @@ public class ImageController : BaseApiController
{
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-cover-image"));
var format = _directoryService.FileSystem.Path.GetExtension(path);
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
@ -76,7 +79,7 @@ public class ImageController : BaseApiController
{
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-cover-image"));
var format = _directoryService.FileSystem.Path.GetExtension(path);
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));
@ -93,7 +96,7 @@ public class ImageController : BaseApiController
{
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId));
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"No cover image");
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-cover-image"));
var format = _directoryService.FileSystem.Path.GetExtension(path);
Response.AddCacheHeader(path);
@ -115,7 +118,7 @@ public class ImageController : BaseApiController
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
{
var destFile = await GenerateCollectionCoverImage(collectionTagId);
if (string.IsNullOrEmpty(destFile)) return BadRequest("No cover image");
if (string.IsNullOrEmpty(destFile)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-cover-image"));
return PhysicalFile(destFile, MimeTypeMap.GetMimeType(_directoryService.FileSystem.Path.GetExtension(destFile)), _directoryService.FileSystem.Path.GetFileName(destFile));
}
var format = _directoryService.FileSystem.Path.GetExtension(path);
@ -137,7 +140,7 @@ public class ImageController : BaseApiController
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
{
var destFile = await GenerateReadingListCoverImage(readingListId);
if (string.IsNullOrEmpty(destFile)) return BadRequest("No cover image");
if (string.IsNullOrEmpty(destFile)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-cover-image"));
return PhysicalFile(destFile, MimeTypeMap.GetMimeType(_directoryService.FileSystem.Path.GetExtension(destFile)), _directoryService.FileSystem.Path.GetFileName(destFile));
}
@ -199,7 +202,7 @@ public class ImageController : BaseApiController
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
if (userId == 0) return BadRequest();
var bookmark = await _unitOfWork.UserRepository.GetBookmarkForPage(pageNum, chapterId, userId);
if (bookmark == null) return BadRequest("Bookmark does not exist");
if (bookmark == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-doesnt-exist"));
var bookmarkDirectory =
(await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.BookmarkDirectory)).Value;
@ -220,7 +223,7 @@ public class ImageController : BaseApiController
{
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
if (userId == 0) return BadRequest();
if (string.IsNullOrEmpty(url)) return BadRequest("Url cannot be null");
if (string.IsNullOrEmpty(url)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "must-be-defined", "Url"));
var encodeFormat = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs;
// Check if the domain exists
@ -235,7 +238,7 @@ public class ImageController : BaseApiController
}
catch (Exception)
{
return BadRequest("There was an issue fetching favicon for domain");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-favicon"));
}
}
@ -256,10 +259,11 @@ public class ImageController : BaseApiController
public async Task<ActionResult> GetCoverUploadImage(string filename, string apiKey)
{
if (await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey) == 0) return BadRequest();
if (filename.Contains("..")) return BadRequest("Invalid Filename");
if (filename.Contains("..")) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-filename"));
var path = Path.Join(_directoryService.TempDirectory, filename);
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path)) return BadRequest($"File does not exist");
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
return BadRequest(await _localizationService.Translate(User.GetUserId(), "file-doesnt-exist"));
var format = _directoryService.FileSystem.Path.GetExtension(path);
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), _directoryService.FileSystem.Path.GetFileName(path));

View file

@ -36,13 +36,14 @@ public class LibraryController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly IEventHub _eventHub;
private readonly ILibraryWatcher _libraryWatcher;
private readonly ILocalizationService _localizationService;
private readonly IEasyCachingProvider _libraryCacheProvider;
private const string CacheKey = "library_";
public LibraryController(IDirectoryService directoryService,
ILogger<LibraryController> logger, IMapper mapper, ITaskScheduler taskScheduler,
IUnitOfWork unitOfWork, IEventHub eventHub, ILibraryWatcher libraryWatcher,
IEasyCachingProviderFactory cachingProviderFactory)
IEasyCachingProviderFactory cachingProviderFactory, ILocalizationService localizationService)
{
_directoryService = directoryService;
_logger = logger;
@ -51,6 +52,7 @@ public class LibraryController : BaseApiController
_unitOfWork = unitOfWork;
_eventHub = eventHub;
_libraryWatcher = libraryWatcher;
_localizationService = localizationService;
_libraryCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.Library);
}
@ -66,7 +68,7 @@ public class LibraryController : BaseApiController
{
if (await _unitOfWork.LibraryRepository.LibraryExists(dto.Name))
{
return BadRequest("Library name already exists. Please choose a unique name to the server.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-name-exists"));
}
var library = new LibraryBuilder(dto.Name, dto.Type)
@ -96,7 +98,7 @@ public class LibraryController : BaseApiController
}
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was a critical issue. Please try again.");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-library"));
_logger.LogInformation("Created a new library: {LibraryName}", library.Name);
await _libraryWatcher.RestartWatching();
@ -160,7 +162,8 @@ public class LibraryController : BaseApiController
public async Task<ActionResult<IEnumerable<JumpKeyDto>>> GetJumpBar(int libraryId)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
if (!await _unitOfWork.UserRepository.HasAccessToLibrary(libraryId, userId)) return BadRequest("User does not have access to library");
if (!await _unitOfWork.UserRepository.HasAccessToLibrary(libraryId, userId))
return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-library-access"));
return Ok(_unitOfWork.LibraryRepository.GetJumpBarAsync(libraryId));
}
@ -175,9 +178,9 @@ public class LibraryController : BaseApiController
public async Task<ActionResult<MemberDto>> UpdateUserLibraries(UpdateLibraryForUserDto updateLibraryForUserDto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username);
if (user == null) return BadRequest("Could not validate user");
if (user == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "user-doesnt-exist"));
var libraryString = string.Join(",", updateLibraryForUserDto.SelectedLibraries.Select(x => x.Name));
var libraryString = string.Join(',', updateLibraryForUserDto.SelectedLibraries.Select(x => x.Name));
_logger.LogInformation("Granting user {UserName} access to: {Libraries}", updateLibraryForUserDto.Username, libraryString);
var allLibraries = await _unitOfWork.LibraryRepository.GetLibrariesAsync();
@ -195,7 +198,6 @@ public class LibraryController : BaseApiController
{
library.AppUsers.Add(user);
}
}
if (!_unitOfWork.HasChanges())
@ -213,7 +215,7 @@ public class LibraryController : BaseApiController
}
return BadRequest("There was a critical issue. Please try again.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-library"));
}
/// <summary>
@ -224,9 +226,9 @@ public class LibraryController : BaseApiController
/// <returns></returns>
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("scan")]
public ActionResult Scan(int libraryId, bool force = false)
public async Task<ActionResult> Scan(int libraryId, bool force = false)
{
if (libraryId <= 0) return BadRequest("Invalid libraryId");
if (libraryId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "libraryId"));
_taskScheduler.ScanLibrary(libraryId, force);
return Ok();
}
@ -277,7 +279,7 @@ public class LibraryController : BaseApiController
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
if (!isAdmin) return BadRequest("API key must belong to an admin");
if (dto.FolderPath.Contains("..")) return BadRequest("Invalid Path");
if (dto.FolderPath.Contains("..")) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-path"));
dto.FolderPath = Services.Tasks.Scanner.Parser.Parser.NormalizePath(dto.FolderPath);
@ -310,12 +312,11 @@ public class LibraryController : BaseApiController
if (TaskScheduler.HasScanTaskRunningForLibrary(libraryId))
{
_logger.LogInformation("User is attempting to delete a library while a scan is in progress");
return BadRequest(
"You cannot delete a library while a scan is in progress. Please wait for scan to complete or restart Kavita then try to delete");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "delete-library-while-scan"));
}
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId);
if (library == null) return BadRequest("Library no longer exists");
if (library == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-doesnt-exist"));
// Due to a bad schema that I can't figure out how to fix, we need to erase all RelatedSeries before we delete the library
// Aka SeriesRelation has an invalid foreign key
@ -354,7 +355,7 @@ public class LibraryController : BaseApiController
}
catch (Exception ex)
{
_logger.LogError(ex, "There was a critical error trying to delete the library");
_logger.LogError(ex, await _localizationService.Translate(User.GetUserId(), "generic-library"));
await _unitOfWork.RollbackAsync();
return Ok(false);
}
@ -384,11 +385,11 @@ public class LibraryController : BaseApiController
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto dto)
{
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(dto.Id, LibraryIncludes.Folders);
if (library == null) return BadRequest("Library doesn't exist");
if (library == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-doesnt-exist"));
var newName = dto.Name.Trim();
if (await _unitOfWork.LibraryRepository.LibraryExists(newName) && !library.Name.Equals(newName))
return BadRequest("Library name already exists");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-name-exists"));
var originalFolders = library.Folders.Select(x => x.Path).ToList();
@ -416,7 +417,7 @@ public class LibraryController : BaseApiController
_unitOfWork.LibraryRepository.Update(library);
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was a critical issue updating the library.");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-library-update"));
if (originalFolders.Count != dto.Folders.Count() || typeUpdate)
{
await _libraryWatcher.RestartWatching();

View file

@ -5,6 +5,8 @@ using API.Data;
using API.DTOs.Account;
using API.DTOs.License;
using API.Entities.Enums;
using API.Extensions;
using API.Services;
using API.Services.Plus;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
@ -18,13 +20,15 @@ public class LicenseController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly ILogger<LicenseController> _logger;
private readonly ILicenseService _licenseService;
private readonly ILocalizationService _localizationService;
public LicenseController(IUnitOfWork unitOfWork, ILogger<LicenseController> logger,
ILicenseService licenseService)
ILicenseService licenseService, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_logger = logger;
_licenseService = licenseService;
_localizationService = localizationService;
}
/// <summary>
@ -73,7 +77,14 @@ public class LicenseController : BaseApiController
[HttpPost]
public async Task<ActionResult> UpdateLicense(UpdateLicenseDto dto)
{
await _licenseService.AddLicense(dto.License.Trim(), dto.Email.Trim());
try
{
await _licenseService.AddLicense(dto.License.Trim(), dto.Email.Trim());
}
catch (Exception ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
return Ok();
}
}

View file

@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using API.DTOs.Filtering;
using API.Services;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
public class LocaleController : BaseApiController
{
private readonly ILocalizationService _localizationService;
public LocaleController(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
[HttpGet]
public ActionResult<IEnumerable<string>> GetAllLocales()
{
var languages = _localizationService.GetLocales().Select(c => new CultureInfo(c)).Select(c =>
new LanguageDto()
{
Title = c.DisplayName,
IsoCode = c.IetfLanguageTag
}).Where(l => !string.IsNullOrEmpty(l.IsoCode));
return Ok(languages);
}
}

View file

@ -10,6 +10,7 @@ using API.DTOs.Filtering;
using API.DTOs.Metadata;
using API.Entities.Enums;
using API.Extensions;
using API.Services;
using Kavita.Common.Extensions;
using Microsoft.AspNetCore.Mvc;
@ -19,10 +20,12 @@ namespace API.Controllers;
public class MetadataController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILocalizationService _localizationService;
public MetadataController(IUnitOfWork unitOfWork)
public MetadataController(IUnitOfWork unitOfWork, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_localizationService = localizationService;
}
/// <summary>
@ -35,7 +38,7 @@ public class MetadataController : BaseApiController
public async Task<ActionResult<IList<GenreTagDto>>> GetAllGenres(string? libraryIds)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var ids = libraryIds?.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
if (ids != null && ids.Count > 0)
{
return Ok(await _unitOfWork.GenreRepository.GetAllGenreDtosForLibrariesAsync(ids, userId));
@ -56,7 +59,7 @@ public class MetadataController : BaseApiController
public async Task<ActionResult<IList<PersonDto>>> GetAllPeople(string? libraryIds)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var ids = libraryIds?.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
if (ids != null && ids.Count > 0)
{
return Ok(await _unitOfWork.PersonRepository.GetAllPeopleDtosForLibrariesAsync(ids, userId));
@ -74,7 +77,7 @@ public class MetadataController : BaseApiController
public async Task<ActionResult<IList<TagDto>>> GetAllTags(string? libraryIds)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var ids = libraryIds?.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
if (ids != null && ids.Count > 0)
{
return Ok(await _unitOfWork.TagRepository.GetAllTagDtosForLibrariesAsync(ids, userId));
@ -92,7 +95,7 @@ public class MetadataController : BaseApiController
[HttpGet("age-ratings")]
public async Task<ActionResult<IList<AgeRatingDto>>> GetAllAgeRatings(string? libraryIds)
{
var ids = libraryIds?.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
if (ids != null && ids.Count > 0)
{
return Ok(await _unitOfWork.LibraryRepository.GetAllAgeRatingsDtosForLibrariesAsync(ids));
@ -115,7 +118,7 @@ public class MetadataController : BaseApiController
[HttpGet("publication-status")]
public ActionResult<IList<AgeRatingDto>> GetAllPublicationStatus(string? libraryIds)
{
var ids = libraryIds?.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
if (ids is {Count: > 0})
{
return Ok(_unitOfWork.LibraryRepository.GetAllPublicationStatusesDtosForLibrariesAsync(ids));
@ -138,7 +141,7 @@ public class MetadataController : BaseApiController
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Instant, VaryByQueryKeys = new []{"libraryIds"})]
public async Task<ActionResult<IList<LanguageDto>>> GetAllLanguages(string? libraryIds)
{
var ids = libraryIds?.Split(",", StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
var ids = libraryIds?.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList();
if (ids is {Count: > 0})
{
return Ok(await _unitOfWork.LibraryRepository.GetAllLanguagesForLibrariesAsync(ids));
@ -168,9 +171,9 @@ public class MetadataController : BaseApiController
[HttpGet("chapter-summary")]
public async Task<ActionResult<string>> GetChapterSummary(int chapterId)
{
if (chapterId <= 0) return BadRequest("Chapter does not exist");
if (chapterId <= 0) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Chapter does not exist");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
return Ok(chapter.Summary);
}
}

View file

@ -17,7 +17,6 @@ using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.Services;
using EasyCaching.Core;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -37,6 +36,7 @@ public class OpdsController : BaseApiController
private readonly IReaderService _readerService;
private readonly ISeriesService _seriesService;
private readonly IAccountService _accountService;
private readonly ILocalizationService _localizationService;
private readonly XmlSerializer _xmlSerializer;
@ -71,7 +71,7 @@ public class OpdsController : BaseApiController
public OpdsController(IUnitOfWork unitOfWork, IDownloadService downloadService,
IDirectoryService directoryService, ICacheService cacheService,
IReaderService readerService, ISeriesService seriesService,
IAccountService accountService, IEasyCachingProvider provider)
IAccountService accountService, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_downloadService = downloadService;
@ -80,6 +80,7 @@ public class OpdsController : BaseApiController
_readerService = readerService;
_seriesService = seriesService;
_accountService = accountService;
_localizationService = localizationService;
_xmlSerializer = new XmlSerializer(typeof(Feed));
_xmlOpenSearchSerializer = new XmlSerializer(typeof(OpenSearchDescription));
@ -90,8 +91,9 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> Get(string apiKey)
{
var userId = await GetUser(apiKey);
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var (baseUrl, prefix) = await GetPrefix();
@ -100,10 +102,10 @@ public class OpdsController : BaseApiController
feed.Entries.Add(new FeedEntry()
{
Id = "onDeck",
Title = "On Deck",
Title = await _localizationService.Translate(userId, "on-deck"),
Content = new FeedEntryContent()
{
Text = "Browse by On Deck"
Text = await _localizationService.Translate(userId, "browse-on-deck")
},
Links = new List<FeedLink>()
{
@ -113,10 +115,10 @@ public class OpdsController : BaseApiController
feed.Entries.Add(new FeedEntry()
{
Id = "recentlyAdded",
Title = "Recently Added",
Title = await _localizationService.Translate(userId, "recently-added"),
Content = new FeedEntryContent()
{
Text = "Browse by Recently Added"
Text = await _localizationService.Translate(userId, "browse-recently-added")
},
Links = new List<FeedLink>()
{
@ -126,10 +128,10 @@ public class OpdsController : BaseApiController
feed.Entries.Add(new FeedEntry()
{
Id = "readingList",
Title = "Reading Lists",
Title = await _localizationService.Translate(userId, "reading-lists"),
Content = new FeedEntryContent()
{
Text = "Browse by Reading Lists"
Text = await _localizationService.Translate(userId, "browse-reading-lists")
},
Links = new List<FeedLink>()
{
@ -139,10 +141,10 @@ public class OpdsController : BaseApiController
feed.Entries.Add(new FeedEntry()
{
Id = "allLibraries",
Title = "All Libraries",
Title = await _localizationService.Translate(userId, "libraries"),
Content = new FeedEntryContent()
{
Text = "Browse by Libraries"
Text = await _localizationService.Translate(userId, "browse-libraries")
},
Links = new List<FeedLink>()
{
@ -152,10 +154,10 @@ public class OpdsController : BaseApiController
feed.Entries.Add(new FeedEntry()
{
Id = "allCollections",
Title = "All Collections",
Title = await _localizationService.Translate(userId, "collections"),
Content = new FeedEntryContent()
{
Text = "Browse by Collections"
Text = await _localizationService.Translate(userId, "browse-collections")
},
Links = new List<FeedLink>()
{
@ -183,12 +185,12 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetLibraries(string apiKey)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 libraries = await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId);
var feed = CreateFeed("All Libraries", $"{prefix}{apiKey}/libraries", apiKey, prefix);
var feed = CreateFeed(await _localizationService.Translate(userId, "libraries"), $"{prefix}{apiKey}/libraries", apiKey, prefix);
SetFeedId(feed, "libraries");
foreach (var library in libraries)
{
@ -210,10 +212,10 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetCollections(string apiKey)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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);
if (user == null) return Unauthorized();
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
@ -222,7 +224,7 @@ public class OpdsController : BaseApiController
: (await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync(userId));
var feed = CreateFeed("All Collections", $"{prefix}{apiKey}/collections", apiKey, prefix);
var feed = CreateFeed(await _localizationService.Translate(userId, "collections"), $"{prefix}{apiKey}/collections", apiKey, prefix);
SetFeedId(feed, "collections");
foreach (var tag in tags)
{
@ -248,10 +250,10 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetCollection(int collectionId, string apiKey, [FromQuery] int pageNumber = 0)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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);
if (user == null) return Unauthorized();
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
@ -292,10 +294,10 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetReadingLists(string apiKey, [FromQuery] int pageNumber = 0)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 readingLists = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId,
true, GetUserParams(pageNumber), false);
@ -333,10 +335,10 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetReadingListItems(int readingListId, string apiKey)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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);
@ -344,10 +346,10 @@ public class OpdsController : BaseApiController
var readingList = userWithLists.ReadingLists.SingleOrDefault(t => t.Id == readingListId);
if (readingList == null)
{
return BadRequest("Reading list does not exist or you don't have access");
return BadRequest(await _localizationService.Translate(userId, "reading-list-restricted"));
}
var feed = CreateFeed(readingList.Title + " Reading List", $"{prefix}{apiKey}/reading-list/{readingListId}", apiKey, prefix);
var feed = CreateFeed(readingList.Title + " " + await _localizationService.Translate(userId, "reading-list"), $"{prefix}{apiKey}/reading-list/{readingListId}", apiKey, prefix);
SetFeedId(feed, $"reading-list-{readingListId}");
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId)).ToList();
@ -364,16 +366,16 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetSeriesForLibrary(int libraryId, string apiKey, [FromQuery] int pageNumber = 0)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 library =
(await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).SingleOrDefault(l =>
l.Id == libraryId);
if (library == null)
{
return BadRequest("User does not have access to this library");
return BadRequest(await _localizationService.Translate(userId, "no-library-access"));
}
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, GetUserParams(pageNumber), _filterDto);
@ -395,14 +397,14 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetRecentlyAdded(string apiKey, [FromQuery] int pageNumber = 1)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 recentlyAdded = await _unitOfWork.SeriesRepository.GetRecentlyAdded(0, userId, GetUserParams(pageNumber), _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(recentlyAdded.Select(s => s.Id));
var feed = CreateFeed("Recently Added", $"{prefix}{apiKey}/recently-added", apiKey, prefix);
var feed = CreateFeed(await _localizationService.Translate(userId, "recently-added"), $"{prefix}{apiKey}/recently-added", apiKey, prefix);
SetFeedId(feed, "recently-added");
AddPagination(feed, recentlyAdded, $"{prefix}{apiKey}/recently-added");
@ -418,19 +420,19 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetOnDeck(string apiKey, [FromQuery] int pageNumber = 1)
{
var userId = await GetUser(apiKey);
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var (baseUrl, prefix) = await GetPrefix();
var userId = await GetUser(apiKey);
var userParams = GetUserParams(pageNumber);
var pagedList = await _unitOfWork.SeriesRepository.GetOnDeck(userId, 0, userParams, _filterDto);
var seriesMetadatas = await _unitOfWork.SeriesRepository.GetSeriesMetadataForIds(pagedList.Select(s => s.Id));
Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages);
var feed = CreateFeed("On Deck", $"{prefix}{apiKey}/on-deck", apiKey, prefix);
var feed = CreateFeed(await _localizationService.Translate(userId, "on-deck"), $"{prefix}{apiKey}/on-deck", apiKey, prefix);
SetFeedId(feed, "on-deck");
AddPagination(feed, pagedList, $"{prefix}{apiKey}/on-deck");
@ -446,20 +448,20 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> SearchSeries(string apiKey, [FromQuery] string query)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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);
if (string.IsNullOrEmpty(query))
{
return BadRequest("You must pass a query parameter");
return BadRequest(await _localizationService.Translate(userId, "query-required"));
}
query = query.Replace(@"%", string.Empty);
// Get libraries user has access to
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesForUserIdAsync(userId)).ToList();
if (!libraries.Any()) return BadRequest("User does not have access to any libraries");
if (!libraries.Any()) return BadRequest(await _localizationService.Translate(userId, "libraries-restricted"));
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
@ -518,13 +520,14 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetSearchDescriptor(string apiKey)
{
var userId = await GetUser(apiKey);
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var (_, prefix) = await GetPrefix();
var feed = new OpenSearchDescription()
{
ShortName = "Search",
Description = "Search for Series, Collections, or Reading Lists",
ShortName = await _localizationService.Translate(userId, "search"),
Description = await _localizationService.Translate(userId, "search-description"),
Url = new SearchLink()
{
Type = FeedLinkType.AtomAcquisition,
@ -542,13 +545,13 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetSeries(string apiKey, int seriesId)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var feed = CreateFeed(series.Name + " - Storyline", $"{prefix}{apiKey}/series/{series.Id}", apiKey, prefix);
var feed = CreateFeed(series!.Name + " - Storyline", $"{prefix}{apiKey}/series/{series.Id}", apiKey, prefix);
SetFeedId(feed, $"series-{series.Id}");
feed.Links.Add(CreateLink(FeedLinkRelation.Image, FeedLinkType.Image, $"{baseUrl}api/image/series-cover?seriesId={seriesId}&apiKey={apiKey}"));
@ -564,7 +567,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volume.Id, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -576,7 +579,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(storylineChapter.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, storylineChapter.VolumeId, storylineChapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -586,7 +589,7 @@ public class OpdsController : BaseApiController
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(special.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, special.VolumeId, special.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -597,26 +600,26 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetVolume(string apiKey, int seriesId, int volumeId)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
var chapters =
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number),
_chapterSortComparer);
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ",
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {_seriesService.FormatChapterName(userId, libraryType)}s ",
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey, prefix);
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{SeriesService.FormatChapterName(libraryType)}s");
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{_seriesService.FormatChapterName(userId, libraryType)}s");
foreach (var chapter in chapters)
{
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapter.Id);
var chapterTest = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapter.Id);
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volumeId, chapter.Id, mangaFile, series, chapterTest, apiKey, prefix, baseUrl));
}
}
@ -627,23 +630,23 @@ public class OpdsController : BaseApiController
[Produces("application/xml")]
public async Task<IActionResult> GetChapter(string apiKey, int seriesId, int volumeId, int chapterId)
{
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
var (baseUrl, prefix) = await GetPrefix();
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 series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId);
if (chapter == null) return BadRequest("Chapter doesn't exist");
if (chapter == null) return BadRequest(await _localizationService.Translate(userId, "chapter-doesnt-exist"));
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s",
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {_seriesService.FormatChapterName(userId, libraryType)}s",
$"{prefix}{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey, prefix);
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{SeriesService.FormatChapterName(libraryType)}-{chapterId}-files");
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{_seriesService.FormatChapterName(userId, libraryType)}-{chapterId}-files");
foreach (var mangaFile in files)
{
feed.Entries.Add(await CreateChapterWithFile(seriesId, volumeId, chapterId, mangaFile, series, chapter, apiKey, prefix, baseUrl));
feed.Entries.Add(await CreateChapterWithFile(userId, seriesId, volumeId, chapterId, mangaFile, series, chapter, apiKey, prefix, baseUrl));
}
return CreateXmlResult(SerializeXml(feed));
@ -661,8 +664,9 @@ public class OpdsController : BaseApiController
[HttpGet("{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}/download/{filename}")]
public async Task<ActionResult> DownloadFile(string apiKey, int seriesId, int volumeId, int chapterId, string filename)
{
var userId = await GetUser(apiKey);
if (!(await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EnableOpds)
return BadRequest("OPDS is not enabled on this server");
return BadRequest(await _localizationService.Translate(userId, "opds-disabled"));
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(await GetUser(apiKey));
if (!await _accountService.HasDownloadPermission(user))
{
@ -781,7 +785,7 @@ public class OpdsController : BaseApiController
};
}
private async Task<FeedEntry> CreateChapterWithFile(int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey, string prefix, string baseUrl)
private async Task<FeedEntry> CreateChapterWithFile(int userId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, SeriesDto series, ChapterDto chapter, string apiKey, string prefix, string baseUrl)
{
var fileSize =
mangaFile.Bytes > 0 ? DirectoryService.GetHumanReadableBytes(mangaFile.Bytes) :
@ -797,7 +801,8 @@ public class OpdsController : BaseApiController
if (volume!.Chapters.Count == 1)
{
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType);
var volumeLabel = await _localizationService.Translate(userId, "volume-num", string.Empty);
SeriesService.RenameVolumeName(volume.Chapters.First(), volume, libraryType, volumeLabel);
if (volume.Name != "0")
{
title += $" - {volume.Name}";
@ -805,11 +810,11 @@ public class OpdsController : BaseApiController
}
else if (volume.Number != 0)
{
title = $"{series.Name} - Volume {volume.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
title = $"{series.Name} - Volume {volume.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
}
else
{
title = $"{series.Name} - {SeriesService.FormatChapterTitle(chapter, libraryType)}";
title = $"{series.Name} - {await _seriesService.FormatChapterTitle(userId, chapter, libraryType)}";
}
// Chunky requires a file at the end. Our API ignores this
@ -857,14 +862,16 @@ public class OpdsController : BaseApiController
[HttpGet("{apiKey}/image")]
public async Task<ActionResult> GetPageStreamedImage(string apiKey, [FromQuery] int libraryId, [FromQuery] int seriesId, [FromQuery] int volumeId,[FromQuery] int chapterId, [FromQuery] int pageNumber)
{
if (pageNumber < 0) return BadRequest("Page cannot be less than 0");
var userId = await GetUser(apiKey);
if (pageNumber < 0) return BadRequest(await _localizationService.Translate(userId, "greater-0", "Page"));
var chapter = await _cacheService.Ensure(chapterId);
if (chapter == null) return BadRequest("There was an issue finding image file for reading");
if (chapter == null) return BadRequest(await _localizationService.Translate(userId, "cache-file-find"));
try
{
var path = _cacheService.GetCachedPagePath(chapter.Id, pageNumber);
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {pageNumber}");
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path))
return BadRequest(await _localizationService.Translate(userId, "no-image-for-page", pageNumber));
var content = await _directoryService.ReadFileAsync(path);
var format = Path.GetExtension(path);
@ -895,8 +902,9 @@ public class OpdsController : BaseApiController
[ResponseCache(Duration = 60 * 60, Location = ResponseCacheLocation.Client, NoStore = false)]
public async Task<ActionResult> GetFavicon(string apiKey)
{
var userId = await GetUser(apiKey);
var files = _directoryService.GetFilesWithExtension(Path.Join(Directory.GetCurrentDirectory(), ".."), @"\.ico");
if (files.Length == 0) return BadRequest("Cannot find icon");
if (files.Length == 0) return BadRequest(await _localizationService.Translate(userId, "favicon-doesnt-exist"));
var path = files[0];
var content = await _directoryService.ReadFileAsync(path);
var format = Path.GetExtension(path);
@ -919,7 +927,7 @@ public class OpdsController : BaseApiController
{
/* Do nothing */
}
throw new KavitaException("User does not exist");
throw new KavitaException(await _localizationService.Get("en", "user-doesnt-exist"));
}
private async Task<FeedLink> CreatePageStreamLink(int libraryId, int seriesId, int volumeId, int chapterId, MangaFile mangaFile, string apiKey, string prefix)

View file

@ -16,6 +16,7 @@ using API.Services;
using API.Services.Plus;
using API.SignalR;
using Hangfire;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
@ -37,13 +38,15 @@ public class ReaderController : BaseApiController
private readonly IAccountService _accountService;
private readonly IEventHub _eventHub;
private readonly IScrobblingService _scrobblingService;
private readonly ILocalizationService _localizationService;
/// <inheritdoc />
public ReaderController(ICacheService cacheService,
IUnitOfWork unitOfWork, ILogger<ReaderController> logger,
IReaderService readerService, IBookmarkService bookmarkService,
IAccountService accountService, IEventHub eventHub,
IScrobblingService scrobblingService)
IScrobblingService scrobblingService,
ILocalizationService localizationService)
{
_cacheService = cacheService;
_unitOfWork = unitOfWork;
@ -53,6 +56,7 @@ public class ReaderController : BaseApiController
_accountService = accountService;
_eventHub = eventHub;
_scrobblingService = scrobblingService;
_localizationService = localizationService;
}
/// <summary>
@ -71,13 +75,13 @@ public class ReaderController : BaseApiController
// Validate the user has access to the PDF
var series = await _unitOfWork.SeriesRepository.GetSeriesForChapter(chapter.Id,
await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername()));
if (series == null) return BadRequest("Invalid Access");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-access"));
try
{
var path = _cacheService.GetCachedFile(chapter);
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"Pdf doesn't exist when it should.");
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "pdf-doesnt-exist"));
return PhysicalFile(path, MimeTypeMap.GetMimeType(Path.GetExtension(path)), Path.GetFileName(path), true);
}
@ -110,7 +114,8 @@ public class ReaderController : BaseApiController
try
{
var path = _cacheService.GetCachedPagePath(chapter.Id, page);
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}. Try refreshing to allow re-cache.");
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path))
return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-image-for-page", page));
var format = Path.GetExtension(path);
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), Path.GetFileName(path), true);
@ -170,7 +175,7 @@ public class ReaderController : BaseApiController
try
{
var path = _cacheService.GetCachedBookmarkPagePath(seriesId, page);
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest($"No such image for page {page}");
if (string.IsNullOrEmpty(path) || !System.IO.File.Exists(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-image-for-page", page));
var format = Path.GetExtension(path);
return PhysicalFile(path, MimeTypeMap.GetMimeType(format), Path.GetFileName(path));
@ -217,7 +222,7 @@ public class ReaderController : BaseApiController
if (chapter == null) return NoContent();
var dto = await _unitOfWork.ChapterRepository.GetChapterInfoDtoAsync(chapterId);
if (dto == null) return BadRequest("Please perform a scan on this series or library and try again");
if (dto == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "perform-scan"));
var mangaFile = chapter.Files.First();
var info = new ChapterInfoDto()
@ -256,7 +261,8 @@ public class ReaderController : BaseApiController
}
else
{
info.Subtitle = "Volume " + info.VolumeNumber;
//info.Subtitle = await _localizationService.Translate(User.GetUserId(), "volume-num", info.VolumeNumber);
info.Subtitle = $"Volume {info.VolumeNumber}";
if (!info.ChapterNumber.Equals(Services.Tasks.Scanner.Parser.Parser.DefaultChapter))
{
info.Subtitle += " " + ReaderService.FormatChapterName(info.LibraryType, true, true) +
@ -309,9 +315,16 @@ public class ReaderController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
await _readerService.MarkSeriesAsRead(user, markReadDto.SeriesId);
try
{
await _readerService.MarkSeriesAsRead(user, markReadDto.SeriesId);
}
catch (KavitaException ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
BackgroundJob.Enqueue(() => _scrobblingService.ScrobbleReadingUpdate(user.Id, markReadDto.SeriesId));
BackgroundJob.Enqueue(() => _unitOfWork.SeriesRepository.ClearOnDeckRemoval(markReadDto.SeriesId, user.Id));
@ -331,7 +344,7 @@ public class ReaderController : BaseApiController
if (user == null) return Unauthorized();
await _readerService.MarkSeriesAsUnread(user, markReadDto.SeriesId);
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
BackgroundJob.Enqueue(() => _scrobblingService.ScrobbleReadingUpdate(user.Id, markReadDto.SeriesId));
return Ok();
@ -357,7 +370,7 @@ public class ReaderController : BaseApiController
return Ok();
}
return BadRequest("Could not save progress");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
}
/// <summary>
@ -372,12 +385,19 @@ public class ReaderController : BaseApiController
var chapters = await _unitOfWork.ChapterRepository.GetChaptersAsync(markVolumeReadDto.VolumeId);
if (user == null) return Unauthorized();
await _readerService.MarkChaptersAsRead(user, markVolumeReadDto.SeriesId, chapters);
try
{
await _readerService.MarkChaptersAsRead(user, markVolumeReadDto.SeriesId, chapters);
}
catch (KavitaException ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName!, markVolumeReadDto.SeriesId,
markVolumeReadDto.VolumeId, 0, chapters.Sum(c => c.Pages)));
if (!await _unitOfWork.CommitAsync()) return BadRequest("Could not save progress");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
BackgroundJob.Enqueue(() => _scrobblingService.ScrobbleReadingUpdate(user.Id, markVolumeReadDto.SeriesId));
BackgroundJob.Enqueue(() => _unitOfWork.SeriesRepository.ClearOnDeckRemoval(markVolumeReadDto.SeriesId, user.Id));
@ -405,7 +425,7 @@ public class ReaderController : BaseApiController
var chapters = await _unitOfWork.ChapterRepository.GetChaptersByIdsAsync(chapterIds);
await _readerService.MarkChaptersAsRead(user, dto.SeriesId, chapters.ToList());
if (!await _unitOfWork.CommitAsync()) return BadRequest("Could not save progress");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
BackgroundJob.Enqueue(() => _scrobblingService.ScrobbleReadingUpdate(user.Id, dto.SeriesId));
BackgroundJob.Enqueue(() => _unitOfWork.SeriesRepository.ClearOnDeckRemoval(dto.SeriesId, user.Id));
return Ok();
@ -439,7 +459,7 @@ public class ReaderController : BaseApiController
return Ok();
}
return BadRequest("Could not save progress");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
}
/// <summary>
@ -460,7 +480,7 @@ public class ReaderController : BaseApiController
await _readerService.MarkChaptersAsRead(user, volume.SeriesId, volume.Chapters);
}
if (!await _unitOfWork.CommitAsync()) return BadRequest("Could not save progress");
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
foreach (var sId in dto.SeriesIds)
{
@ -497,7 +517,7 @@ public class ReaderController : BaseApiController
return Ok();
}
return BadRequest("Could not save progress");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
}
/// <summary>
@ -529,7 +549,7 @@ public class ReaderController : BaseApiController
{
var userId = User.GetUserId();
if (!await _readerService.SaveReadingProgress(progressDto, userId))
return BadRequest("Could not save progress");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-read-progress"));
return Ok(true);
@ -589,7 +609,7 @@ public class ReaderController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user == null) return Unauthorized();
if (user.Bookmarks == null) return Ok("Nothing to remove");
if (user.Bookmarks == null) return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
try
{
@ -616,7 +636,7 @@ public class ReaderController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Could not clear bookmarks");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-clear-bookmarks"));
}
/// <summary>
@ -629,7 +649,7 @@ public class ReaderController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user == null) return Unauthorized();
if (user.Bookmarks == null) return Ok("Nothing to remove");
if (user.Bookmarks == null) return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
try
{
@ -653,7 +673,7 @@ public class ReaderController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Could not clear bookmarks");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-clear-bookmarks"));
}
/// <summary>
@ -692,15 +712,16 @@ public class ReaderController : BaseApiController
if (user == null) return new UnauthorizedResult();
if (!await _accountService.HasBookmarkPermission(user))
return BadRequest("You do not have permission to bookmark");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-permission"));
var chapter = await _cacheService.Ensure(bookmarkDto.ChapterId);
if (chapter == null) return BadRequest("Could not find cached image. Reload and try again.");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "cache-file-find"));
bookmarkDto.Page = _readerService.CapPageToChapter(chapter, bookmarkDto.Page);
var path = _cacheService.GetCachedPagePath(chapter.Id, bookmarkDto.Page);
if (!await _bookmarkService.BookmarkPage(user, bookmarkDto, path)) return BadRequest("Could not save bookmark");
if (!await _bookmarkService.BookmarkPage(user, bookmarkDto, path))
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-save"));
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
return Ok();
@ -719,10 +740,10 @@ public class ReaderController : BaseApiController
if (user.Bookmarks.IsNullOrEmpty()) return Ok();
if (!await _accountService.HasBookmarkPermission(user))
return BadRequest("You do not have permission to unbookmark");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-permission"));
if (!await _bookmarkService.RemoveBookmarkPage(user, bookmarkDto))
return BadRequest("Could not remove bookmark");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-save"));
BackgroundJob.Enqueue(() => _cacheService.CleanupBookmarkCache(bookmarkDto.SeriesId));
return Ok();
}
@ -806,9 +827,10 @@ public class ReaderController : BaseApiController
[HttpDelete("ptoc")]
public async Task<ActionResult> DeletePersonalToc([FromQuery] int chapterId, [FromQuery] int pageNum, [FromQuery] string title)
{
if (string.IsNullOrWhiteSpace(title)) return BadRequest("Name cannot be empty");
if (pageNum < 0) return BadRequest("Must be valid page number");
var toc = await _unitOfWork.UserTableOfContentRepository.Get(User.GetUserId(), chapterId, pageNum, title);
var userId = User.GetUserId();
if (string.IsNullOrWhiteSpace(title)) return BadRequest(await _localizationService.Translate(userId, "name-required"));
if (pageNum < 0) return BadRequest(await _localizationService.Translate(userId, "valid-number"));
var toc = await _unitOfWork.UserTableOfContentRepository.Get(userId, chapterId, pageNum, title);
if (toc == null) return Ok();
_unitOfWork.UserTableOfContentRepository.Remove(toc);
await _unitOfWork.CommitAsync();
@ -825,13 +847,13 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> CreatePersonalToC(CreatePersonalToCDto dto)
{
// Validate there isn't already an existing page title combo?
if (string.IsNullOrWhiteSpace(dto.Title)) return BadRequest("Name cannot be empty");
if (dto.PageNumber < 0) return BadRequest("Must be valid page number");
var userId = User.GetUserId();
if (string.IsNullOrWhiteSpace(dto.Title)) return BadRequest(await _localizationService.Translate(userId, "name-required"));
if (dto.PageNumber < 0) return BadRequest(await _localizationService.Translate(userId, "valid-number"));
if (await _unitOfWork.UserTableOfContentRepository.IsUnique(userId, dto.ChapterId, dto.PageNumber,
dto.Title.Trim()))
{
return BadRequest("Duplicate ToC entry already exists");
return BadRequest(await _localizationService.Translate(userId, "duplicate-bookmark"));
}
_unitOfWork.UserTableOfContentRepository.Attach(new AppUserTableOfContent()

View file

@ -21,11 +21,14 @@ public class ReadingListController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IReadingListService _readingListService;
private readonly ILocalizationService _localizationService;
public ReadingListController(IUnitOfWork unitOfWork, IReadingListService readingListService)
public ReadingListController(IUnitOfWork unitOfWork, IReadingListService readingListService,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_readingListService = readingListService;
_localizationService = localizationService;
}
/// <summary>
@ -99,13 +102,13 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
if (await _readingListService.UpdateReadingListItemPosition(dto)) return Ok("Updated");
if (await _readingListService.UpdateReadingListItemPosition(dto)) return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
return BadRequest("Couldn't update position");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-position"));
}
/// <summary>
@ -119,15 +122,15 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
if (await _readingListService.DeleteReadingListItem(dto))
{
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
return BadRequest("Couldn't delete item");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-item-delete"));
}
/// <summary>
@ -141,15 +144,15 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
if (await _readingListService.RemoveFullyReadItems(readingListId, user))
{
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
return BadRequest("Could not remove read items");
return BadRequest("Couldn't delete item(s)");
}
/// <summary>
@ -163,12 +166,13 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(readingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
if (await _readingListService.DeleteReadingList(readingListId, user)) return Ok("List was deleted");
if (await _readingListService.DeleteReadingList(readingListId, user))
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-deleted"));
return BadRequest("There was an issue deleting reading list");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-reading-list-delete"));
}
/// <summary>
@ -188,7 +192,7 @@ public class ReadingListController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByTitleAsync(user.Id, dto.Title));
@ -203,12 +207,12 @@ public class ReadingListController : BaseApiController
public async Task<ActionResult> UpdateList(UpdateReadingListDto dto)
{
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
if (readingList == null) return BadRequest("List does not exist");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
var user = await _readingListService.UserHasReadingListAccess(readingList.Id, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
try
@ -217,10 +221,10 @@ public class ReadingListController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
/// <summary>
@ -234,11 +238,11 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
var chapterIdsForSeries =
await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new [] {dto.SeriesId});
@ -253,7 +257,7 @@ public class ReadingListController : BaseApiController
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
}
catch
@ -261,7 +265,7 @@ public class ReadingListController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
}
@ -276,10 +280,10 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
foreach (var chapterId in dto.ChapterIds)
@ -298,7 +302,7 @@ public class ReadingListController : BaseApiController
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
}
catch
@ -306,7 +310,7 @@ public class ReadingListController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
}
/// <summary>
@ -320,10 +324,10 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
var ids = await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync(dto.SeriesIds.ToArray());
@ -341,7 +345,7 @@ public class ReadingListController : BaseApiController
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
}
catch
@ -349,7 +353,7 @@ public class ReadingListController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
}
[HttpPost("update-by-volume")]
@ -358,10 +362,10 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
var chapterIdsForVolume =
(await _unitOfWork.ChapterRepository.GetChaptersAsync(dto.VolumeId)).Select(c => c.Id).ToList();
@ -377,7 +381,7 @@ public class ReadingListController : BaseApiController
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
}
catch
@ -385,7 +389,7 @@ public class ReadingListController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
}
[HttpPost("update-by-chapter")]
@ -394,10 +398,10 @@ public class ReadingListController : BaseApiController
var user = await _readingListService.UserHasReadingListAccess(dto.ReadingListId, User.GetUsername());
if (user == null)
{
return BadRequest("You do not have permissions on this reading list or the list doesn't exist");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-permission"));
}
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
// If there are adds, tell tracking this has been modified
if (await _readingListService.AddChaptersToReadingList(dto.SeriesId, new List<int>() { dto.ChapterId }, readingList))
@ -410,7 +414,7 @@ public class ReadingListController : BaseApiController
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "reading-list-updated"));
}
}
catch
@ -418,7 +422,7 @@ public class ReadingListController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
}
/// <summary>
@ -446,7 +450,7 @@ public class ReadingListController : BaseApiController
{
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).ToList();
var readingListItem = items.SingleOrDefault(rl => rl.ChapterId == currentChapterId);
if (readingListItem == null) return BadRequest("Id does not exist");
if (readingListItem == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var index = items.IndexOf(readingListItem) + 1;
if (items.Count > index)
{
@ -467,7 +471,7 @@ public class ReadingListController : BaseApiController
{
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).ToList();
var readingListItem = items.SingleOrDefault(rl => rl.ChapterId == currentChapterId);
if (readingListItem == null) return BadRequest("Id does not exist");
if (readingListItem == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var index = items.IndexOf(readingListItem) - 1;
if (0 <= index)
{

View file

@ -8,6 +8,7 @@ using API.DTOs;
using API.DTOs.Recommendation;
using API.Extensions;
using API.Helpers;
using API.Services;
using API.Services.Plus;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc;
@ -21,15 +22,18 @@ public class RecommendedController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly IRecommendationService _recommendationService;
private readonly ILicenseService _licenseService;
private readonly ILocalizationService _localizationService;
private readonly IEasyCachingProvider _cacheProvider;
public const string CacheKey = "recommendation_";
public RecommendedController(IUnitOfWork unitOfWork, IRecommendationService recommendationService,
ILicenseService licenseService, IEasyCachingProviderFactory cachingProviderFactory)
ILicenseService licenseService, IEasyCachingProviderFactory cachingProviderFactory,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_recommendationService = recommendationService;
_licenseService = licenseService;
_localizationService = localizationService;
_cacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRecommendations);
}
@ -50,7 +54,7 @@ public class RecommendedController : BaseApiController
if (!await _unitOfWork.UserRepository.HasAccessToSeries(userId, seriesId))
{
return BadRequest("User does not have access to this Series");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "series-restricted"));
}
var cacheKey = $"{CacheKey}-{seriesId}-{userId}";

View file

@ -14,9 +14,7 @@ using AutoMapper;
using EasyCaching.Core;
using Hangfire;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace API.Controllers;
@ -65,7 +63,6 @@ public class ReviewController : BaseApiController
var cacheKey = CacheKey + seriesId;
IEnumerable<UserReviewDto> externalReviews;
var setCache = false;
var result = await _cacheProvider.GetAsync<IEnumerable<UserReviewDto>>(cacheKey);
if (result.HasValue)
@ -74,35 +71,15 @@ public class ReviewController : BaseApiController
}
else
{
externalReviews = await _reviewService.GetReviewsForSeries(userId, seriesId);
setCache = true;
}
// if (_cache.TryGetValue(cacheKey, out string cachedData))
// {
// externalReviews = JsonConvert.DeserializeObject<IEnumerable<UserReviewDto>>(cachedData);
// }
// else
// {
// externalReviews = await _reviewService.GetReviewsForSeries(userId, seriesId);
// setCache = true;
// }
// Fetch external reviews and splice them in
foreach (var r in externalReviews)
{
userRatings.Add(r);
}
if (setCache)
{
// var cacheEntryOptions = new MemoryCacheEntryOptions()
// .SetSize(userRatings.Count)
// .SetAbsoluteExpiration(TimeSpan.FromHours(10));
//_cache.Set(cacheKey, JsonConvert.SerializeObject(externalReviews), cacheEntryOptions);
externalReviews = (await _reviewService.GetReviewsForSeries(userId, seriesId)).ToList();
await _cacheProvider.SetAsync(cacheKey, externalReviews, TimeSpan.FromHours(10));
_logger.LogDebug("Caching external reviews for {Key}", cacheKey);
}
// Fetch external reviews and splice them in
userRatings.AddRange(externalReviews);
return Ok(userRatings.Take(10));
}

View file

@ -10,6 +10,7 @@ using API.Entities.Scrobble;
using API.Extensions;
using API.Helpers;
using API.Helpers.Builders;
using API.Services;
using API.Services.Plus;
using Hangfire;
using Microsoft.AspNetCore.Authorization;
@ -26,12 +27,15 @@ public class ScrobblingController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly IScrobblingService _scrobblingService;
private readonly ILogger<ScrobblingController> _logger;
private readonly ILocalizationService _localizationService;
public ScrobblingController(IUnitOfWork unitOfWork, IScrobblingService scrobblingService, ILogger<ScrobblingController> logger)
public ScrobblingController(IUnitOfWork unitOfWork, IScrobblingService scrobblingService,
ILogger<ScrobblingController> logger, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_scrobblingService = scrobblingService;
_logger = logger;
_localizationService = localizationService;
}
[HttpGet("anilist-token")]
@ -153,7 +157,8 @@ public class ScrobblingController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ScrobbleHolds);
if (user == null) return Unauthorized();
if (user.ScrobbleHolds.Any(s => s.SeriesId == seriesId)) return Ok("Nothing to do");
if (user.ScrobbleHolds.Any(s => s.SeriesId == seriesId))
return Ok(await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
var seriesHold = new ScrobbleHoldBuilder().WithSeriesId(seriesId).Build();
user.ScrobbleHolds.Add(seriesHold);
@ -181,7 +186,8 @@ public class ScrobblingController : BaseApiController
{
// Handle other exceptions or log the error
_logger.LogError(ex, "An error occurred while adding the hold");
return StatusCode(StatusCodes.Status500InternalServerError, "An error occurred while adding the hold");
return StatusCode(StatusCodes.Status500InternalServerError,
await _localizationService.Translate(User.GetUserId(), "nothing-to-do"));
}
}

View file

@ -5,6 +5,7 @@ using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Search;
using API.Extensions;
using API.Services;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
@ -15,10 +16,12 @@ namespace API.Controllers;
public class SearchController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly ILocalizationService _localizationService;
public SearchController(IUnitOfWork unitOfWork)
public SearchController(IUnitOfWork unitOfWork, ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_localizationService = localizationService;
}
/// <summary>
@ -55,7 +58,7 @@ public class SearchController : BaseApiController
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized();
var libraries = _unitOfWork.LibraryRepository.GetLibraryIdsForUserIdAsync(user.Id, QueryContext.Search).ToList();
if (!libraries.Any()) return BadRequest("User does not have access to any libraries");
if (!libraries.Any()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "libraries-restricted"));
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);

View file

@ -15,6 +15,7 @@ using API.Helpers;
using API.Services;
using API.Services.Plus;
using EasyCaching.Core;
using Kavita.Common;
using Kavita.Common.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
@ -30,6 +31,7 @@ public class SeriesController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly ISeriesService _seriesService;
private readonly ILicenseService _licenseService;
private readonly ILocalizationService _localizationService;
private readonly IEasyCachingProvider _ratingCacheProvider;
private readonly IEasyCachingProvider _reviewCacheProvider;
private readonly IEasyCachingProvider _recommendationCacheProvider;
@ -37,13 +39,14 @@ public class SeriesController : BaseApiController
public SeriesController(ILogger<SeriesController> logger, ITaskScheduler taskScheduler, IUnitOfWork unitOfWork,
ISeriesService seriesService, ILicenseService licenseService,
IEasyCachingProviderFactory cachingProviderFactory)
IEasyCachingProviderFactory cachingProviderFactory, ILocalizationService localizationService)
{
_logger = logger;
_taskScheduler = taskScheduler;
_unitOfWork = unitOfWork;
_seriesService = seriesService;
_licenseService = licenseService;
_localizationService = localizationService;
_ratingCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusRatings);
_reviewCacheProvider = cachingProviderFactory.GetCachingProvider(EasyCacheProfiles.KavitaPlusReviews);
@ -58,7 +61,7 @@ public class SeriesController : BaseApiController
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto);
// Apply progress/rating information (I can't work out how to do this in initial query)
if (series == null) return BadRequest("Could not get series for library");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series"));
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
@ -101,7 +104,7 @@ public class SeriesController : BaseApiController
if (await _seriesService.DeleteMultipleSeries(dto.SeriesIds)) return Ok();
return BadRequest("There was an issue deleting the series requested");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-series-delete"));
}
/// <summary>
@ -149,7 +152,8 @@ public class SeriesController : BaseApiController
public async Task<ActionResult> UpdateSeriesRating(UpdateSeriesRatingDto updateSeriesRatingDto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Ratings);
if (!await _seriesService.UpdateRating(user!, updateSeriesRatingDto)) return BadRequest("There was a critical error.");
if (!await _seriesService.UpdateRating(user!, updateSeriesRatingDto))
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
return Ok();
}
@ -162,8 +166,8 @@ public class SeriesController : BaseApiController
public async Task<ActionResult> UpdateSeries(UpdateSeriesDto updateSeries)
{
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(updateSeries.Id);
if (series == null) return BadRequest("Series does not exist");
if (series == null)
return BadRequest(await _localizationService.Translate(User.GetUserId(), "series-doesnt-exist"));
series.NormalizedName = series.Name.ToNormalized();
if (!string.IsNullOrEmpty(updateSeries.SortName?.Trim()))
@ -199,7 +203,7 @@ public class SeriesController : BaseApiController
return Ok();
}
return BadRequest("There was an error with updating the series");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-series-update"));
}
/// <summary>
@ -218,7 +222,7 @@ public class SeriesController : BaseApiController
await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, userId, userParams, filterDto);
// Apply progress/rating information (I can't work out how to do this in initial query)
if (series == null) return BadRequest("Could not get series");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series"));
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
@ -254,7 +258,7 @@ public class SeriesController : BaseApiController
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto);
// Apply progress/rating information (I can't work out how to do this in initial query)
if (series == null) return BadRequest("Could not get series");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series"));
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
@ -370,10 +374,10 @@ public class SeriesController : BaseApiController
}
}
return Ok("Successfully updated");
return Ok(await _localizationService.Translate(User.GetUserId(), "series-updated"));
}
return BadRequest("Could not update metadata");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "update-metadata-fail"));
}
/// <summary>
@ -390,7 +394,7 @@ public class SeriesController : BaseApiController
await _unitOfWork.SeriesRepository.GetSeriesDtoForCollectionAsync(collectionId, userId, userParams);
// Apply progress/rating information (I can't work out how to do this in initial query)
if (series == null) return BadRequest("Could not get series for collection");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "no-series-collection"));
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
@ -407,7 +411,7 @@ public class SeriesController : BaseApiController
[HttpPost("series-by-ids")]
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetAllSeriesById(SeriesByIdsDto dto)
{
if (dto.SeriesIds == null) return BadRequest("Must pass seriesIds");
if (dto.SeriesIds == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "invalid-payload"));
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForIdsAsync(dto.SeriesIds, userId));
}
@ -420,10 +424,11 @@ public class SeriesController : BaseApiController
/// <remarks>This is cached for an hour</remarks>
[ResponseCache(CacheProfileName = "Month", VaryByQueryKeys = new [] {"ageRating"})]
[HttpGet("age-rating")]
public ActionResult<string> GetAgeRating(int ageRating)
public async Task<ActionResult<string>> GetAgeRating(int ageRating)
{
var val = (AgeRating) ageRating;
if (val == AgeRating.NotApplicable) return "No Restriction";
if (val == AgeRating.NotApplicable)
return await _localizationService.Translate(User.GetUserId(), "age-restriction-not-applicable");
return Ok(val.ToDescription());
}
@ -439,7 +444,14 @@ public class SeriesController : BaseApiController
public async Task<ActionResult<SeriesDetailDto>> GetSeriesDetailBreakdown(int seriesId)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
return await _seriesService.GetSeriesDetail(seriesId, userId);
try
{
return await _seriesService.GetSeriesDetail(seriesId, userId);
}
catch (KavitaException ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
}
@ -485,7 +497,7 @@ public class SeriesController : BaseApiController
return Ok();
}
return BadRequest("There was an issue updating relationships");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-relationship"));
}

View file

@ -41,11 +41,13 @@ public class ServerController : BaseApiController
private readonly ITaskScheduler _taskScheduler;
private readonly IUnitOfWork _unitOfWork;
private readonly IEasyCachingProviderFactory _cachingProviderFactory;
private readonly ILocalizationService _localizationService;
public ServerController(ILogger<ServerController> logger,
IBackupService backupService, IArchiveService archiveService, IVersionUpdaterService versionUpdaterService, IStatsService statsService,
ICleanupService cleanupService, IScannerService scannerService, IAccountService accountService,
ITaskScheduler taskScheduler, IUnitOfWork unitOfWork, IEasyCachingProviderFactory cachingProviderFactory)
ITaskScheduler taskScheduler, IUnitOfWork unitOfWork, IEasyCachingProviderFactory cachingProviderFactory,
ILocalizationService localizationService)
{
_logger = logger;
_backupService = backupService;
@ -58,6 +60,7 @@ public class ServerController : BaseApiController
_taskScheduler = taskScheduler;
_unitOfWork = unitOfWork;
_cachingProviderFactory = cachingProviderFactory;
_localizationService = localizationService;
}
/// <summary>
@ -103,12 +106,12 @@ public class ServerController : BaseApiController
/// </summary>
/// <returns></returns>
[HttpPost("analyze-files")]
public ActionResult AnalyzeFiles()
public async Task<ActionResult> AnalyzeFiles()
{
_logger.LogInformation("{UserName} is performing file analysis from admin dashboard", User.GetUsername());
if (TaskScheduler.HasAlreadyEnqueuedTask(ScannerService.Name, "AnalyzeFiles",
Array.Empty<object>(), TaskScheduler.DefaultQueue, true))
return Ok("Job already running");
return Ok(await _localizationService.Translate(User.GetUserId(), "job-already-running"));
BackgroundJob.Enqueue(() => _scannerService.AnalyzeFiles());
return Ok();
@ -127,7 +130,7 @@ public class ServerController : BaseApiController
/// <summary>
/// Returns non-sensitive information about the current system
/// </summary>
/// <remarks>This is just for the UI and is extremly lightweight</remarks>
/// <remarks>This is just for the UI and is extremely lightweight</remarks>
/// <returns></returns>
[HttpGet("server-info-slim")]
public async Task<ActionResult<ServerInfoDto>> GetSlimVersion()
@ -146,8 +149,7 @@ public class ServerController : BaseApiController
var encoding = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).EncodeMediaAs;
if (encoding == EncodeFormat.PNG)
{
return BadRequest(
"You cannot convert to PNG. For covers, use Refresh Covers. Bookmarks and favicons cannot be encoded back.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "encode-as-warning"));
}
_taskScheduler.CovertAllCoversToEncoding();
@ -160,7 +162,7 @@ public class ServerController : BaseApiController
/// </summary>
/// <returns></returns>
[HttpGet("logs")]
public ActionResult GetLogs()
public async Task<ActionResult> GetLogs()
{
var files = _backupService.GetLogFiles();
try
@ -171,7 +173,7 @@ public class ServerController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
}

View file

@ -33,9 +33,11 @@ public class SettingsController : BaseApiController
private readonly IMapper _mapper;
private readonly IEmailService _emailService;
private readonly ILibraryWatcher _libraryWatcher;
private readonly ILocalizationService _localizationService;
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler,
IDirectoryService directoryService, IMapper mapper, IEmailService emailService, ILibraryWatcher libraryWatcher)
IDirectoryService directoryService, IMapper mapper, IEmailService emailService, ILibraryWatcher libraryWatcher,
ILocalizationService localizationService)
{
_logger = logger;
_unitOfWork = unitOfWork;
@ -44,6 +46,7 @@ public class SettingsController : BaseApiController
_mapper = mapper;
_emailService = emailService;
_libraryWatcher = libraryWatcher;
_localizationService = localizationService;
}
[HttpGet("base-url")]
@ -224,7 +227,7 @@ public class SettingsController : BaseApiController
foreach (var ipAddress in updateSettingsDto.IpAddresses.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
{
if (!IPAddress.TryParse(ipAddress.Trim(), out _)) {
return BadRequest($"IP Address '{ipAddress}' is invalid");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "ip-address-invalid", ipAddress));
}
}
@ -279,7 +282,7 @@ public class SettingsController : BaseApiController
// Validate new directory can be used
if (!await _directoryService.CheckWriteAccess(bookmarkDirectory))
{
return BadRequest("Bookmark Directory does not have correct permissions for Kavita to use");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "bookmark-dir-permissions"));
}
originalBookmarkDirectory = setting.Value;
@ -308,7 +311,7 @@ public class SettingsController : BaseApiController
{
if (updateSettingsDto.TotalBackups > 30 || updateSettingsDto.TotalBackups < 1)
{
return BadRequest("Total Backups must be between 1 and 30");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "total-backups"));
}
setting.Value = updateSettingsDto.TotalBackups + string.Empty;
_unitOfWork.SettingsRepository.Update(setting);
@ -318,7 +321,7 @@ public class SettingsController : BaseApiController
{
if (updateSettingsDto.TotalLogs > 30 || updateSettingsDto.TotalLogs < 1)
{
return BadRequest("Total Logs must be between 1 and 30");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "total-logs"));
}
setting.Value = updateSettingsDto.TotalLogs + string.Empty;
_unitOfWork.SettingsRepository.Update(setting);
@ -366,7 +369,7 @@ public class SettingsController : BaseApiController
{
_logger.LogError(ex, "There was an exception when updating server settings");
await _unitOfWork.RollbackAsync();
return BadRequest("There was a critical issue. Please try again.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error"));
}

View file

@ -19,12 +19,15 @@ public class StatsController : BaseApiController
private readonly IStatisticService _statService;
private readonly IUnitOfWork _unitOfWork;
private readonly UserManager<AppUser> _userManager;
private readonly ILocalizationService _localizationService;
public StatsController(IStatisticService statService, IUnitOfWork unitOfWork, UserManager<AppUser> userManager)
public StatsController(IStatisticService statService, IUnitOfWork unitOfWork,
UserManager<AppUser> userManager, ILocalizationService localizationService)
{
_statService = statService;
_unitOfWork = unitOfWork;
_userManager = userManager;
_localizationService = localizationService;
}
[HttpGet("user/{userId}/read")]
@ -33,7 +36,7 @@ public class StatsController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user!.Id != userId && !await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole))
return Unauthorized("You are not authorized to view another user's statistics");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "stats-permission-denied"));
return Ok(await _statService.GetUserReadStatistics(userId, new List<int>()));
}

View file

@ -16,11 +16,14 @@ public class TachiyomiController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly ITachiyomiService _tachiyomiService;
private readonly ILocalizationService _localizationService;
public TachiyomiController(IUnitOfWork unitOfWork, ITachiyomiService tachiyomiService)
public TachiyomiController(IUnitOfWork unitOfWork, ITachiyomiService tachiyomiService,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_tachiyomiService = tachiyomiService;
_localizationService = localizationService;
}
/// <summary>
@ -31,7 +34,7 @@ public class TachiyomiController : BaseApiController
[HttpGet("latest-chapter")]
public async Task<ActionResult<ChapterDto>> GetLatestChapter(int seriesId)
{
if (seriesId < 1) return BadRequest("seriesId must be greater than 0");
if (seriesId < 1) return BadRequest(await _localizationService.Translate(User.GetUserId(), "greater-0", "SeriesId"));
return Ok(await _tachiyomiService.GetLatestChapter(seriesId, User.GetUserId()));
}

View file

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Theme;
using API.Extensions;
using API.Services;
using API.Services.Tasks;
using Kavita.Common;
@ -15,12 +16,15 @@ public class ThemeController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly IThemeService _themeService;
private readonly ITaskScheduler _taskScheduler;
private readonly ILocalizationService _localizationService;
public ThemeController(IUnitOfWork unitOfWork, IThemeService themeService, ITaskScheduler taskScheduler)
public ThemeController(IUnitOfWork unitOfWork, IThemeService themeService, ITaskScheduler taskScheduler,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_themeService = themeService;
_taskScheduler = taskScheduler;
_localizationService = localizationService;
}
[ResponseCache(CacheProfileName = "10Minute")]
@ -43,7 +47,15 @@ public class ThemeController : BaseApiController
[HttpPost("update-default")]
public async Task<ActionResult> UpdateDefault(UpdateDefaultThemeDto dto)
{
await _themeService.UpdateDefault(dto.ThemeId);
try
{
await _themeService.UpdateDefault(dto.ThemeId);
}
catch (KavitaException ex)
{
return BadRequest(await _localizationService.Translate(User.GetUserId(), "theme-doesnt-exist"));
}
return Ok();
}
@ -61,7 +73,7 @@ public class ThemeController : BaseApiController
}
catch (KavitaException ex)
{
return BadRequest(ex.Message);
return BadRequest(await _localizationService.Translate(User.GetUserId(), ex.Message));
}
}
}

View file

@ -25,10 +25,12 @@ public class UploadController : BaseApiController
private readonly IDirectoryService _directoryService;
private readonly IEventHub _eventHub;
private readonly IReadingListService _readingListService;
private readonly ILocalizationService _localizationService;
/// <inheritdoc />
public UploadController(IUnitOfWork unitOfWork, IImageService imageService, ILogger<UploadController> logger,
ITaskScheduler taskScheduler, IDirectoryService directoryService, IEventHub eventHub, IReadingListService readingListService)
ITaskScheduler taskScheduler, IDirectoryService directoryService, IEventHub eventHub, IReadingListService readingListService,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_imageService = imageService;
@ -37,6 +39,7 @@ public class UploadController : BaseApiController
_directoryService = directoryService;
_eventHub = eventHub;
_readingListService = readingListService;
_localizationService = localizationService;
}
/// <summary>
@ -57,9 +60,9 @@ public class UploadController : BaseApiController
.DownloadFileAsync(_directoryService.TempDirectory, $"coverupload_{dateString}.{format}");
if (string.IsNullOrEmpty(path) || !_directoryService.FileSystem.File.Exists(path))
return BadRequest($"Could not download file");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-not-valid"));
if (!await _imageService.IsImage(path)) return BadRequest("Url does not return a valid image");
if (!await _imageService.IsImage(path)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-not-valid"));
return $"coverupload_{dateString}.{format}";
}
@ -67,10 +70,10 @@ public class UploadController : BaseApiController
{
// Unauthorized
if (ex.StatusCode == 401)
return BadRequest("The server requires authentication to load the url externally");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-not-valid"));
}
return BadRequest("Unable to download image, please use another url or upload by file");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-not-valid"));
}
/// <summary>
@ -87,13 +90,13 @@ public class UploadController : BaseApiController
// See if we can do this all in memory without touching underlying system
if (string.IsNullOrEmpty(uploadFileDto.Url))
{
return BadRequest("You must pass a url to use");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-required"));
}
try
{
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(uploadFileDto.Id);
if (series == null) return BadRequest("Invalid Series");
if (series == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "series-doesnt-exist"));
var filePath = await CreateThumbnail(uploadFileDto, $"{ImageService.GetSeriesFormat(uploadFileDto.Id)}");
if (!string.IsNullOrEmpty(filePath))
@ -118,7 +121,7 @@ public class UploadController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Unable to save cover image to Series");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-cover-series-save"));
}
/// <summary>
@ -135,13 +138,13 @@ public class UploadController : BaseApiController
// See if we can do this all in memory without touching underlying system
if (string.IsNullOrEmpty(uploadFileDto.Url))
{
return BadRequest("You must pass a url to use");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-required"));
}
try
{
var tag = await _unitOfWork.CollectionTagRepository.GetTagAsync(uploadFileDto.Id);
if (tag == null) return BadRequest("Invalid Tag id");
if (tag == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "collection-doesnt-exist"));
var filePath = await CreateThumbnail(uploadFileDto, $"{ImageService.GetCollectionTagFormat(uploadFileDto.Id)}");
if (!string.IsNullOrEmpty(filePath))
@ -166,7 +169,7 @@ public class UploadController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Unable to save cover image to Collection Tag");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-cover-collection-save"));
}
/// <summary>
@ -183,16 +186,16 @@ public class UploadController : BaseApiController
// See if we can do this all in memory without touching underlying system
if (string.IsNullOrEmpty(uploadFileDto.Url))
{
return BadRequest("You must pass a url to use");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-required"));
}
if (_readingListService.UserHasReadingListAccess(uploadFileDto.Id, User.GetUsername()) == null)
return Unauthorized("You do not have access");
return Unauthorized(await _localizationService.Translate(User.GetUserId(), "access-denied"));
try
{
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(uploadFileDto.Id);
if (readingList == null) return BadRequest("Reading list is not valid");
if (readingList == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "reading-list-doesnt-exist"));
var filePath = await CreateThumbnail(uploadFileDto, $"{ImageService.GetReadingListFormat(uploadFileDto.Id)}");
if (!string.IsNullOrEmpty(filePath))
@ -217,7 +220,7 @@ public class UploadController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Unable to save cover image to Reading List");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-cover-reading-list-save"));
}
private async Task<string> CreateThumbnail(UploadFileDto uploadFileDto, string filename, int thumbnailSize = 0)
@ -247,13 +250,13 @@ public class UploadController : BaseApiController
// See if we can do this all in memory without touching underlying system
if (string.IsNullOrEmpty(uploadFileDto.Url))
{
return BadRequest("You must pass a url to use");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "url-required"));
}
try
{
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(uploadFileDto.Id);
if (chapter == null) return BadRequest("Invalid Chapter");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var filePath = await CreateThumbnail(uploadFileDto, $"{ImageService.GetChapterFormat(uploadFileDto.Id, chapter.VolumeId)}");
if (!string.IsNullOrEmpty(filePath))
@ -286,7 +289,7 @@ public class UploadController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Unable to save cover image to Chapter");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-cover-chapter-save"));
}
/// <summary>
@ -345,7 +348,7 @@ public class UploadController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Unable to save cover image to Library");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-cover-library-save"));
}
/// <summary>
@ -360,7 +363,7 @@ public class UploadController : BaseApiController
try
{
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(uploadFileDto.Id);
if (chapter == null) return BadRequest("Chapter no longer exists");
if (chapter == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist"));
var originalFile = chapter.CoverImage;
chapter.CoverImage = string.Empty;
chapter.CoverImageLocked = false;
@ -385,7 +388,7 @@ public class UploadController : BaseApiController
await _unitOfWork.RollbackAsync();
}
return BadRequest("Unable to resetting cover lock for Chapter");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "reset-chapter-lock"));
}
}

View file

@ -5,6 +5,7 @@ using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.Extensions;
using API.Services;
using API.SignalR;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
@ -18,12 +19,15 @@ public class UsersController : BaseApiController
private readonly IUnitOfWork _unitOfWork;
private readonly IMapper _mapper;
private readonly IEventHub _eventHub;
private readonly ILocalizationService _localizationService;
public UsersController(IUnitOfWork unitOfWork, IMapper mapper, IEventHub eventHub)
public UsersController(IUnitOfWork unitOfWork, IMapper mapper, IEventHub eventHub,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_mapper = mapper;
_eventHub = eventHub;
_localizationService = localizationService;
}
[Authorize(Policy = "RequireAdminRole")]
@ -38,7 +42,7 @@ public class UsersController : BaseApiController
if (await _unitOfWork.CommitAsync()) return Ok();
return BadRequest("Could not delete the user.");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-user-delete"));
}
/// <summary>
@ -66,7 +70,7 @@ public class UsersController : BaseApiController
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId);
if (library == null) return BadRequest("Library does not exist");
if (library == null) return BadRequest(await _localizationService.Translate(User.GetUserId(), "library-doesnt-exist"));
return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, userId));
}
@ -113,16 +117,17 @@ public class UsersController : BaseApiController
existingPreferences.SwipeToPaginate = preferencesDto.SwipeToPaginate;
existingPreferences.CollapseSeriesRelationships = preferencesDto.CollapseSeriesRelationships;
existingPreferences.ShareReviews = preferencesDto.ShareReviews;
if (_localizationService.GetLocales().Contains(preferencesDto.Locale))
{
existingPreferences.Locale = preferencesDto.Locale;
}
_unitOfWork.UserRepository.Update(existingPreferences);
if (await _unitOfWork.CommitAsync())
{
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
return Ok(preferencesDto);
}
if (!await _unitOfWork.CommitAsync()) return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-user-pref"));
return BadRequest("There was an issue saving preferences.");
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
return Ok(preferencesDto);
}
/// <summary>

View file

@ -7,6 +7,7 @@ using API.DTOs.Filtering;
using API.DTOs.WantToRead;
using API.Extensions;
using API.Helpers;
using API.Services;
using API.Services.Plus;
using Hangfire;
using Microsoft.AspNetCore.Mvc;
@ -21,11 +22,14 @@ public class WantToReadController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly IScrobblingService _scrobblingService;
private readonly ILocalizationService _localizationService;
public WantToReadController(IUnitOfWork unitOfWork, IScrobblingService scrobblingService)
public WantToReadController(IUnitOfWork unitOfWork, IScrobblingService scrobblingService,
ILocalizationService localizationService)
{
_unitOfWork = unitOfWork;
_scrobblingService = scrobblingService;
_localizationService = localizationService;
}
/// <summary>
@ -85,7 +89,7 @@ public class WantToReadController : BaseApiController
return Ok();
}
return BadRequest("There was an issue updating Read List");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-reading-list-update"));
}
/// <summary>
@ -113,6 +117,6 @@ public class WantToReadController : BaseApiController
return Ok();
}
return BadRequest("There was an issue updating Read List");
return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-reading-list-update"));
}
}