.NET 7 + Spring Cleaning (#1677)

* Updated to net7.0

* Updated GA to .net 7

* Updated System.IO.Abstractions to use New factory.

* Converted Regex into SourceGenerator in Parser.

* Updated more regex to source generators.

* Enabled Nullability and more regex changes throughout codebase.

* Parser is 100% GeneratedRegexified

* Lots of nullability code

* Enabled nullability for all repositories.

* Fixed another unit test

* Refactored some code around and took care of some todos.

* Updating code for nullability and cleaning up methods that aren't used anymore. Refctored all uses of Parser.Normalize() to use new extension

* More nullability exercises. 500 warnings to go.

* Fixed a bug where custom file uploads for entities wouldn't save in webP.

* Nullability is done for all DTOs

* Fixed all unit tests and nullability for the project. Only OPDS is left which will be done with an upcoming OPDS enhancement.

* Use localization in book service after validating

* Code smells

* Switched to preview build of swashbuckle for .net7 support

* Fixed up merge issues

* Disable emulate comic book when on single page reader

* Fixed a regression where double page renderer wouldn't layout the images correctly

* Updated to swashbuckle which support .net 7

* Fixed a bad GA action

* Some code cleanup

* More code smells

* Took care of most of nullable issues

* Fixed a broken test due to having more than one test run in parallel

* I'm really not sure why the unit tests are failing or are so extremely slow on .net 7

* Updated all dependencies

* Fixed up build and removed hardcoded framework from build scripts. (this merge removes Regex Source generators). Unit tests are completely busted.

* Unit tests and code cleanup. Needs shakeout now.

* Adjusted Series model since a few fields are not-nullable. Removed dead imports on the project.

* Refactored to use Builder pattern for all unit tests.

* Switched nullability down to warnings. It wasn't possible to switch due to constraint issues in DB Migration.
This commit is contained in:
Joe Milazzo 2023-03-05 14:55:13 -06:00 committed by GitHub
parent 76fe3fd64a
commit 5d1dd7b3f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
283 changed files with 4221 additions and 4593 deletions

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Web;
using API.Constants;
using API.Data;
using API.Data.Repositories;
@ -23,7 +22,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace API.Controllers;
@ -213,10 +211,13 @@ public class AccountController : BaseApiController
var dto = _mapper.Map<UserDto>(user);
dto.Token = await _tokenService.CreateToken(user);
dto.RefreshToken = await _tokenService.CreateRefreshToken(user);
var pref = await _unitOfWork.UserRepository.GetPreferencesAsync(user.UserName);
var pref = await _unitOfWork.UserRepository.GetPreferencesAsync(user.UserName!);
if (pref == null) return Ok(dto);
pref.Theme ??= await _unitOfWork.SiteThemeRepository.GetDefaultTheme();
dto.Preferences = _mapper.Map<UserPreferencesDto>(pref);
return dto;
return Ok(dto);
}
/// <summary>
@ -248,7 +249,7 @@ public class AccountController : BaseApiController
.GetFields(BindingFlags.Public | BindingFlags.Static)
.Where(f => f.FieldType == typeof(string))
.ToDictionary(f => f.Name,
f => (string) f.GetValue(null)).Values.ToList();
f => (string) f.GetValue(null)!).Values.ToList();
}
@ -260,6 +261,7 @@ public class AccountController : BaseApiController
public async Task<ActionResult<string>> ResetApiKey()
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized();
user.ApiKey = HashUtil.ApiKey();
@ -281,7 +283,7 @@ public class AccountController : BaseApiController
/// <param name="dto"></param>
/// <returns>Returns just if the email was sent or server isn't reachable</returns>
[HttpPost("update/email")]
public async Task<ActionResult> UpdateEmail(UpdateEmailDto dto)
public async Task<ActionResult> UpdateEmail(UpdateEmailDto? dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized("You do not have permission");
@ -297,7 +299,7 @@ public class AccountController : BaseApiController
}
// 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("Nothing to do");
// Check if email is used by another user
var existingUserEmail = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
@ -335,7 +337,7 @@ public class AccountController : BaseApiController
{
EmailAddress = string.IsNullOrEmpty(user.Email) ? dto.Email : user.Email,
InstallId = BuildInfo.Version.ToString(),
InvitingUser = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).First().UserName,
InvitingUser = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).First().UserName!,
ServerConfirmationLink = emailLink
});
}
@ -357,7 +359,7 @@ public class AccountController : BaseApiController
}
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
return Ok();
}
@ -367,9 +369,9 @@ public class AccountController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized("You do not have permission");
if (dto == null) return BadRequest("Invalid payload");
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
if (!await _accountService.HasChangeRestrictionRole(user)) return BadRequest("You do not have permission");
user.AgeRestriction = isAdmin ? AgeRating.NotApplicable : dto.AgeRating;
user.AgeRestrictionIncludeUnknowns = isAdmin || dto.IncludeUnknowns;
@ -387,7 +389,7 @@ public class AccountController : BaseApiController
return BadRequest("There was an error updating the age restriction");
}
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
return Ok();
}
@ -402,13 +404,14 @@ public class AccountController : BaseApiController
public async Task<ActionResult> UpdateAccount(UpdateUserDto dto)
{
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");
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(dto.UserId);
if (user == null) return BadRequest("User does not exist");
// Check if username is changing
if (!user.UserName.Equals(dto.Username))
if (!user.UserName!.Equals(dto.Username))
{
// Validate username change
var errors = await _accountService.ValidateUsername(dto.Username);
@ -488,12 +491,13 @@ public class AccountController : BaseApiController
public async Task<ActionResult<string>> GetInviteUrl(int userId, bool withBaseUrl)
{
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
if (user.EmailConfirmed)
return BadRequest("User is already confirmed");
if (string.IsNullOrEmpty(user.ConfirmationToken))
return BadRequest("Manual setup is unable to be completed. Please cancel and recreate the invite.");
return await _accountService.GenerateEmailLink(Request, user.ConfirmationToken, "confirm-email", user.Email, withBaseUrl);
return await _accountService.GenerateEmailLink(Request, user.ConfirmationToken, "confirm-email", user.Email!, withBaseUrl);
}
@ -520,23 +524,14 @@ public class AccountController : BaseApiController
if (emailValidationErrors.Any())
{
var invitedUser = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (await _userManager.IsEmailConfirmedAsync(invitedUser))
return BadRequest($"User is already registered as {invitedUser.UserName}");
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.");
}
}
// Create a new user
var user = new AppUser()
{
UserName = dto.Email,
Email = dto.Email,
ApiKey = HashUtil.ApiKey(),
UserPreferences = new AppUserPreferences
{
Theme = await _unitOfWork.SiteThemeRepository.GetDefaultTheme()
}
};
var user = DbFactory.AppUser(dto.Email, dto.Email, await _unitOfWork.SiteThemeRepository.GetDefaultTheme());
try
{
@ -612,7 +607,7 @@ public class AccountController : BaseApiController
await _emailService.SendConfirmationEmail(new ConfirmationEmailDto()
{
EmailAddress = dto.Email,
InvitingUser = adminUser.UserName,
InvitingUser = adminUser.UserName!,
ServerConfirmationLink = emailLink
});
}
@ -680,14 +675,14 @@ public class AccountController : BaseApiController
await _unitOfWork.CommitAsync();
user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user.UserName,
AppUserIncludes.UserPreferences);
user = (await _unitOfWork.UserRepository.GetUserByUsernameAsync(user.UserName,
AppUserIncludes.UserPreferences))!;
// Perform Login code
return new UserDto
{
Username = user.UserName,
Email = user.Email,
Username = user.UserName!,
Email = user.Email!,
Token = await _tokenService.CreateToken(user),
RefreshToken = await _tokenService.CreateRefreshToken(user),
ApiKey = user.ApiKey,
@ -731,7 +726,7 @@ public class AccountController : BaseApiController
// For the user's connected devices to pull the new information in
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate,
MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
// Perform Login code
return Ok();
@ -832,14 +827,14 @@ public class AccountController : BaseApiController
await _unitOfWork.CommitAsync();
user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user.UserName,
user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user.UserName!,
AppUserIncludes.UserPreferences);
// Perform Login code
return new UserDto
{
Username = user.UserName,
Email = user.Email,
Username = user!.UserName!,
Email = user.Email!,
Token = await _tokenService.CreateToken(user),
RefreshToken = await _tokenService.CreateRefreshToken(user),
ApiKey = user.ApiKey,
@ -873,8 +868,8 @@ public class AccountController : BaseApiController
{
await _emailService.SendMigrationEmail(new EmailMigrationDto()
{
EmailAddress = user.Email,
Username = user.UserName,
EmailAddress = user.Email!,
Username = user.UserName!,
ServerConfirmationLink = emailLink,
InstallId = (await _unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallId)).Value
});
@ -908,8 +903,8 @@ public class AccountController : BaseApiController
if (emailValidationErrors.Any())
{
var invitedUser = await _unitOfWork.UserRepository.GetUserByEmailAsync(dto.Email);
if (await _userManager.IsEmailConfirmedAsync(invitedUser))
return BadRequest($"User is already registered as {invitedUser.UserName}");
if (await _userManager.IsEmailConfirmedAsync(invitedUser!))
return BadRequest($"User is already registered as {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.");

View file

@ -37,6 +37,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");
var bookTitle = string.Empty;
switch (dto.SeriesFormat)
{
@ -93,6 +94,7 @@ public class BookController : BaseApiController
{
if (chapterId <= 0) return BadRequest("Chapter is not valid");
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Chapter is not valid");
using var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath, BookService.BookReaderOptions);
var key = BookService.CoalesceKeyForAnyFile(book, file);
@ -116,8 +118,9 @@ public class BookController : BaseApiController
public async Task<ActionResult<ICollection<BookChapterItem>>> GetBookChapters(int chapterId)
{
if (chapterId <= 0) return BadRequest("Chapter is not valid");
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Chapter is not valid");
try
{
return Ok(await _bookService.GenerateTableOfContents(chapter));
@ -140,6 +143,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");
var path = _cacheService.GetCachedFile(chapter);
var baseUrl = "//" + Request.Host + Request.PathBase + "/api/";

View file

@ -1,7 +1,5 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs.ReadingLists;
using API.DTOs.ReadingLists.CBL;
using API.Extensions;
using API.Services;

View file

@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs.CollectionTags;
using API.Entities.Metadata;
using API.Extensions;
using API.Services;
using API.Services.Tasks.Metadata;
using API.SignalR;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@ -35,16 +33,17 @@ public class CollectionController : BaseApiController
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<IEnumerable<CollectionTagDto>> GetAllTags()
public async Task<ActionResult<IEnumerable<CollectionTagDto>>> GetAllTags()
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Unauthorized();
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
if (isAdmin)
{
return await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync();
return Ok(await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync());
}
return await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync(user.Id);
return Ok(await _unitOfWork.CollectionTagRepository.GetAllPromotedTagDtosAsync(user.Id));
}
/// <summary>
@ -55,13 +54,13 @@ public class CollectionController : BaseApiController
/// <returns></returns>
[Authorize(Policy = "RequireAdminRole")]
[HttpGet("search")]
public async Task<IEnumerable<CollectionTagDto>> SearchTags(string queryString)
public async Task<ActionResult<IEnumerable<CollectionTagDto>>> SearchTags(string queryString)
{
queryString ??= string.Empty;
queryString = queryString.Replace(@"%", string.Empty);
if (queryString.Length == 0) return await GetAllTags();
return await _unitOfWork.CollectionTagRepository.SearchTagDtosAsync(queryString, User.GetUserId());
return Ok(await _unitOfWork.CollectionTagRepository.SearchTagDtosAsync(queryString, User.GetUserId()));
}
/// <summary>
@ -126,7 +125,7 @@ public class CollectionController : BaseApiController
{
try
{
var tag = await _unitOfWork.CollectionTagRepository.GetFullTagAsync(updateSeriesForTagDto.Tag.Id);
var tag = await _unitOfWork.CollectionTagRepository.GetTagAsync(updateSeriesForTagDto.Tag.Id, CollectionTagIncludes.SeriesMetadata);
if (tag == null) return BadRequest("Not a valid Tag");
tag.SeriesMetadatas ??= new List<SeriesMetadata>();

View file

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
@ -9,9 +7,7 @@ using API.DTOs.Device;
using API.Extensions;
using API.Services;
using API.SignalR;
using ExCSS;
using Kavita.Common;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
@ -39,6 +35,7 @@ public class DeviceController : BaseApiController
public async Task<ActionResult> CreateOrUpdateDevice(CreateDeviceDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
if (user == null) return Unauthorized();
var device = await _deviceService.Create(dto, user);
if (device == null) return BadRequest("There was an error when creating the device");
@ -50,6 +47,7 @@ public class DeviceController : BaseApiController
public async Task<ActionResult> UpdateDevice(UpdateDeviceDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Devices);
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");
@ -67,6 +65,7 @@ public class DeviceController : BaseApiController
{
if (deviceId <= 0) return BadRequest("Not a valid deviceId");
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");

View file

@ -93,13 +93,13 @@ public class DownloadController : BaseApiController
public async Task<ActionResult> DownloadVolume(int volumeId)
{
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
var files = await _unitOfWork.VolumeRepository.GetFilesForVolume(volumeId);
var volume = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(volumeId);
if (volume == null) return BadRequest("Volume doesn't exist");
var files = await _unitOfWork.VolumeRepository.GetFilesForVolume(volumeId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId);
try
{
return await DownloadFiles(files, $"download_{User.GetUsername()}_v{volumeId}", $"{series.Name} - Volume {volume.Number}.zip");
return await DownloadFiles(files, $"download_{User.GetUsername()}_v{volumeId}", $"{series!.Name} - Volume {volume.Number}.zip");
}
catch (KavitaException ex)
{
@ -110,6 +110,7 @@ public class DownloadController : BaseApiController
private async Task<bool> HasDownloadPermission()
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return false;
return await _accountService.HasDownloadPermission(user);
}
@ -130,11 +131,12 @@ public class DownloadController : BaseApiController
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
var files = await _unitOfWork.ChapterRepository.GetFilesForChapterAsync(chapterId);
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId);
if (chapter == null) return BadRequest("Invalid chapter");
var volume = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapter.VolumeId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume!.SeriesId);
try
{
return await DownloadFiles(files, $"download_{User.GetUsername()}_c{chapterId}", $"{series.Name} - Chapter {chapter.Number}.zip");
return await DownloadFiles(files, $"download_{User.GetUsername()}_c{chapterId}", $"{series!.Name} - Chapter {chapter.Number}.zip");
}
catch (KavitaException ex)
{
@ -177,8 +179,9 @@ public class DownloadController : BaseApiController
public async Task<ActionResult> DownloadSeries(int seriesId)
{
if (!await HasDownloadPermission()) return BadRequest("You do not have permission");
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId);
if (series == null) return BadRequest("Invalid Series");
var files = await _unitOfWork.SeriesRepository.GetFilesForSeries(seriesId);
try
{
return await DownloadFiles(files, $"download_{User.GetUsername()}_s{seriesId}", $"{series.Name}.zip");
@ -201,13 +204,13 @@ public class DownloadController : BaseApiController
if (!downloadBookmarkDto.Bookmarks.Any()) return BadRequest("Bookmarks cannot be empty");
// We know that all bookmarks will be for one single seriesId
var userId = User.GetUserId();
var username = User.GetUsername();
var userId = User.GetUserId()!;
var username = User.GetUsername()!;
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(downloadBookmarkDto.Bookmarks.First().SeriesId);
var files = await _bookmarkService.GetBookmarkFilesById(downloadBookmarkDto.Bookmarks.Select(b => b.Id));
var filename = $"{series.Name} - Bookmarks.zip";
var filename = $"{series!.Name} - Bookmarks.zip";
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 0F));
var seriesIds = string.Join("_", downloadBookmarkDto.Bookmarks.Select(b => b.SeriesId).Distinct());

View file

@ -1,9 +1,7 @@
using System;
using System.IO;
using System.IO;
using API.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace API.Controllers;

View file

@ -1,5 +1,3 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

View file

@ -7,7 +7,6 @@ using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.JumpBar;
using API.DTOs.Search;
using API.DTOs.System;
using API.Entities;
using API.Entities.Enums;
@ -17,7 +16,6 @@ using API.Services;
using API.Services.Tasks.Scanner;
using API.SignalR;
using AutoMapper;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
@ -209,6 +207,7 @@ public class LibraryController : BaseApiController
{
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(dto.ApiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
// Validate user has Admin privileges
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
@ -244,7 +243,6 @@ public class LibraryController : BaseApiController
try
{
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId, LibraryIncludes.None);
if (TaskScheduler.HasScanTaskRunningForLibrary(libraryId))
{
_logger.LogInformation("User is attempting to delete a library while a scan is in progress");
@ -252,6 +250,9 @@ public class LibraryController : BaseApiController
"You cannot delete a library while a scan is in progress. Please wait for scan to complete or restart Kavita then try to delete");
}
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId);
if (library == null) return BadRequest("Library no longer exists");
// 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
foreach (var s in await _unitOfWork.SeriesRepository.GetSeriesForLibraryIdAsync(library.Id,
@ -317,8 +318,10 @@ public class LibraryController : BaseApiController
[HttpPost("update")]
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto dto)
{
var newName = dto.Name.Trim();
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(dto.Id, LibraryIncludes.Folders);
if (library == null) return BadRequest("Library doesn't exist");
var newName = dto.Name.Trim();
if (await _unitOfWork.LibraryRepository.LibraryExists(newName) && !library.Name.Equals(newName))
return BadRequest("Library name already exists");

View file

@ -194,6 +194,7 @@ public class OpdsController : BaseApiController
return BadRequest("OPDS is not enabled on this server");
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
IEnumerable<CollectionTagDto> tags = isAdmin ? (await _unitOfWork.CollectionTagRepository.GetAllTagDtosAsync())
@ -230,6 +231,7 @@ public class OpdsController : BaseApiController
return BadRequest("OPDS is not enabled on this server");
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
if (user == null) return Unauthorized();
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
IEnumerable <CollectionTagDto> tags;
@ -310,7 +312,8 @@ public class OpdsController : BaseApiController
var userId = await GetUser(apiKey);
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
var userWithLists = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user.UserName, AppUserIncludes.ReadingListsWithItems);
var userWithLists = await _unitOfWork.UserRepository.GetUserByUsernameAsync(user!.UserName!, AppUserIncludes.ReadingListsWithItems);
if (userWithLists == null) return Unauthorized();
var readingList = userWithLists.ReadingLists.SingleOrDefault(t => t.Id == readingListId);
if (readingList == null)
{
@ -432,7 +435,6 @@ public class OpdsController : BaseApiController
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");
var isAdmin = await _unitOfWork.UserRepository.IsUserAdminAsync(user);
@ -569,7 +571,7 @@ public class OpdsController : BaseApiController
(await _unitOfWork.ChapterRepository.GetChaptersAsync(volumeId)).OrderBy(x => double.Parse(x.Number),
_chapterSortComparer);
var feed = CreateFeed(series.Name + " - Volume " + volume.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ", $"{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey);
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s ", $"{apiKey}/series/{seriesId}/volume/{volumeId}", apiKey);
SetFeedId(feed, $"series-{series.Id}-volume-{volume.Id}-{SeriesService.FormatChapterName(libraryType)}s");
foreach (var chapter in chapters)
{
@ -597,11 +599,12 @@ public class OpdsController : BaseApiController
var userId = await GetUser(apiKey);
var series = await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId, userId);
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(volumeId);
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId);
if (chapter == null) return BadRequest("Chapter doesn't 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", $"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey);
var feed = CreateFeed(series.Name + " - Volume " + volume!.Name + $" - {SeriesService.FormatChapterName(libraryType)}s", $"{apiKey}/series/{seriesId}/volume/{volumeId}/chapter/{chapterId}", apiKey);
SetFeedId(feed, $"series-{series.Id}-volume-{volumeId}-{SeriesService.FormatChapterName(libraryType)}-{chapterId}-files");
foreach (var mangaFile in files)
{
@ -768,7 +771,7 @@ public class OpdsController : BaseApiController
DirectoryService.GetHumanReadableBytes(_directoryService.GetTotalSize(new List<string>()
{mangaFile.FilePath}));
var fileType = _downloadService.GetContentTypeFromFile(mangaFile.FilePath);
var filename = Uri.EscapeDataString(Path.GetFileName(mangaFile.FilePath) ?? string.Empty);
var filename = Uri.EscapeDataString(Path.GetFileName(mangaFile.FilePath));
var libraryType = await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(series.LibraryId);
var volume = await _unitOfWork.VolumeRepository.GetVolumeDtoAsync(volumeId, await GetUser(apiKey));
@ -890,7 +893,7 @@ public class OpdsController : BaseApiController
return link;
}
private static FeedLink CreateLink(string rel, string type, string href, string title = null)
private static FeedLink CreateLink(string rel, string type, string href, string? title = null)
{
return new FeedLink()
{

View file

@ -38,10 +38,10 @@ public class PluginController : BaseApiController
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
if (userId <= 0) return Unauthorized();
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
_logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({UserId})'s API Key", pluginName, user.UserName, userId);
_logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({UserId})'s API Key", pluginName, user!.UserName, userId);
return new UserDto
{
Username = user.UserName,
Username = user.UserName!,
Token = await _tokenService.CreateToken(user),
ApiKey = user.ApiKey,
};

View file

@ -13,7 +13,6 @@ using API.Entities;
using API.Entities.Enums;
using API.Extensions;
using API.Services;
using API.Services.Tasks;
using API.SignalR;
using Hangfire;
using Microsoft.AspNetCore.Authorization;
@ -164,7 +163,7 @@ public class ReaderController : BaseApiController
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "extractPdf"})]
public async Task<ActionResult<IEnumerable<FileDimensionDto>>> GetFileDimensions(int chapterId, bool extractPdf = false)
{
if (chapterId <= 0) return null;
if (chapterId <= 0) return ArraySegment<FileDimensionDto>.Empty;
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
if (chapter == null) return BadRequest("Could not find Chapter");
return Ok(_cacheService.GetCachedFileDimensions(chapterId));
@ -179,9 +178,9 @@ public class ReaderController : BaseApiController
/// <returns></returns>
[HttpGet("chapter-info")]
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Hour, VaryByQueryKeys = new []{"chapterId", "extractPdf", "includeDimensions"})]
public async Task<ActionResult<ChapterInfoDto>> GetChapterInfo(int chapterId, bool extractPdf = false, bool includeDimensions = false)
public async Task<ActionResult<ChapterInfoDto?>> GetChapterInfo(int chapterId, bool extractPdf = false, bool includeDimensions = false)
{
if (chapterId <= 0) return null; // This can happen occasionally from UI, we should just ignore
if (chapterId <= 0) return Ok(null); // This can happen occasionally from UI, we should just ignore
var chapter = await _cacheService.Ensure(chapterId, extractPdf);
if (chapter == null) return BadRequest("Could not find Chapter");
@ -249,7 +248,7 @@ public class ReaderController : BaseApiController
return Ok(new BookmarkInfoDto()
{
SeriesName = series.Name,
SeriesName = series!.Name,
SeriesFormat = series.Format,
SeriesId = series.Id,
LibraryId = series.LibraryId,
@ -267,6 +266,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkRead(MarkReadDto markReadDto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
await _readerService.MarkSeriesAsRead(user, markReadDto.SeriesId);
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
@ -284,6 +284,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkUnread(MarkReadDto markReadDto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
await _readerService.MarkSeriesAsUnread(user, markReadDto.SeriesId);
if (!await _unitOfWork.CommitAsync()) return BadRequest("There was an issue saving progress");
@ -300,6 +301,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkVolumeAsUnread(MarkVolumeReadDto markVolumeReadDto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
var chapters = await _unitOfWork.ChapterRepository.GetChaptersAsync(markVolumeReadDto.VolumeId);
await _readerService.MarkChaptersAsUnread(user, markVolumeReadDto.SeriesId, chapters);
@ -323,9 +325,10 @@ public class ReaderController : BaseApiController
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
var chapters = await _unitOfWork.ChapterRepository.GetChaptersAsync(markVolumeReadDto.VolumeId);
if (user == null) return Unauthorized();
await _readerService.MarkChaptersAsRead(user, markVolumeReadDto.SeriesId, chapters);
await _eventHub.SendMessageAsync(MessageFactory.UserProgressUpdate,
MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName, markVolumeReadDto.SeriesId,
MessageFactory.UserProgressUpdateEvent(user.Id, user.UserName!, markVolumeReadDto.SeriesId,
markVolumeReadDto.VolumeId, 0, chapters.Sum(c => c.Pages)));
if (await _unitOfWork.CommitAsync())
@ -346,6 +349,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkMultipleAsRead(MarkVolumesReadDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
user.Progresses ??= new List<AppUserProgress>();
var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
@ -374,6 +378,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkMultipleAsUnread(MarkVolumesReadDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
user.Progresses ??= new List<AppUserProgress>();
var chapterIds = await _unitOfWork.VolumeRepository.GetChapterIdsByVolumeIds(dto.VolumeIds);
@ -401,6 +406,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkMultipleSeriesAsRead(MarkMultipleSeriesAsReadDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
user.Progresses ??= new List<AppUserProgress>();
var volumes = await _unitOfWork.VolumeRepository.GetVolumesForSeriesAsync(dto.SeriesIds.ToArray(), true);
@ -426,6 +432,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> MarkMultipleSeriesAsUnread(MarkMultipleSeriesAsReadDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
user.Progresses ??= new List<AppUserProgress>();
var volumes = await _unitOfWork.VolumeRepository.GetVolumesForSeriesAsync(dto.SeriesIds.ToArray(), true);
@ -509,6 +516,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult<bool>> MarkChaptersUntilAsRead(int seriesId, float chapterNumber)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
if (user == null) return Unauthorized();
user.Progresses ??= new List<AppUserProgress>();
// Tachiyomi sends chapter 0.0f when there's no chapters read.
@ -546,6 +554,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetBookmarks(int chapterId)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user == null) return Unauthorized();
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForChapter(user.Id, chapterId));
}
@ -559,6 +568,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetAllBookmarks(FilterDto filterDto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user == null) return Unauthorized();
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
return Ok(await _unitOfWork.UserRepository.GetAllBookmarkDtos(user.Id, filterDto));
@ -573,6 +583,7 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> RemoveBookmarks(RemoveBookmarkForSeriesDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user == null) return Unauthorized();
if (user.Bookmarks == null) return Ok("Nothing to remove");
try
@ -612,7 +623,8 @@ public class ReaderController : BaseApiController
public async Task<ActionResult> BulkRemoveBookmarks(BulkRemoveBookmarkForSeriesDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user.Bookmarks == null) return Ok("Nothing to remove");
if (user == null) return Unauthorized();
if (user?.Bookmarks == null) return Ok("Nothing to remove");
try
{
@ -648,7 +660,8 @@ public class ReaderController : BaseApiController
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetBookmarksForVolume(int volumeId)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
if (user == null) return Unauthorized();
if (user?.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForVolume(user.Id, volumeId));
}
@ -661,7 +674,8 @@ public class ReaderController : BaseApiController
public async Task<ActionResult<IEnumerable<BookmarkDto>>> GetBookmarksForSeries(int seriesId)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Bookmarks);
if (user.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
if (user == null) return Unauthorized();
if (user?.Bookmarks == null) return Ok(Array.Empty<BookmarkDto>());
return Ok(await _unitOfWork.UserRepository.GetBookmarkDtosForSeries(user.Id, seriesId));
}

View file

@ -1,22 +1,15 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Comparators;
using API.Data;
using API.Data.Repositories;
using API.DTOs.ReadingLists;
using API.DTOs.ReadingLists.CBL;
using API.Entities;
using API.Extensions;
using API.Helpers;
using API.Services;
using API.SignalR;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers;
@ -186,8 +179,8 @@ public class ReadingListController : BaseApiController
[HttpPost("create")]
public async Task<ActionResult<ReadingListDto>> CreateList(CreateReadingListDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.ReadingLists);
if (user == null) return Unauthorized();
try
{

View file

@ -58,7 +58,7 @@ public class RecommendedController : BaseApiController
[HttpGet("highly-rated")]
public async Task<ActionResult<PagedList<SeriesDto>>> GetHighlyRated(int libraryId, [FromQuery] UserParams userParams)
{
var userId = User.GetUserId();
var userId = User.GetUserId()!;
userParams ??= new UserParams();
var series = await _unitOfWork.SeriesRepository.GetHighlyRated(userId, libraryId, userParams);
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);

View file

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
@ -54,6 +53,7 @@ public class SearchController : BaseApiController
queryString = Services.Tasks.Scanner.Parser.Parser.CleanQuery(queryString);
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");

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Constants;
using API.Data;
@ -11,7 +10,6 @@ using API.DTOs.Metadata;
using API.DTOs.SeriesDetail;
using API.Entities;
using API.Entities.Enums;
using API.Entities.Metadata;
using API.Extensions;
using API.Helpers;
using API.Services;
@ -137,7 +135,7 @@ 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("There was a critical error.");
return Ok();
}
@ -159,14 +157,14 @@ public class SeriesController : BaseApiController
}
series.Name = updateSeries.Name.Trim();
series.NormalizedName = Services.Tasks.Scanner.Parser.Parser.Normalize(series.Name);
if (!string.IsNullOrEmpty(updateSeries.SortName.Trim()))
series.NormalizedName = series.Name.ToNormalized();
if (!string.IsNullOrEmpty(updateSeries.SortName?.Trim()))
{
series.SortName = updateSeries.SortName.Trim();
}
series.LocalizedName = updateSeries.LocalizedName.Trim();
series.NormalizedLocalizedName = Services.Tasks.Scanner.Parser.Parser.Normalize(series.LocalizedName);
series.LocalizedName = updateSeries.LocalizedName?.Trim();
series.NormalizedLocalizedName = series.LocalizedName?.ToNormalized();
series.NameLocked = updateSeries.NameLocked;
series.SortNameLocked = updateSeries.SortNameLocked;

View file

@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -8,7 +7,6 @@ using API.DTOs.Jobs;
using API.DTOs.Stats;
using API.DTOs.Update;
using API.Extensions;
using API.Logging;
using API.Services;
using API.Services.Tasks;
using Hangfire;
@ -16,7 +14,6 @@ using Hangfire.Storage;
using Kavita.Common;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using TaskScheduler = API.Services.TaskScheduler;

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

View file

@ -32,7 +32,7 @@ public class StatsController : BaseApiController
public async Task<ActionResult<UserReadStatistics>> GetUserReadStatistics(int userId)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user.Id != userId && !await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole))
if (user!.Id != userId && !await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole))
return Unauthorized("You are not authorized to view another user's statistics");
return Ok(await _statService.GetUserReadStatistics(userId, new List<int>()));
@ -116,7 +116,7 @@ public class StatsController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
if (!isAdmin && userId != user.Id) return BadRequest();
if (!isAdmin && userId != user!.Id) return BadRequest();
return Ok(await _statService.ReadCountByDay(userId, days));
}
@ -136,7 +136,7 @@ public class StatsController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
if (!isAdmin && userId != user.Id) return BadRequest();
if (!isAdmin && userId != user!.Id) return BadRequest();
return Ok(await _statService.GetReadingHistory(userId));
}

View file

@ -43,7 +43,8 @@ public class TachiyomiController : BaseApiController
[HttpPost("mark-chapter-until-as-read")]
public async Task<ActionResult<bool>> MarkChaptersUntilAsRead(int seriesId, float chapterNumber)
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
var user = (await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
AppUserIncludes.Progress))!;
return Ok(await _tachiyomiService.MarkChaptersUntilAsRead(user, seriesId, chapterNumber));
}
}

View file

@ -2,7 +2,6 @@
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Theme;
using API.Extensions;
using API.Services;
using API.Services.Tasks;
using Kavita.Common;

View file

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Threading.Tasks;
using API.Data;
using API.DTOs.Uploads;
@ -10,7 +9,6 @@ using Flurl.Http;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NetVips;
namespace API.Controllers;
@ -93,8 +91,10 @@ public class UploadController : BaseApiController
try
{
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, ImageService.GetSeriesFormat(uploadFileDto.Id));
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(uploadFileDto.Id);
if (series == null) return BadRequest("Invalid Series");
var convertToWebP = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).ConvertCoverToWebP;
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, ImageService.GetSeriesFormat(uploadFileDto.Id), convertToWebP);
if (!string.IsNullOrEmpty(filePath))
{
@ -140,8 +140,10 @@ public class UploadController : BaseApiController
try
{
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, $"{ImageService.GetCollectionTagFormat(uploadFileDto.Id)}");
var tag = await _unitOfWork.CollectionTagRepository.GetTagAsync(uploadFileDto.Id);
if (tag == null) return BadRequest("Invalid Tag id");
var convertToWebP = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).ConvertCoverToWebP;
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, $"{ImageService.GetCollectionTagFormat(uploadFileDto.Id)}", convertToWebP);
if (!string.IsNullOrEmpty(filePath))
{
@ -190,8 +192,10 @@ public class UploadController : BaseApiController
try
{
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, $"{ImageService.GetReadingListFormat(uploadFileDto.Id)}");
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(uploadFileDto.Id);
if (readingList == null) return BadRequest("Reading list is not valid");
var convertToWebP = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).ConvertCoverToWebP;
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, $"{ImageService.GetReadingListFormat(uploadFileDto.Id)}", convertToWebP);
if (!string.IsNullOrEmpty(filePath))
{
@ -238,7 +242,9 @@ public class UploadController : BaseApiController
try
{
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(uploadFileDto.Id);
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, $"{ImageService.GetChapterFormat(uploadFileDto.Id, chapter.VolumeId)}");
if (chapter == null) return BadRequest("Invalid Chapter");
var convertToWebP = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).ConvertCoverToWebP;
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url, $"{ImageService.GetChapterFormat(uploadFileDto.Id, chapter.VolumeId)}", convertToWebP);
if (!string.IsNullOrEmpty(filePath))
{
@ -246,8 +252,11 @@ public class UploadController : BaseApiController
chapter.CoverImageLocked = true;
_unitOfWork.ChapterRepository.Update(chapter);
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(chapter.VolumeId);
volume.CoverImage = chapter.CoverImage;
_unitOfWork.VolumeRepository.Update(volume);
if (volume != null)
{
volume.CoverImage = chapter.CoverImage;
_unitOfWork.VolumeRepository.Update(volume);
}
}
if (_unitOfWork.HasChanges())
@ -301,8 +310,9 @@ public class UploadController : BaseApiController
try
{
var convertToWebP = (await _unitOfWork.SettingsRepository.GetSettingsDtoAsync()).ConvertCoverToWebP;
var filePath = _imageService.CreateThumbnailFromBase64(uploadFileDto.Url,
$"{ImageService.GetLibraryFormat(uploadFileDto.Id)}", ImageService.LibraryThumbnailWidth);
$"{ImageService.GetLibraryFormat(uploadFileDto.Id)}", convertToWebP, ImageService.LibraryThumbnailWidth);
if (!string.IsNullOrEmpty(filePath))
{
@ -340,19 +350,20 @@ public class UploadController : BaseApiController
try
{
var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(uploadFileDto.Id);
if (chapter == null) return BadRequest("Chapter no longer exists");
var originalFile = chapter.CoverImage;
chapter.CoverImage = string.Empty;
chapter.CoverImageLocked = false;
_unitOfWork.ChapterRepository.Update(chapter);
var volume = await _unitOfWork.VolumeRepository.GetVolumeAsync(chapter.VolumeId);
var volume = (await _unitOfWork.VolumeRepository.GetVolumeAsync(chapter.VolumeId))!;
volume.CoverImage = chapter.CoverImage;
_unitOfWork.VolumeRepository.Update(volume);
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId);
var series = (await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId))!;
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
System.IO.File.Delete(originalFile);
if (originalFile != null) System.IO.File.Delete(originalFile);
_taskScheduler.RefreshSeriesMetadata(series.LibraryId, series.Id, true);
return Ok();
}

View file

@ -4,10 +4,7 @@ using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Filtering;
using API.Entities.Enums;
using API.Extensions;
using API.Helpers;
using API.SignalR;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
@ -68,6 +65,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");
return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, userId));
}
@ -83,9 +81,8 @@ public class UsersController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
AppUserIncludes.UserPreferences);
var existingPreferences = user.UserPreferences;
preferencesDto.Theme ??= await _unitOfWork.SiteThemeRepository.GetDefaultTheme();
if (user == null) return Unauthorized();
var existingPreferences = user!.UserPreferences;
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
existingPreferences.ScalingOption = preferencesDto.ScalingOption;
@ -107,8 +104,8 @@ public class UsersController : BaseApiController
existingPreferences.BookReaderImmersiveMode = preferencesDto.BookReaderImmersiveMode;
existingPreferences.GlobalPageLayoutMode = preferencesDto.GlobalPageLayoutMode;
existingPreferences.BlurUnreadSummaries = preferencesDto.BlurUnreadSummaries;
existingPreferences.Theme = await _unitOfWork.SiteThemeRepository.GetThemeById(preferencesDto.Theme.Id);
existingPreferences.LayoutMode = preferencesDto.LayoutMode;
existingPreferences.Theme = preferencesDto.Theme ?? await _unitOfWork.SiteThemeRepository.GetDefaultTheme();
existingPreferences.PromptForDownloadSize = preferencesDto.PromptForDownloadSize;
existingPreferences.NoTransitions = preferencesDto.NoTransitions;
existingPreferences.SwipeToPaginate = preferencesDto.SwipeToPaginate;
@ -117,7 +114,7 @@ public class UsersController : BaseApiController
if (await _unitOfWork.CommitAsync())
{
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName!), user.Id);
return Ok(preferencesDto);
}

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.Data.Repositories;
@ -56,6 +55,7 @@ public class WantToReadController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
AppUserIncludes.WantToRead);
if (user == null) return Unauthorized();
var existingIds = user.WantToRead.Select(s => s.Id).ToList();
existingIds.AddRange(dto.SeriesIds);
@ -84,6 +84,7 @@ public class WantToReadController : BaseApiController
{
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
AppUserIncludes.WantToRead);
if (user == null) return Unauthorized();
user.WantToRead = user.WantToRead.Where(s => !dto.SeriesIds.Contains(s.Id)).ToList();