mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
UTC Dates + CDisplayEx API Enhancements (#1781)
* Introduced a new claim on the Token to get UserId as well as Username, thus allowing for many places of reduced DB calls. All users will need to reauthenticate. Introduced UTC Dates throughout the application, they are not exposed in all DTOs, that will come later when we fully switch over. For now, Utc dates will be updated along side timezone specific dates. Refactored get-progress/progress api to be 50% faster by reducing how much data is loaded from the query. * Speed up the following apis: collection/search, download/bookmarks, reader/bookmark-info, recommended/quick-reads, recommended/quick-catchup-reads, recommended/highly-rated, recommended/more-in, recommended/rediscover, want-to-read/ * Added a migration to sync all dates with their new UTC counterpart. * Added LastReadingProgressUtc onto ChapterDto for some browsing apis, but not all. Added LastReadingProgressUtc to reading list items. Refactored the migration to run raw SQL which is much faster. * Added LastReadingProgressUtc onto ChapterDto for some browsing apis, but not all. Added LastReadingProgressUtc to reading list items. Refactored the migration to run raw SQL which is much faster. * Fixed the unit tests * Fixed an issue with auto mapper which was causing progress page number to not get sent to UI * series/volume has chapter last reading progress * Added filesize and library name on reading list item dto for CDisplayEx. * Some minor code cleanup * Forgot to fill a field
This commit is contained in:
parent
36b48404c1
commit
7616eb5b0f
@ -448,8 +448,9 @@ public class CleanupServiceTests : AbstractDbTest
|
||||
// Delete the Chapter
|
||||
_context.Chapter.Remove(c);
|
||||
await _unitOfWork.CommitAsync();
|
||||
Assert.Single(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
Assert.Empty(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
|
||||
// NOTE: This may not be needed, the underlying DB structure seems fixed as of v0.7
|
||||
await cleanupService.CleanupDbEntries();
|
||||
|
||||
Assert.Empty(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1));
|
||||
|
@ -41,7 +41,6 @@ public class AccountController : BaseApiController
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IAccountService _accountService;
|
||||
private readonly IEmailService _emailService;
|
||||
private readonly IHostEnvironment _environment;
|
||||
private readonly IEventHub _eventHub;
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -50,8 +49,7 @@ public class AccountController : BaseApiController
|
||||
ITokenService tokenService, IUnitOfWork unitOfWork,
|
||||
ILogger<AccountController> logger,
|
||||
IMapper mapper, IAccountService accountService,
|
||||
IEmailService emailService, IHostEnvironment environment,
|
||||
IEventHub eventHub)
|
||||
IEmailService emailService, IEventHub eventHub)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
@ -61,7 +59,6 @@ public class AccountController : BaseApiController
|
||||
_mapper = mapper;
|
||||
_accountService = accountService;
|
||||
_emailService = emailService;
|
||||
_environment = environment;
|
||||
_eventHub = eventHub;
|
||||
}
|
||||
|
||||
@ -202,7 +199,7 @@ public class AccountController : BaseApiController
|
||||
}
|
||||
|
||||
// Update LastActive on account
|
||||
user.LastActive = DateTime.Now;
|
||||
user.UpdateLastActive();
|
||||
user.UserPreferences ??= new AppUserPreferences
|
||||
{
|
||||
Theme = await _unitOfWork.SiteThemeRepository.GetDefaultTheme()
|
||||
|
@ -61,8 +61,7 @@ public class CollectionController : BaseApiController
|
||||
queryString = queryString.Replace(@"%", string.Empty);
|
||||
if (queryString.Length == 0) return await GetAllTags();
|
||||
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return await _unitOfWork.CollectionTagRepository.SearchTagDtosAsync(queryString, user.Id);
|
||||
return await _unitOfWork.CollectionTagRepository.SearchTagDtosAsync(queryString, User.GetUserId());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -201,19 +201,20 @@ 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 user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(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";
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.DownloadProgressEvent(User.GetUsername(), Path.GetFileNameWithoutExtension(filename), 0F));
|
||||
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 0F));
|
||||
var seriesIds = string.Join("_", downloadBookmarkDto.Bookmarks.Select(b => b.SeriesId).Distinct());
|
||||
var filePath = _archiveService.CreateZipForDownload(files,
|
||||
$"download_{user.Id}_{seriesIds}_bookmarks");
|
||||
$"download_{userId}_{seriesIds}_bookmarks");
|
||||
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress,
|
||||
MessageFactory.DownloadProgressEvent(User.GetUsername(), Path.GetFileNameWithoutExtension(filename), 1F));
|
||||
MessageFactory.DownloadProgressEvent(username, Path.GetFileNameWithoutExtension(filename), 1F));
|
||||
|
||||
|
||||
return PhysicalFile(filePath, DefaultContentType, filename, true);
|
||||
|
@ -244,8 +244,7 @@ public class ReaderController : BaseApiController
|
||||
[HttpGet("bookmark-info")]
|
||||
public async Task<ActionResult<BookmarkInfoDto>> GetBookmarkInfo(int seriesId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var totalPages = await _cacheService.CacheBookmarkForSeries(user.Id, seriesId);
|
||||
var totalPages = await _cacheService.CacheBookmarkForSeries(User.GetUserId(), seriesId);
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(seriesId, SeriesIncludes.None);
|
||||
|
||||
return Ok(new BookmarkInfoDto()
|
||||
@ -451,25 +450,15 @@ public class ReaderController : BaseApiController
|
||||
[HttpGet("get-progress")]
|
||||
public async Task<ActionResult<ProgressDto>> GetProgress(int chapterId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Progress);
|
||||
var progressBookmark = new ProgressDto()
|
||||
var progress = await _unitOfWork.AppUserProgressRepository.GetUserProgressDtoAsync(chapterId, User.GetUserId());
|
||||
if (progress == null) return Ok(new ProgressDto()
|
||||
{
|
||||
PageNum = 0,
|
||||
ChapterId = chapterId,
|
||||
VolumeId = 0,
|
||||
SeriesId = 0
|
||||
};
|
||||
if (user.Progresses == null) return Ok(progressBookmark);
|
||||
var progress = user.Progresses.FirstOrDefault(x => x.AppUserId == user.Id && x.ChapterId == chapterId);
|
||||
|
||||
if (progress != null)
|
||||
{
|
||||
progressBookmark.SeriesId = progress.SeriesId;
|
||||
progressBookmark.VolumeId = progress.VolumeId;
|
||||
progressBookmark.PageNum = progress.PagesRead;
|
||||
progressBookmark.BookScrollId = progress.BookScrollId;
|
||||
}
|
||||
return Ok(progressBookmark);
|
||||
});
|
||||
return Ok(progress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -480,9 +469,7 @@ public class ReaderController : BaseApiController
|
||||
[HttpPost("progress")]
|
||||
public async Task<ActionResult> BookmarkProgress(ProgressDto progressDto)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
|
||||
if (await _readerService.SaveReadingProgress(progressDto, user.Id)) return Ok(true);
|
||||
if (await _readerService.SaveReadingProgress(progressDto, User.GetUserId())) return Ok(true);
|
||||
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
@ -506,7 +493,7 @@ public class ReaderController : BaseApiController
|
||||
/// <param name="seriesId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("has-progress")]
|
||||
public async Task<ActionResult<ChapterDto>> HasProgress(int seriesId)
|
||||
public async Task<ActionResult<bool>> HasProgress(int seriesId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.AppUserProgressRepository.HasAnyProgressOnSeriesAsync(seriesId, userId));
|
||||
|
@ -82,8 +82,7 @@ public class ReadingListController : BaseApiController
|
||||
[HttpGet("items")]
|
||||
public async Task<ActionResult<IEnumerable<ReadingListItemDto>>> GetListForUser(int readingListId)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId);
|
||||
var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, User.GetUserId());
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,8 @@ public class RecommendedController : BaseApiController
|
||||
[HttpGet("quick-reads")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetQuickReads(int libraryId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
|
||||
userParams ??= new UserParams();
|
||||
var series = await _unitOfWork.SeriesRepository.GetQuickReads(user.Id, libraryId, userParams);
|
||||
var series = await _unitOfWork.SeriesRepository.GetQuickReads(User.GetUserId(), libraryId, userParams);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
return Ok(series);
|
||||
@ -44,10 +42,8 @@ public class RecommendedController : BaseApiController
|
||||
[HttpGet("quick-catchup-reads")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetQuickCatchupReads(int libraryId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
|
||||
userParams ??= new UserParams();
|
||||
var series = await _unitOfWork.SeriesRepository.GetQuickCatchupReads(user.Id, libraryId, userParams);
|
||||
var series = await _unitOfWork.SeriesRepository.GetQuickCatchupReads(User.GetUserId(), libraryId, userParams);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
return Ok(series);
|
||||
@ -62,11 +58,10 @@ public class RecommendedController : BaseApiController
|
||||
[HttpGet("highly-rated")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetHighlyRated(int libraryId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
|
||||
var userId = User.GetUserId();
|
||||
userParams ??= new UserParams();
|
||||
var series = await _unitOfWork.SeriesRepository.GetHighlyRated(user.Id, libraryId, userParams);
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
||||
var series = await _unitOfWork.SeriesRepository.GetHighlyRated(userId, libraryId, userParams);
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
return Ok(series);
|
||||
}
|
||||
@ -81,11 +76,11 @@ public class RecommendedController : BaseApiController
|
||||
[HttpGet("more-in")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetMoreIn(int libraryId, int genreId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var userId = User.GetUserId();
|
||||
|
||||
userParams ??= new UserParams();
|
||||
var series = await _unitOfWork.SeriesRepository.GetMoreIn(user.Id, libraryId, genreId, userParams);
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
||||
var series = await _unitOfWork.SeriesRepository.GetMoreIn(userId, libraryId, genreId, userParams);
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
return Ok(series);
|
||||
@ -100,10 +95,8 @@ public class RecommendedController : BaseApiController
|
||||
[HttpGet("rediscover")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetRediscover(int libraryId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
|
||||
userParams ??= new UserParams();
|
||||
var series = await _unitOfWork.SeriesRepository.GetRediscover(user.Id, libraryId, userParams);
|
||||
var series = await _unitOfWork.SeriesRepository.GetRediscover(User.GetUserId(), libraryId, userParams);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
return Ok(series);
|
||||
|
@ -7,6 +7,7 @@ using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.SeriesDetail;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
@ -121,11 +122,12 @@ public class SeriesController : BaseApiController
|
||||
[HttpGet("chapter")]
|
||||
public async Task<ActionResult<ChapterDto>> GetChapter(int chapterId)
|
||||
{
|
||||
return Ok(await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId));
|
||||
var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(chapterId);
|
||||
return Ok(await _unitOfWork.ChapterRepository.AddChapterModifiers(User.GetUserId(), chapter));
|
||||
}
|
||||
|
||||
[HttpGet("chapter-metadata")]
|
||||
public async Task<ActionResult<ChapterDto>> GetChapterMetadata(int chapterId)
|
||||
public async Task<ActionResult<ChapterMetadataDto>> GetChapterMetadata(int chapterId)
|
||||
{
|
||||
return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId));
|
||||
}
|
||||
|
@ -32,8 +32,7 @@ public class TachiyomiController : BaseApiController
|
||||
public async Task<ActionResult<ChapterDto>> GetLatestChapter(int seriesId)
|
||||
{
|
||||
if (seriesId < 1) return BadRequest("seriesId must be greater than 0");
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _tachiyomiService.GetLatestChapter(seriesId, userId));
|
||||
return Ok(await _tachiyomiService.GetLatestChapter(seriesId, User.GetUserId()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -49,7 +49,7 @@ public class UploadController : BaseApiController
|
||||
[HttpPost("upload-by-url")]
|
||||
public async Task<ActionResult<string>> GetImageFromFile(UploadUrlDto dto)
|
||||
{
|
||||
var dateString = $"{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToLongTimeString()}".Replace('/', '_').Replace(':', '_');
|
||||
var dateString = $"{DateTime.UtcNow.ToShortDateString()}_{DateTime.UtcNow.ToLongTimeString()}".Replace('/', '_').Replace(':', '_');
|
||||
var format = _directoryService.FileSystem.Path.GetExtension(dto.Url.Split('?')[0]).Replace(".", string.Empty);
|
||||
try
|
||||
{
|
||||
|
@ -35,8 +35,7 @@ public class WantToReadController : BaseApiController
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetWantToRead([FromQuery] UserParams userParams, FilterDto filterDto)
|
||||
{
|
||||
userParams ??= new UserParams();
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(user.Id, userParams, filterDto);
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(User.GetUserId(), userParams, filterDto);
|
||||
Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages);
|
||||
return Ok(pagedList);
|
||||
}
|
||||
@ -44,8 +43,7 @@ public class WantToReadController : BaseApiController
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<bool>> IsSeriesInWantToRead([FromQuery] int seriesId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.IsSeriesInWantToRead(user.Id, seriesId));
|
||||
return Ok(await _unitOfWork.SeriesRepository.IsSeriesInWantToRead(User.GetUserId(), seriesId));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using API.DTOs.Metadata;
|
||||
using API.DTOs.Reader;
|
||||
using API.Entities.Enums;
|
||||
using API.Entities.Interfaces;
|
||||
|
||||
@ -11,7 +9,7 @@ namespace API.DTOs;
|
||||
/// A Chapter is the lowest grouping of a reading medium. A Chapter contains a set of MangaFiles which represents the underlying
|
||||
/// file (abstracted from type).
|
||||
/// </summary>
|
||||
public class ChapterDto : IHasReadTimeEstimate
|
||||
public class ChapterDto : IHasReadTimeEstimate, IEntityDate
|
||||
{
|
||||
public int Id { get; init; }
|
||||
/// <summary>
|
||||
@ -43,6 +41,10 @@ public class ChapterDto : IHasReadTimeEstimate
|
||||
/// </summary>
|
||||
public int PagesRead { get; set; }
|
||||
/// <summary>
|
||||
/// The last time a chapter was read by current authenticated user
|
||||
/// </summary>
|
||||
public DateTime LastReadingProgressUtc { get; set; }
|
||||
/// <summary>
|
||||
/// If the Cover Image is locked for this entity
|
||||
/// </summary>
|
||||
public bool CoverImageLocked { get; set; }
|
||||
@ -53,7 +55,10 @@ public class ChapterDto : IHasReadTimeEstimate
|
||||
/// <summary>
|
||||
/// When chapter was created
|
||||
/// </summary>
|
||||
public DateTime Created { get; init; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
/// <summary>
|
||||
/// When the chapter was released.
|
||||
/// </summary>
|
||||
@ -77,7 +82,6 @@ public class ChapterDto : IHasReadTimeEstimate
|
||||
/// Total words in a Chapter (books only)
|
||||
/// </summary>
|
||||
public long WordCount { get; set; } = 0L;
|
||||
|
||||
/// <summary>
|
||||
/// Formatted Volume title ie) Volume 2.
|
||||
/// </summary>
|
||||
|
@ -30,4 +30,8 @@ public class DeviceDto
|
||||
/// Last time this device was used to send a file
|
||||
/// </summary>
|
||||
public DateTime LastUsed { get; set; }
|
||||
/// <summary>
|
||||
/// Last time this device was used to send a file
|
||||
/// </summary>
|
||||
public DateTime LastUsedUtc { get; set; }
|
||||
}
|
||||
|
@ -20,5 +20,13 @@ public class JobDto
|
||||
/// Last time the job was run
|
||||
/// </summary>
|
||||
public DateTime? LastExecution { get; set; }
|
||||
/// <summary>
|
||||
/// When the job was created
|
||||
/// </summary>
|
||||
public DateTime? CreatedAtUtc { get; set; }
|
||||
/// <summary>
|
||||
/// Last time the job was run
|
||||
/// </summary>
|
||||
public DateTime? LastExecutionUtc { get; set; }
|
||||
public string Cron { get; set; }
|
||||
}
|
||||
|
@ -16,12 +16,8 @@ public class ProgressDto
|
||||
[Required]
|
||||
public int LibraryId { get; set; }
|
||||
/// <summary>
|
||||
/// For Book reader, this can be an optional string of the id of a part marker, to help resume reading position
|
||||
/// For EPUB reader, this can be an optional string of the id of a part marker, to help resume reading position
|
||||
/// on pages that combine multiple "chapters".
|
||||
/// </summary>
|
||||
public string BookScrollId { get; set; }
|
||||
/// <summary>
|
||||
/// Last time the progress was synced from UI or external app
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; }
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ public class ReadingListItemDto
|
||||
public int VolumeId { get; set; }
|
||||
public int LibraryId { get; set; }
|
||||
public LibraryType LibraryType { get; set; }
|
||||
public string LibraryName { get; set; }
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// Release Date from Chapter
|
||||
@ -28,4 +29,13 @@ public class ReadingListItemDto
|
||||
/// Used internally only
|
||||
/// </summary>
|
||||
public int ReadingListId { get; set; }
|
||||
/// <summary>
|
||||
/// The last time a reading list item (underlying chapter) was read by current authenticated user
|
||||
/// </summary>
|
||||
public DateTime LastReadingProgressUtc { get; set; }
|
||||
/// <summary>
|
||||
/// File size of underlying item
|
||||
/// </summary>
|
||||
/// <remarks>This is only used for CDisplayEx</remarks>
|
||||
public long FileSize { get; set; }
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using API.Entities.Enums.Theme;
|
||||
using API.Entities.Interfaces;
|
||||
using API.Services;
|
||||
|
||||
namespace API.DTOs.Theme;
|
||||
@ -7,7 +8,7 @@ namespace API.DTOs.Theme;
|
||||
/// <summary>
|
||||
/// Represents a set of css overrides the user can upload to Kavita and will load into webui
|
||||
/// </summary>
|
||||
public class SiteThemeDto
|
||||
public class SiteThemeDto : IEntityDate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
/// <summary>
|
||||
@ -29,5 +30,7 @@ public class SiteThemeDto
|
||||
public ThemeProvider Provider { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
public string Selector => "bg-" + Name.ToLower();
|
||||
}
|
||||
|
@ -112,18 +112,19 @@ public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
|
||||
|
||||
private static void OnEntityTracked(object sender, EntityTrackedEventArgs e)
|
||||
{
|
||||
if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IEntityDate entity)
|
||||
{
|
||||
entity.Created = DateTime.Now;
|
||||
entity.LastModified = DateTime.Now;
|
||||
}
|
||||
if (e.FromQuery || e.Entry.State != EntityState.Added || e.Entry.Entity is not IEntityDate entity) return;
|
||||
|
||||
entity.Created = DateTime.Now;
|
||||
entity.LastModified = DateTime.Now;
|
||||
entity.CreatedUtc = DateTime.UtcNow;
|
||||
entity.LastModifiedUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private static void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
|
||||
{
|
||||
if (e.NewState == EntityState.Modified && e.Entry.Entity is IEntityDate entity)
|
||||
entity.LastModified = DateTime.Now;
|
||||
if (e.NewState != EntityState.Modified || e.Entry.Entity is not IEntityDate entity) return;
|
||||
entity.LastModified = DateTime.Now;
|
||||
entity.LastModifiedUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private void OnSaveChanges()
|
||||
|
@ -156,7 +156,8 @@ public static class DbFactory
|
||||
FilePath = filePath,
|
||||
Format = format,
|
||||
Pages = pages,
|
||||
LastModified = File.GetLastWriteTime(filePath)
|
||||
LastModified = File.GetLastWriteTime(filePath),
|
||||
LastModifiedUtc = File.GetLastWriteTimeUtc(filePath),
|
||||
};
|
||||
}
|
||||
|
||||
|
153
API/Data/MigrateToUtcDates.cs
Normal file
153
API/Data/MigrateToUtcDates.cs
Normal file
@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Introduced in v0.6.1.38 or v0.7.0,
|
||||
/// </summary>
|
||||
public static class MigrateToUtcDates
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
// if current version is > 0.6.1.38, then we can exit and not perform
|
||||
var settings = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
if (Version.Parse(settings.InstallVersion) > new Version(0, 6, 1, 38))
|
||||
{
|
||||
return;
|
||||
}
|
||||
logger.LogCritical("Running MigrateToUtcDates migration. Please be patient, this may take some time depending on the size of your library. Do not abort, this can break your Database");
|
||||
|
||||
#region Series
|
||||
logger.LogInformation("Updating Dates on Series...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE Series SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc'),
|
||||
[LastChapterAddedUtc] = datetime([LastChapterAdded], 'utc'),
|
||||
[LastFolderScannedUtc] = datetime([LastFolderScanned], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Series...Done");
|
||||
#endregion
|
||||
|
||||
#region Library
|
||||
logger.LogInformation("Updating Dates on Libraries...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE Library SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Libraries...Done");
|
||||
#endregion
|
||||
|
||||
#region Volume
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Updating Dates on Volumes...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE Volume SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc');
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Volumes...Done");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Updating Dates on Volumes...Failed");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Chapter
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Updating Dates on Chapters...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE Chapter SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Chapters...Done");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Updating Dates on Chapters...Failed");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AppUserBookmark
|
||||
logger.LogInformation("Updating Dates on Bookmarks...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE AppUserBookmark SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Bookmarks...Done");
|
||||
#endregion
|
||||
|
||||
#region AppUserProgress
|
||||
logger.LogInformation("Updating Dates on Progress...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE AppUserProgresses SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Progress...Done");
|
||||
#endregion
|
||||
|
||||
#region Device
|
||||
logger.LogInformation("Updating Dates on Device...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE Device SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc'),
|
||||
[LastUsedUtc] = datetime([LastUsed], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on Device...Done");
|
||||
#endregion
|
||||
|
||||
#region MangaFile
|
||||
logger.LogInformation("Updating Dates on MangaFile...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE MangaFile SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc'),
|
||||
[LastFileAnalysisUtc] = datetime([LastFileAnalysis], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on MangaFile...Done");
|
||||
#endregion
|
||||
|
||||
#region ReadingList
|
||||
logger.LogInformation("Updating Dates on ReadingList...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE ReadingList SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on ReadingList...Done");
|
||||
#endregion
|
||||
|
||||
#region SiteTheme
|
||||
logger.LogInformation("Updating Dates on SiteTheme...");
|
||||
await dataContext.Database.ExecuteSqlRawAsync(@"
|
||||
UPDATE SiteTheme SET
|
||||
[LastModifiedUtc] = datetime([LastModified], 'utc'),
|
||||
[CreatedUtc] = datetime([Created], 'utc')
|
||||
;
|
||||
");
|
||||
logger.LogInformation("Updating Dates on SiteTheme...Done");
|
||||
#endregion
|
||||
|
||||
logger.LogInformation("MigrateToUtcDates migration finished");
|
||||
|
||||
}
|
||||
}
|
1836
API/Data/Migrations/20230210153842_UtcTimes.Designer.cs
generated
Normal file
1836
API/Data/Migrations/20230210153842_UtcTimes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
323
API/Data/Migrations/20230210153842_UtcTimes.cs
Normal file
323
API/Data/Migrations/20230210153842_UtcTimes.cs
Normal file
@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class UtcTimes : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "Volume",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Volume",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "SiteTheme",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "SiteTheme",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastChapterAddedUtc",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastFolderScannedUtc",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "ReadingList",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "ReadingList",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "MangaFile",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastFileAnalysisUtc",
|
||||
table: "MangaFile",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "MangaFile",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "Library",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Library",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "Device",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Device",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastUsedUtc",
|
||||
table: "Device",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "Chapter",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Chapter",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "AspNetUsers",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastActiveUtc",
|
||||
table: "AspNetUsers",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "AppUserProgresses",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "AppUserProgresses",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedUtc",
|
||||
table: "AppUserBookmark",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "LastModifiedUtc",
|
||||
table: "AppUserBookmark",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AppUserProgresses_ChapterId",
|
||||
table: "AppUserProgresses",
|
||||
column: "ChapterId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_AppUserProgresses_Chapter_ChapterId",
|
||||
table: "AppUserProgresses",
|
||||
column: "ChapterId",
|
||||
principalTable: "Chapter",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_AppUserProgresses_Chapter_ChapterId",
|
||||
table: "AppUserProgresses");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_AppUserProgresses_ChapterId",
|
||||
table: "AppUserProgresses");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "Volume");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Volume");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "SiteTheme");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "SiteTheme");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastChapterAddedUtc",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastFolderScannedUtc",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Series");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "ReadingList");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "ReadingList");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "MangaFile");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastFileAnalysisUtc",
|
||||
table: "MangaFile");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "MangaFile");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "Library");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Library");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "Device");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Device");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastUsedUtc",
|
||||
table: "Device");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "Chapter");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastActiveUtc",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "AppUserProgresses");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "AppUserProgresses");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedUtc",
|
||||
table: "AppUserBookmark");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastModifiedUtc",
|
||||
table: "AppUserBookmark");
|
||||
}
|
||||
}
|
||||
}
|
@ -72,6 +72,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
@ -82,6 +85,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastActive")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastActiveUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -146,12 +152,18 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Page")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -283,9 +295,15 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -302,6 +320,8 @@ namespace API.Data.Migrations
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("AppUserProgresses");
|
||||
@ -373,6 +393,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -382,6 +405,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("MaxHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -475,6 +501,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EmailAddress")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -484,9 +513,15 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastUsed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastUsedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -554,6 +589,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("FolderWatching")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
@ -577,6 +615,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastScanned")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -611,6 +652,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -623,9 +667,15 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastFileAnalysis")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastFileAnalysisUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -797,9 +847,15 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedTitle")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -874,6 +930,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FolderPath")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -883,12 +942,21 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastChapterAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastChapterAddedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastFolderScanned")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastFolderScannedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -1004,6 +1072,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -1013,6 +1084,9 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@ -1062,9 +1136,15 @@ namespace API.Data.Migrations
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModifiedUtc")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("MaxHoursToRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@ -1333,6 +1413,12 @@ namespace API.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Chapter", null)
|
||||
.WithMany("UserProgress")
|
||||
.HasForeignKey("ChapterId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("API.Entities.Series", null)
|
||||
.WithMany("Progress")
|
||||
.HasForeignKey("SeriesId")
|
||||
@ -1707,6 +1793,8 @@ namespace API.Data.Migrations
|
||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("UserProgress");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Library", b =>
|
||||
|
@ -1,8 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data.Repositories;
|
||||
@ -21,15 +24,18 @@ public interface IAppUserProgressRepository
|
||||
Task<AppUserProgress> GetAnyProgress();
|
||||
Task<IEnumerable<AppUserProgress>> GetUserProgressForSeriesAsync(int seriesId, int userId);
|
||||
Task<IEnumerable<AppUserProgress>> GetAllProgress();
|
||||
Task<ProgressDto> GetUserProgressDtoAsync(int chapterId, int userId);
|
||||
}
|
||||
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public AppUserProgressRepository(DataContext context)
|
||||
public AppUserProgressRepository(DataContext context, IMapper mapper)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public void Update(AppUserProgress userProgress)
|
||||
@ -114,6 +120,14 @@ public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
return await _context.AppUserProgresses.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ProgressDto> GetUserProgressDtoAsync(int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId && p.ChapterId == chapterId)
|
||||
.ProjectTo<ProgressDto>(_mapper.ConfigurationProvider)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<AppUserProgress> GetUserProgressAsync(int chapterId, int userId)
|
||||
{
|
||||
return await _context.AppUserProgresses
|
||||
|
@ -18,7 +18,7 @@ public enum ChapterIncludes
|
||||
{
|
||||
None = 1,
|
||||
Volumes = 2,
|
||||
Files = 4
|
||||
Files = 4,
|
||||
}
|
||||
|
||||
public interface IChapterRepository
|
||||
@ -28,8 +28,8 @@ public interface IChapterRepository
|
||||
Task<IChapterInfoDto> GetChapterInfoDtoAsync(int chapterId);
|
||||
Task<int> GetChapterTotalPagesAsync(int chapterId);
|
||||
Task<Chapter> GetChapterAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files);
|
||||
Task<ChapterDto> GetChapterDtoAsync(int chapterId);
|
||||
Task<ChapterMetadataDto> GetChapterMetadataDtoAsync(int chapterId);
|
||||
Task<ChapterDto> GetChapterDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files);
|
||||
Task<ChapterMetadataDto> GetChapterMetadataDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files);
|
||||
Task<IList<MangaFile>> GetFilesForChapterAsync(int chapterId);
|
||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||
Task<IList<MangaFile>> GetFilesForChaptersAsync(IReadOnlyList<int> chapterIds);
|
||||
@ -37,6 +37,7 @@ public interface IChapterRepository
|
||||
Task<IList<string>> GetAllCoverImagesAsync();
|
||||
Task<IList<Chapter>> GetAllChaptersWithNonWebPCovers();
|
||||
Task<IEnumerable<string>> GetCoverImagesForLockedChaptersAsync();
|
||||
Task<ChapterDto> AddChapterModifiers(int userId, ChapterDto chapter);
|
||||
}
|
||||
public class ChapterRepository : IChapterRepository
|
||||
{
|
||||
@ -121,24 +122,24 @@ public class ChapterRepository : IChapterRepository
|
||||
return _context.Chapter
|
||||
.Where(c => c.Id == chapterId)
|
||||
.Select(c => c.Pages)
|
||||
.SingleOrDefaultAsync();
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
||||
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.Includes(includes)
|
||||
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
.FirstOrDefaultAsync(c => c.Id == chapterId);
|
||||
|
||||
return chapter;
|
||||
}
|
||||
|
||||
public async Task<ChapterMetadataDto> GetChapterMetadataDtoAsync(int chapterId)
|
||||
public async Task<ChapterMetadataDto> GetChapterMetadataDtoAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files)
|
||||
{
|
||||
var chapter = await _context.Chapter
|
||||
.Include(c => c.Files)
|
||||
.Includes(includes)
|
||||
.ProjectTo<ChapterMetadataDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
@ -168,14 +169,9 @@ public class ChapterRepository : IChapterRepository
|
||||
/// <returns></returns>
|
||||
public async Task<Chapter> GetChapterAsync(int chapterId, ChapterIncludes includes = ChapterIncludes.Files)
|
||||
{
|
||||
var query = _context.Chapter
|
||||
.AsSplitQuery();
|
||||
|
||||
if (includes.HasFlag(ChapterIncludes.Files)) query = query.Include(c => c.Files);
|
||||
if (includes.HasFlag(ChapterIncludes.Volumes)) query = query.Include(c => c.Volume);
|
||||
|
||||
return await query
|
||||
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||
return await _context.Chapter
|
||||
.Includes(includes)
|
||||
.FirstOrDefaultAsync(c => c.Id == chapterId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -247,4 +243,24 @@ public class ChapterRepository : IChapterRepository
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ChapterDto> AddChapterModifiers(int userId, ChapterDto chapter)
|
||||
{
|
||||
var progress = await _context.AppUserProgresses.Where(x =>
|
||||
x.AppUserId == userId && x.ChapterId == chapter.Id)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync();
|
||||
if (progress != null)
|
||||
{
|
||||
chapter.PagesRead = progress.PagesRead ;
|
||||
chapter.LastReadingProgressUtc = progress.LastModifiedUtc;
|
||||
}
|
||||
else
|
||||
{
|
||||
chapter.PagesRead = 0;
|
||||
chapter.LastReadingProgressUtc = DateTime.MinValue;
|
||||
}
|
||||
|
||||
return chapter;
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ public class ReadingListRepository : IReadingListRepository
|
||||
chapter.ReleaseDate,
|
||||
ReadingListItem = data,
|
||||
ChapterTitleName = chapter.TitleName,
|
||||
FileSize = chapter.Files.Sum(f => f.Bytes)
|
||||
|
||||
})
|
||||
.Join(_context.Volume, s => s.ReadingListItem.VolumeId, volume => volume.Id, (data, volume) => new
|
||||
@ -158,6 +159,7 @@ public class ReadingListRepository : IReadingListRepository
|
||||
data.ChapterNumber,
|
||||
data.ReleaseDate,
|
||||
data.ChapterTitleName,
|
||||
data.FileSize,
|
||||
VolumeId = volume.Id,
|
||||
VolumeNumber = volume.Name,
|
||||
})
|
||||
@ -174,6 +176,8 @@ public class ReadingListRepository : IReadingListRepository
|
||||
data.VolumeId,
|
||||
data.ReleaseDate,
|
||||
data.ChapterTitleName,
|
||||
data.FileSize,
|
||||
LibraryName = _context.Library.Where(l => l.Id == s.LibraryId).Select(l => l.Name).Single(),
|
||||
LibraryType = _context.Library.Where(l => l.Id == s.LibraryId).Select(l => l.Type).Single()
|
||||
})
|
||||
.Select(data => new ReadingListItemDto()
|
||||
@ -192,7 +196,9 @@ public class ReadingListRepository : IReadingListRepository
|
||||
ReadingListId = data.ReadingListItem.ReadingListId,
|
||||
ReleaseDate = data.ReleaseDate,
|
||||
LibraryType = data.LibraryType,
|
||||
ChapterTitleName = data.ChapterTitleName
|
||||
ChapterTitleName = data.ChapterTitleName,
|
||||
LibraryName = data.LibraryName,
|
||||
FileSize = data.FileSize
|
||||
})
|
||||
.Where(o => userLibraries.Contains(o.LibraryId))
|
||||
.OrderBy(rli => rli.Order)
|
||||
@ -218,6 +224,7 @@ public class ReadingListRepository : IReadingListRepository
|
||||
if (progressItem == null) continue;
|
||||
|
||||
progressItem.PagesRead = progress.PagesRead;
|
||||
progressItem.LastReadingProgressUtc = progress.LastModifiedUtc;
|
||||
}
|
||||
|
||||
return items;
|
||||
@ -233,7 +240,7 @@ public class ReadingListRepository : IReadingListRepository
|
||||
|
||||
public async Task<IEnumerable<ReadingListItemDto>> AddReadingProgressModifiers(int userId, IList<ReadingListItemDto> items)
|
||||
{
|
||||
var chapterIds = items.Select(i => i.ChapterId).Distinct().ToList();
|
||||
var chapterIds = items.Select(i => i.ChapterId).Distinct();
|
||||
var userProgress = await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId && chapterIds.Contains(p.ChapterId))
|
||||
.AsNoTracking()
|
||||
@ -241,8 +248,10 @@ public class ReadingListRepository : IReadingListRepository
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var progress = userProgress.Where(p => p.ChapterId == item.ChapterId);
|
||||
item.PagesRead = progress.Sum(p => p.PagesRead);
|
||||
var progress = userProgress.Where(p => p.ChapterId == item.ChapterId).ToList();
|
||||
if (progress.Count == 0) continue;
|
||||
item.PagesRead = progress.Sum(p => p.PagesRead);
|
||||
item.LastReadingProgressUtc = progress.Max(p => p.LastModifiedUtc);
|
||||
}
|
||||
|
||||
return items;
|
||||
|
@ -218,7 +218,10 @@ public class VolumeRepository : IVolumeRepository
|
||||
{
|
||||
foreach (var c in v.Chapters)
|
||||
{
|
||||
c.PagesRead = userProgress.Where(p => p.ChapterId == c.Id).Sum(p => p.PagesRead);
|
||||
var progresses = userProgress.Where(p => p.ChapterId == c.Id).ToList();
|
||||
if (progresses.Count == 0) continue;
|
||||
c.PagesRead = progresses.Sum(p => p.PagesRead);
|
||||
c.LastReadingProgressUtc = progresses.Max(p => p.LastModifiedUtc);
|
||||
}
|
||||
|
||||
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
|
||||
|
@ -51,7 +51,7 @@ public class UnitOfWork : IUnitOfWork
|
||||
|
||||
public ISettingsRepository SettingsRepository => new SettingsRepository(_context, _mapper);
|
||||
|
||||
public IAppUserProgressRepository AppUserProgressRepository => new AppUserProgressRepository(_context);
|
||||
public IAppUserProgressRepository AppUserProgressRepository => new AppUserProgressRepository(_context, _mapper);
|
||||
public ICollectionTagRepository CollectionTagRepository => new CollectionTagRepository(_context, _mapper);
|
||||
public IChapterRepository ChapterRepository => new ChapterRepository(_context, _mapper);
|
||||
public IReadingListRepository ReadingListRepository => new ReadingListRepository(_context, _mapper);
|
||||
|
@ -11,7 +11,9 @@ namespace API.Entities;
|
||||
public class AppUser : IdentityUser<int>, IHasConcurrencyToken
|
||||
{
|
||||
public DateTime Created { get; set; } = DateTime.Now;
|
||||
public DateTime CreatedUtc { get; set; } = DateTime.UtcNow;
|
||||
public DateTime LastActive { get; set; }
|
||||
public DateTime LastActiveUtc { get; set; }
|
||||
public ICollection<Library> Libraries { get; set; }
|
||||
public ICollection<AppUserRole> UserRoles { get; set; }
|
||||
public ICollection<AppUserProgress> Progresses { get; set; }
|
||||
@ -60,4 +62,10 @@ public class AppUser : IdentityUser<int>, IHasConcurrencyToken
|
||||
RowVersion++;
|
||||
}
|
||||
|
||||
public void UpdateLastActive()
|
||||
{
|
||||
LastActive = DateTime.Now;
|
||||
LastActiveUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,4 +27,6 @@ public class AppUserBookmark : IEntityDate
|
||||
public int AppUserId { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
}
|
||||
|
@ -47,6 +47,9 @@ public class AppUserProgress : IEntityDate
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
// Relationships
|
||||
/// <summary>
|
||||
/// Navigational Property for EF. Links to a unique AppUser
|
||||
|
@ -24,6 +24,9 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
||||
public ICollection<MangaFile> Files { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Relative path to the (managed) image file representing the cover image
|
||||
/// </summary>
|
||||
@ -101,6 +104,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate
|
||||
public ICollection<Genre> Genres { get; set; } = new List<Genre>();
|
||||
public ICollection<Tag> Tags { get; set; } = new List<Tag>();
|
||||
|
||||
public ICollection<AppUserProgress> UserProgress { get; set; }
|
||||
|
||||
|
||||
|
||||
|
@ -41,6 +41,15 @@ public class Device : IEntityDate
|
||||
/// Last time this device was used to send a file
|
||||
/// </summary>
|
||||
public DateTime LastUsed { get; set; }
|
||||
public DateTime LastUsedUtc { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
public void UpdateLastUsed()
|
||||
{
|
||||
LastUsed = DateTime.Now;
|
||||
LastUsedUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
@ -16,4 +16,16 @@ public class FolderPath
|
||||
// Relationship
|
||||
public Library Library { get; set; }
|
||||
public int LibraryId { get; set; }
|
||||
|
||||
public void UpdateLastScanned(DateTime? time)
|
||||
{
|
||||
if (time == null)
|
||||
{
|
||||
LastScanned = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastScanned = (DateTime) time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,6 @@ public interface IEntityDate
|
||||
{
|
||||
DateTime Created { get; set; }
|
||||
DateTime LastModified { get; set; }
|
||||
DateTime CreatedUtc { get; set; }
|
||||
DateTime LastModifiedUtc { get; set; }
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ public class Library : IEntityDate
|
||||
public bool ManageCollections { get; set; } = true;
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last time Library was scanned
|
||||
/// </summary>
|
||||
@ -42,4 +45,21 @@ public class Library : IEntityDate
|
||||
public ICollection<AppUser> AppUsers { get; set; }
|
||||
public ICollection<Series> Series { get; set; }
|
||||
|
||||
public void UpdateLastModified()
|
||||
{
|
||||
LastModified = DateTime.Now;
|
||||
LastModifiedUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateLastScanned(DateTime? time)
|
||||
{
|
||||
if (time == null)
|
||||
{
|
||||
LastScanned = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
LastScanned = (DateTime) time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,15 @@ public class MangaFile : IEntityDate
|
||||
/// </summary>
|
||||
/// <remarks>This gets updated anytime the file is scanned</remarks>
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Last time file analysis ran on this file
|
||||
/// </summary>
|
||||
public DateTime LastFileAnalysis { get; set; }
|
||||
public DateTime LastFileAnalysisUtc { get; set; }
|
||||
|
||||
|
||||
// Relationship Mapping
|
||||
@ -53,5 +58,12 @@ public class MangaFile : IEntityDate
|
||||
public void UpdateLastModified()
|
||||
{
|
||||
LastModified = File.GetLastWriteTime(FilePath);
|
||||
LastModifiedUtc = File.GetLastWriteTimeUtc(FilePath);
|
||||
}
|
||||
|
||||
public void UpdateLastFileAnalysis()
|
||||
{
|
||||
LastFileAnalysis = DateTime.Now;
|
||||
LastFileAnalysisUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ public class ReadingList : IEntityDate
|
||||
public ICollection<ReadingListItem> Items { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
// Relationships
|
||||
public int AppUserId { get; set; }
|
||||
|
@ -19,5 +19,4 @@ public class ReadingListItem
|
||||
public Series Series { get; set; }
|
||||
public Volume Volume { get; set; }
|
||||
public Chapter Chapter { get; set; }
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,10 @@ public class Series : IEntityDate, IHasReadTimeEstimate
|
||||
/// Whenever a modification occurs. Ie) New volumes, removed volumes, title update, etc
|
||||
/// </summary>
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Absolute path to the (managed) image file
|
||||
/// </summary>
|
||||
@ -64,6 +68,10 @@ public class Series : IEntityDate, IHasReadTimeEstimate
|
||||
/// </summary>
|
||||
public DateTime LastFolderScanned { get; set; }
|
||||
/// <summary>
|
||||
/// Last time the folder was scanned in Utc
|
||||
/// </summary>
|
||||
public DateTime LastFolderScannedUtc { get; set; }
|
||||
/// <summary>
|
||||
/// The type of all the files attached to this series
|
||||
/// </summary>
|
||||
public MangaFormat Format { get; set; } = MangaFormat.Unknown;
|
||||
@ -76,6 +84,7 @@ public class Series : IEntityDate, IHasReadTimeEstimate
|
||||
/// When a Chapter was last added onto the Series
|
||||
/// </summary>
|
||||
public DateTime LastChapterAdded { get; set; }
|
||||
public DateTime LastChapterAddedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total Word count of all chapters in this chapter.
|
||||
@ -104,4 +113,16 @@ public class Series : IEntityDate, IHasReadTimeEstimate
|
||||
public List<Volume> Volumes { get; set; }
|
||||
public Library Library { get; set; }
|
||||
public int LibraryId { get; set; }
|
||||
|
||||
public void UpdateLastFolderScanned()
|
||||
{
|
||||
LastFolderScanned = DateTime.Now;
|
||||
LastFolderScannedUtc = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateLastChapterAdded()
|
||||
{
|
||||
LastChapterAdded = DateTime.Now;
|
||||
LastChapterAddedUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
@ -35,4 +35,6 @@ public class SiteTheme : IEntityDate, ITheme
|
||||
public ThemeProvider Provider { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
}
|
||||
|
@ -19,6 +19,9 @@ public class Volume : IEntityDate, IHasReadTimeEstimate
|
||||
public IList<Chapter> Chapters { get; set; }
|
||||
public DateTime Created { get; set; }
|
||||
public DateTime LastModified { get; set; }
|
||||
public DateTime CreatedUtc { get; set; }
|
||||
public DateTime LastModifiedUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Absolute path to the (managed) image file
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Kavita.Common;
|
||||
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
||||
|
||||
namespace API.Extensions;
|
||||
|
||||
@ -7,8 +8,15 @@ public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
public static string GetUsername(this ClaimsPrincipal user)
|
||||
{
|
||||
var userClaim = user.FindFirst(ClaimTypes.NameIdentifier);
|
||||
var userClaim = user.FindFirst(JwtRegisteredClaimNames.Name);
|
||||
if (userClaim == null) throw new KavitaException("User is not authenticated");
|
||||
return userClaim.Value;
|
||||
}
|
||||
|
||||
public static int GetUserId(this ClaimsPrincipal user)
|
||||
{
|
||||
var userClaim = user.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userClaim == null) throw new KavitaException("User is not authenticated");
|
||||
return int.Parse(userClaim.Value);
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +133,13 @@ public static class QueryableExtensions
|
||||
queryable = queryable.Include(v => v.Volume);
|
||||
}
|
||||
|
||||
if (includes.HasFlag(ChapterIncludes.Files))
|
||||
{
|
||||
queryable = queryable
|
||||
.Include(c => c.Files);
|
||||
}
|
||||
|
||||
|
||||
return queryable.AsSplitQuery();
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,11 @@ public class AutoMapperProfiles : Profile
|
||||
CreateMap<AgeRating, AgeRatingDto>();
|
||||
CreateMap<PublicationStatus, PublicationStatusDto>();
|
||||
|
||||
CreateMap<AppUserProgress, ProgressDto>()
|
||||
.ForMember(dest => dest.PageNum,
|
||||
opt =>
|
||||
opt.MapFrom(
|
||||
src => src.PagesRead));
|
||||
CreateMap<SeriesMetadata, SeriesMetadataDto>()
|
||||
.ForMember(dest => dest.Writers,
|
||||
opt =>
|
||||
|
@ -278,7 +278,7 @@ public class ArchiveService : IArchiveService
|
||||
/// <exception cref="KavitaException"></exception>
|
||||
public string CreateZipForDownload(IEnumerable<string> files, string tempFolder)
|
||||
{
|
||||
var dateString = DateTime.Now.ToShortDateString().Replace("/", "_");
|
||||
var dateString = DateTime.UtcNow.ToShortDateString().Replace("/", "_");
|
||||
|
||||
var tempLocation = Path.Join(_directoryService.TempDirectory, $"{tempFolder}_{dateString}");
|
||||
var potentialExistingFile = _directoryService.FileSystem.FileInfo.FromFileName(Path.Join(_directoryService.TempDirectory, $"kavita_{tempFolder}_{dateString}.zip"));
|
||||
|
@ -114,7 +114,7 @@ public class DeviceService : IDeviceService
|
||||
throw new KavitaException("Cannot Send non Epub or Pdf to devices as not supported on Kindle");
|
||||
|
||||
|
||||
device.LastUsed = DateTime.Now;
|
||||
device.UpdateLastUsed();
|
||||
_unitOfWork.DeviceRepository.Update(device);
|
||||
await _unitOfWork.CommitAsync();
|
||||
var success = await _emailService.SendFilesToEmail(new SendToDto()
|
||||
|
@ -231,6 +231,7 @@ public class ReaderService : IReaderService
|
||||
var userProgress =
|
||||
await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(progressDto.ChapterId, userId);
|
||||
|
||||
|
||||
if (userProgress == null)
|
||||
{
|
||||
// Create a user object
|
||||
|
@ -436,7 +436,7 @@ public class SeriesService : ISeriesService
|
||||
var libraries = await _unitOfWork.LibraryRepository.GetLibraryForIdsAsync(libraryIds);
|
||||
foreach (var library in libraries)
|
||||
{
|
||||
library.LastModified = DateTime.Now;
|
||||
library.UpdateLastModified();
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ public class BackupService : IBackupService
|
||||
await SendProgress(0F, "Started backup");
|
||||
await SendProgress(0.1F, "Copying core files");
|
||||
|
||||
var dateString = $"{DateTime.Now.ToShortDateString()}_{DateTime.Now.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
||||
var dateString = $"{DateTime.UtcNow.ToShortDateString()}_{DateTime.UtcNow.ToLongTimeString()}".Replace("/", "_").Replace(":", "_");
|
||||
var zipPath = _directoryService.FileSystem.Path.Join(backupDirectory, $"kavita_backup_{dateString}.zip");
|
||||
|
||||
if (File.Exists(zipPath))
|
||||
|
@ -233,7 +233,7 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
||||
|
||||
private void UpdateFileAnalysis(MangaFile file)
|
||||
{
|
||||
file.LastFileAnalysis = DateTime.Now;
|
||||
file.UpdateLastFileAnalysis();
|
||||
_unitOfWork.MangaFileRepository.Update(file);
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ public class ProcessSeries : IProcessSeries
|
||||
// Update series FolderPath here
|
||||
await UpdateSeriesFolderPath(parsedInfos, library, series);
|
||||
|
||||
series.LastFolderScanned = DateTime.Now;
|
||||
series.UpdateLastFolderScanned();
|
||||
|
||||
if (_unitOfWork.HasChanges())
|
||||
{
|
||||
@ -562,7 +562,7 @@ public class ProcessSeries : IProcessSeries
|
||||
"[ScannerService] Adding new chapter, {Series} - Vol {Volume} Ch {Chapter}", info.Series, info.Volumes, info.Chapters);
|
||||
chapter = DbFactory.Chapter(info);
|
||||
volume.Chapters.Add(chapter);
|
||||
series.LastChapterAdded = DateTime.Now;
|
||||
series.UpdateLastChapterAdded();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -528,13 +528,13 @@ public class ScannerService : IScannerService
|
||||
|
||||
_logger.LogInformation("[ScannerService] Finished file scan in {ScanAndUpdateTime} milliseconds. Updating database", scanElapsedTime);
|
||||
|
||||
var time = DateTime.Now;
|
||||
var time = DateTime.UtcNow;
|
||||
foreach (var folderPath in library.Folders)
|
||||
{
|
||||
folderPath.LastScanned = time;
|
||||
folderPath.UpdateLastScanned(time);
|
||||
}
|
||||
|
||||
library.LastScanned = time;
|
||||
library.UpdateLastScanned(time);
|
||||
|
||||
|
||||
_unitOfWork.LibraryRepository.Update(library);
|
||||
|
@ -10,6 +10,7 @@ using API.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using static System.Security.Claims.ClaimTypes;
|
||||
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
||||
|
||||
|
||||
@ -22,6 +23,7 @@ public interface ITokenService
|
||||
Task<string> CreateRefreshToken(AppUser user);
|
||||
}
|
||||
|
||||
|
||||
public class TokenService : ITokenService
|
||||
{
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
@ -38,19 +40,20 @@ public class TokenService : ITokenService
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.NameId, user.UserName)
|
||||
new Claim(JwtRegisteredClaimNames.Name, user.UserName),
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
};
|
||||
|
||||
var roles = await _userManager.GetRolesAsync(user);
|
||||
|
||||
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
|
||||
claims.AddRange(roles.Select(role => new Claim(Role, role)));
|
||||
|
||||
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor()
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.Now.AddDays(14),
|
||||
Expires = DateTime.UtcNow.AddDays(14),
|
||||
SigningCredentials = creds
|
||||
};
|
||||
|
||||
|
@ -57,7 +57,7 @@ public class PresenceTracker : IPresenceTracker
|
||||
}
|
||||
|
||||
// Update the last active for the user
|
||||
user.LastActive = DateTime.Now;
|
||||
user.UpdateLastActive();
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
|
@ -225,6 +225,7 @@ public class Startup
|
||||
|
||||
// v0.6.8 or v0.7
|
||||
await MigrateUserProgressLibraryId.Migrate(unitOfWork, logger);
|
||||
await MigrateToUtcDates.Migrate(unitOfWork, dataContext, logger);
|
||||
|
||||
// Update the version in the DB after all migrations are run
|
||||
var installVersion = await unitOfWork.SettingsRepository.GetSettingAsync(ServerSettingKey.InstallVersion);
|
||||
|
@ -16,6 +16,7 @@ export interface ReadingListItem {
|
||||
releaseDate: string;
|
||||
title: string;
|
||||
libraryType: LibraryType;
|
||||
libraryName: string;
|
||||
}
|
||||
|
||||
export interface ReadingList {
|
||||
|
328
openapi.json
328
openapi.json
@ -7,7 +7,7 @@
|
||||
"name": "GPL-3.0",
|
||||
"url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE"
|
||||
},
|
||||
"version": "0.6.1.34"
|
||||
"version": "0.6.1.37"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
@ -4048,17 +4048,17 @@
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChapterDto"
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChapterDto"
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChapterDto"
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6152,17 +6152,17 @@
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChapterDto"
|
||||
"$ref": "#/components/schemas/ChapterMetadataDto"
|
||||
}
|
||||
},
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChapterDto"
|
||||
"$ref": "#/components/schemas/ChapterMetadataDto"
|
||||
}
|
||||
},
|
||||
"text/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ChapterDto"
|
||||
"$ref": "#/components/schemas/ChapterMetadataDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9339,10 +9339,18 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastActive": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastActiveUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"libraries": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -9470,6 +9478,14 @@
|
||||
"lastModified": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@ -9633,6 +9649,14 @@
|
||||
"description": "Last date this was updated",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"appUser": {
|
||||
"$ref": "#/components/schemas/AppUser"
|
||||
},
|
||||
@ -9888,6 +9912,14 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"coverImage": {
|
||||
"type": "string",
|
||||
"description": "Relative path to the (managed) image file representing the cover image",
|
||||
@ -9988,6 +10020,13 @@
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"userProgress": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/AppUserProgress"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"volume": {
|
||||
"$ref": "#/components/schemas/Volume"
|
||||
},
|
||||
@ -10042,6 +10081,11 @@
|
||||
"description": "Calculated at API time. Number of pages read for this Chapter for logged in user.",
|
||||
"format": "int32"
|
||||
},
|
||||
"lastReadingProgressUtc": {
|
||||
"type": "string",
|
||||
"description": "The last time a chapter was read by current authenticated user",
|
||||
"format": "date-time"
|
||||
},
|
||||
"coverImageLocked": {
|
||||
"type": "boolean",
|
||||
"description": "If the Cover Image is locked for this entity"
|
||||
@ -10056,6 +10100,18 @@
|
||||
"description": "When chapter was created",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"releaseDate": {
|
||||
"type": "string",
|
||||
"description": "When the chapter was released.",
|
||||
@ -10189,6 +10245,145 @@
|
||||
"additionalProperties": false,
|
||||
"description": "Information about the Chapter for the Reader to render"
|
||||
},
|
||||
"ChapterMetadataDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"chapterId": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"writers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"coverArtists": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"publishers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"characters": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"pencillers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"inkers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"colorists": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"letterers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"editors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"translators": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PersonDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"genres": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GenreTagDto"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TagDto"
|
||||
},
|
||||
"description": "Collection of all Tags from underlying chapters for a Series",
|
||||
"nullable": true
|
||||
},
|
||||
"ageRating": {
|
||||
"$ref": "#/components/schemas/AgeRating"
|
||||
},
|
||||
"releaseDate": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"publicationStatus": {
|
||||
"$ref": "#/components/schemas/PublicationStatus"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "Summary for the Chapter/Issue",
|
||||
"nullable": true
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"description": "Language for the Chapter/Issue",
|
||||
"nullable": true
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number in the TotalCount of issues",
|
||||
"format": "int32"
|
||||
},
|
||||
"totalCount": {
|
||||
"type": "integer",
|
||||
"description": "Total number of issues for the series",
|
||||
"format": "int32"
|
||||
},
|
||||
"wordCount": {
|
||||
"type": "integer",
|
||||
"description": "Number of Words for this chapter. Only applies to Epub",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"description": "Exclusively metadata about a given chapter"
|
||||
},
|
||||
"CollectionTag": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -10533,6 +10728,10 @@
|
||||
"description": "Last time this device was used to send a file",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastUsedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"created": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@ -10540,6 +10739,14 @@
|
||||
"lastModified": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@ -10571,6 +10778,11 @@
|
||||
"type": "string",
|
||||
"description": "Last time this device was used to send a file",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastUsedUtc": {
|
||||
"type": "string",
|
||||
"description": "Last time this device was used to send a file",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@ -11075,6 +11287,18 @@
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"createdAtUtc": {
|
||||
"type": "string",
|
||||
"description": "When the job was created",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"lastExecutionUtc": {
|
||||
"type": "string",
|
||||
"description": "Last time the job was run",
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"cron": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@ -11173,6 +11397,14 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastScanned": {
|
||||
"type": "string",
|
||||
"description": "Last time Library was scanned",
|
||||
@ -11331,11 +11563,23 @@
|
||||
"description": "Last time underlying file was modified",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastFileAnalysis": {
|
||||
"type": "string",
|
||||
"description": "Last time file analysis ran on this file",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastFileAnalysisUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"chapter": {
|
||||
"$ref": "#/components/schemas/Chapter"
|
||||
},
|
||||
@ -11652,13 +11896,8 @@
|
||||
},
|
||||
"bookScrollId": {
|
||||
"type": "string",
|
||||
"description": "For Book reader, this can be an optional string of the id of a part marker, to help resume reading position\r\non pages that combine multiple \"chapters\".",
|
||||
"description": "For EPUB reader, this can be an optional string of the id of a part marker, to help resume reading position\r\non pages that combine multiple \"chapters\".",
|
||||
"nullable": true
|
||||
},
|
||||
"lastModified": {
|
||||
"type": "string",
|
||||
"description": "Last time the progress was synced from UI or external app",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -11809,6 +12048,14 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"appUserId": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
@ -11950,6 +12197,10 @@
|
||||
"libraryType": {
|
||||
"$ref": "#/components/schemas/LibraryType"
|
||||
},
|
||||
"libraryName": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
@ -11963,6 +12214,16 @@
|
||||
"type": "integer",
|
||||
"description": "Used internally only",
|
||||
"format": "int32"
|
||||
},
|
||||
"lastReadingProgressUtc": {
|
||||
"type": "string",
|
||||
"description": "The last time a reading list item (underlying chapter) was read by current authenticated user",
|
||||
"format": "date-time"
|
||||
},
|
||||
"fileSize": {
|
||||
"type": "integer",
|
||||
"description": "File size of underlying item",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@ -12418,6 +12679,14 @@
|
||||
"description": "Whenever a modification occurs. Ie) New volumes, removed volumes, title update, etc",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"coverImage": {
|
||||
"type": "string",
|
||||
"description": "Absolute path to the (managed) image file",
|
||||
@ -12442,6 +12711,11 @@
|
||||
"description": "Last time the folder was scanned",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastFolderScannedUtc": {
|
||||
"type": "string",
|
||||
"description": "Last time the folder was scanned in Utc",
|
||||
"format": "date-time"
|
||||
},
|
||||
"format": {
|
||||
"$ref": "#/components/schemas/MangaFormat"
|
||||
},
|
||||
@ -12459,6 +12733,10 @@
|
||||
"description": "When a Chapter was last added onto the Series",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastChapterAddedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"wordCount": {
|
||||
"type": "integer",
|
||||
"description": "Total Word count of all chapters in this chapter.",
|
||||
@ -13473,6 +13751,14 @@
|
||||
"lastModified": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@ -13510,6 +13796,14 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"selector": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
@ -14488,6 +14782,14 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"createdUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"lastModifiedUtc": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"coverImage": {
|
||||
"type": "string",
|
||||
"description": "Absolute path to the (managed) image file",
|
||||
|
Loading…
x
Reference in New Issue
Block a user