mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Merge pull request #80 from Kareadita/feature/lazy-loading
Lazy Loading + Response Caching
This commit is contained in:
commit
8bc7465306
@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
|||||||
using API.Constants;
|
using API.Constants;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
|
using API.Errors;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using API.Interfaces.Services;
|
using API.Interfaces.Services;
|
||||||
@ -39,18 +40,41 @@ namespace API.Controllers
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
|
||||||
[HttpPost("reset-password")]
|
[HttpPost("reset-password")]
|
||||||
public async Task<ActionResult> UpdatePassword(ResetPasswordDto resetPasswordDto)
|
public async Task<ActionResult> UpdatePassword(ResetPasswordDto resetPasswordDto)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{UserName} is changing {ResetUser}'s password", User.GetUsername(), resetPasswordDto.UserName);
|
_logger.LogInformation("{UserName} is changing {ResetUser}'s password", User.GetUsername(), resetPasswordDto.UserName);
|
||||||
var user = await _userManager.Users.SingleAsync(x => x.UserName == resetPasswordDto.UserName);
|
var user = await _userManager.Users.SingleAsync(x => x.UserName == resetPasswordDto.UserName);
|
||||||
|
var isAdmin = await _userManager.IsInRoleAsync(user, PolicyConstants.AdminRole);
|
||||||
|
|
||||||
|
if (resetPasswordDto.UserName != User.GetUsername() && !isAdmin) return Unauthorized("You are not permitted to this operation.");
|
||||||
|
|
||||||
|
// Validate Password
|
||||||
|
foreach (var validator in _userManager.PasswordValidators)
|
||||||
|
{
|
||||||
|
var validationResult = await validator.ValidateAsync(_userManager, user, resetPasswordDto.Password);
|
||||||
|
if (!validationResult.Succeeded)
|
||||||
|
{
|
||||||
|
return BadRequest(
|
||||||
|
validationResult.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _userManager.RemovePasswordAsync(user);
|
var result = await _userManager.RemovePasswordAsync(user);
|
||||||
if (!result.Succeeded) return BadRequest("Unable to update password");
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
_logger.LogError("Could not update password");
|
||||||
|
return BadRequest(result.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
result = await _userManager.AddPasswordAsync(user, resetPasswordDto.Password);
|
result = await _userManager.AddPasswordAsync(user, resetPasswordDto.Password);
|
||||||
if (!result.Succeeded) return BadRequest("Unable to update password");
|
if (!result.Succeeded)
|
||||||
|
{
|
||||||
|
_logger.LogError("Could not update password");
|
||||||
|
return BadRequest(result.Errors.Select(e => new ApiException(400, e.Code, e.Description)));
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogInformation("{User}'s Password has been reset", resetPasswordDto.UserName);
|
_logger.LogInformation("{User}'s Password has been reset", resetPasswordDto.UserName);
|
||||||
return Ok();
|
return Ok();
|
||||||
|
62
API/Controllers/ImageController.cs
Normal file
62
API/Controllers/ImageController.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.DTOs;
|
||||||
|
using API.Extensions;
|
||||||
|
using API.Interfaces;
|
||||||
|
using API.Interfaces.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace API.Controllers
|
||||||
|
{
|
||||||
|
public class ImageController : BaseApiController
|
||||||
|
{
|
||||||
|
private readonly IDirectoryService _directoryService;
|
||||||
|
private readonly ICacheService _cacheService;
|
||||||
|
private readonly ILogger<ImageController> _logger;
|
||||||
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
|
||||||
|
public ImageController(IDirectoryService directoryService, ICacheService cacheService,
|
||||||
|
ILogger<ImageController> logger, IUnitOfWork unitOfWork)
|
||||||
|
{
|
||||||
|
_directoryService = directoryService;
|
||||||
|
_cacheService = cacheService;
|
||||||
|
_logger = logger;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("chapter-cover")]
|
||||||
|
public async Task<ActionResult> GetChapterCoverImage(int chapterId)
|
||||||
|
{
|
||||||
|
var content = await _unitOfWork.VolumeRepository.GetChapterCoverImageAsync(chapterId);
|
||||||
|
if (content == null) return BadRequest("No cover image");
|
||||||
|
const string format = "jpeg";
|
||||||
|
|
||||||
|
Response.AddCacheHeader(content);
|
||||||
|
return File(content, "image/" + format);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("volume-cover")]
|
||||||
|
public async Task<ActionResult> GetVolumeCoverImage(int volumeId)
|
||||||
|
{
|
||||||
|
var content = await _unitOfWork.SeriesRepository.GetVolumeCoverImageAsync(volumeId);
|
||||||
|
if (content == null) return BadRequest("No cover image");
|
||||||
|
const string format = "jpeg";
|
||||||
|
|
||||||
|
Response.AddCacheHeader(content);
|
||||||
|
return File(content, "image/" + format);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("series-cover")]
|
||||||
|
public async Task<ActionResult> GetSeriesCoverImage(int seriesId)
|
||||||
|
{
|
||||||
|
var content = await _unitOfWork.SeriesRepository.GetSeriesCoverImageAsync(seriesId);
|
||||||
|
if (content == null) return BadRequest("No cover image");
|
||||||
|
const string format = "jpeg";
|
||||||
|
|
||||||
|
Response.AddCacheHeader(content);
|
||||||
|
return File(content, "image/" + format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -228,10 +228,6 @@ namespace API.Controllers
|
|||||||
[HttpGet("search")]
|
[HttpGet("search")]
|
||||||
public async Task<ActionResult<IEnumerable<SearchResultDto>>> Search(string queryString)
|
public async Task<ActionResult<IEnumerable<SearchResultDto>>> Search(string queryString)
|
||||||
{
|
{
|
||||||
//NOTE: What about normalizing search query and only searching against normalizedname in Series?
|
|
||||||
// So One Punch would match One-Punch
|
|
||||||
// This also means less indexes we need.
|
|
||||||
// TODO: Add indexes of what we are searching on
|
|
||||||
queryString = queryString.Replace(@"%", "");
|
queryString = queryString.Replace(@"%", "");
|
||||||
|
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
@ -40,12 +40,9 @@ namespace API.Controllers
|
|||||||
|
|
||||||
var content = await _directoryService.ReadFileAsync(path);
|
var content = await _directoryService.ReadFileAsync(path);
|
||||||
var format = Path.GetExtension(path).Replace(".", "");
|
var format = Path.GetExtension(path).Replace(".", "");
|
||||||
|
|
||||||
// Look into HttpContext.Cache so we can utilize a memorystream for Zip entries (want to limit response time by 300ms)
|
|
||||||
// Calculates SHA1 Hash for byte[]
|
// Calculates SHA1 Hash for byte[]
|
||||||
using var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
|
Response.AddCacheHeader(content);
|
||||||
Response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(content).Select(x => x.ToString("X2"))));
|
|
||||||
Response.Headers.Add("Cache-Control", "private");
|
|
||||||
|
|
||||||
return File(content, "image/" + format);
|
return File(content, "image/" + format);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using API.Data;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities.Enums;
|
using API.Entities.Enums;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
@ -9,6 +10,8 @@ using API.Helpers.Converters;
|
|||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore.Internal;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace API.Controllers
|
||||||
@ -19,12 +22,14 @@ namespace API.Controllers
|
|||||||
private readonly ILogger<SettingsController> _logger;
|
private readonly ILogger<SettingsController> _logger;
|
||||||
private readonly IUnitOfWork _unitOfWork;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ITaskScheduler _taskScheduler;
|
private readonly ITaskScheduler _taskScheduler;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler)
|
public SettingsController(ILogger<SettingsController> logger, IUnitOfWork unitOfWork, ITaskScheduler taskScheduler, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
_taskScheduler = taskScheduler;
|
_taskScheduler = taskScheduler;
|
||||||
|
_configuration = configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
@ -51,6 +56,9 @@ namespace API.Controllers
|
|||||||
|
|
||||||
// We do not allow CacheDirectory changes, so we will ignore.
|
// We do not allow CacheDirectory changes, so we will ignore.
|
||||||
var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync();
|
var currentSettings = await _unitOfWork.SettingsRepository.GetSettingsAsync();
|
||||||
|
|
||||||
|
var logLevelOptions = new LogLevelOptions();
|
||||||
|
_configuration.GetSection("Logging:LogLevel").Bind(logLevelOptions);
|
||||||
|
|
||||||
foreach (var setting in currentSettings)
|
foreach (var setting in currentSettings)
|
||||||
{
|
{
|
||||||
@ -72,8 +80,15 @@ namespace API.Controllers
|
|||||||
Environment.SetEnvironmentVariable("KAVITA_PORT", setting.Value);
|
Environment.SetEnvironmentVariable("KAVITA_PORT", setting.Value);
|
||||||
_unitOfWork.SettingsRepository.Update(setting);
|
_unitOfWork.SettingsRepository.Update(setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (setting.Key == ServerSettingKey.LoggingLevel && updateSettingsDto.LoggingLevel + "" != setting.Value)
|
||||||
|
{
|
||||||
|
setting.Value = updateSettingsDto.LoggingLevel + "";
|
||||||
|
_unitOfWork.SettingsRepository.Update(setting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_configuration.GetSection("Logging:LogLevel:Default").Value = updateSettingsDto.LoggingLevel + "";
|
||||||
if (!_unitOfWork.HasChanges()) return Ok("Nothing was updated");
|
if (!_unitOfWork.HasChanges()) return Ok("Nothing was updated");
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges() || !await _unitOfWork.Complete())
|
if (!_unitOfWork.HasChanges() || !await _unitOfWork.Complete())
|
||||||
@ -90,5 +105,12 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
return Ok(CronConverter.Options);
|
return Ok(CronConverter.Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
|
[HttpGet("log-levels")]
|
||||||
|
public ActionResult<IEnumerable<string>> GetLogLevels()
|
||||||
|
{
|
||||||
|
return Ok(new string[] {"Trace", "Debug", "Information", "Warning", "Critical", "None"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,28 +4,27 @@ namespace API.DTOs
|
|||||||
{
|
{
|
||||||
public class ChapterDto
|
public class ChapterDto
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Range { get; set; }
|
public string Range { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Smallest number of the Range.
|
/// Smallest number of the Range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Number { get; set; }
|
public string Number { get; init; }
|
||||||
public byte[] CoverImage { get; set; }
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Total number of pages in all MangaFiles
|
/// Total number of pages in all MangaFiles
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Pages { get; set; }
|
public int Pages { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The files that represent this Chapter
|
/// The files that represent this Chapter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<MangaFileDto> Files { get; set; }
|
public ICollection<MangaFileDto> Files { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculated at API time. Number of pages read for this Chapter for logged in user.
|
/// Calculated at API time. Number of pages read for this Chapter for logged in user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PagesRead { get; set; }
|
public int PagesRead { get; set; }
|
||||||
public int VolumeId { get; set; }
|
public int VolumeId { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,11 +7,11 @@ namespace API.DTOs
|
|||||||
public class CreateLibraryDto
|
public class CreateLibraryDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string Name { get; set; }
|
public string Name { get; init; }
|
||||||
[Required]
|
[Required]
|
||||||
public LibraryType Type { get; set; }
|
public LibraryType Type { get; init; }
|
||||||
[Required]
|
[Required]
|
||||||
[MinLength(1)]
|
[MinLength(1)]
|
||||||
public IEnumerable<string> Folders { get; set; }
|
public IEnumerable<string> Folders { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,14 +2,14 @@
|
|||||||
{
|
{
|
||||||
public class ImageDto
|
public class ImageDto
|
||||||
{
|
{
|
||||||
public int Page { get; set; }
|
public int Page { get; init; }
|
||||||
public string Filename { get; init; }
|
public string Filename { get; init; }
|
||||||
public string FullPath { get; init; }
|
public string FullPath { get; init; }
|
||||||
public int Width { get; init; }
|
public int Width { get; init; }
|
||||||
public int Height { get; init; }
|
public int Height { get; init; }
|
||||||
public string Format { get; init; }
|
public string Format { get; init; }
|
||||||
public byte[] Content { get; init; }
|
public byte[] Content { get; init; }
|
||||||
public string MangaFileName { get; set; }
|
public string MangaFileName { get; init; }
|
||||||
public bool NeedsSplitting { get; set; }
|
public bool NeedsSplitting { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,9 +6,9 @@ namespace API.DTOs
|
|||||||
public class LibraryDto
|
public class LibraryDto
|
||||||
{
|
{
|
||||||
public int Id { get; init; }
|
public int Id { get; init; }
|
||||||
public string Name { get; set; }
|
public string Name { get; init; }
|
||||||
public string CoverImage { get; set; }
|
public string CoverImage { get; init; }
|
||||||
public LibraryType Type { get; set; }
|
public LibraryType Type { get; init; }
|
||||||
public ICollection<string> Folders { get; set; }
|
public ICollection<string> Folders { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class LoginDto
|
public class LoginDto
|
||||||
{
|
{
|
||||||
public string Username { get; set; }
|
public string Username { get; init; }
|
||||||
public string Password { get; set; }
|
public string Password { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,9 +4,9 @@ namespace API.DTOs
|
|||||||
{
|
{
|
||||||
public class MangaFileDto
|
public class MangaFileDto
|
||||||
{
|
{
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; init; }
|
||||||
public int NumberOfPages { get; set; }
|
public int NumberOfPages { get; init; }
|
||||||
public MangaFormat Format { get; set; }
|
public MangaFormat Format { get; init; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,6 @@
|
|||||||
{
|
{
|
||||||
public class MarkReadDto
|
public class MarkReadDto
|
||||||
{
|
{
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,11 +8,11 @@ namespace API.DTOs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class MemberDto
|
public class MemberDto
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; init; }
|
||||||
public string Username { get; set; }
|
public string Username { get; init; }
|
||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; init; }
|
||||||
public DateTime LastActive { get; set; }
|
public DateTime LastActive { get; init; }
|
||||||
public IEnumerable<LibraryDto> Libraries { get; set; }
|
public IEnumerable<LibraryDto> Libraries { get; init; }
|
||||||
public IEnumerable<string> Roles { get; set; }
|
public IEnumerable<string> Roles { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,10 +5,10 @@ namespace API.DTOs
|
|||||||
public class RegisterDto
|
public class RegisterDto
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public string Username { get; set; }
|
public string Username { get; init; }
|
||||||
[Required]
|
[Required]
|
||||||
[StringLength(16, MinimumLength = 4)]
|
[StringLength(16, MinimumLength = 4)]
|
||||||
public string Password { get; set; }
|
public string Password { get; init; }
|
||||||
public bool IsAdmin { get; set; }
|
public bool IsAdmin { get; init; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +0,0 @@
|
|||||||
namespace API.DTOs
|
|
||||||
{
|
|
||||||
public class SearchQueryDto
|
|
||||||
{
|
|
||||||
public string QueryString { get; init; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,9 +6,7 @@
|
|||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
public string OriginalName { get; init; }
|
public string OriginalName { get; init; }
|
||||||
public string SortName { get; init; }
|
public string SortName { get; init; }
|
||||||
public byte[] CoverImage { get; init; } // This should be optional or a thumbImage (much smaller)
|
|
||||||
|
|
||||||
|
|
||||||
// Grouping information
|
// Grouping information
|
||||||
public string LibraryName { get; set; }
|
public string LibraryName { get; set; }
|
||||||
public int LibraryId { get; set; }
|
public int LibraryId { get; set; }
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
public string LocalizedName { get; init; }
|
public string LocalizedName { get; init; }
|
||||||
public string SortName { get; init; }
|
public string SortName { get; init; }
|
||||||
public string Summary { get; init; }
|
public string Summary { get; init; }
|
||||||
public byte[] CoverImage { get; init; }
|
|
||||||
public int Pages { get; init; }
|
public int Pages { get; init; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sum of pages read from linked Volumes. Calculated at API-time.
|
/// Sum of pages read from linked Volumes. Calculated at API-time.
|
||||||
|
@ -9,7 +9,6 @@ namespace API.DTOs
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int Number { get; set; }
|
public int Number { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public byte[] CoverImage { get; set; }
|
|
||||||
public int Pages { get; set; }
|
public int Pages { get; set; }
|
||||||
public int PagesRead { get; set; }
|
public int PagesRead { get; set; }
|
||||||
public DateTime LastModified { get; set; }
|
public DateTime LastModified { get; set; }
|
||||||
|
9
API/Data/LogLevelOptions.cs
Normal file
9
API/Data/LogLevelOptions.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Data
|
||||||
|
{
|
||||||
|
public class LogLevelOptions
|
||||||
|
{
|
||||||
|
public const string Logging = "LogLevel";
|
||||||
|
|
||||||
|
public string Default { get; set; }
|
||||||
|
}
|
||||||
|
}
|
727
API/Data/Migrations/20210313001830_SearchIndex.Designer.cs
generated
Normal file
727
API/Data/Migrations/20210313001830_SearchIndex.Designer.cs
generated
Normal file
@ -0,0 +1,727 @@
|
|||||||
|
// <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("20210313001830_SearchIndex")]
|
||||||
|
partial class SearchIndex
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "5.0.1");
|
||||||
|
|
||||||
|
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>("HideReadOnDetails")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PageSplitOption")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ReadingDirection")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ScalingOption")
|
||||||
|
.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<int>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Number")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Range")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
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<int>("NumberOfPages")
|
||||||
|
.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")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
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<bool>("IsSpecial")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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("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.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("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("Volumes");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Chapters");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
API/Data/Migrations/20210313001830_SearchIndex.cs
Normal file
23
API/Data/Migrations/20210313001830_SearchIndex.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class SearchIndex : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Series_Name_NormalizedName_LocalizedName",
|
||||||
|
table: "Series",
|
||||||
|
columns: new[] { "Name", "NormalizedName", "LocalizedName" },
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Series_Name_NormalizedName_LocalizedName",
|
||||||
|
table: "Series");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -365,6 +365,9 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
b.HasIndex("LibraryId");
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
|
b.HasIndex("Name", "NormalizedName", "LocalizedName")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Series");
|
b.ToTable("Series");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -80,20 +80,16 @@ namespace API.Data
|
|||||||
|
|
||||||
public async Task<IEnumerable<SearchResultDto>> SearchSeries(int[] libraryIds, string searchQuery)
|
public async Task<IEnumerable<SearchResultDto>> SearchSeries(int[] libraryIds, string searchQuery)
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
return await _context.Series
|
||||||
var series = await _context.Series
|
|
||||||
.Where(s => libraryIds.Contains(s.LibraryId))
|
.Where(s => libraryIds.Contains(s.LibraryId))
|
||||||
.Where(s => EF.Functions.Like(s.Name, $"%{searchQuery}%")
|
.Where(s => EF.Functions.Like(s.Name, $"%{searchQuery}%")
|
||||||
|| EF.Functions.Like(s.OriginalName, $"%{searchQuery}%"))
|
|| EF.Functions.Like(s.OriginalName, $"%{searchQuery}%")
|
||||||
.Include(s => s.Library) // NOTE: Is there a way to do this faster?
|
|| EF.Functions.Like(s.LocalizedName, $"%{searchQuery}%"))
|
||||||
|
.Include(s => s.Library)
|
||||||
.OrderBy(s => s.SortName)
|
.OrderBy(s => s.SortName)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.ProjectTo<SearchResultDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<SearchResultDto>(_mapper.ConfigurationProvider)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
|
||||||
_logger.LogDebug("Processed SearchSeries in {ElapsedMilliseconds} milliseconds", sw.ElapsedMilliseconds);
|
|
||||||
return series;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId)
|
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId)
|
||||||
@ -109,7 +105,6 @@ namespace API.Data
|
|||||||
await AddVolumeModifiers(userId, volumes);
|
await AddVolumeModifiers(userId, volumes);
|
||||||
|
|
||||||
return volumes;
|
return volumes;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -143,6 +138,16 @@ namespace API.Data
|
|||||||
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId)
|
||||||
|
{
|
||||||
|
return await _context.Volume
|
||||||
|
.Where(vol => vol.Id == volumeId)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||||
|
.SingleAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId)
|
public async Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId)
|
||||||
{
|
{
|
||||||
var volume = await _context.Volume
|
var volume = await _context.Volume
|
||||||
@ -234,6 +239,25 @@ namespace API.Data
|
|||||||
s.UserReview = rating.Review;
|
s.UserReview = rating.Review;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GetVolumeCoverImageAsync(int volumeId)
|
||||||
|
{
|
||||||
|
return await _context.Volume
|
||||||
|
.Where(v => v.Id == volumeId)
|
||||||
|
.Select(v => v.CoverImage)
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> GetSeriesCoverImageAsync(int seriesId)
|
||||||
|
{
|
||||||
|
return await _context.Series
|
||||||
|
.Where(s => s.Id == seriesId)
|
||||||
|
.Select(s => s.CoverImage)
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddVolumeModifiers(int userId, List<VolumeDto> volumes)
|
private async Task AddVolumeModifiers(int userId, List<VolumeDto> volumes)
|
||||||
{
|
{
|
||||||
var userProgress = await _context.AppUserProgresses
|
var userProgress = await _context.AppUserProgresses
|
||||||
|
@ -50,7 +50,21 @@ namespace API.Data
|
|||||||
.Where(c => c.VolumeId == volumeId)
|
.Where(c => c.VolumeId == volumeId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the cover image for a chapter id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chapterId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<byte[]> GetChapterCoverImageAsync(int chapterId)
|
||||||
|
{
|
||||||
|
return await _context.Chapter
|
||||||
|
.Where(c => c.Id == chapterId)
|
||||||
|
.Select(c => c.CoverImage)
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using API.Entities.Interfaces;
|
using API.Entities.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace API.Entities
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
|
[Index(nameof(Name), nameof(NormalizedName), nameof(LocalizedName), IsUnique = true)]
|
||||||
public class Series : IEntityDate
|
public class Series : IEntityDate
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
@ -56,5 +56,9 @@ namespace API.Extensions
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
|
||||||
|
where T : class, IStartupTask
|
||||||
|
=> services.AddTransient<IStartupTask, T>();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using API.Helpers;
|
using API.Helpers;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
@ -18,6 +19,26 @@ namespace API.Extensions
|
|||||||
response.Headers.Add("Pagination", JsonSerializer.Serialize(paginationHeader, options));
|
response.Headers.Add("Pagination", JsonSerializer.Serialize(paginationHeader, options));
|
||||||
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
|
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates SHA1 hash for a byte[] and sets as ETag. Ensures Cache-Control: private header is added.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <param name="content">If byte[] is null or empty, will only add cache-control</param>
|
||||||
|
public static void AddCacheHeader(this HttpResponse response, byte[] content)
|
||||||
|
{
|
||||||
|
// Calculates SHA1 Hash for byte[]
|
||||||
|
if (content == null || content.Length <= 0) return;
|
||||||
|
using var sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider();
|
||||||
|
response.Headers.Add("ETag", string.Concat(sha1.ComputeHash(content).Select(x => x.ToString("X2"))));
|
||||||
|
|
||||||
|
// Not Needed with Response Caching
|
||||||
|
// if (!response.Headers.Keys.Contains("Cache-Control"))
|
||||||
|
// {
|
||||||
|
// response.Headers.Add("Cache-Control", "private");
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,6 +34,12 @@ namespace API.Interfaces
|
|||||||
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId, int userId);
|
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId, int userId);
|
||||||
Task<Volume> GetVolumeAsync(int volumeId);
|
Task<Volume> GetVolumeAsync(int volumeId);
|
||||||
Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId);
|
Task<VolumeDto> GetVolumeDtoAsync(int volumeId, int userId);
|
||||||
|
/// <summary>
|
||||||
|
/// A fast lookup of just the volume information with no tracking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volumeId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<VolumeDto> GetVolumeDtoAsync(int volumeId);
|
||||||
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(int[] seriesIds);
|
Task<IEnumerable<Volume>> GetVolumesForSeriesAsync(int[] seriesIds);
|
||||||
Task<bool> DeleteSeriesAsync(int seriesId);
|
Task<bool> DeleteSeriesAsync(int seriesId);
|
||||||
Task<Volume> GetVolumeByIdAsync(int volumeId);
|
Task<Volume> GetVolumeByIdAsync(int volumeId);
|
||||||
@ -46,5 +52,8 @@ namespace API.Interfaces
|
|||||||
/// <param name="series"></param>
|
/// <param name="series"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task AddSeriesModifiers(int userId, List<SeriesDto> series);
|
Task AddSeriesModifiers(int userId, List<SeriesDto> series);
|
||||||
|
|
||||||
|
Task<byte[]> GetVolumeCoverImageAsync(int volumeId);
|
||||||
|
Task<byte[]> GetSeriesCoverImageAsync(int seriesId);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,5 +12,6 @@ namespace API.Interfaces
|
|||||||
Task<ChapterDto> GetChapterDtoAsync(int chapterId);
|
Task<ChapterDto> GetChapterDtoAsync(int chapterId);
|
||||||
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
||||||
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
Task<IList<Chapter>> GetChaptersAsync(int volumeId);
|
||||||
|
Task<byte[]> GetChapterCoverImageAsync(int chapterId);
|
||||||
}
|
}
|
||||||
}
|
}
|
10
API/Interfaces/Services/IStartupTask.cs
Normal file
10
API/Interfaces/Services/IStartupTask.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace API.Interfaces.Services
|
||||||
|
{
|
||||||
|
public interface IStartupTask
|
||||||
|
{
|
||||||
|
Task ExecuteAsync(CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
}
|
@ -422,7 +422,7 @@ namespace API.Parser
|
|||||||
|
|
||||||
public static string Normalize(string name)
|
public static string Normalize(string name)
|
||||||
{
|
{
|
||||||
return name.ToLower().Replace("-", "").Replace(" ", "");
|
return name.ToLower().Replace("-", "").Replace(" ", "").Replace(":", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,13 +31,10 @@ namespace API.Services
|
|||||||
|
|
||||||
public void EnsureCacheDirectory()
|
public void EnsureCacheDirectory()
|
||||||
{
|
{
|
||||||
// TODO: Replace with DirectoryService.ExistOrCreate()
|
|
||||||
_logger.LogDebug("Checking if valid Cache directory: {CacheDirectory}", CacheDirectory);
|
_logger.LogDebug("Checking if valid Cache directory: {CacheDirectory}", CacheDirectory);
|
||||||
var di = new DirectoryInfo(CacheDirectory);
|
if (_directoryService.ExistOrCreate(CacheDirectory))
|
||||||
if (!di.Exists)
|
|
||||||
{
|
{
|
||||||
_logger.LogError("Cache directory {CacheDirectory} is not accessible or does not exist. Creating...", CacheDirectory);
|
_logger.LogError("Cache directory {CacheDirectory} is not accessible or does not exist. Creating...", CacheDirectory);
|
||||||
Directory.CreateDirectory(CacheDirectory);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,9 +39,9 @@ namespace API.Services
|
|||||||
_cleanupService = cleanupService;
|
_cleanupService = cleanupService;
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
|
|
||||||
|
//Hangfire.RecurringJob.RemoveIfExists();
|
||||||
ScheduleTasks();
|
ScheduleTasks();
|
||||||
//JobStorage.Current.GetMonitoringApi().
|
//JobStorage.Current.GetMonitoringApi().EnqueuedJobs()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ namespace API.Services.Tasks
|
|||||||
_scannedSeries = null;
|
_scannedSeries = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DisableConcurrentExecution(timeoutInSeconds: 360)]
|
//[DisableConcurrentExecution(timeoutInSeconds: 360)]
|
||||||
public void ScanLibrary(int libraryId, bool forceUpdate)
|
public void ScanLibrary(int libraryId, bool forceUpdate)
|
||||||
{
|
{
|
||||||
_forceUpdate = forceUpdate;
|
_forceUpdate = forceUpdate;
|
||||||
|
44
API/Services/WarmupServiceStartupTask.cs
Normal file
44
API/Services/WarmupServiceStartupTask.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Interfaces.Services;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace API.Services
|
||||||
|
{
|
||||||
|
public class WarmupServicesStartupTask : IStartupTask
|
||||||
|
{
|
||||||
|
private readonly IServiceCollection _services;
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
public WarmupServicesStartupTask(IServiceCollection services, IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_services = services;
|
||||||
|
_provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExecuteAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
using (var scope = _provider.CreateScope())
|
||||||
|
{
|
||||||
|
foreach (var singleton in GetServices(_services))
|
||||||
|
{
|
||||||
|
scope.ServiceProvider.GetServices(singleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<Type> GetServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
return services
|
||||||
|
.Where(descriptor => descriptor.ImplementationType != typeof(WarmupServicesStartupTask))
|
||||||
|
.Where(descriptor => !descriptor.ServiceType.ContainsGenericParameters)
|
||||||
|
.Select(descriptor => descriptor.ServiceType)
|
||||||
|
.Distinct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,16 +1,22 @@
|
|||||||
|
using System;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using API.Data;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Middleware;
|
using API.Middleware;
|
||||||
|
using API.Services;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.ResponseCompression;
|
using Microsoft.AspNetCore.ResponseCompression;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
namespace API
|
namespace API
|
||||||
@ -40,7 +46,6 @@ namespace API
|
|||||||
{
|
{
|
||||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
|
||||||
});
|
});
|
||||||
// This doesn't seem to work.
|
|
||||||
services.AddResponseCompression(options =>
|
services.AddResponseCompression(options =>
|
||||||
{
|
{
|
||||||
options.Providers.Add<BrotliCompressionProvider>();
|
options.Providers.Add<BrotliCompressionProvider>();
|
||||||
@ -55,6 +60,12 @@ namespace API
|
|||||||
options.Level = CompressionLevel.Fastest;
|
options.Level = CompressionLevel.Fastest;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddResponseCaching();
|
||||||
|
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddStartupTask<WarmupServicesStartupTask>()
|
||||||
|
.TryAddSingleton(services);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +92,7 @@ namespace API
|
|||||||
app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200"));
|
app.UseCors(policy => policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("http://localhost:4200"));
|
||||||
}
|
}
|
||||||
|
|
||||||
//app.UseResponseCaching();
|
app.UseResponseCaching();
|
||||||
|
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
|
|
||||||
@ -94,6 +105,19 @@ namespace API
|
|||||||
ContentTypeProvider = new FileExtensionContentTypeProvider()
|
ContentTypeProvider = new FileExtensionContentTypeProvider()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
context.Response.GetTypedHeaders().CacheControl =
|
||||||
|
new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
Public = false,
|
||||||
|
MaxAge = TimeSpan.FromSeconds(10)
|
||||||
|
};
|
||||||
|
context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] =
|
||||||
|
new string[] { "Accept-Encoding" };
|
||||||
|
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user