mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Merge pull request #28 from Kareadita/feature/read-progress
Read Progress
This commit is contained in:
commit
767f835e7b
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Constants;
|
using API.Constants;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
@ -17,20 +18,20 @@ namespace API.Controllers
|
|||||||
private readonly UserManager<AppUser> _userManager;
|
private readonly UserManager<AppUser> _userManager;
|
||||||
private readonly SignInManager<AppUser> _signInManager;
|
private readonly SignInManager<AppUser> _signInManager;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ILogger<AccountController> _logger;
|
private readonly ILogger<AccountController> _logger;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
public AccountController(UserManager<AppUser> userManager,
|
public AccountController(UserManager<AppUser> userManager,
|
||||||
SignInManager<AppUser> signInManager,
|
SignInManager<AppUser> signInManager,
|
||||||
ITokenService tokenService, IUserRepository userRepository,
|
ITokenService tokenService, IUnitOfWork unitOfWork,
|
||||||
ILogger<AccountController> logger,
|
ILogger<AccountController> logger,
|
||||||
IMapper mapper)
|
IMapper mapper)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
_userRepository = userRepository;
|
_unitOfWork = unitOfWork;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
@ -38,7 +39,7 @@ namespace API.Controllers
|
|||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
|
public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
|
||||||
{
|
{
|
||||||
if (await UserExists(registerDto.Username))
|
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDto.Username))
|
||||||
{
|
{
|
||||||
return BadRequest("Username is taken.");
|
return BadRequest("Username is taken.");
|
||||||
}
|
}
|
||||||
@ -54,6 +55,20 @@ namespace API.Controllers
|
|||||||
|
|
||||||
if (!roleResult.Succeeded) return BadRequest(result.Errors);
|
if (!roleResult.Succeeded) return BadRequest(result.Errors);
|
||||||
|
|
||||||
|
// When we register an admin, we need to grant them access to all Libraries.
|
||||||
|
if (registerDto.IsAdmin)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"{user.UserName} is being registered as admin. Granting access to all libraries.");
|
||||||
|
var libraries = await _unitOfWork.LibraryRepository.GetLibrariesAsync();
|
||||||
|
foreach (var lib in libraries)
|
||||||
|
{
|
||||||
|
lib.AppUsers ??= new List<AppUser>();
|
||||||
|
lib.AppUsers.Add(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await _unitOfWork.Complete()) _logger.LogInformation("There was an issue granting library access. Please do this manually.");
|
||||||
|
|
||||||
return new UserDto
|
return new UserDto
|
||||||
{
|
{
|
||||||
Username = user.UserName,
|
Username = user.UserName,
|
||||||
@ -76,8 +91,8 @@ namespace API.Controllers
|
|||||||
|
|
||||||
// Update LastActive on account
|
// Update LastActive on account
|
||||||
user.LastActive = DateTime.Now;
|
user.LastActive = DateTime.Now;
|
||||||
_userRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
await _userRepository.SaveAllAsync();
|
await _unitOfWork.Complete();
|
||||||
|
|
||||||
_logger.LogInformation($"{user.UserName} logged in at {user.LastActive}");
|
_logger.LogInformation($"{user.UserName} logged in at {user.LastActive}");
|
||||||
|
|
||||||
@ -87,10 +102,5 @@ namespace API.Controllers
|
|||||||
Token = await _tokenService.CreateToken(user)
|
Token = await _tokenService.CreateToken(user)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> UserExists(string username)
|
|
||||||
{
|
|
||||||
return await _userManager.Users.AnyAsync(user => user.UserName == username.ToLower());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -7,7 +8,6 @@ using API.Entities;
|
|||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Hangfire;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -18,26 +18,20 @@ namespace API.Controllers
|
|||||||
public class LibraryController : BaseApiController
|
public class LibraryController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
|
||||||
private readonly ILogger<LibraryController> _logger;
|
private readonly ILogger<LibraryController> _logger;
|
||||||
private readonly IUserRepository _userRepository;
|
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
private readonly ITaskScheduler _taskScheduler;
|
private readonly ITaskScheduler _taskScheduler;
|
||||||
private readonly ISeriesRepository _seriesRepository;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ICacheService _cacheService;
|
|
||||||
|
|
||||||
public LibraryController(IDirectoryService directoryService,
|
public LibraryController(IDirectoryService directoryService,
|
||||||
ILibraryRepository libraryRepository, ILogger<LibraryController> logger, IUserRepository userRepository,
|
ILogger<LibraryController> logger, IMapper mapper, ITaskScheduler taskScheduler,
|
||||||
IMapper mapper, ITaskScheduler taskScheduler, ISeriesRepository seriesRepository, ICacheService cacheService)
|
IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
_libraryRepository = libraryRepository;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_userRepository = userRepository;
|
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
_taskScheduler = taskScheduler;
|
_taskScheduler = taskScheduler;
|
||||||
_seriesRepository = seriesRepository;
|
_unitOfWork = unitOfWork;
|
||||||
_cacheService = cacheService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -49,38 +43,33 @@ namespace API.Controllers
|
|||||||
[HttpPost("create")]
|
[HttpPost("create")]
|
||||||
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
|
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
|
||||||
{
|
{
|
||||||
if (await _libraryRepository.LibraryExists(createLibraryDto.Name))
|
if (await _unitOfWork.LibraryRepository.LibraryExists(createLibraryDto.Name))
|
||||||
{
|
{
|
||||||
return BadRequest("Library name already exists. Please choose a unique name to the server.");
|
return BadRequest("Library name already exists. Please choose a unique name to the server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var admins = (await _userRepository.GetAdminUsersAsync()).ToList();
|
|
||||||
|
|
||||||
var library = new Library
|
var library = new Library
|
||||||
{
|
{
|
||||||
Name = createLibraryDto.Name,
|
Name = createLibraryDto.Name,
|
||||||
Type = createLibraryDto.Type,
|
Type = createLibraryDto.Type,
|
||||||
AppUsers = admins,
|
|
||||||
Folders = createLibraryDto.Folders.Select(x => new FolderPath {Path = x}).ToList()
|
Folders = createLibraryDto.Folders.Select(x => new FolderPath {Path = x}).ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_unitOfWork.LibraryRepository.Add(library);
|
||||||
|
|
||||||
|
var admins = (await _unitOfWork.UserRepository.GetAdminUsersAsync()).ToList();
|
||||||
foreach (var admin in admins)
|
foreach (var admin in admins)
|
||||||
{
|
{
|
||||||
// If user is null, then set it
|
|
||||||
admin.Libraries ??= new List<Library>();
|
admin.Libraries ??= new List<Library>();
|
||||||
admin.Libraries.Add(library);
|
admin.Libraries.Add(library);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (await _userRepository.SaveAllAsync())
|
if (!await _unitOfWork.Complete()) return BadRequest("There was a critical issue. Please try again.");
|
||||||
{
|
|
||||||
_logger.LogInformation($"Created a new library: {library.Name}");
|
|
||||||
var createdLibrary = await _libraryRepository.GetLibraryForNameAsync(library.Name);
|
|
||||||
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(createdLibrary.Id, false));
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
return BadRequest("There was a critical issue. Please try again.");
|
_logger.LogInformation($"Created a new library: {library.Name}");
|
||||||
|
_taskScheduler.ScanLibrary(library.Id);
|
||||||
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -105,30 +94,50 @@ namespace API.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibraries()
|
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibraries()
|
||||||
{
|
{
|
||||||
return Ok(await _libraryRepository.GetLibrariesAsync());
|
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtosAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
[HttpPut("update-for")]
|
[HttpPost("grant-access")]
|
||||||
public async Task<ActionResult<MemberDto>> AddLibraryToUser(UpdateLibraryForUserDto updateLibraryForUserDto)
|
public async Task<ActionResult<MemberDto>> UpdateUserLibraries(UpdateLibraryForUserDto updateLibraryForUserDto)
|
||||||
{
|
{
|
||||||
var user = await _userRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(updateLibraryForUserDto.Username);
|
||||||
|
|
||||||
if (user == null) return BadRequest("Could not validate user");
|
if (user == null) return BadRequest("Could not validate user");
|
||||||
|
|
||||||
user.Libraries = new List<Library>();
|
var libraryString = String.Join(",", updateLibraryForUserDto.SelectedLibraries.Select(x => x.Name));
|
||||||
|
_logger.LogInformation($"Granting user {updateLibraryForUserDto.Username} access to: {libraryString}");
|
||||||
|
|
||||||
foreach (var selectedLibrary in updateLibraryForUserDto.SelectedLibraries)
|
var allLibraries = await _unitOfWork.LibraryRepository.GetLibrariesAsync();
|
||||||
|
foreach (var library in allLibraries)
|
||||||
{
|
{
|
||||||
user.Libraries.Add(_mapper.Map<Library>(selectedLibrary));
|
library.AppUsers ??= new List<AppUser>();
|
||||||
|
var libraryContainsUser = library.AppUsers.Any(u => u.UserName == user.UserName);
|
||||||
|
var libraryIsSelected = updateLibraryForUserDto.SelectedLibraries.Any(l => l.Id == library.Id);
|
||||||
|
if (libraryContainsUser && !libraryIsSelected)
|
||||||
|
{
|
||||||
|
// Remove
|
||||||
|
library.AppUsers.Remove(user);
|
||||||
|
}
|
||||||
|
else if (!libraryContainsUser && libraryIsSelected)
|
||||||
|
{
|
||||||
|
library.AppUsers.Add(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await _userRepository.SaveAllAsync())
|
}
|
||||||
|
|
||||||
|
if (!_unitOfWork.HasChanges())
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Added: {updateLibraryForUserDto.SelectedLibraries} to {updateLibraryForUserDto.Username}");
|
_logger.LogInformation($"Added: {updateLibraryForUserDto.SelectedLibraries} to {updateLibraryForUserDto.Username}");
|
||||||
return Ok(user);
|
return Ok(_mapper.Map<MemberDto>(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await _unitOfWork.Complete())
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Added: {updateLibraryForUserDto.SelectedLibraries} to {updateLibraryForUserDto.Username}");
|
||||||
|
return Ok(_mapper.Map<MemberDto>(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return BadRequest("There was a critical issue. Please try again.");
|
return BadRequest("There was a critical issue. Please try again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,20 +145,21 @@ namespace API.Controllers
|
|||||||
[HttpPost("scan")]
|
[HttpPost("scan")]
|
||||||
public ActionResult Scan(int libraryId)
|
public ActionResult Scan(int libraryId)
|
||||||
{
|
{
|
||||||
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(libraryId, true));
|
_taskScheduler.ScanLibrary(libraryId, true);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("libraries-for")]
|
[HttpGet("libraries")]
|
||||||
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser(string username)
|
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser()
|
||||||
{
|
{
|
||||||
return Ok(await _libraryRepository.GetLibrariesDtoForUsernameAsync(username));
|
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtosForUsernameAsync(User.GetUsername()));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("series")]
|
[HttpGet("series")]
|
||||||
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId)
|
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId)
|
||||||
{
|
{
|
||||||
return Ok(await _seriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId));
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
@ -158,14 +168,14 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
var username = User.GetUsername();
|
var username = User.GetUsername();
|
||||||
_logger.LogInformation($"Library {libraryId} is being deleted by {username}.");
|
_logger.LogInformation($"Library {libraryId} is being deleted by {username}.");
|
||||||
var series = await _seriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId);
|
var series = await _unitOfWork.SeriesRepository.GetSeriesForLibraryIdAsync(libraryId);
|
||||||
var volumes = (await _seriesRepository.GetVolumesForSeriesAsync(series.Select(x => x.Id).ToArray()))
|
var volumes = (await _unitOfWork.SeriesRepository.GetVolumesForSeriesAsync(series.Select(x => x.Id).ToArray()))
|
||||||
.Select(x => x.Id).ToArray();
|
.Select(x => x.Id).ToArray();
|
||||||
var result = await _libraryRepository.DeleteLibrary(libraryId);
|
var result = await _unitOfWork.LibraryRepository.DeleteLibrary(libraryId);
|
||||||
|
|
||||||
if (result && volumes.Any())
|
if (result && volumes.Any())
|
||||||
{
|
{
|
||||||
BackgroundJob.Enqueue(() => _cacheService.CleanupVolumes(volumes));
|
_taskScheduler.CleanupVolumes(volumes);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
@ -175,7 +185,7 @@ namespace API.Controllers
|
|||||||
[HttpPost("update")]
|
[HttpPost("update")]
|
||||||
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto libraryForUserDto)
|
public async Task<ActionResult> UpdateLibrary(UpdateLibraryDto libraryForUserDto)
|
||||||
{
|
{
|
||||||
var library = await _libraryRepository.GetLibraryForIdAsync(libraryForUserDto.Id);
|
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryForUserDto.Id);
|
||||||
|
|
||||||
var originalFolders = library.Folders.Select(x => x.Path);
|
var originalFolders = library.Folders.Select(x => x.Path);
|
||||||
var differenceBetweenFolders = originalFolders.Except(libraryForUserDto.Folders);
|
var differenceBetweenFolders = originalFolders.Except(libraryForUserDto.Folders);
|
||||||
@ -183,21 +193,16 @@ namespace API.Controllers
|
|||||||
library.Name = libraryForUserDto.Name;
|
library.Name = libraryForUserDto.Name;
|
||||||
library.Folders = libraryForUserDto.Folders.Select(s => new FolderPath() {Path = s}).ToList();
|
library.Folders = libraryForUserDto.Folders.Select(s => new FolderPath() {Path = s}).ToList();
|
||||||
|
|
||||||
|
_unitOfWork.LibraryRepository.Update(library);
|
||||||
|
|
||||||
|
if (!await _unitOfWork.Complete()) return BadRequest("There was a critical issue updating the library.");
|
||||||
_libraryRepository.Update(library);
|
|
||||||
|
|
||||||
if (await _libraryRepository.SaveAllAsync())
|
|
||||||
{
|
|
||||||
if (differenceBetweenFolders.Any())
|
if (differenceBetweenFolders.Any())
|
||||||
{
|
{
|
||||||
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(library.Id, true));
|
_taskScheduler.ScanLibrary(library.Id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
|
||||||
|
|
||||||
return BadRequest("There was a critical issue updating the library.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,13 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Data;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
|
using API.Entities;
|
||||||
|
using API.Extensions;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace API.Controllers
|
||||||
{
|
{
|
||||||
@ -9,11 +15,16 @@ namespace API.Controllers
|
|||||||
{
|
{
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
private readonly ICacheService _cacheService;
|
private readonly ICacheService _cacheService;
|
||||||
|
private readonly ILogger<ReaderController> _logger;
|
||||||
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
|
|
||||||
public ReaderController(IDirectoryService directoryService, ICacheService cacheService)
|
public ReaderController(IDirectoryService directoryService, ICacheService cacheService,
|
||||||
|
ILogger<ReaderController> logger, IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
_cacheService = cacheService;
|
_cacheService = cacheService;
|
||||||
|
_logger = logger;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("image")]
|
[HttpGet("image")]
|
||||||
@ -28,5 +39,54 @@ namespace API.Controllers
|
|||||||
|
|
||||||
return Ok(file);
|
return Ok(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("get-bookmark")]
|
||||||
|
public async Task<ActionResult<int>> GetBookmark(int volumeId)
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
if (user.Progresses == null) return Ok(0);
|
||||||
|
var progress = user.Progresses.SingleOrDefault(x => x.AppUserId == user.Id && x.VolumeId == volumeId);
|
||||||
|
|
||||||
|
if (progress != null) return Ok(progress.PagesRead);
|
||||||
|
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("bookmark")]
|
||||||
|
public async Task<ActionResult> Bookmark(BookmarkDto bookmarkDto)
|
||||||
|
{
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
_logger.LogInformation($"Saving {user.UserName} progress for {bookmarkDto.VolumeId} to page {bookmarkDto.PageNum}");
|
||||||
|
|
||||||
|
user.Progresses ??= new List<AppUserProgress>();
|
||||||
|
var userProgress = user.Progresses.SingleOrDefault(x => x.VolumeId == bookmarkDto.VolumeId && x.AppUserId == user.Id);
|
||||||
|
|
||||||
|
if (userProgress == null)
|
||||||
|
{
|
||||||
|
|
||||||
|
user.Progresses.Add(new AppUserProgress
|
||||||
|
{
|
||||||
|
PagesRead = bookmarkDto.PageNum,
|
||||||
|
VolumeId = bookmarkDto.VolumeId,
|
||||||
|
SeriesId = bookmarkDto.SeriesId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
userProgress.PagesRead = bookmarkDto.PageNum;
|
||||||
|
userProgress.SeriesId = bookmarkDto.SeriesId;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_unitOfWork.UserRepository.Update(user);
|
||||||
|
|
||||||
|
if (await _unitOfWork.Complete())
|
||||||
|
{
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return BadRequest("Could not save progress");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,8 +4,6 @@ using System.Threading.Tasks;
|
|||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Extensions;
|
using API.Extensions;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using AutoMapper;
|
|
||||||
using Hangfire;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -15,26 +13,20 @@ namespace API.Controllers
|
|||||||
public class SeriesController : BaseApiController
|
public class SeriesController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly ILogger<SeriesController> _logger;
|
private readonly ILogger<SeriesController> _logger;
|
||||||
private readonly IMapper _mapper;
|
|
||||||
private readonly ITaskScheduler _taskScheduler;
|
private readonly ITaskScheduler _taskScheduler;
|
||||||
private readonly ISeriesRepository _seriesRepository;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ICacheService _cacheService;
|
|
||||||
|
|
||||||
public SeriesController(ILogger<SeriesController> logger, IMapper mapper,
|
public SeriesController(ILogger<SeriesController> logger, ITaskScheduler taskScheduler, IUnitOfWork unitOfWork)
|
||||||
ITaskScheduler taskScheduler, ISeriesRepository seriesRepository,
|
|
||||||
ICacheService cacheService)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mapper = mapper;
|
|
||||||
_taskScheduler = taskScheduler;
|
_taskScheduler = taskScheduler;
|
||||||
_seriesRepository = seriesRepository;
|
_unitOfWork = unitOfWork;
|
||||||
_cacheService = cacheService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{seriesId}")]
|
[HttpGet("{seriesId}")]
|
||||||
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
|
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
|
||||||
{
|
{
|
||||||
return Ok(await _seriesRepository.GetSeriesDtoByIdAsync(seriesId));
|
return Ok(await _unitOfWork.SeriesRepository.GetSeriesDtoByIdAsync(seriesId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
@ -42,13 +34,13 @@ namespace API.Controllers
|
|||||||
public async Task<ActionResult<bool>> DeleteSeries(int seriesId)
|
public async Task<ActionResult<bool>> DeleteSeries(int seriesId)
|
||||||
{
|
{
|
||||||
var username = User.GetUsername();
|
var username = User.GetUsername();
|
||||||
var volumes = (await _seriesRepository.GetVolumesForSeriesAsync(new []{seriesId})).Select(x => x.Id).ToArray();
|
var volumes = (await _unitOfWork.SeriesRepository.GetVolumesForSeriesAsync(new []{seriesId})).Select(x => x.Id).ToArray();
|
||||||
_logger.LogInformation($"Series {seriesId} is being deleted by {username}.");
|
_logger.LogInformation($"Series {seriesId} is being deleted by {username}.");
|
||||||
var result = await _seriesRepository.DeleteSeriesAsync(seriesId);
|
var result = await _unitOfWork.SeriesRepository.DeleteSeriesAsync(seriesId);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
BackgroundJob.Enqueue(() => _cacheService.CleanupVolumes(volumes));
|
_taskScheduler.CleanupVolumes(volumes);
|
||||||
}
|
}
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
@ -56,13 +48,14 @@ namespace API.Controllers
|
|||||||
[HttpGet("volumes")]
|
[HttpGet("volumes")]
|
||||||
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
|
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
|
||||||
{
|
{
|
||||||
return Ok(await _seriesRepository.GetVolumesDtoAsync(seriesId));
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
|
return Ok(await _unitOfWork.SeriesRepository.GetVolumesDtoAsync(seriesId, user.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("volume")]
|
[HttpGet("volume")]
|
||||||
public async Task<ActionResult<VolumeDto>> GetVolume(int volumeId)
|
public async Task<ActionResult<VolumeDto>> GetVolume(int volumeId)
|
||||||
{
|
{
|
||||||
return Ok(await _seriesRepository.GetVolumeDtoAsync(volumeId));
|
return Ok(await _unitOfWork.SeriesRepository.GetVolumeDtoAsync(volumeId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,26 +12,21 @@ namespace API.Controllers
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public class UsersController : BaseApiController
|
public class UsersController : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
|
||||||
|
|
||||||
public UsersController(IUserRepository userRepository, ILibraryRepository libraryRepository)
|
public UsersController(IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
_userRepository = userRepository;
|
_unitOfWork = unitOfWork;
|
||||||
_libraryRepository = libraryRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
[HttpDelete("delete-user")]
|
[HttpDelete("delete-user")]
|
||||||
public async Task<ActionResult> DeleteUser(string username)
|
public async Task<ActionResult> DeleteUser(string username)
|
||||||
{
|
{
|
||||||
var user = await _userRepository.GetUserByUsernameAsync(username);
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
|
||||||
_userRepository.Delete(user);
|
_unitOfWork.UserRepository.Delete(user);
|
||||||
|
|
||||||
if (await _userRepository.SaveAllAsync())
|
if (await _unitOfWork.Complete()) return Ok();
|
||||||
{
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
return BadRequest("Could not delete the user.");
|
return BadRequest("Could not delete the user.");
|
||||||
}
|
}
|
||||||
@ -40,18 +35,13 @@ namespace API.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<IEnumerable<MemberDto>>> GetUsers()
|
public async Task<ActionResult<IEnumerable<MemberDto>>> GetUsers()
|
||||||
{
|
{
|
||||||
return Ok(await _userRepository.GetMembersAsync());
|
return Ok(await _unitOfWork.UserRepository.GetMembersAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("has-library-access")]
|
[HttpGet("has-library-access")]
|
||||||
public async Task<ActionResult<bool>> HasLibraryAccess(int libraryId)
|
public async Task<ActionResult<bool>> HasLibraryAccess(int libraryId)
|
||||||
{
|
{
|
||||||
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
|
var libs = await _unitOfWork.LibraryRepository.GetLibraryDtosForUsernameAsync(User.GetUsername());
|
||||||
|
|
||||||
if (user == null) return BadRequest("Could not validate user");
|
|
||||||
|
|
||||||
var libs = await _libraryRepository.GetLibrariesDtoForUsernameAsync(user.UserName);
|
|
||||||
|
|
||||||
return Ok(libs.Any(x => x.Id == libraryId));
|
return Ok(libs.Any(x => x.Id == libraryId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,16 @@
|
|||||||
{
|
{
|
||||||
public class SeriesDto
|
public class SeriesDto
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; init; }
|
||||||
public string Name { get; set; }
|
public string Name { get; init; }
|
||||||
public string OriginalName { get; set; }
|
public string OriginalName { get; init; }
|
||||||
public string SortName { get; set; }
|
public string SortName { get; init; }
|
||||||
public string Summary { get; set; }
|
public string Summary { get; init; }
|
||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; init; }
|
||||||
|
public int Pages { get; init; }
|
||||||
|
/// <summary>
|
||||||
|
/// Sum of pages read from linked Volumes. Calculated at API-time.
|
||||||
|
/// </summary>
|
||||||
|
public int PagesRead { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,5 +8,6 @@ namespace API.DTOs
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; set; }
|
||||||
public int Pages { get; set; }
|
public int Pages { get; set; }
|
||||||
|
public int PagesRead { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
9
API/Data/BookmarkDto.cs
Normal file
9
API/Data/BookmarkDto.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Data
|
||||||
|
{
|
||||||
|
public class BookmarkDto
|
||||||
|
{
|
||||||
|
public int VolumeId { get; init; }
|
||||||
|
public int PageNum { get; init; }
|
||||||
|
public int SeriesId { get; init; }
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,8 @@ namespace API.Data
|
|||||||
public DbSet<Library> Library { get; set; }
|
public DbSet<Library> Library { get; set; }
|
||||||
public DbSet<Series> Series { get; set; }
|
public DbSet<Series> Series { get; set; }
|
||||||
public DbSet<Volume> Volume { get; set; }
|
public DbSet<Volume> Volume { get; set; }
|
||||||
|
public DbSet<AppUser> AppUser { get; set; }
|
||||||
|
public DbSet<AppUserProgress> AppUserProgresses { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
@ -21,28 +23,33 @@ namespace API.Data
|
|||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(Library library)
|
||||||
|
{
|
||||||
|
_context.Library.Add(library);
|
||||||
|
}
|
||||||
|
|
||||||
public void Update(Library library)
|
public void Update(Library library)
|
||||||
{
|
{
|
||||||
_context.Entry(library).State = EntityState.Modified;
|
_context.Entry(library).State = EntityState.Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SaveAllAsync()
|
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName)
|
||||||
{
|
{
|
||||||
return await _context.SaveChangesAsync() > 0;
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
}
|
var libs = await _context.Library
|
||||||
|
|
||||||
public bool SaveAll()
|
|
||||||
{
|
|
||||||
return _context.SaveChanges() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<LibraryDto>> GetLibrariesDtoForUsernameAsync(string userName)
|
|
||||||
{
|
|
||||||
// TODO: Speed this query up
|
|
||||||
return await _context.Library
|
|
||||||
.Include(l => l.AppUsers)
|
.Include(l => l.AppUsers)
|
||||||
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
|
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
|
||||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync();
|
||||||
|
Console.WriteLine("Processed GetLibraryDtosForUsernameAsync in {0} milliseconds", sw.ElapsedMilliseconds);
|
||||||
|
return libs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Library>> GetLibrariesAsync()
|
||||||
|
{
|
||||||
|
return await _context.Library
|
||||||
|
.Include(l => l.AppUsers)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +69,7 @@ namespace API.Data
|
|||||||
return await _context.SaveChangesAsync() > 0;
|
return await _context.SaveChangesAsync() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<LibraryDto>> GetLibrariesAsync()
|
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync()
|
||||||
{
|
{
|
||||||
return await _context.Library
|
return await _context.Library
|
||||||
.Include(f => f.Folders)
|
.Include(f => f.Folders)
|
||||||
@ -74,6 +81,7 @@ namespace API.Data
|
|||||||
return await _context.Library
|
return await _context.Library
|
||||||
.Where(x => x.Id == libraryId)
|
.Where(x => x.Id == libraryId)
|
||||||
.Include(f => f.Folders)
|
.Include(f => f.Folders)
|
||||||
|
.Include(l => l.Series)
|
||||||
.SingleAsync();
|
.SingleAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
576
API/Data/Migrations/20210114214506_UserProgress.Designer.cs
generated
Normal file
576
API/Data/Migrations/20210114214506_UserProgress.Designer.cs
generated
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
// <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("20210114214506_UserProgress")]
|
||||||
|
partial class UserProgress
|
||||||
|
{
|
||||||
|
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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PagesRead")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
b.ToTable("AppUserProgress");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.FolderPath", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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>("Chapter")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumberOfPages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
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>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("OriginalName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SortName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
|
b.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
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?>("ProgressId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ProgressId");
|
||||||
|
|
||||||
|
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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("AppUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.Volume", null)
|
||||||
|
.WithMany("Progresses")
|
||||||
|
.HasForeignKey("VolumeId");
|
||||||
|
|
||||||
|
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.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.Volume", "Volume")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("VolumeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Volume");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.AppUserProgress", "Progress")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ProgressId");
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.Series", "Series")
|
||||||
|
.WithMany("Volumes")
|
||||||
|
.HasForeignKey("SeriesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Progress");
|
||||||
|
|
||||||
|
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("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("Files");
|
||||||
|
|
||||||
|
b.Navigation("Progresses");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
API/Data/Migrations/20210114214506_UserProgress.cs
Normal file
84
API/Data/Migrations/20210114214506_UserProgress.cs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class UserProgress : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ProgressId",
|
||||||
|
table: "Volume",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AppUserProgress",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
PagesRead = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
AppUserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
VolumeId = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AppUserProgress", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AppUserProgress_AspNetUsers_AppUserId",
|
||||||
|
column: x => x.AppUserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AppUserProgress_Volume_VolumeId",
|
||||||
|
column: x => x.VolumeId,
|
||||||
|
principalTable: "Volume",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Volume_ProgressId",
|
||||||
|
table: "Volume",
|
||||||
|
column: "ProgressId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AppUserProgress_AppUserId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
column: "AppUserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AppUserProgress_VolumeId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
column: "VolumeId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Volume_AppUserProgress_ProgressId",
|
||||||
|
table: "Volume",
|
||||||
|
column: "ProgressId",
|
||||||
|
principalTable: "AppUserProgress",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Volume_AppUserProgress_ProgressId",
|
||||||
|
table: "Volume");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AppUserProgress");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Volume_ProgressId",
|
||||||
|
table: "Volume");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProgressId",
|
||||||
|
table: "Volume");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
562
API/Data/Migrations/20210117180406_ReadStatusModifications.Designer.cs
generated
Normal file
562
API/Data/Migrations/20210117180406_ReadStatusModifications.Designer.cs
generated
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
// <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("20210117180406_ReadStatusModifications")]
|
||||||
|
partial class ReadStatusModifications
|
||||||
|
{
|
||||||
|
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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.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.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.FolderPath", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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>("Chapter")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumberOfPages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
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>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("OriginalName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("SortName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Summary")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("LibraryId");
|
||||||
|
|
||||||
|
b.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany("Progresses")
|
||||||
|
.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.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.Volume", "Volume")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("VolumeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Volume");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
API/Data/Migrations/20210117180406_ReadStatusModifications.cs
Normal file
154
API/Data/Migrations/20210117180406_ReadStatusModifications.cs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class ReadStatusModifications : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_AppUserProgress_AspNetUsers_AppUserId",
|
||||||
|
table: "AppUserProgress");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_AppUserProgress_Volume_VolumeId",
|
||||||
|
table: "AppUserProgress");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Volume_AppUserProgress_ProgressId",
|
||||||
|
table: "Volume");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Volume_ProgressId",
|
||||||
|
table: "Volume");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_AppUserProgress",
|
||||||
|
table: "AppUserProgress");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_AppUserProgress_VolumeId",
|
||||||
|
table: "AppUserProgress");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ProgressId",
|
||||||
|
table: "Volume");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "AppUserProgress",
|
||||||
|
newName: "AppUserProgresses");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_AppUserProgress_AppUserId",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
newName: "IX_AppUserProgresses_AppUserId");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "VolumeId",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "INTEGER",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "SeriesId",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_AppUserProgresses",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
column: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_AppUserProgresses_AspNetUsers_AppUserId",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
column: "AppUserId",
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_AppUserProgresses_AspNetUsers_AppUserId",
|
||||||
|
table: "AppUserProgresses");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_AppUserProgresses",
|
||||||
|
table: "AppUserProgresses");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SeriesId",
|
||||||
|
table: "AppUserProgresses");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "AppUserProgresses",
|
||||||
|
newName: "AppUserProgress");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_AppUserProgresses_AppUserId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
newName: "IX_AppUserProgress_AppUserId");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ProgressId",
|
||||||
|
table: "Volume",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "VolumeId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "INTEGER");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_AppUserProgress",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
column: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Volume_ProgressId",
|
||||||
|
table: "Volume",
|
||||||
|
column: "ProgressId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AppUserProgress_VolumeId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
column: "VolumeId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_AppUserProgress_AspNetUsers_AppUserId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
column: "AppUserId",
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_AppUserProgress_Volume_VolumeId",
|
||||||
|
table: "AppUserProgress",
|
||||||
|
column: "VolumeId",
|
||||||
|
principalTable: "Volume",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Volume_AppUserProgress_ProgressId",
|
||||||
|
table: "Volume",
|
||||||
|
column: "ProgressId",
|
||||||
|
principalTable: "AppUserProgress",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
565
API/Data/Migrations/20210117181421_SeriesPages.Designer.cs
generated
Normal file
565
API/Data/Migrations/20210117181421_SeriesPages.Designer.cs
generated
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
// <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("20210117181421_SeriesPages")]
|
||||||
|
partial class SeriesPages
|
||||||
|
{
|
||||||
|
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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.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.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.FolderPath", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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>("Chapter")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumberOfPages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
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>("Name")
|
||||||
|
.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.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany("Progresses")
|
||||||
|
.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.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.Volume", "Volume")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("VolumeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Volume");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("Files");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
API/Data/Migrations/20210117181421_SeriesPages.cs
Normal file
24
API/Data/Migrations/20210117181421_SeriesPages.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class SeriesPages : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Pages",
|
||||||
|
table: "Series",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Pages",
|
||||||
|
table: "Series");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -118,6 +118,31 @@ namespace API.Data.Migrations
|
|||||||
b.ToTable("AspNetUsers");
|
b.ToTable("AspNetUsers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.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.AppUserRole", b =>
|
modelBuilder.Entity("API.Entities.AppUserRole", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("UserId")
|
b.Property<int>("UserId")
|
||||||
@ -230,6 +255,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<string>("OriginalName")
|
b.Property<string>("OriginalName")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("SortName")
|
b.Property<string>("SortName")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
@ -376,6 +404,17 @@ namespace API.Data.Migrations
|
|||||||
b.ToTable("AspNetUserTokens");
|
b.ToTable("AspNetUserTokens");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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.AppUserRole", b =>
|
modelBuilder.Entity("API.Entities.AppUserRole", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.AppRole", "Role")
|
b.HasOne("API.Entities.AppRole", "Role")
|
||||||
@ -497,6 +536,8 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("API.Entities.AppUser", b =>
|
modelBuilder.Entity("API.Entities.AppUser", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Progresses");
|
||||||
|
|
||||||
b.Navigation("UserRoles");
|
b.Navigation("UserRoles");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
@ -21,6 +23,11 @@ namespace API.Data
|
|||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Add(Series series)
|
||||||
|
{
|
||||||
|
_context.Series.Add(series);
|
||||||
|
}
|
||||||
|
|
||||||
public void Update(Series series)
|
public void Update(Series series)
|
||||||
{
|
{
|
||||||
_context.Entry(series).State = EntityState.Modified;
|
_context.Entry(series).State = EntityState.Modified;
|
||||||
@ -46,20 +53,59 @@ namespace API.Data
|
|||||||
return _context.Series.SingleOrDefault(x => x.Name == name);
|
return _context.Series.SingleOrDefault(x => x.Name == name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId)
|
public async Task<IEnumerable<Series>> GetSeriesForLibraryIdAsync(int libraryId)
|
||||||
{
|
{
|
||||||
return await _context.Series
|
return await _context.Series
|
||||||
.Where(series => series.LibraryId == libraryId)
|
.Where(s => s.LibraryId == libraryId)
|
||||||
.OrderBy(s => s.SortName)
|
.OrderBy(s => s.SortName)
|
||||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId)
|
public async Task<IEnumerable<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId)
|
||||||
{
|
{
|
||||||
return await _context.Volume
|
var sw = Stopwatch.StartNew();
|
||||||
|
var series = await _context.Series
|
||||||
|
.Where(s => s.LibraryId == libraryId)
|
||||||
|
.OrderBy(s => s.SortName)
|
||||||
|
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (userId > 0)
|
||||||
|
{
|
||||||
|
var userProgress = await _context.AppUserProgresses
|
||||||
|
.Where(p => p.AppUserId == userId && series.Select(s => s.Id).Contains(p.SeriesId))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var s in series)
|
||||||
|
{
|
||||||
|
s.PagesRead = userProgress.Where(p => p.SeriesId == s.Id).Sum(p => p.PagesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Processed GetSeriesDtoForLibraryIdAsync in {0} milliseconds", sw.ElapsedMilliseconds);
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId)
|
||||||
|
{
|
||||||
|
var volumes = await _context.Volume
|
||||||
.Where(vol => vol.SeriesId == seriesId)
|
.Where(vol => vol.SeriesId == seriesId)
|
||||||
.OrderBy(volume => volume.Number)
|
.OrderBy(volume => volume.Number)
|
||||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider).ToListAsync();
|
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync();
|
||||||
|
var userProgress = await _context.AppUserProgresses
|
||||||
|
.Where(p => p.AppUserId == userId && volumes.Select(s => s.Id).Contains(p.VolumeId))
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var v in volumes)
|
||||||
|
{
|
||||||
|
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumes;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Volume> GetVolumes(int seriesId)
|
public IEnumerable<Volume> GetVolumes(int seriesId)
|
||||||
@ -112,5 +158,10 @@ namespace API.Data
|
|||||||
|
|
||||||
return await _context.SaveChangesAsync() > 0;
|
return await _context.SaveChangesAsync() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Volume> GetVolumeByIdAsync(int volumeId)
|
||||||
|
{
|
||||||
|
return await _context.Volume.SingleOrDefaultAsync(x => x.Id == volumeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
36
API/Data/UnitOfWork.cs
Normal file
36
API/Data/UnitOfWork.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.Entities;
|
||||||
|
using API.Interfaces;
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace API.Data
|
||||||
|
{
|
||||||
|
public class UnitOfWork : IUnitOfWork
|
||||||
|
{
|
||||||
|
private readonly DataContext _context;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
private readonly UserManager<AppUser> _userManager;
|
||||||
|
|
||||||
|
public UnitOfWork(DataContext context, IMapper mapper, UserManager<AppUser> userManager)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_mapper = mapper;
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISeriesRepository SeriesRepository => new SeriesRepository(_context, _mapper);
|
||||||
|
public IUserRepository UserRepository => new UserRepository(_context, _userManager);
|
||||||
|
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
||||||
|
|
||||||
|
public async Task<bool> Complete()
|
||||||
|
{
|
||||||
|
return await _context.SaveChangesAsync() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasChanges()
|
||||||
|
{
|
||||||
|
return _context.ChangeTracker.HasChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,6 @@ using API.Constants;
|
|||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Entities;
|
using API.Entities;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
using AutoMapper;
|
|
||||||
using AutoMapper.QueryableExtensions;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
@ -15,13 +13,11 @@ namespace API.Data
|
|||||||
public class UserRepository : IUserRepository
|
public class UserRepository : IUserRepository
|
||||||
{
|
{
|
||||||
private readonly DataContext _context;
|
private readonly DataContext _context;
|
||||||
private readonly IMapper _mapper;
|
|
||||||
private readonly UserManager<AppUser> _userManager;
|
private readonly UserManager<AppUser> _userManager;
|
||||||
|
|
||||||
public UserRepository(DataContext context, IMapper mapper, UserManager<AppUser> userManager)
|
public UserRepository(DataContext context, UserManager<AppUser> userManager)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_mapper = mapper;
|
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,27 +28,18 @@ namespace API.Data
|
|||||||
|
|
||||||
public void Delete(AppUser user)
|
public void Delete(AppUser user)
|
||||||
{
|
{
|
||||||
_context.Users.Remove(user);
|
_context.AppUser.Remove(user);
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SaveAllAsync()
|
|
||||||
{
|
|
||||||
return await _context.SaveChangesAsync() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<AppUser>> GetUsersAsync()
|
|
||||||
{
|
|
||||||
return await _context.Users.ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AppUser> GetUserByIdAsync(int id)
|
|
||||||
{
|
|
||||||
return await _context.Users.FindAsync(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an AppUser by username. Returns back Progress information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="username"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public async Task<AppUser> GetUserByUsernameAsync(string username)
|
public async Task<AppUser> GetUserByUsernameAsync(string username)
|
||||||
{
|
{
|
||||||
return await _context.Users
|
return await _context.Users
|
||||||
|
.Include(u => u.Progresses)
|
||||||
.SingleOrDefaultAsync(x => x.UserName == username);
|
.SingleOrDefaultAsync(x => x.UserName == username);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +50,7 @@ namespace API.Data
|
|||||||
|
|
||||||
public async Task<IEnumerable<MemberDto>> GetMembersAsync()
|
public async Task<IEnumerable<MemberDto>> GetMembersAsync()
|
||||||
{
|
{
|
||||||
return await _userManager.Users
|
return await _context.Users
|
||||||
.Include(x => x.Libraries)
|
.Include(x => x.Libraries)
|
||||||
.Include(r => r.UserRoles)
|
.Include(r => r.UserRoles)
|
||||||
.ThenInclude(r => r.Role)
|
.ThenInclude(r => r.Role)
|
||||||
@ -83,16 +70,8 @@ namespace API.Data
|
|||||||
Folders = l.Folders.Select(x => x.Path).ToList()
|
Folders = l.Folders.Select(x => x.Path).ToList()
|
||||||
}).ToList()
|
}).ToList()
|
||||||
})
|
})
|
||||||
|
.AsNoTracking()
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MemberDto> GetMemberAsync(string username)
|
|
||||||
{
|
|
||||||
return await _context.Users.Where(x => x.UserName == username)
|
|
||||||
.Include(x => x.Libraries)
|
|
||||||
.ProjectTo<MemberDto>(_mapper.ConfigurationProvider)
|
|
||||||
.SingleOrDefaultAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,13 +12,12 @@ namespace API.Entities
|
|||||||
public DateTime Created { get; set; } = DateTime.Now;
|
public DateTime Created { get; set; } = DateTime.Now;
|
||||||
public DateTime LastActive { get; set; }
|
public DateTime LastActive { get; set; }
|
||||||
public ICollection<Library> Libraries { get; set; }
|
public ICollection<Library> Libraries { get; set; }
|
||||||
|
public ICollection<AppUserRole> UserRoles { get; set; }
|
||||||
|
public ICollection<AppUserProgress> Progresses { get; set; }
|
||||||
|
|
||||||
[ConcurrencyCheck]
|
[ConcurrencyCheck]
|
||||||
public uint RowVersion { get; set; }
|
public uint RowVersion { get; set; }
|
||||||
|
|
||||||
public ICollection<AppUserRole> UserRoles { get; set; }
|
|
||||||
|
|
||||||
//public ICollection<SeriesProgress> SeriesProgresses { get; set; }
|
|
||||||
|
|
||||||
public void OnSavingChanges()
|
public void OnSavingChanges()
|
||||||
{
|
{
|
||||||
RowVersion++;
|
RowVersion++;
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
namespace API.Entities
|
|
||||||
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the progress a single user has on a given Volume.
|
/// Represents the progress a single user has on a given Volume.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AppUserProgress
|
public class AppUserProgress
|
||||||
{
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public int PagesRead { get; set; }
|
||||||
|
public int VolumeId { get; set; }
|
||||||
|
public int SeriesId { get; set; }
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
public AppUser AppUser { get; set; }
|
||||||
|
public int AppUserId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,8 +9,6 @@ namespace API.Entities
|
|||||||
[Description("Comic")]
|
[Description("Comic")]
|
||||||
Comic = 1,
|
Comic = 1,
|
||||||
[Description("Book")]
|
[Description("Book")]
|
||||||
Book = 2,
|
Book = 2
|
||||||
[Description("Raw")]
|
|
||||||
Raw = 3
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,9 +4,12 @@ namespace API.Entities
|
|||||||
public class MangaFile
|
public class MangaFile
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Absolute path to the archive file
|
||||||
|
/// </summary>
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Do not expect this to be set. If this MangaFile represents a volume file, this will be null.
|
/// Used to track if multiple MangaFiles (archives) represent a single Volume. If only one volume file, this will be 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Chapter { get; set; }
|
public int Chapter { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -26,6 +26,12 @@ namespace API.Entities
|
|||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
public DateTime LastModified { get; set; }
|
public DateTime LastModified { get; set; }
|
||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Sum of all Volume pages
|
||||||
|
/// </summary>
|
||||||
|
public int Pages { get; set; }
|
||||||
|
|
||||||
|
// Relationships
|
||||||
public ICollection<Volume> Volumes { get; set; }
|
public ICollection<Volume> Volumes { get; set; }
|
||||||
public Library Library { get; set; }
|
public Library Library { get; set; }
|
||||||
public int LibraryId { get; set; }
|
public int LibraryId { get; set; }
|
||||||
|
@ -15,10 +15,9 @@ namespace API.Entities
|
|||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; set; }
|
||||||
public int Pages { get; set; }
|
public int Pages { get; set; }
|
||||||
|
|
||||||
// public string CachePath {get; set;} // Path where cache is located. Default null, resets to null on deletion.
|
|
||||||
//public ICollection<AppUserProgress> AppUserProgress { get; set; }
|
|
||||||
|
|
||||||
// Many-to-One relationships
|
|
||||||
|
// Relationships
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
{
|
{
|
||||||
public class ApiException
|
public class ApiException
|
||||||
{
|
{
|
||||||
public int Status { get; set; }
|
public int Status { get; init; }
|
||||||
public string Message { get; set; }
|
public string Message { get; init; }
|
||||||
public string Details { get; set; }
|
public string Details { get; init; }
|
||||||
|
|
||||||
public ApiException(int status, string message = null, string details = null)
|
public ApiException(int status, string message = null, string details = null)
|
||||||
{
|
{
|
||||||
|
@ -17,12 +17,11 @@ namespace API.Extensions
|
|||||||
{
|
{
|
||||||
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
|
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
|
||||||
services.AddScoped<ITaskScheduler, TaskScheduler>();
|
services.AddScoped<ITaskScheduler, TaskScheduler>();
|
||||||
services.AddScoped<IUserRepository, UserRepository>();
|
services.AddScoped<IDirectoryService, DirectoryService>();
|
||||||
services.AddScoped<ITokenService, TokenService>();
|
services.AddScoped<ITokenService, TokenService>();
|
||||||
services.AddScoped<ICacheService, CacheService>();
|
services.AddScoped<ICacheService, CacheService>();
|
||||||
services.AddScoped<ISeriesRepository, SeriesRepository>();
|
services.AddScoped<IUnitOfWork, UnitOfWork>();
|
||||||
services.AddScoped<IDirectoryService, DirectoryService>();
|
|
||||||
services.AddScoped<ILibraryRepository, LibraryRepository>();
|
|
||||||
|
|
||||||
|
|
||||||
services.AddDbContext<DataContext>(options =>
|
services.AddDbContext<DataContext>(options =>
|
||||||
|
@ -13,14 +13,6 @@ namespace API.Interfaces
|
|||||||
/// <returns>List of folder names</returns>
|
/// <returns>List of folder names</returns>
|
||||||
IEnumerable<string> ListDirectory(string rootPath);
|
IEnumerable<string> ListDirectory(string rootPath);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lists out top-level files for a given directory.
|
|
||||||
/// TODO: Implement ability to provide a filter for file types (done in another implementation on DirectoryService)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rootPath">Absolute path </param>
|
|
||||||
/// <returns>List of folder names</returns>
|
|
||||||
IList<string> ListFiles(string rootPath);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a library id, scans folders for said library. Parses files and generates DB updates. Will overwrite
|
/// Given a library id, scans folders for said library. Parses files and generates DB updates. Will overwrite
|
||||||
/// cover images if forceUpdate is true.
|
/// cover images if forceUpdate is true.
|
||||||
@ -29,6 +21,8 @@ namespace API.Interfaces
|
|||||||
/// <param name="forceUpdate">Force overwriting for cover images</param>
|
/// <param name="forceUpdate">Force overwriting for cover images</param>
|
||||||
void ScanLibrary(int libraryId, bool forceUpdate);
|
void ScanLibrary(int libraryId, bool forceUpdate);
|
||||||
|
|
||||||
|
void ScanLibraries();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the path a volume would be extracted to.
|
/// Returns the path a volume would be extracted to.
|
||||||
/// Deprecated.
|
/// Deprecated.
|
||||||
|
@ -7,15 +7,14 @@ namespace API.Interfaces
|
|||||||
{
|
{
|
||||||
public interface ILibraryRepository
|
public interface ILibraryRepository
|
||||||
{
|
{
|
||||||
|
void Add(Library library);
|
||||||
void Update(Library library);
|
void Update(Library library);
|
||||||
Task<bool> SaveAllAsync();
|
Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync();
|
||||||
Task<IEnumerable<LibraryDto>> GetLibrariesAsync();
|
|
||||||
Task<bool> LibraryExists(string libraryName);
|
Task<bool> LibraryExists(string libraryName);
|
||||||
Task<Library> GetLibraryForIdAsync(int libraryId);
|
Task<Library> GetLibraryForIdAsync(int libraryId);
|
||||||
bool SaveAll();
|
Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName);
|
||||||
Task<IEnumerable<LibraryDto>> GetLibrariesDtoForUsernameAsync(string userName);
|
Task<IEnumerable<Library>> GetLibrariesAsync();
|
||||||
Task<Library> GetLibraryForNameAsync(string libraryName);
|
Task<Library> GetLibraryForNameAsync(string libraryName);
|
||||||
|
|
||||||
Task<bool> DeleteLibrary(int libraryId);
|
Task<bool> DeleteLibrary(int libraryId);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,13 +7,13 @@ namespace API.Interfaces
|
|||||||
{
|
{
|
||||||
public interface ISeriesRepository
|
public interface ISeriesRepository
|
||||||
{
|
{
|
||||||
|
void Add(Series series);
|
||||||
void Update(Series series);
|
void Update(Series series);
|
||||||
Task<bool> SaveAllAsync();
|
|
||||||
Task<Series> GetSeriesByNameAsync(string name);
|
Task<Series> GetSeriesByNameAsync(string name);
|
||||||
Series GetSeriesByName(string name);
|
Series GetSeriesByName(string name);
|
||||||
bool SaveAll();
|
Task<IEnumerable<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId, int userId);
|
||||||
Task<IEnumerable<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId);
|
Task<IEnumerable<Series>> GetSeriesForLibraryIdAsync(int libraryId);
|
||||||
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId);
|
Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId, int userId);
|
||||||
IEnumerable<Volume> GetVolumes(int seriesId);
|
IEnumerable<Volume> GetVolumes(int seriesId);
|
||||||
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId);
|
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId);
|
||||||
|
|
||||||
@ -22,5 +22,7 @@ namespace API.Interfaces
|
|||||||
|
|
||||||
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,6 +2,8 @@
|
|||||||
{
|
{
|
||||||
public interface ITaskScheduler
|
public interface ITaskScheduler
|
||||||
{
|
{
|
||||||
|
public void ScanLibrary(int libraryId, bool forceUpdate = false);
|
||||||
|
|
||||||
|
public void CleanupVolumes(int[] volumeIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
13
API/Interfaces/IUnitOfWork.cs
Normal file
13
API/Interfaces/IUnitOfWork.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace API.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUnitOfWork
|
||||||
|
{
|
||||||
|
ISeriesRepository SeriesRepository { get; }
|
||||||
|
IUserRepository UserRepository { get; }
|
||||||
|
ILibraryRepository LibraryRepository { get; }
|
||||||
|
Task<bool> Complete();
|
||||||
|
bool HasChanges();
|
||||||
|
}
|
||||||
|
}
|
@ -8,13 +8,9 @@ namespace API.Interfaces
|
|||||||
public interface IUserRepository
|
public interface IUserRepository
|
||||||
{
|
{
|
||||||
void Update(AppUser user);
|
void Update(AppUser user);
|
||||||
Task<bool> SaveAllAsync();
|
public void Delete(AppUser user);
|
||||||
Task<IEnumerable<AppUser>> GetUsersAsync();
|
|
||||||
Task<AppUser> GetUserByIdAsync(int id);
|
|
||||||
Task<AppUser> GetUserByUsernameAsync(string username);
|
Task<AppUser> GetUserByUsernameAsync(string username);
|
||||||
Task<IEnumerable<MemberDto>> GetMembersAsync();
|
Task<IEnumerable<MemberDto>> GetMembersAsync();
|
||||||
Task<MemberDto> GetMemberAsync(string username);
|
|
||||||
public void Delete(AppUser user);
|
|
||||||
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
|
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,16 +13,16 @@ namespace API.Services
|
|||||||
public class CacheService : ICacheService
|
public class CacheService : ICacheService
|
||||||
{
|
{
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
private readonly ISeriesRepository _seriesRepository;
|
|
||||||
private readonly ILogger<CacheService> _logger;
|
private readonly ILogger<CacheService> _logger;
|
||||||
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly NumericComparer _numericComparer;
|
private readonly NumericComparer _numericComparer;
|
||||||
private readonly string _cacheDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../cache/"));
|
private readonly string _cacheDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../cache/"));
|
||||||
|
|
||||||
public CacheService(IDirectoryService directoryService, ISeriesRepository seriesRepository, ILogger<CacheService> logger)
|
public CacheService(IDirectoryService directoryService, ILogger<CacheService> logger, IUnitOfWork unitOfWork)
|
||||||
{
|
{
|
||||||
_directoryService = directoryService;
|
_directoryService = directoryService;
|
||||||
_seriesRepository = seriesRepository;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_unitOfWork = unitOfWork;
|
||||||
_numericComparer = new NumericComparer();
|
_numericComparer = new NumericComparer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Volume volume = await _seriesRepository.GetVolumeAsync(volumeId);
|
Volume volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId);
|
||||||
foreach (var file in volume.Files)
|
foreach (var file in volume.Files)
|
||||||
{
|
{
|
||||||
var extractPath = GetVolumeCachePath(volumeId, file);
|
var extractPath = GetVolumeCachePath(volumeId, file);
|
||||||
@ -109,12 +109,10 @@ namespace API.Services
|
|||||||
if (page + 1 < (mangaFile.NumberOfPages + pagesSoFar))
|
if (page + 1 < (mangaFile.NumberOfPages + pagesSoFar))
|
||||||
{
|
{
|
||||||
var path = GetVolumeCachePath(volume.Id, mangaFile);
|
var path = GetVolumeCachePath(volume.Id, mangaFile);
|
||||||
|
var files = DirectoryService.GetFiles(path);
|
||||||
|
Array.Sort(files, _numericComparer);
|
||||||
|
|
||||||
var files = _directoryService.ListFiles(path);
|
return files.ElementAt(page - pagesSoFar);
|
||||||
var array = files.ToArray();
|
|
||||||
Array.Sort(array, _numericComparer); // TODO: Find a way to apply numericComparer to IList.
|
|
||||||
|
|
||||||
return array.ElementAt((page + 1) - pagesSoFar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pagesSoFar += mangaFile.NumberOfPages;
|
pagesSoFar += mangaFile.NumberOfPages;
|
||||||
|
@ -23,37 +23,40 @@ namespace API.Services
|
|||||||
public class DirectoryService : IDirectoryService
|
public class DirectoryService : IDirectoryService
|
||||||
{
|
{
|
||||||
private readonly ILogger<DirectoryService> _logger;
|
private readonly ILogger<DirectoryService> _logger;
|
||||||
private readonly ISeriesRepository _seriesRepository;
|
private readonly IUnitOfWork _unitOfWork;
|
||||||
private readonly ILibraryRepository _libraryRepository;
|
|
||||||
|
|
||||||
private ConcurrentDictionary<string, ConcurrentBag<ParserInfo>> _scannedSeries;
|
private ConcurrentDictionary<string, ConcurrentBag<ParserInfo>> _scannedSeries;
|
||||||
|
|
||||||
public DirectoryService(ILogger<DirectoryService> logger,
|
public DirectoryService(ILogger<DirectoryService> logger, IUnitOfWork unitOfWork)
|
||||||
ISeriesRepository seriesRepository,
|
|
||||||
ILibraryRepository libraryRepository)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_seriesRepository = seriesRepository;
|
_unitOfWork = unitOfWork;
|
||||||
_libraryRepository = libraryRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a set of regex search criteria, get files in the given path.
|
/// Given a set of regex search criteria, get files in the given path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Directory to search</param>
|
/// <param name="path">Directory to search</param>
|
||||||
/// <param name="searchPatternExpression">Regex version of search pattern (ie \.mp3|\.mp4)</param>
|
/// <param name="searchPatternExpression">Regex version of search pattern (ie \.mp3|\.mp4). Defaults to * meaning all files.</param>
|
||||||
/// <param name="searchOption">SearchOption to use, defaults to TopDirectoryOnly</param>
|
/// <param name="searchOption">SearchOption to use, defaults to TopDirectoryOnly</param>
|
||||||
/// <returns>List of file paths</returns>
|
/// <returns>List of file paths</returns>
|
||||||
private static IEnumerable<string> GetFiles(string path,
|
private static IEnumerable<string> GetFilesWithCertainExtensions(string path,
|
||||||
string searchPatternExpression = "",
|
string searchPatternExpression = "",
|
||||||
SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||||
{
|
{
|
||||||
|
if (!Directory.Exists(path)) return ImmutableList<string>.Empty;
|
||||||
var reSearchPattern = new Regex(searchPatternExpression, RegexOptions.IgnoreCase);
|
var reSearchPattern = new Regex(searchPatternExpression, RegexOptions.IgnoreCase);
|
||||||
return Directory.EnumerateFiles(path, "*", searchOption)
|
return Directory.EnumerateFiles(path, "*", searchOption)
|
||||||
.Where(file =>
|
.Where(file =>
|
||||||
reSearchPattern.IsMatch(Path.GetExtension(file)));
|
reSearchPattern.IsMatch(Path.GetExtension(file)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string[] GetFiles(string path)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(path)) return Array.Empty<string>();
|
||||||
|
return Directory.GetFiles(path);
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<string> ListDirectory(string rootPath)
|
public IEnumerable<string> ListDirectory(string rootPath)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty;
|
if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty;
|
||||||
@ -67,13 +70,6 @@ namespace API.Services
|
|||||||
return dirs;
|
return dirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<string> ListFiles(string rootPath)
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(rootPath)) return ImmutableList<string>.Empty;
|
|
||||||
return Directory.GetFiles(rootPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes files found during a library scan. Generates a collection of series->volume->files for DB processing later.
|
/// Processes files found during a library scan. Generates a collection of series->volume->files for DB processing later.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -112,19 +108,21 @@ namespace API.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Series UpdateSeries(string seriesName, ParserInfo[] infos, bool forceUpdate)
|
private Series UpdateSeries(Series series, ParserInfo[] infos, bool forceUpdate)
|
||||||
{
|
{
|
||||||
var series = _seriesRepository.GetSeriesByName(seriesName) ?? new Series
|
|
||||||
{
|
|
||||||
Name = seriesName,
|
|
||||||
OriginalName = seriesName,
|
|
||||||
SortName = seriesName,
|
|
||||||
Summary = "" // TODO: Check if comicInfo.xml in file and parse metadata out.
|
|
||||||
};
|
|
||||||
|
|
||||||
var volumes = UpdateVolumes(series, infos, forceUpdate);
|
var volumes = UpdateVolumes(series, infos, forceUpdate);
|
||||||
series.Volumes = volumes;
|
series.Volumes = volumes;
|
||||||
|
series.Pages = volumes.Sum(v => v.Pages);
|
||||||
|
if (series.CoverImage == null || forceUpdate)
|
||||||
|
{
|
||||||
series.CoverImage = volumes.OrderBy(x => x.Number).FirstOrDefault()?.CoverImage;
|
series.CoverImage = volumes.OrderBy(x => x.Number).FirstOrDefault()?.CoverImage;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(series.Summary) || forceUpdate)
|
||||||
|
{
|
||||||
|
series.Summary = ""; // TODO: Check if comicInfo.xml in file and parse metadata out.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +150,7 @@ namespace API.Services
|
|||||||
private ICollection<Volume> UpdateVolumes(Series series, ParserInfo[] infos, bool forceUpdate)
|
private ICollection<Volume> UpdateVolumes(Series series, ParserInfo[] infos, bool forceUpdate)
|
||||||
{
|
{
|
||||||
ICollection<Volume> volumes = new List<Volume>();
|
ICollection<Volume> volumes = new List<Volume>();
|
||||||
IList<Volume> existingVolumes = _seriesRepository.GetVolumes(series.Id).ToList();
|
IList<Volume> existingVolumes = _unitOfWork.SeriesRepository.GetVolumes(series.Id).ToList();
|
||||||
|
|
||||||
foreach (var info in infos)
|
foreach (var info in infos)
|
||||||
{
|
{
|
||||||
@ -212,13 +210,22 @@ namespace API.Services
|
|||||||
return volumes;
|
return volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ScanLibraries()
|
||||||
|
{
|
||||||
|
var libraries = Task.Run(() => _unitOfWork.LibraryRepository.GetLibrariesAsync()).Result.ToList();
|
||||||
|
foreach (var lib in libraries)
|
||||||
|
{
|
||||||
|
ScanLibrary(lib.Id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ScanLibrary(int libraryId, bool forceUpdate)
|
public void ScanLibrary(int libraryId, bool forceUpdate)
|
||||||
{
|
{
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
Library library;
|
Library library;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
library = Task.Run(() => _libraryRepository.GetLibraryForIdAsync(libraryId)).Result;
|
library = Task.Run(() => _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId)).Result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -255,19 +262,35 @@ namespace API.Services
|
|||||||
var series = filtered.ToImmutableDictionary(v => v.Key, v => v.Value);
|
var series = filtered.ToImmutableDictionary(v => v.Key, v => v.Value);
|
||||||
|
|
||||||
// Perform DB activities
|
// Perform DB activities
|
||||||
library.Series = new List<Series>(); // Temp delete everything until we can mark items Unavailable
|
var allSeries = Task.Run(() => _unitOfWork.SeriesRepository.GetSeriesForLibraryIdAsync(libraryId)).Result.ToList();
|
||||||
foreach (var seriesKey in series.Keys)
|
foreach (var seriesKey in series.Keys)
|
||||||
{
|
{
|
||||||
var mangaSeries = UpdateSeries(seriesKey, series[seriesKey].ToArray(), forceUpdate);
|
var mangaSeries = allSeries.SingleOrDefault(s => s.Name == seriesKey) ?? new Series
|
||||||
_logger.LogInformation($"Created/Updated series {mangaSeries.Name}");
|
{
|
||||||
|
Name = seriesKey,
|
||||||
|
OriginalName = seriesKey,
|
||||||
|
SortName = seriesKey,
|
||||||
|
Summary = ""
|
||||||
|
};
|
||||||
|
mangaSeries = UpdateSeries(mangaSeries, series[seriesKey].ToArray(), forceUpdate);
|
||||||
|
_logger.LogInformation($"Created/Updated series {mangaSeries.Name} for {library.Name} library");
|
||||||
|
library.Series ??= new List<Series>();
|
||||||
library.Series.Add(mangaSeries);
|
library.Series.Add(mangaSeries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove series that are no longer on disk
|
||||||
|
foreach (var existingSeries in allSeries)
|
||||||
|
{
|
||||||
|
if (!series.ContainsKey(existingSeries.Name) || !series.ContainsKey(existingSeries.OriginalName))
|
||||||
|
{
|
||||||
|
// Delete series, there is no file to backup any longer.
|
||||||
|
library.Series.Remove(existingSeries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_unitOfWork.LibraryRepository.Update(library);
|
||||||
|
|
||||||
_libraryRepository.Update(library);
|
if (Task.Run(() => _unitOfWork.Complete()).Result)
|
||||||
|
|
||||||
if (_libraryRepository.SaveAll())
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series.");
|
_logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series.");
|
||||||
}
|
}
|
||||||
@ -285,46 +308,6 @@ namespace API.Services
|
|||||||
return Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/");
|
return Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// TODO: Delete this method
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="archivePath"></param>
|
|
||||||
/// <param name="volumeId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private string ExtractArchive(string archivePath, int volumeId)
|
|
||||||
{
|
|
||||||
if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath))
|
|
||||||
{
|
|
||||||
_logger.LogError($"Archive {archivePath} could not be found.");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var extractPath = GetExtractPath(volumeId);
|
|
||||||
|
|
||||||
if (Directory.Exists(extractPath))
|
|
||||||
{
|
|
||||||
_logger.LogInformation($"Archive {archivePath} has already been extracted. Returning existing folder.");
|
|
||||||
return extractPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
using ZipArchive archive = ZipFile.OpenRead(archivePath);
|
|
||||||
|
|
||||||
// TODO: Throw error if we couldn't extract
|
|
||||||
var needsFlattening = archive.Entries.Count > 0 && !Path.HasExtension(archive.Entries.ElementAt(0).FullName);
|
|
||||||
if (!archive.HasFiles() && !needsFlattening) return "";
|
|
||||||
|
|
||||||
archive.ExtractToDirectory(extractPath);
|
|
||||||
_logger.LogInformation($"Extracting archive to {extractPath}");
|
|
||||||
|
|
||||||
if (needsFlattening)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Extracted archive is nested in root folder, flattening...");
|
|
||||||
new DirectoryInfo(extractPath).Flatten();
|
|
||||||
}
|
|
||||||
|
|
||||||
return extractPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ExtractArchive(string archivePath, string extractPath)
|
public string ExtractArchive(string archivePath, string extractPath)
|
||||||
{
|
{
|
||||||
if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath))
|
if (!File.Exists(archivePath) || !Parser.Parser.IsArchive(archivePath))
|
||||||
@ -426,7 +409,7 @@ namespace API.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
files = DirectoryService.GetFiles(currentDir, Parser.Parser.MangaFileExtensions)
|
files = DirectoryService.GetFilesWithCertainExtensions(currentDir, Parser.Parser.MangaFileExtensions)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException e) {
|
catch (UnauthorizedAccessException e) {
|
||||||
|
@ -6,18 +6,33 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
public class TaskScheduler : ITaskScheduler
|
public class TaskScheduler : ITaskScheduler
|
||||||
{
|
{
|
||||||
|
private readonly ICacheService _cacheService;
|
||||||
private readonly ILogger<TaskScheduler> _logger;
|
private readonly ILogger<TaskScheduler> _logger;
|
||||||
private readonly BackgroundJobServer _client;
|
private readonly IDirectoryService _directoryService;
|
||||||
|
public BackgroundJobServer Client => new BackgroundJobServer();
|
||||||
|
|
||||||
public TaskScheduler(ICacheService cacheService, ILogger<TaskScheduler> logger)
|
public TaskScheduler(ICacheService cacheService, ILogger<TaskScheduler> logger,
|
||||||
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
|
_cacheService = cacheService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_client = new BackgroundJobServer();
|
_directoryService = directoryService;
|
||||||
|
|
||||||
_logger.LogInformation("Scheduling/Updating cache cleanup on a daily basis.");
|
_logger.LogInformation("Scheduling/Updating cache cleanup on a daily basis.");
|
||||||
RecurringJob.AddOrUpdate(() => cacheService.Cleanup(), Cron.Daily);
|
RecurringJob.AddOrUpdate(() => _cacheService.Cleanup(), Cron.Daily);
|
||||||
|
RecurringJob.AddOrUpdate(() => directoryService.ScanLibraries(), Cron.Daily);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ScanLibrary(int libraryId, bool forceUpdate = false)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Enqueuing library scan for: {libraryId}");
|
||||||
|
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(libraryId, forceUpdate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanupVolumes(int[] volumeIds)
|
||||||
|
{
|
||||||
|
BackgroundJob.Enqueue(() => _cacheService.CleanupVolumes(volumeIds));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user