diff --git a/API.Tests/Services/CleanupServiceTests.cs b/API.Tests/Services/CleanupServiceTests.cs
index 77fa9ac1f..84e4d5fd5 100644
--- a/API.Tests/Services/CleanupServiceTests.cs
+++ b/API.Tests/Services/CleanupServiceTests.cs
@@ -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));
diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs
index a9229fa7c..9f05add96 100644
--- a/API/Controllers/AccountController.cs
+++ b/API/Controllers/AccountController.cs
@@ -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;
///
@@ -50,8 +49,7 @@ public class AccountController : BaseApiController
ITokenService tokenService, IUnitOfWork unitOfWork,
ILogger 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()
diff --git a/API/Controllers/CollectionController.cs b/API/Controllers/CollectionController.cs
index cd9402fe7..0800f3977 100644
--- a/API/Controllers/CollectionController.cs
+++ b/API/Controllers/CollectionController.cs
@@ -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());
}
///
diff --git a/API/Controllers/DownloadController.cs b/API/Controllers/DownloadController.cs
index 98fa072e9..6c44c2659 100644
--- a/API/Controllers/DownloadController.cs
+++ b/API/Controllers/DownloadController.cs
@@ -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);
diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs
index 800743766..79948ce60 100644
--- a/API/Controllers/ReaderController.cs
+++ b/API/Controllers/ReaderController.cs
@@ -244,8 +244,7 @@ public class ReaderController : BaseApiController
[HttpGet("bookmark-info")]
public async Task> 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> 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);
}
///
@@ -480,9 +469,7 @@ public class ReaderController : BaseApiController
[HttpPost("progress")]
public async Task 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
///
///
[HttpGet("has-progress")]
- public async Task> HasProgress(int seriesId)
+ public async Task> HasProgress(int seriesId)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
return Ok(await _unitOfWork.AppUserProgressRepository.HasAnyProgressOnSeriesAsync(seriesId, userId));
diff --git a/API/Controllers/ReadingListController.cs b/API/Controllers/ReadingListController.cs
index 72678780c..1ecebf23c 100644
--- a/API/Controllers/ReadingListController.cs
+++ b/API/Controllers/ReadingListController.cs
@@ -82,8 +82,7 @@ public class ReadingListController : BaseApiController
[HttpGet("items")]
public async Task>> 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);
}
diff --git a/API/Controllers/RecommendedController.cs b/API/Controllers/RecommendedController.cs
index 893cb852a..f945c735d 100644
--- a/API/Controllers/RecommendedController.cs
+++ b/API/Controllers/RecommendedController.cs
@@ -26,10 +26,8 @@ public class RecommendedController : BaseApiController
[HttpGet("quick-reads")]
public async Task>> 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>> 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>> 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>> 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>> 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);
diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs
index c93e93fe9..c0059e0e5 100644
--- a/API/Controllers/SeriesController.cs
+++ b/API/Controllers/SeriesController.cs
@@ -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> 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> GetChapterMetadata(int chapterId)
+ public async Task> GetChapterMetadata(int chapterId)
{
return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId));
}
diff --git a/API/Controllers/TachiyomiController.cs b/API/Controllers/TachiyomiController.cs
index 77f32764d..84bde35d1 100644
--- a/API/Controllers/TachiyomiController.cs
+++ b/API/Controllers/TachiyomiController.cs
@@ -32,8 +32,7 @@ public class TachiyomiController : BaseApiController
public async Task> 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()));
}
///
diff --git a/API/Controllers/UploadController.cs b/API/Controllers/UploadController.cs
index 3a52866e0..434117567 100644
--- a/API/Controllers/UploadController.cs
+++ b/API/Controllers/UploadController.cs
@@ -49,7 +49,7 @@ public class UploadController : BaseApiController
[HttpPost("upload-by-url")]
public async Task> 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
{
diff --git a/API/Controllers/WantToReadController.cs b/API/Controllers/WantToReadController.cs
index 12bbeaa85..424681cf4 100644
--- a/API/Controllers/WantToReadController.cs
+++ b/API/Controllers/WantToReadController.cs
@@ -35,8 +35,7 @@ public class WantToReadController : BaseApiController
public async Task>> 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> 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));
}
///
diff --git a/API/DTOs/ChapterDto.cs b/API/DTOs/ChapterDto.cs
index 60e08b554..b93bd124a 100644
--- a/API/DTOs/ChapterDto.cs
+++ b/API/DTOs/ChapterDto.cs
@@ -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).
///
-public class ChapterDto : IHasReadTimeEstimate
+public class ChapterDto : IHasReadTimeEstimate, IEntityDate
{
public int Id { get; init; }
///
@@ -43,6 +41,10 @@ public class ChapterDto : IHasReadTimeEstimate
///
public int PagesRead { get; set; }
///
+ /// The last time a chapter was read by current authenticated user
+ ///
+ public DateTime LastReadingProgressUtc { get; set; }
+ ///
/// If the Cover Image is locked for this entity
///
public bool CoverImageLocked { get; set; }
@@ -53,7 +55,10 @@ public class ChapterDto : IHasReadTimeEstimate
///
/// When chapter was created
///
- 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; }
///
/// When the chapter was released.
///
@@ -77,7 +82,6 @@ public class ChapterDto : IHasReadTimeEstimate
/// Total words in a Chapter (books only)
///
public long WordCount { get; set; } = 0L;
-
///
/// Formatted Volume title ie) Volume 2.
///
diff --git a/API/DTOs/Device/DeviceDto.cs b/API/DTOs/Device/DeviceDto.cs
index e5344f31e..c05671113 100644
--- a/API/DTOs/Device/DeviceDto.cs
+++ b/API/DTOs/Device/DeviceDto.cs
@@ -30,4 +30,8 @@ public class DeviceDto
/// Last time this device was used to send a file
///
public DateTime LastUsed { get; set; }
+ ///
+ /// Last time this device was used to send a file
+ ///
+ public DateTime LastUsedUtc { get; set; }
}
diff --git a/API/DTOs/Jobs/JobDto.cs b/API/DTOs/Jobs/JobDto.cs
index 5af700528..dc566961e 100644
--- a/API/DTOs/Jobs/JobDto.cs
+++ b/API/DTOs/Jobs/JobDto.cs
@@ -20,5 +20,13 @@ public class JobDto
/// Last time the job was run
///
public DateTime? LastExecution { get; set; }
+ ///
+ /// When the job was created
+ ///
+ public DateTime? CreatedAtUtc { get; set; }
+ ///
+ /// Last time the job was run
+ ///
+ public DateTime? LastExecutionUtc { get; set; }
public string Cron { get; set; }
}
diff --git a/API/DTOs/ProgressDto.cs b/API/DTOs/ProgressDto.cs
index b3ffd2914..e5f796bcc 100644
--- a/API/DTOs/ProgressDto.cs
+++ b/API/DTOs/ProgressDto.cs
@@ -16,12 +16,8 @@ public class ProgressDto
[Required]
public int LibraryId { get; set; }
///
- /// 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".
///
public string BookScrollId { get; set; }
- ///
- /// Last time the progress was synced from UI or external app
- ///
- public DateTime LastModified { get; set; }
}
diff --git a/API/DTOs/ReadingLists/ReadingListItemDto.cs b/API/DTOs/ReadingLists/ReadingListItemDto.cs
index 91c436264..79fdd9d8f 100644
--- a/API/DTOs/ReadingLists/ReadingListItemDto.cs
+++ b/API/DTOs/ReadingLists/ReadingListItemDto.cs
@@ -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; }
///
/// Release Date from Chapter
@@ -28,4 +29,13 @@ public class ReadingListItemDto
/// Used internally only
///
public int ReadingListId { get; set; }
+ ///
+ /// The last time a reading list item (underlying chapter) was read by current authenticated user
+ ///
+ public DateTime LastReadingProgressUtc { get; set; }
+ ///
+ /// File size of underlying item
+ ///
+ /// This is only used for CDisplayEx
+ public long FileSize { get; set; }
}
diff --git a/API/DTOs/Theme/SiteThemeDto.cs b/API/DTOs/Theme/SiteThemeDto.cs
index 7c44a1cd0..6e3650e21 100644
--- a/API/DTOs/Theme/SiteThemeDto.cs
+++ b/API/DTOs/Theme/SiteThemeDto.cs
@@ -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;
///
/// Represents a set of css overrides the user can upload to Kavita and will load into webui
///
-public class SiteThemeDto
+public class SiteThemeDto : IEntityDate
{
public int Id { get; set; }
///
@@ -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();
}
diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs
index 1606f9c71..9a6af8b3e 100644
--- a/API/Data/DataContext.cs
+++ b/API/Data/DataContext.cs
@@ -112,18 +112,19 @@ public sealed class DataContext : IdentityDbContext
+/// Introduced in v0.6.1.38 or v0.7.0,
+///
+public static class MigrateToUtcDates
+{
+ public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger 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");
+
+ }
+}
diff --git a/API/Data/Migrations/20230210153842_UtcTimes.Designer.cs b/API/Data/Migrations/20230210153842_UtcTimes.Designer.cs
new file mode 100644
index 000000000..ff9394649
--- /dev/null
+++ b/API/Data/Migrations/20230210153842_UtcTimes.Designer.cs
@@ -0,0 +1,1836 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20230210153842_UtcTimes")]
+ partial class UtcTimes
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRestriction")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRestrictionIncludeUnknowns")
+ .HasColumnType("INTEGER");
+
+ b.Property("ApiKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("ConfirmationToken")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LastActiveUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserBookmark", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("FileName")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Page")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserBookmark");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
+ b.Property("BackgroundColor")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("#000000");
+
+ b.Property("BlurUnreadSummaries")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderFontFamily")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderFontSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderImmersiveMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLineSpacing")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderMargin")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderTapToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookThemeName")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("Dark");
+
+ b.Property("EmulateBook")
+ .HasColumnType("INTEGER");
+
+ b.Property("GlobalPageLayoutMode")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("LayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("NoTransitions")
+ .HasColumnType("INTEGER");
+
+ b.Property("PageSplitOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("PromptForDownloadSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ScalingOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowScreenHints")
+ .HasColumnType("INTEGER");
+
+ b.Property("SwipeToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("ThemeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId")
+ .IsUnique();
+
+ b.HasIndex("ThemeId");
+
+ b.ToTable("AppUserPreferences");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserProgress", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookScrollId")
+ .HasColumnType("TEXT");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PagesRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserProgresses");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRating", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("INTEGER");
+
+ b.Property("Review")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserRating");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("AvgHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSpecial")
+ .HasColumnType("INTEGER");
+
+ b.Property("Language")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("MaxHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("Number")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("Range")
+ .HasColumnType("TEXT");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesGroup")
+ .HasColumnType("TEXT");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("TitleName")
+ .HasColumnType("TEXT");
+
+ b.Property("TotalCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WordCount")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("VolumeId");
+
+ b.ToTable("Chapter");
+ });
+
+ modelBuilder.Entity("API.Entities.CollectionTag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Id", "Promoted")
+ .IsUnique();
+
+ b.ToTable("CollectionTag");
+ });
+
+ modelBuilder.Entity("API.Entities.Device", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("EmailAddress")
+ .HasColumnType("TEXT");
+
+ b.Property("IpAddress")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastUsed")
+ .HasColumnType("TEXT");
+
+ b.Property("LastUsedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Platform")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("Device");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LastScanned")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Genre", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedTitle")
+ .IsUnique();
+
+ b.ToTable("Genre");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("FolderWatching")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("IncludeInDashboard")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("IncludeInRecommended")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("IncludeInSearch")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastScanned")
+ .HasColumnType("TEXT");
+
+ b.Property("ManageCollections")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("API.Entities.MangaFile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Bytes")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Extension")
+ .HasColumnType("TEXT");
+
+ b.Property("FilePath")
+ .HasColumnType("TEXT");
+
+ b.Property("Format")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastFileAnalysis")
+ .HasColumnType("TEXT");
+
+ b.Property("LastFileAnalysisUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChapterId");
+
+ b.ToTable("MangaFile");
+ });
+
+ modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRatingLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("CharacterLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ColoristLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverArtistLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("EditorLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("GenresLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("InkerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Language")
+ .HasColumnType("TEXT");
+
+ b.Property("LanguageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("LettererLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("PencillerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublicationStatus")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublicationStatusLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublisherLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReleaseYear")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReleaseYearLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("SummaryLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("TagsLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("TotalCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("TranslatorLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("WriterLocked")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId")
+ .IsUnique();
+
+ b.HasIndex("Id", "SeriesId")
+ .IsUnique();
+
+ b.ToTable("SeriesMetadata");
+ });
+
+ modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("RelationKind")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TargetSeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("TargetSeriesId");
+
+ b.ToTable("SeriesRelation");
+ });
+
+ modelBuilder.Entity("API.Entities.Person", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("Role")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Person");
+ });
+
+ modelBuilder.Entity("API.Entities.ReadingList", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("ReadingList");
+ });
+
+ modelBuilder.Entity("API.Entities.ReadingListItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingListId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("ReadingListId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("VolumeId");
+
+ b.ToTable("ReadingListItem");
+ });
+
+ modelBuilder.Entity("API.Entities.Series", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AvgHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("FolderPath")
+ .HasColumnType("TEXT");
+
+ b.Property("Format")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastChapterAdded")
+ .HasColumnType("TEXT");
+
+ b.Property("LastChapterAddedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastFolderScanned")
+ .HasColumnType("TEXT");
+
+ b.Property("LastFolderScannedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LocalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("LocalizedNameLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("MaxHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("MinHoursToRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NameLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedLocalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("OriginalName")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortName")
+ .HasColumnType("TEXT");
+
+ b.Property("SortNameLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("WordCount")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("Series");
+ });
+
+ modelBuilder.Entity("API.Entities.ServerSetting", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("ServerSetting");
+ });
+
+ modelBuilder.Entity("API.Entities.ServerStatistics", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("FileCount")
+ .HasColumnType("INTEGER");
+
+ b.Property