From be2b78fa5a968be0878d67daa3f666d23fb6bc9c Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Thu, 24 Jun 2021 19:31:42 -0500 Subject: [PATCH] Manga Redesign (#321) * Code cleanup, refactored FileRepository into Unit of Work. * Added AutoCloseMenu and ReaderMode user perferences to match UI * Added extra information to ChapterInfo * Build changes * Updated the readme to have open collective information and thanks to sponsors * Fixed an issue with UnitOfWork refactor and how stats was bootsrapped. Replaced stats.kavitareader with a temp url to test out redirection bug. --- .../Comparers/NaturalSortComparerTest.cs | 4 + API/API.csproj | 124 +++ API/Comparators/StringLogicalComparer.cs | 20 +- .../CustomOptions/StatsOptions.cs | 30 + API/Controllers/CollectionController.cs | 1 - API/Controllers/ReaderController.cs | 25 +- API/Controllers/ServerController.cs | 2 - API/Controllers/UsersController.cs | 2 + API/DTOs/CollectionTagDto.cs | 4 +- API/DTOs/Reader/ChapterInfoDto.cs | 16 + API/DTOs/UpdateSeriesMetadataDto.cs | 1 - API/DTOs/UserPreferencesDto.cs | 2 + ...10622164318_NewUserPreferences.Designer.cs | 869 ++++++++++++++++++ .../20210622164318_NewUserPreferences.cs | 35 + .../Migrations/DataContextModelSnapshot.cs | 6 + API/Data/SeriesRepository.cs | 2 +- API/Data/UnitOfWork.cs | 3 +- API/Entities/AppUserPreferences.cs | 13 +- API/Entities/Enums/ReaderMode.cs | 14 + API/Entities/Series.cs | 2 +- .../ApplicationServiceExtensions.cs | 16 +- API/Extensions/ServiceCollectionExtensions.cs | 2 +- API/Interfaces/IUnitOfWork.cs | 1 + API/Interfaces/Services/IArchiveService.cs | 1 - API/Program.cs | 8 +- API/Services/CacheService.cs | 4 - API/Services/Clients/StatsApiClient.cs | 7 +- API/Services/ComicInfo.cs | 1 + .../StartupTasksHostedService.cs | 6 +- API/Services/StatsService.cs | 12 +- CONTRIBUTING.md | 56 ++ Kavita.Common/Kavita.Common.csproj | 2 +- Logo/dottrace.svg | 33 + Logo/jetbrains.svg | 66 ++ Logo/kavita.svg | 124 +++ Logo/resharper.svg | 50 + Logo/rider.svg | 42 + Logo/sentry.svg | 1 + README.md | 79 +- build.sh | 9 + 40 files changed, 1608 insertions(+), 87 deletions(-) create mode 100644 API/Configurations/CustomOptions/StatsOptions.cs create mode 100644 API/DTOs/Reader/ChapterInfoDto.cs create mode 100644 API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs create mode 100644 API/Data/Migrations/20210622164318_NewUserPreferences.cs create mode 100644 API/Entities/Enums/ReaderMode.cs create mode 100644 CONTRIBUTING.md create mode 100644 Logo/dottrace.svg create mode 100644 Logo/jetbrains.svg create mode 100644 Logo/kavita.svg create mode 100644 Logo/resharper.svg create mode 100644 Logo/rider.svg create mode 100644 Logo/sentry.svg diff --git a/API.Tests/Comparers/NaturalSortComparerTest.cs b/API.Tests/Comparers/NaturalSortComparerTest.cs index 39bad2003..d7c58d45a 100644 --- a/API.Tests/Comparers/NaturalSortComparerTest.cs +++ b/API.Tests/Comparers/NaturalSortComparerTest.cs @@ -42,6 +42,10 @@ namespace API.Tests.Comparers new[] {"3and4.cbz", "The World God Only Knows - Oneshot.cbz", "5.cbz", "1and2.cbz"}, new[] {"1and2.cbz", "3and4.cbz", "5.cbz", "The World God Only Knows - Oneshot.cbz"} )] + [InlineData( + new[] {"Solo Leveling - c000 (v01) - p000 [Cover] [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p001 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p002 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p003 [dig] [Yen Press] [LuCaZ].jpg"}, + new[] {"Solo Leveling - c000 (v01) - p000 [Cover] [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p001 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p002 [dig] [Yen Press] [LuCaZ].jpg", "Solo Leveling - c000 (v01) - p003 [dig] [Yen Press] [LuCaZ].jpg"} + )] public void TestNaturalSortComparer(string[] input, string[] expected) { Array.Sort(input, _nc); diff --git a/API/API.csproj b/API/API.csproj index 458830ca1..50a464d0b 100644 --- a/API/API.csproj +++ b/API/API.csproj @@ -64,23 +64,147 @@ + + + + <_ContentIncludedByDefault Remove="logs\kavita.json" /> + <_ContentIncludedByDefault Remove="wwwroot\3rdpartylicenses.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\6.d9925ea83359bb4c7278.js" /> + <_ContentIncludedByDefault Remove="wwwroot\6.d9925ea83359bb4c7278.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\7.860cdd6fd9d758e6c210.js" /> + <_ContentIncludedByDefault Remove="wwwroot\7.860cdd6fd9d758e6c210.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\8.028f6737a2f0621d40c7.js" /> + <_ContentIncludedByDefault Remove="wwwroot\8.028f6737a2f0621d40c7.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\EBGaramond-Italic-VariableFont_wght.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\EBGaramond-VariableFont_wght.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\EBGarmond\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Black.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-BlackItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-BoldItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraBold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraBoldItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraLight.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ExtraLightItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Italic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Light.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-LightItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Medium.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-MediumItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-SemiBold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-SemiBoldItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-Thin.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\FiraSans-ThinItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Fira_Sans\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Black.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-BlackItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-BoldItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Italic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Light.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-LightItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-Thin.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\Lato-ThinItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Lato\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Italic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\LibreBaskerville-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Baskerville\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Italic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\LibreCaslonText-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Libre_Caslon\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Black.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-BlackItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-BoldItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Italic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Light.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-LightItalic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\Merriweather-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Merriweather\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-ExtraBold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\NanumGothic-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Nanum_Gothic\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\Oswald-VariableFont_wght.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\README.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Bold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-ExtraLight.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Light.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Medium.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\Oswald\static\Oswald-SemiBold.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\RocknRoll_One\OFL.txt" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\fonts\RocknRoll_One\RocknRollOne-Regular.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder-min.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2-min.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.dark-min.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.dark.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\error-placeholder2.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder-min.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.dark-min.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.dark.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\image-placeholder.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\images\preset-light.png" /> + <_ContentIncludedByDefault Remove="wwwroot\assets\themes\dark.scss" /> + <_ContentIncludedByDefault Remove="wwwroot\common.ad975892146299f80adb.js" /> + <_ContentIncludedByDefault Remove="wwwroot\common.ad975892146299f80adb.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\EBGaramond-VariableFont_wght.2a1da2dbe7a28d63f8cb.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.0fea24969112a781acd2.eot" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.c967a94cfbe2b06627ff.woff2" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.dc2cbadd690e1d4b2c9c.woff" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.e33e2cf6e02cac2ccb77.svg" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-brands-400.ec82f282c7f54b637098.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.06b9d19ced8d17f3d5cb.svg" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.08f9891a6f44d9546678.eot" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1008b5226941c24f4468.woff2" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1069ea55beaa01060302.woff" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-regular-400.1495f578452eb676f730.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.10ecefc282f2761808bf.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.371dbce0dd46bd4d2033.svg" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.3a24a60e7f9c6574864a.eot" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.3ceb50e7bcafb577367c.woff2" /> + <_ContentIncludedByDefault Remove="wwwroot\fa-solid-900.46fdbd2d897f8824e63c.woff" /> + <_ContentIncludedByDefault Remove="wwwroot\favicon.ico" /> + <_ContentIncludedByDefault Remove="wwwroot\FiraSans-Regular.1c0bf0728b51cb9f2ddc.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\index.html" /> + <_ContentIncludedByDefault Remove="wwwroot\Lato-Regular.9919edff6283018571ad.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\LibreBaskerville-Regular.a27f99ca45522bb3d56d.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\main.44f5c0973044295d8be0.js" /> + <_ContentIncludedByDefault Remove="wwwroot\main.44f5c0973044295d8be0.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\Merriweather-Regular.55c73e48e04ec926ebfe.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\NanumGothic-Regular.6c84540de7730f833d6c.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\polyfills.348e08e9d0e910a15938.js" /> + <_ContentIncludedByDefault Remove="wwwroot\polyfills.348e08e9d0e910a15938.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\RocknRollOne-Regular.c75da4712d1e65ed1f69.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\runtime.ea545c6916f85411478f.js" /> + <_ContentIncludedByDefault Remove="wwwroot\runtime.ea545c6916f85411478f.js.map" /> + <_ContentIncludedByDefault Remove="wwwroot\styles.4bd902bb3037f36f2c64.css" /> + <_ContentIncludedByDefault Remove="wwwroot\styles.4bd902bb3037f36f2c64.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js" /> + <_ContentIncludedByDefault Remove="wwwroot\vendor.6b2a0912ae80e6fd297f.js.map" /> diff --git a/API/Comparators/StringLogicalComparer.cs b/API/Comparators/StringLogicalComparer.cs index fe930c45c..67aa72225 100644 --- a/API/Comparators/StringLogicalComparer.cs +++ b/API/Comparators/StringLogicalComparer.cs @@ -2,7 +2,7 @@ // Version 2 // Taken from: https://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C -using System; +using static System.Char; namespace API.Comparators { @@ -20,26 +20,26 @@ namespace API.Comparators if (string.IsNullOrEmpty(s2)) return -1; //WE style, special case - var sp1 = Char.IsLetterOrDigit(s1, 0); - var sp2 = Char.IsLetterOrDigit(s2, 0); + var sp1 = IsLetterOrDigit(s1, 0); + var sp2 = IsLetterOrDigit(s2, 0); if(sp1 && !sp2) return 1; if(!sp1 && sp2) return -1; int i1 = 0, i2 = 0; //current index while(true) { - var c1 = Char.IsDigit(s1, i1); - var c2 = Char.IsDigit(s2, i2); + var c1 = IsDigit(s1, i1); + var c2 = IsDigit(s2, i2); int r; // temp result if(!c1 && !c2) { - bool letter1 = Char.IsLetter(s1, i1); - bool letter2 = Char.IsLetter(s2, i2); + bool letter1 = IsLetter(s1, i1); + bool letter2 = IsLetter(s2, i2); if((letter1 && letter2) || (!letter1 && !letter2)) { if(letter1 && letter2) { - r = Char.ToLower(s1[i1]).CompareTo(Char.ToLower(s2[i2])); + r = ToLower(s1[i1]).CompareTo(ToLower(s2[i2])); } else { @@ -114,8 +114,8 @@ namespace API.Comparators { nzStart = start; end = start; - bool countZeros = true; - while(Char.IsDigit(s, end)) + var countZeros = true; + while(IsDigit(s, end)) { if(countZeros && s[end].Equals('0')) { diff --git a/API/Configurations/CustomOptions/StatsOptions.cs b/API/Configurations/CustomOptions/StatsOptions.cs new file mode 100644 index 000000000..ac0cd0ac5 --- /dev/null +++ b/API/Configurations/CustomOptions/StatsOptions.cs @@ -0,0 +1,30 @@ +using System; + +namespace API.Configurations.CustomOptions +{ + public class StatsOptions + { + public string ServerUrl { get; set; } + public string ServerSecret { get; set; } + public string SendDataAt { get; set; } + + private const char Separator = ':'; + + public short SendDataHour => GetValueFromSendAt(0); + public short SendDataMinute => GetValueFromSendAt(1); + + // The expected SendDataAt format is: Hour:Minute. Ex: 19:45 + private short GetValueFromSendAt(int index) + { + var key = $"{nameof(StatsOptions)}:{nameof(SendDataAt)}"; + + if (string.IsNullOrEmpty(SendDataAt)) + throw new InvalidOperationException($"{key} is invalid. Check the app settings file"); + + if (short.TryParse(SendDataAt.Split(Separator)[index], out var parsedValue)) + return parsedValue; + + throw new InvalidOperationException($"Could not parse {key}. Check the app settings file"); + } + } +} \ No newline at end of file diff --git a/API/Controllers/CollectionController.cs b/API/Controllers/CollectionController.cs index 6ad5fdbaf..e09f8592a 100644 --- a/API/Controllers/CollectionController.cs +++ b/API/Controllers/CollectionController.cs @@ -10,7 +10,6 @@ using API.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace API.Controllers { diff --git a/API/Controllers/ReaderController.cs b/API/Controllers/ReaderController.cs index 2ac3d51fe..b9bc15fb7 100644 --- a/API/Controllers/ReaderController.cs +++ b/API/Controllers/ReaderController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using API.Comparators; using API.DTOs; +using API.DTOs.Reader; using API.Entities; using API.Extensions; using API.Interfaces; @@ -49,15 +50,27 @@ namespace API.Controllers return File(content, "image/" + format); } - - [HttpGet("chapter-path")] - public async Task> GetImagePath(int chapterId) + + [HttpGet("chapter-info")] + public async Task> GetChapterInfo(int chapterId) { var chapter = await _cacheService.Ensure(chapterId); - if (chapter == null) return BadRequest("There was an issue finding image file for reading"); - + if (chapter == null) return BadRequest("Could not find Chapter"); + var volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(chapter.VolumeId); + if (volume == null) return BadRequest("Could not find Volume"); var (_, mangaFile) = await _cacheService.GetCachedPagePath(chapter, 0); - return Ok(mangaFile.FilePath); + var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId); + + return Ok(new ChapterInfoDto() + { + ChapterNumber = chapter.Range, + VolumeNumber = volume.Number + string.Empty, + VolumeId = volume.Id, + FileName = Path.GetFileName(mangaFile.FilePath), + SeriesName = series?.Name, + IsSpecial = chapter.IsSpecial, + Pages = chapter.Pages, + }); } [HttpGet("get-bookmark")] diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs index 7bedceb3f..398de3efc 100644 --- a/API/Controllers/ServerController.cs +++ b/API/Controllers/ServerController.cs @@ -1,10 +1,8 @@ using System; using System.IO; -using System.IO.Compression; using System.Threading.Tasks; using API.Extensions; using API.Interfaces.Services; -using API.Services; using Kavita.Common; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 3a9a44d6c..ee4c9ac66 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -61,6 +61,8 @@ namespace API.Controllers existingPreferences.ReadingDirection = preferencesDto.ReadingDirection; existingPreferences.ScalingOption = preferencesDto.ScalingOption; existingPreferences.PageSplitOption = preferencesDto.PageSplitOption; + existingPreferences.AutoCloseMenu = preferencesDto.AutoCloseMenu; + existingPreferences.ReaderMode = preferencesDto.ReaderMode; existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin; existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing; existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily; diff --git a/API/DTOs/CollectionTagDto.cs b/API/DTOs/CollectionTagDto.cs index 72027e84a..26f256562 100644 --- a/API/DTOs/CollectionTagDto.cs +++ b/API/DTOs/CollectionTagDto.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace API.DTOs +namespace API.DTOs { public class CollectionTagDto { diff --git a/API/DTOs/Reader/ChapterInfoDto.cs b/API/DTOs/Reader/ChapterInfoDto.cs new file mode 100644 index 000000000..850149016 --- /dev/null +++ b/API/DTOs/Reader/ChapterInfoDto.cs @@ -0,0 +1,16 @@ +namespace API.DTOs.Reader +{ + public class ChapterInfoDto + { + + public string ChapterNumber { get; set; } + public string VolumeNumber { get; set; } + public int VolumeId { get; set; } + public string SeriesName { get; set; } + public string ChapterTitle { get; set; } = ""; + public int Pages { get; set; } + public string FileName { get; set; } + public bool IsSpecial { get; set; } + + } +} \ No newline at end of file diff --git a/API/DTOs/UpdateSeriesMetadataDto.cs b/API/DTOs/UpdateSeriesMetadataDto.cs index fd71526b7..a9c852632 100644 --- a/API/DTOs/UpdateSeriesMetadataDto.cs +++ b/API/DTOs/UpdateSeriesMetadataDto.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using API.Entities; namespace API.DTOs { diff --git a/API/DTOs/UserPreferencesDto.cs b/API/DTOs/UserPreferencesDto.cs index 0d8f3ae68..03dbeaa5e 100644 --- a/API/DTOs/UserPreferencesDto.cs +++ b/API/DTOs/UserPreferencesDto.cs @@ -7,6 +7,8 @@ namespace API.DTOs public ReadingDirection ReadingDirection { get; set; } public ScalingOption ScalingOption { get; set; } public PageSplitOption PageSplitOption { get; set; } + public ReaderMode ReaderMode { get; set; } + public bool AutoCloseMenu { get; set; } public bool BookReaderDarkMode { get; set; } = false; public int BookReaderMargin { get; set; } public int BookReaderLineSpacing { get; set; } diff --git a/API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs b/API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs new file mode 100644 index 000000000..2797f05ab --- /dev/null +++ b/API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs @@ -0,0 +1,869 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20210622164318_NewUserPreferences")] + partial class NewUserPreferences + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.4"); + + 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"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .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"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BookReaderDarkMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("SiteDarkMode") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + 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("LastModified") + .HasColumnType("TEXT"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + 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.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"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("BLOB"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .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("BLOB"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .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.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.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + 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("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("BLOB"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId") + .IsUnique(); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.SeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.HasIndex("Id", "SeriesId") + .IsUnique(); + + b.ToTable("SeriesMetadata"); + }); + + 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.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("BLOB"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.Property("CollectionTagsId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionTagsId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("CollectionTagSeriesMetadata"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Ratings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.HasOne("API.Entities.AppRole", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUser", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.HasOne("API.Entities.Volume", "Volume") + .WithMany("Chapters") + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Folders") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Files") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("Series") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.SeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("Metadata") + .HasForeignKey("API.Entities.SeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Volumes") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("AppUsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", null) + .WithMany() + .HasForeignKey("LibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.HasOne("API.Entities.CollectionTag", null) + .WithMany() + .HasForeignKey("CollectionTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("Files"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("Metadata"); + + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20210622164318_NewUserPreferences.cs b/API/Data/Migrations/20210622164318_NewUserPreferences.cs new file mode 100644 index 000000000..bd75d5b2c --- /dev/null +++ b/API/Data/Migrations/20210622164318_NewUserPreferences.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class NewUserPreferences : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AutoCloseMenu", + table: "AppUserPreferences", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "ReaderMode", + table: "AppUserPreferences", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AutoCloseMenu", + table: "AppUserPreferences"); + + migrationBuilder.DropColumn( + name: "ReaderMode", + table: "AppUserPreferences"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index f14402ece..c6d49fc2a 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -127,6 +127,9 @@ namespace API.Data.Migrations b.Property("AppUserId") .HasColumnType("INTEGER"); + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + b.Property("BookReaderDarkMode") .HasColumnType("INTEGER"); @@ -151,6 +154,9 @@ namespace API.Data.Migrations b.Property("PageSplitOption") .HasColumnType("INTEGER"); + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + b.Property("ReadingDirection") .HasColumnType("INTEGER"); diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index 0f725444b..07d7102e1 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -289,7 +289,7 @@ namespace API.Data /// /// /// Library to restrict to, if 0, will apply to all libraries - /// How many series to pick. + /// Contains pagination information /// public async Task> GetRecentlyAdded(int libraryId, int userId, UserParams userParams) { diff --git a/API/Data/UnitOfWork.cs b/API/Data/UnitOfWork.cs index ba89d0612..394e6fed1 100644 --- a/API/Data/UnitOfWork.cs +++ b/API/Data/UnitOfWork.cs @@ -29,7 +29,8 @@ namespace API.Data public IAppUserProgressRepository AppUserProgressRepository => new AppUserProgressRepository(_context); public ICollectionTagRepository CollectionTagRepository => new CollectionTagRepository(_context, _mapper); - + public IFileRepository FileRepository => new FileRepository(_context); + public bool Commit() { return _context.SaveChanges() > 0; diff --git a/API/Entities/AppUserPreferences.cs b/API/Entities/AppUserPreferences.cs index fb5fe9bc2..149512e00 100644 --- a/API/Entities/AppUserPreferences.cs +++ b/API/Entities/AppUserPreferences.cs @@ -17,7 +17,18 @@ namespace API.Entities /// Manga Reader Option: Which side of a split image should we show first /// public PageSplitOption PageSplitOption { get; set; } = PageSplitOption.SplitRightToLeft; - + /// + /// Manga Reader Option: How the manga reader should perform paging or reading of the file + /// + /// Webtoon uses scrolling to page, MANGA_LR uses paging by clicking left/right side of reader, MANGA_UD uses paging + /// by clicking top/bottom sides of reader. + /// + /// + public ReaderMode ReaderMode { get; set; } + /// + /// Manga Reader Option: Allow the menu to close after 6 seconds without interaction + /// + public bool AutoCloseMenu { get; set; } /// /// Book Reader Option: Should the background color be dark /// diff --git a/API/Entities/Enums/ReaderMode.cs b/API/Entities/Enums/ReaderMode.cs new file mode 100644 index 000000000..04156df24 --- /dev/null +++ b/API/Entities/Enums/ReaderMode.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +namespace API.Entities.Enums +{ + public enum ReaderMode + { + [Description("Left and Right")] + MANGA_LR = 0, + [Description("Up and Down")] + MANGA_UP = 1, + [Description("Webtoon")] + WEBTOON = 2 + } +} \ No newline at end of file diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs index 4ea8f1cf4..4d8a48be4 100644 --- a/API/Entities/Series.cs +++ b/API/Entities/Series.cs @@ -32,7 +32,7 @@ namespace API.Entities /// /// Summary information related to the Series /// - public string Summary { get; set; } // TODO: Migrate into SeriesMetdata + public string Summary { get; set; } // TODO: Migrate into SeriesMetdata (with Metadata update) public DateTime Created { get; set; } public DateTime LastModified { get; set; } public byte[] CoverImage { get; set; } diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index c3db5c08a..e713acbe1 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -1,5 +1,4 @@ -using System; -using API.Data; +using API.Data; using API.Helpers; using API.Interfaces; using API.Interfaces.Services; @@ -34,7 +33,6 @@ namespace API.Extensions services.AddScoped(); services.AddSqLite(config, env); - services.ConfigRepositories(); services.AddLogging(loggingBuilder => { @@ -56,17 +54,5 @@ namespace API.Extensions return services; } - - private static IServiceCollection ConfigRepositories(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - - return services; - } - - public static IServiceCollection AddStartupTask(this IServiceCollection services) - where T : class, IStartupTask - => services.AddTransient(); } } \ No newline at end of file diff --git a/API/Extensions/ServiceCollectionExtensions.cs b/API/Extensions/ServiceCollectionExtensions.cs index a9d12b471..1b752431c 100644 --- a/API/Extensions/ServiceCollectionExtensions.cs +++ b/API/Extensions/ServiceCollectionExtensions.cs @@ -16,7 +16,7 @@ namespace API.Extensions { services.AddHttpClient(client => { - client.BaseAddress = new Uri("http://stats.kavitareader.com"); + client.BaseAddress = new Uri("https://kavitastats.majora2007.duckdns.org"); client.DefaultRequestHeaders.Add("api-key", "MsnvA2DfQqxSK5jh"); }); diff --git a/API/Interfaces/IUnitOfWork.cs b/API/Interfaces/IUnitOfWork.cs index df326c3e2..63051d2e3 100644 --- a/API/Interfaces/IUnitOfWork.cs +++ b/API/Interfaces/IUnitOfWork.cs @@ -11,6 +11,7 @@ namespace API.Interfaces ISettingsRepository SettingsRepository { get; } IAppUserProgressRepository AppUserProgressRepository { get; } ICollectionTagRepository CollectionTagRepository { get; } + IFileRepository FileRepository { get; } bool Commit(); Task CommitAsync(); bool HasChanges(); diff --git a/API/Interfaces/Services/IArchiveService.cs b/API/Interfaces/Services/IArchiveService.cs index f77784878..18869b7cd 100644 --- a/API/Interfaces/Services/IArchiveService.cs +++ b/API/Interfaces/Services/IArchiveService.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO.Compression; using System.Threading.Tasks; using API.Archive; -using API.Entities; namespace API.Interfaces.Services { diff --git a/API/Program.cs b/API/Program.cs index b084c2ef3..fc906cca1 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using API.Data; using API.Entities; +using API.Services.HostedServices; using Kavita.Common; using Kavita.Common.EnvironmentInfo; using Microsoft.AspNetCore.Hosting; @@ -20,7 +21,7 @@ namespace API { public class Program { - private static int HttpPort; + private static int _httpPort; protected Program() { @@ -48,7 +49,7 @@ namespace API } // Get HttpPort from Config - HttpPort = Configuration.GetPort(GetAppSettingFilename()); + _httpPort = Configuration.GetPort(GetAppSettingFilename()); var host = CreateHostBuilder(args).Build(); @@ -64,7 +65,6 @@ namespace API await context.Database.MigrateAsync(); await Seed.SeedRoles(roleManager); await Seed.SeedSettings(context); - } catch (Exception ex) { @@ -81,7 +81,7 @@ namespace API { webBuilder.UseKestrel((opts) => { - opts.ListenAnyIP(HttpPort, options => + opts.ListenAnyIP(_httpPort, options => { options.Protocols = HttpProtocols.Http1AndHttp2; }); diff --git a/API/Services/CacheService.cs b/API/Services/CacheService.cs index 2ce9b375b..73c279657 100644 --- a/API/Services/CacheService.cs +++ b/API/Services/CacheService.cs @@ -63,10 +63,6 @@ namespace API.Services } new DirectoryInfo(extractPath).Flatten(); - // if (fileCount > 1) - // { - // new DirectoryInfo(extractPath).Flatten(); - // } return chapter; } diff --git a/API/Services/Clients/StatsApiClient.cs b/API/Services/Clients/StatsApiClient.cs index 10b7ba543..00dddfad3 100644 --- a/API/Services/Clients/StatsApiClient.cs +++ b/API/Services/Clients/StatsApiClient.cs @@ -2,20 +2,25 @@ using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; + +using API.Configurations.CustomOptions; using API.DTOs; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace API.Services.Clients { public class StatsApiClient { private readonly HttpClient _client; + private readonly StatsOptions _options; private readonly ILogger _logger; - public StatsApiClient(HttpClient client, ILogger logger) + public StatsApiClient(HttpClient client, IOptions options, ILogger logger) { _client = client; _logger = logger; + _options = options.Value ?? throw new ArgumentNullException(nameof(options)); } public async Task SendDataToStatsServer(UsageStatisticsDto data) diff --git a/API/Services/ComicInfo.cs b/API/Services/ComicInfo.cs index 8277cfb35..55e823ee4 100644 --- a/API/Services/ComicInfo.cs +++ b/API/Services/ComicInfo.cs @@ -9,6 +9,7 @@ public string Publisher { get; set; } public string Genre { get; set; } public int PageCount { get; set; } + // ReSharper disable once InconsistentNaming public string LanguageISO { get; set; } public string Web { get; set; } } diff --git a/API/Services/HostedServices/StartupTasksHostedService.cs b/API/Services/HostedServices/StartupTasksHostedService.cs index dcdb22cca..95f87006e 100644 --- a/API/Services/HostedServices/StartupTasksHostedService.cs +++ b/API/Services/HostedServices/StartupTasksHostedService.cs @@ -28,7 +28,7 @@ namespace API.Services.HostedServices { await ManageStartupStatsTasks(scope, taskScheduler); } - catch (Exception e) + catch (Exception) { //If stats startup fail the user can keep using the app } @@ -36,9 +36,9 @@ namespace API.Services.HostedServices private async Task ManageStartupStatsTasks(IServiceScope serviceScope, ITaskScheduler taskScheduler) { - var settingsRepository = serviceScope.ServiceProvider.GetRequiredService(); + var unitOfWork = serviceScope.ServiceProvider.GetRequiredService(); - var settingsDto = await settingsRepository.GetSettingsDtoAsync(); + var settingsDto = await unitOfWork.SettingsRepository.GetSettingsDtoAsync(); if (!settingsDto.AllowStatCollection) return; diff --git a/API/Services/StatsService.cs b/API/Services/StatsService.cs index 4d5e3a315..2c315c99d 100644 --- a/API/Services/StatsService.cs +++ b/API/Services/StatsService.cs @@ -25,15 +25,15 @@ namespace API.Services private readonly StatsApiClient _client; private readonly DataContext _dbContext; private readonly ILogger _logger; - private readonly IFileRepository _fileRepository; + private readonly IUnitOfWork _unitOfWork; public StatsService(StatsApiClient client, DataContext dbContext, ILogger logger, - IFileRepository fileRepository) + IUnitOfWork unitOfWork) { _client = client; _dbContext = dbContext; _logger = logger; - _fileRepository = fileRepository; + _unitOfWork = unitOfWork; } private static string FinalPath => Path.Combine(Directory.GetCurrentDirectory(), TempFilePath, TempFileName); @@ -77,9 +77,9 @@ namespace API.Services _logger.LogInformation("Deleting the file from disk"); if (FileExists) File.Delete(FinalPath); } - catch (Exception e) + catch (Exception ex) { - _logger.LogError("Error Finalizing Stats collection flow", e); + _logger.LogError(ex, "Error Finalizing Stats collection flow"); throw; } } @@ -121,7 +121,7 @@ namespace API.Services .Select(x => new LibInfo {Type = x.Key, Count = x.Count()}) .ToArrayAsync(); - var uniqueFileTypes = await _fileRepository.GetFileExtensions(); + var uniqueFileTypes = await _unitOfWork.FileRepository.GetFileExtensions(); var usageInfo = new UsageInfoDto { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..ab4a8a30e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ +# How to Contribute # + +We're always looking for people to help make Kavita even better, there are a number of ways to contribute. + +## Documentation ## +Setup guides, FAQ, the more information we have on the [wiki](https://github.com/Kareadita/Kavita/wiki) the better. + +## Development ## + +### Tools required ### +- Visual Studio 2019 or higher (https://www.visualstudio.com/vs/). The community version is free and works fine. [Download it here](https://www.visualstudio.com/downloads/). +- Rider (optional to Visual Studio) (https://www.jetbrains.com/rider/) +- HTML/Javascript editor of choice (VS Code/Sublime Text/Webstorm/Atom/etc) +- [Git](https://git-scm.com/downloads) +- [NodeJS](https://nodejs.org/en/download/) (Node 14.X.X or higher) +- .NET 5.0+ + +### Getting started ### + +1. Fork Kavita +2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github) + - Kavita as of v0.4.2 requires Kavita-webui to be cloned next to the Kavita. Fork and clone this as well. +3. Install the required Node Packages + - cd kavita-webui + - `npm install` + - `npm install -g @angular/cli` +4. Start webui server `ng serve` +5. Build the project in Visual Studio/Rider, Setting startup project to `API` +6. Debug the project in Visual Studio/Rider +7. Open http://localhost:4200 +8. (Deployment only) Run build.sh and pass the Runtime Identifier for your OS or just build.sh for all supported RIDs. + + +### Contributing Code ### +- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Kareadita/Kavita/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first) +- Rebase from Kavita's develop branch, don't merge +- Make meaningful commits, or squash them +- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements +- Reach out to us on the discord if you have any questions +- Add tests (unit/integration) +- Commit with *nix line endings for consistency (We checkout Windows and commit *nix) +- One feature/bug fix per pull request to keep things clean and easy to understand +- Use 4 spaces instead of tabs, this is the default for VS 2019 and WebStorm (to my knowledge) + - Use 2 spaces for Kavita-webui files + +### Pull Requesting ### +- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it +- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability +- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it +- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed) + - new-feature (Good) + - fix-bug (Good) + - patch (Bad) + - develop (Bad) + +If you have any questions about any of this, please let us know. diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 673974db1..3b92d09f8 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -4,7 +4,7 @@ net5.0 kareadita.github.io Kavita - 0.4.1.1 + 0.4.2.0 en diff --git a/Logo/dottrace.svg b/Logo/dottrace.svg new file mode 100644 index 000000000..b879517cd --- /dev/null +++ b/Logo/dottrace.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Logo/jetbrains.svg b/Logo/jetbrains.svg new file mode 100644 index 000000000..75d4d2177 --- /dev/null +++ b/Logo/jetbrains.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Logo/kavita.svg b/Logo/kavita.svg new file mode 100644 index 000000000..f56f8a7c5 --- /dev/null +++ b/Logo/kavita.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Logo/resharper.svg b/Logo/resharper.svg new file mode 100644 index 000000000..24c987a78 --- /dev/null +++ b/Logo/resharper.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Logo/rider.svg b/Logo/rider.svg new file mode 100644 index 000000000..82da35b0b --- /dev/null +++ b/Logo/rider.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + rider + + + + + + + + + + + + + + diff --git a/Logo/sentry.svg b/Logo/sentry.svg new file mode 100644 index 000000000..40bd18594 --- /dev/null +++ b/Logo/sentry.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/README.md b/README.md index a3fd09193..2dda67528 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Kavita +# []() Kavita
![Cover Image](https://github.com/Kareadita/kareadita.github.io/blob/main/img/features/seriesdetail.PNG?raw=true) @@ -9,44 +9,41 @@ your reading collection with your friends and family! [![Release](https://img.shields.io/github/release/Kareadita/Kavita.svg?style=flat&maxAge=3600)](https://github.com/Kareadita/Kavita/releases) [![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://github.com/Kareadita/Kavita/blob/master/LICENSE) -[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://discord.gg/eczRp9eeem) [![Downloads](https://img.shields.io/github/downloads/Kareadita/Kavita/total.svg?style=flat)](https://github.com/Kareadita/Kavita/releases) [![Docker Pulls](https://img.shields.io/docker/pulls/kizaing/kavita.svg)](https://hub.docker.com/r/kizaing/kavita/) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Kareadita_Kavita&metric=alert_status)](https://sonarcloud.io/dashboard?id=Kareadita_Kavita) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=Kareadita_Kavita&metric=sqale_rating)](https://sonarcloud.io/dashboard?id=Kareadita_Kavita) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=Kareadita_Kavita&metric=security_rating)](https://sonarcloud.io/dashboard?id=Kareadita_Kavita) [![Donate via Paypal](https://img.shields.io/badge/donate-paypal-blue.svg?style=popout&logo=paypal)](https://paypal.me/majora2007?locale.x=en_US) +[![Backers on Open Collective](https://opencollective.com/kavita/backers/badge.svg)](#backers) +[![Sponsors on Open Collective](https://opencollective.com/kavita/sponsors/badge.svg)](#sponsors)
-## Goals: +## Goals - [x] Serve up Manga/Webtoons/Comics (cbr, cbz, zip/rar, 7zip, raw images) and Books (epub, mobi, azw, djvu, pdf) -- [x] First class responsive readers that work great on any device +- [x] First class responsive readers that work great on any device (phone, tablet, desktop) - [x] Provide a dark theme for web app - [ ] Provide hooks into metadata providers to fetch metadata for Comics, Manga, and Books - [ ] Metadata should allow for collections, want to read integration from 3rd party services, genres. - [x] Ability to manage users, access, and ratings - [ ] Ability to sync ratings and reviews to external services -- [x] Fully Accessible +- [x] Fully Accessible with active accessibility audits +- [x] Dedicated webtoon reader - [ ] And so much [more...](https://github.com/Kareadita/Kavita/projects) +## Support +[![Reddit](https://img.shields.io/badge/reddit-discussion-FF4500.svg?maxAge=60)](https://www.reddit.com/r/KavitaManga/) +[![Discord](https://img.shields.io/badge/discord-chat-7289DA.svg?maxAge=60)](https://discord.gg/eczRp9eeem) +[![GitHub - Bugs and Feature Requests Only](https://img.shields.io/badge/github-issues-red.svg?maxAge=60)](https://github.com/Kareadita/Kavita/issues) -# How to contribute -- Ensure you've cloned Kavita-webui. You should have Projects/Kavita and Projects/Kavita-webui -- In Kavita-webui, run ng serve. This will start the webserver on localhost:4200 -- Run API project in Kavita, this will start the backend on localhost:5000 - - -## Deploy local build -- Run build.sh and pass the Runtime Identifier for your OS or just build.sh for all supported RIDs. - -## How to install +## Setup +### Non-Docker - Unzip the archive for your target OS - Place in a directory that is writable. If on windows, do not place in Program Files - Linux users must ensure the directory & kavita.db is writable by Kavita (might require starting server once) - Run Kavita - If you are updating, do not copy appsettings.json from the new version over. It will override your TokenKey and you will have to reauthenticate on your devices. -## Docker +### Docker Running your Kavita server in docker is super easy! Barely an inconvenience. You can run it with this command: ``` @@ -72,17 +69,51 @@ services: restart: unless-stopped ``` -**Note: Kavita is under heavy development and is being updated all the time, so the tag for current builds is :nightly. The :latest tag will be the latest stable release. There is also the :alpine tag if you want a smaller image, but it is only available for x64 systems.** +**Note: Kavita is under heavy development and is being updated all the time, so the tag for current builds is `:nightly`. The `:latest` tag will be the latest stable release. There is also the `:alpine` tag if you want a smaller image, but it is only available for x64 systems.** -## Got an Idea? -Got a great idea? Throw it up on the FeatHub or vote on another persons. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features. +## Feature Requests +Got a great idea? Throw it up on the FeatHub or vote on another idea. Please check the [Project Board](https://github.com/Kareadita/Kavita/projects) first for a list of planned features. [![Feature Requests](https://feathub.com/Kareadita/Kavita?format=svg)](https://feathub.com/Kareadita/Kavita) -## Want to help? -I am looking for developers with a passion for building the next Plex for Reading. Developers with C#/ASP.NET, Angular 11 please reach out on [Discord](https://discord.gg/eczRp9eeem). + +## Contributors + +This project exists thanks to all the people who contribute. [Contribute](CONTRIBUTING.md). + + ## Donate -If you like Kavita, have gotten good use out of it or feel like you want to say thanks with a few bucks, feel free to donate. Money will -likely go towards beer or hosting. +If you like Kavita, have gotten good use out of it or feel like you want to say thanks with a few bucks, feel free to donate. Money will go towards +expenses related to Kavita. You can back us through OpenCollective. + [![Donate via Paypal](https://img.shields.io/badge/donate-paypal-blue.svg?style=popout&logo=paypal)](https://paypal.me/majora2007?locale.x=en_US) + +## Backers + +Thank you to all our backers! 🙏 [Become a backer](https://opencollective.com/Kavita#backer) + + + +## Sponsors + +Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor](https://opencollective.com/Kavita#sponsor) + + + +## Mega Sponsors + + +## JetBrains +Thank you to [ JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools. + +* [ Rider](http://www.jetbrains.com/rider/) +* [ dotTrace](http://www.jetbrains.com/dottrace/) + +## Sentry +Thank you to [ Sentry](https://sentry.io/welcome/) for providing us with free license to their software. + +### License + +* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) +* Copyright 2010-2021 \ No newline at end of file diff --git a/build.sh b/build.sh index d32da32dc..adb753827 100644 --- a/build.sh +++ b/build.sh @@ -48,9 +48,15 @@ Build() BuildUI() { ProgressStart 'Building UI' + echo 'Removing old wwwroot' + rm -rf API/wwwroot/* cd ../Kavita-webui/ || exit + echo 'Installing web dependencies' npm install + echo 'Building UI' npm run prod + echo 'Copying back to Kavita wwwroot' + cp -r dist/* ../Kavita/API/wwwroot cd ../Kavita/ || exit ProgressEnd 'Building UI' } @@ -68,6 +74,9 @@ Package() cd API echo dotnet publish -c Release --self-contained --runtime $runtime -o "$lOutputFolder" --framework $framework dotnet publish -c Release --self-contained --runtime $runtime -o "$lOutputFolder" --framework $framework + + echo "Recopying wwwroot due to bug" + cp -r ./wwwroot/* $lOutputFolder/wwwroot echo "Copying Install information" cp ../INSTALL.txt "$lOutputFolder"/README.txt