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.
This commit is contained in:
parent
b958342394
commit
be2b78fa5a
40 changed files with 1608 additions and 87 deletions
124
API/API.csproj
124
API/API.csproj
|
|
@ -64,23 +64,147 @@
|
|||
<ItemGroup>
|
||||
<None Remove="Hangfire-log.db" />
|
||||
<None Remove="obj\**" />
|
||||
<None Remove="wwwroot\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Interfaces\IMetadataService.cs" />
|
||||
<Compile Remove="obj\**" />
|
||||
<Compile Remove="wwwroot\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="obj\**" />
|
||||
<EmbeddedResource Remove="wwwroot\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="obj\**" />
|
||||
<Content Remove="wwwroot\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
{
|
||||
|
|
|
|||
30
API/Configurations/CustomOptions/StatsOptions.cs
Normal file
30
API/Configurations/CustomOptions/StatsOptions.cs
Normal file
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<ActionResult<string>> GetImagePath(int chapterId)
|
||||
|
||||
[HttpGet("chapter-info")]
|
||||
public async Task<ActionResult<ChapterInfoDto>> 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")]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class CollectionTagDto
|
||||
{
|
||||
|
|
|
|||
16
API/DTOs/Reader/ChapterInfoDto.cs
Normal file
16
API/DTOs/Reader/ChapterInfoDto.cs
Normal file
|
|
@ -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; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using API.Entities;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
869
API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs
generated
Normal file
869
API/Data/Migrations/20210622164318_NewUserPreferences.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,869 @@
|
|||
// <auto-generated />
|
||||
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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastActive")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoCloseMenu")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("BookReaderFontSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderLineSpacing")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderMargin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("BookReaderTapToPaginate")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReaderMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("SiteDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppUserPreferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppUserProgress", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookScrollId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("PagesRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("VolumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.ToTable("AppUserProgresses");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppUserRating", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Rating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Review")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId");
|
||||
|
||||
b.ToTable("AppUserRating");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.AppUserRole", b =>
|
||||
{
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Number")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Range")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("VolumeId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("VolumeId");
|
||||
|
||||
b.ToTable("Chapter");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.CollectionTag", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<string>("NormalizedTitle")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("Promoted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Id", "Promoted")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CollectionTag");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastScanned")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LibraryId");
|
||||
|
||||
b.ToTable("FolderPath");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Library", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CoverImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Library");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.MangaFile", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ChapterId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("FilePath")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Format")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChapterId");
|
||||
|
||||
b.ToTable("MangaFile");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Series", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("OriginalName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SortName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("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<int>("Key")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("ServerSetting");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte[]>("CoverImage")
|
||||
.HasColumnType("BLOB");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("Volume");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("AppUserLibrary", b =>
|
||||
{
|
||||
b.Property<int>("AppUsersId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LibrariesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("AppUsersId", "LibrariesId");
|
||||
|
||||
b.HasIndex("LibrariesId");
|
||||
|
||||
b.ToTable("AppUserLibrary");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CollectionTagSeriesMetadata", b =>
|
||||
{
|
||||
b.Property<int>("CollectionTagsId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SeriesMetadatasId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CollectionTagsId", "SeriesMetadatasId");
|
||||
|
||||
b.HasIndex("SeriesMetadatasId");
|
||||
|
||||
b.ToTable("CollectionTagSeriesMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("RoleId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
|
||||
{
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<int>", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", 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
|
||||
}
|
||||
}
|
||||
}
|
||||
35
API/Data/Migrations/20210622164318_NewUserPreferences.cs
Normal file
35
API/Data/Migrations/20210622164318_NewUserPreferences.cs
Normal file
|
|
@ -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<bool>(
|
||||
name: "AutoCloseMenu",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,6 +127,9 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoCloseMenu")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
@ -151,6 +154,9 @@ namespace API.Data.Migrations
|
|||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReaderMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ namespace API.Data
|
|||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="libraryId">Library to restrict to, if 0, will apply to all libraries</param>
|
||||
/// <param name="limit">How many series to pick.</param>
|
||||
/// <param name="userParams">Contains pagination information</param>
|
||||
/// <returns></returns>
|
||||
public async Task<PagedList<SeriesDto>> GetRecentlyAdded(int libraryId, int userId, UserParams userParams)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,18 @@ namespace API.Entities
|
|||
/// Manga Reader Option: Which side of a split image should we show first
|
||||
/// </summary>
|
||||
public PageSplitOption PageSplitOption { get; set; } = PageSplitOption.SplitRightToLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Manga Reader Option: How the manga reader should perform paging or reading of the file
|
||||
/// <example>
|
||||
/// 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.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
public ReaderMode ReaderMode { get; set; }
|
||||
/// <summary>
|
||||
/// Manga Reader Option: Allow the menu to close after 6 seconds without interaction
|
||||
/// </summary>
|
||||
public bool AutoCloseMenu { get; set; }
|
||||
/// <summary>
|
||||
/// Book Reader Option: Should the background color be dark
|
||||
/// </summary>
|
||||
|
|
|
|||
14
API/Entities/Enums/ReaderMode.cs
Normal file
14
API/Entities/Enums/ReaderMode.cs
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ namespace API.Entities
|
|||
/// <summary>
|
||||
/// Summary information related to the Series
|
||||
/// </summary>
|
||||
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; }
|
||||
|
|
|
|||
|
|
@ -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<IBookService, BookService>();
|
||||
|
||||
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<ISettingsRepository, SettingsRepository>();
|
||||
services.AddScoped<IFileRepository, FileRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
|
||||
where T : class, IStartupTask
|
||||
=> services.AddTransient<IStartupTask, T>();
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ namespace API.Extensions
|
|||
{
|
||||
services.AddHttpClient<StatsApiClient>(client =>
|
||||
{
|
||||
client.BaseAddress = new Uri("http://stats.kavitareader.com");
|
||||
client.BaseAddress = new Uri("https://kavitastats.majora2007.duckdns.org");
|
||||
client.DefaultRequestHeaders.Add("api-key", "MsnvA2DfQqxSK5jh");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ namespace API.Interfaces
|
|||
ISettingsRepository SettingsRepository { get; }
|
||||
IAppUserProgressRepository AppUserProgressRepository { get; }
|
||||
ICollectionTagRepository CollectionTagRepository { get; }
|
||||
IFileRepository FileRepository { get; }
|
||||
bool Commit();
|
||||
Task<bool> CommitAsync();
|
||||
bool HasChanges();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -63,10 +63,6 @@ namespace API.Services
|
|||
}
|
||||
|
||||
new DirectoryInfo(extractPath).Flatten();
|
||||
// if (fileCount > 1)
|
||||
// {
|
||||
// new DirectoryInfo(extractPath).Flatten();
|
||||
// }
|
||||
|
||||
return chapter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StatsApiClient> _logger;
|
||||
|
||||
public StatsApiClient(HttpClient client, ILogger<StatsApiClient> logger)
|
||||
public StatsApiClient(HttpClient client, IOptions<StatsOptions> options, ILogger<StatsApiClient> logger)
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public async Task SendDataToStatsServer(UsageStatisticsDto data)
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ISettingsRepository>();
|
||||
var unitOfWork = serviceScope.ServiceProvider.GetRequiredService<IUnitOfWork>();
|
||||
|
||||
var settingsDto = await settingsRepository.GetSettingsDtoAsync();
|
||||
var settingsDto = await unitOfWork.SettingsRepository.GetSettingsDtoAsync();
|
||||
|
||||
if (!settingsDto.AllowStatCollection) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@ namespace API.Services
|
|||
private readonly StatsApiClient _client;
|
||||
private readonly DataContext _dbContext;
|
||||
private readonly ILogger<StatsService> _logger;
|
||||
private readonly IFileRepository _fileRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public StatsService(StatsApiClient client, DataContext dbContext, ILogger<StatsService> 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
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue