UX Changes, Tasks, WebP, and More! (#1280)
* When account updates occur for a user, send an event to them to tell them to refresh their account information (if they are on the site at the time). This way if we revoke permissions, the site will reactively adapt. * Some cleanup on the user preferences to remove some calls we don't need anymore. * Removed old bulk cleanup bookmark code as it's no longer needed. * Tweaked the messaging for stat collection to reflect what we collect now versus when this was initially implemented. * Implemented the ability for users to configure their servers to save bookmarks as webP. Reorganized the tabs for Admin dashboard to account for upcoming features. * Implemented the ability to bulk convert bookmarks (as many times as the user wants). Added a display of Reoccurring Jobs to the Tasks admin tab. Currently it's just placeholder, but will be enhanced further later in the release. * Tweaked the wording around the convert switch. * Moved System actions to the task tab * Added a controller just for Tachiyomi so we can have dedicated APIs for that client. Deprecated an existing API on the Reader route. * Fixed the unit tests
This commit is contained in:
parent
dd83b6a9a1
commit
e0a2fc615f
51 changed files with 971 additions and 271 deletions
|
@ -15,6 +15,7 @@ using API.Entities.Enums;
|
|||
using API.Errors;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.SignalR;
|
||||
using AutoMapper;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -40,13 +41,16 @@ namespace API.Controllers
|
|||
private readonly IAccountService _accountService;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly IHostEnvironment _environment;
|
||||
private readonly IEventHub _eventHub;
|
||||
|
||||
/// <inheritdoc />
|
||||
public AccountController(UserManager<AppUser> userManager,
|
||||
SignInManager<AppUser> signInManager,
|
||||
ITokenService tokenService, IUnitOfWork unitOfWork,
|
||||
ILogger<AccountController> logger,
|
||||
IMapper mapper, IAccountService accountService, IEmailService emailService, IHostEnvironment environment)
|
||||
IMapper mapper, IAccountService accountService,
|
||||
IEmailService emailService, IHostEnvironment environment,
|
||||
IEventHub eventHub)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
|
@ -57,6 +61,7 @@ namespace API.Controllers
|
|||
_accountService = accountService;
|
||||
_emailService = emailService;
|
||||
_environment = environment;
|
||||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -289,6 +294,7 @@ namespace API.Controllers
|
|||
{
|
||||
dto.Roles.Add(PolicyConstants.PlebRole);
|
||||
}
|
||||
|
||||
if (existingRoles.Except(dto.Roles).Any() || dto.Roles.Except(existingRoles).Any())
|
||||
{
|
||||
var roles = dto.Roles;
|
||||
|
@ -326,9 +332,9 @@ namespace API.Controllers
|
|||
lib.AppUsers.Add(user);
|
||||
}
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok();
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
||||
{
|
||||
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
|
|
@ -424,6 +424,7 @@ namespace API.Controllers
|
|||
/// </summary>
|
||||
/// <remarks>This is built for Tachiyomi and is not expected to be called by any other place</remarks>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Deprecated. Use 'Tachiyomi/mark-chapter-until-as-read'")]
|
||||
[HttpPost("mark-chapter-until-as-read")]
|
||||
public async Task<ActionResult<bool>> MarkChaptersUntilAsRead(int seriesId, float chapterNumber)
|
||||
{
|
||||
|
@ -497,7 +498,7 @@ namespace API.Controllers
|
|||
user.Bookmarks = user.Bookmarks.Where(bmk => bmk.SeriesId != dto.SeriesId).ToList();
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync())
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Jobs;
|
||||
using API.DTOs.Stats;
|
||||
using API.DTOs.Update;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using Hangfire;
|
||||
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 = System.Threading.Tasks.TaskScheduler;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
|
@ -29,10 +34,11 @@ namespace API.Controllers
|
|||
private readonly IStatsService _statsService;
|
||||
private readonly ICleanupService _cleanupService;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly IBookmarkService _bookmarkService;
|
||||
|
||||
public ServerController(IHostApplicationLifetime applicationLifetime, ILogger<ServerController> logger, IConfiguration config,
|
||||
IBackupService backupService, IArchiveService archiveService, IVersionUpdaterService versionUpdaterService, IStatsService statsService,
|
||||
ICleanupService cleanupService, IEmailService emailService)
|
||||
ICleanupService cleanupService, IEmailService emailService, IBookmarkService bookmarkService)
|
||||
{
|
||||
_applicationLifetime = applicationLifetime;
|
||||
_logger = logger;
|
||||
|
@ -43,6 +49,7 @@ namespace API.Controllers
|
|||
_statsService = statsService;
|
||||
_cleanupService = cleanupService;
|
||||
_emailService = emailService;
|
||||
_bookmarkService = bookmarkService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -76,11 +83,10 @@ namespace API.Controllers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("backup-db")]
|
||||
public async Task<ActionResult> BackupDatabase()
|
||||
public ActionResult BackupDatabase()
|
||||
{
|
||||
_logger.LogInformation("{UserName} is backing up database of server from admin dashboard", User.GetUsername());
|
||||
await _backupService.BackupDatabase();
|
||||
|
||||
RecurringJob.Trigger("backup");
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -94,6 +100,17 @@ namespace API.Controllers
|
|||
return Ok(await _statsService.GetServerInfo());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the scheduling of the convert bookmarks job. Only one job will run at a time.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpPost("convert-bookmarks")]
|
||||
public ActionResult ScheduleConvertBookmarks()
|
||||
{
|
||||
BackgroundJob.Enqueue(() => _bookmarkService.ConvertAllBookmarkToWebP());
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("logs")]
|
||||
public async Task<ActionResult> GetLogs()
|
||||
{
|
||||
|
@ -134,5 +151,24 @@ namespace API.Controllers
|
|||
{
|
||||
return await _emailService.CheckIfAccessible(Request.Host.ToString());
|
||||
}
|
||||
|
||||
[HttpGet("jobs")]
|
||||
public ActionResult<IEnumerable<JobDto>> GetJobs()
|
||||
{
|
||||
var recurringJobs = Hangfire.JobStorage.Current.GetConnection().GetRecurringJobs().Select(
|
||||
dto =>
|
||||
new JobDto() {
|
||||
Id = dto.Id,
|
||||
Title = dto.Id.Replace('-', ' '),
|
||||
Cron = dto.Cron,
|
||||
CreatedAt = dto.CreatedAt,
|
||||
LastExecution = dto.LastExecution,
|
||||
});
|
||||
|
||||
// For now, let's just do something simple
|
||||
//var enqueuedJobs = JobStorage.Current.GetMonitoringApi().EnqueuedJobs("default", 0, int.MaxValue);
|
||||
return Ok(recurringJobs);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -169,6 +169,13 @@ namespace API.Controllers
|
|||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
if (setting.Key == ServerSettingKey.ConvertBookmarkToWebP && updateSettingsDto.ConvertBookmarkToWebP + string.Empty != setting.Value)
|
||||
{
|
||||
setting.Value = updateSettingsDto.ConvertBookmarkToWebP + string.Empty;
|
||||
_unitOfWork.SettingsRepository.Update(setting);
|
||||
}
|
||||
|
||||
|
||||
if (setting.Key == ServerSettingKey.BookmarkDirectory && bookmarkDirectory != setting.Value)
|
||||
{
|
||||
// Validate new directory can be used
|
||||
|
|
79
API/Controllers/TachiyomiController.cs
Normal file
79
API/Controllers/TachiyomiController.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
public class TachiyomiController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IReaderService _readerService;
|
||||
|
||||
public TachiyomiController(IUnitOfWork unitOfWork, IReaderService readerService)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_readerService = readerService;
|
||||
}
|
||||
|
||||
[HttpGet("latest-chapter")]
|
||||
public async Task<ActionResult<ChapterDto>> GetLatestChapter(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
|
||||
var currentChapter = await _readerService.GetContinuePoint(seriesId, userId);
|
||||
|
||||
var prevChapterId =
|
||||
await _readerService.GetPrevChapterIdAsync(seriesId, currentChapter.VolumeId, currentChapter.Id, userId);
|
||||
|
||||
if (prevChapterId == -1) return null;
|
||||
|
||||
var prevChapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(prevChapterId);
|
||||
|
||||
return Ok(prevChapter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks every chapter that is sorted below the passed number as Read. This will not mark any specials as read.
|
||||
/// </summary>
|
||||
/// <remarks>This is built for Tachiyomi and is not expected to be called by any other place</remarks>
|
||||
/// <returns></returns>
|
||||
[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);
|
||||
user.Progresses ??= new List<AppUserProgress>();
|
||||
|
||||
switch (chapterNumber)
|
||||
{
|
||||
// Tachiyomi sends chapter 0.0f when there's no chapters read.
|
||||
// Due to the encoding for volumes this marks all chapters in volume 0 (loose chapters) as read so we ignore it
|
||||
case 0.0f:
|
||||
return true;
|
||||
case < 1.0f:
|
||||
{
|
||||
// This is a hack to track volume number. We need to map it back by x100
|
||||
var volumeNumber = int.Parse($"{chapterNumber * 100f}");
|
||||
await _readerService.MarkVolumesUntilAsRead(user, seriesId, volumeNumber);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
await _readerService.MarkChaptersUntilAsRead(user, seriesId, chapterNumber);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (!_unitOfWork.HasChanges()) return Ok(true);
|
||||
if (await _unitOfWork.CommitAsync()) return Ok(true);
|
||||
|
||||
await _unitOfWork.RollbackAsync();
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ using API.Data.Repositories;
|
|||
using API.DTOs;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.SignalR;
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -17,11 +18,13 @@ namespace API.Controllers
|
|||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IEventHub _eventHub;
|
||||
|
||||
public UsersController(IUnitOfWork unitOfWork, IMapper mapper)
|
||||
public UsersController(IUnitOfWork unitOfWork, IMapper mapper, IEventHub eventHub)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_mapper = mapper;
|
||||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
|
@ -69,7 +72,9 @@ namespace API.Controllers
|
|||
[HttpPost("update-preferences")]
|
||||
public async Task<ActionResult<UserPreferencesDto>> UpdatePreferences(UserPreferencesDto preferencesDto)
|
||||
{
|
||||
var existingPreferences = await _unitOfWork.UserRepository.GetPreferencesAsync(User.GetUsername());
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(),
|
||||
AppUserIncludes.UserPreferences);
|
||||
var existingPreferences = user.UserPreferences;
|
||||
|
||||
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
|
||||
existingPreferences.ScalingOption = preferencesDto.ScalingOption;
|
||||
|
@ -98,6 +103,7 @@ namespace API.Controllers
|
|||
|
||||
if (await _unitOfWork.CommitAsync())
|
||||
{
|
||||
await _eventHub.SendMessageToAsync(MessageFactory.UserUpdate, MessageFactory.UserUpdateEvent(user.Id, user.UserName), user.Id);
|
||||
return Ok(preferencesDto);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue