using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using API.Constants; using API.Data; using API.DTOs; using API.DTOs.Account; using API.DTOs.Filtering; using API.DTOs.Metadata; using API.DTOs.Progress; using API.DTOs.Statistics; using API.DTOs.Uploads; using API.Extensions; using API.Helpers; using API.Services; using AutoMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace API.Controllers; /// /// All APIs here are subject to be removed and are no longer maintained. Will be removed v0.9.0 /// [Route("api/")] public class DeprecatedController : BaseApiController { private readonly IUnitOfWork _unitOfWork; private readonly ILocalizationService _localizationService; private readonly ITaskScheduler _taskScheduler; private readonly ILogger _logger; private readonly IStatisticService _statService; private readonly IMapper _mapper; public DeprecatedController(IUnitOfWork unitOfWork, ILocalizationService localizationService, ITaskScheduler taskScheduler, ILogger logger, IStatisticService statService, IMapper mapper) { _unitOfWork = unitOfWork; _localizationService = localizationService; _taskScheduler = taskScheduler; _logger = logger; _statService = statService; _mapper = mapper; } /// /// Return all Series that are in the current logged-in user's Want to Read list, filtered (deprecated, use v2) /// /// This will be removed in v0.9.0 /// /// /// [HttpPost("want-to-read")] [Obsolete("use v2 instead. This will be removed in v0.9.0")] public async Task>> GetWantToRead([FromQuery] UserParams? userParams, FilterDto filterDto) { userParams ??= new UserParams(); var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(UserId, userParams, filterDto); Response.AddPaginationHeader(pagedList.CurrentPage, pagedList.PageSize, pagedList.TotalCount, pagedList.TotalPages); return Ok(pagedList); } /// /// All chapter entities will load this data by default. Will not be maintained as of v0.8.1 /// /// /// [Obsolete("All chapter entities will load this data by default. Will be removed in v0.9.0")] [HttpGet("series/chapter-metadata")] public async Task> GetChapterMetadata(int chapterId) { return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId)); } /// /// Gets series with the applied Filter /// /// This is considered v1 and no longer used by Kavita, but will be supported for sometime. See series/v2 /// /// /// /// [HttpPost("series")] [Obsolete("use v2. Will be removed in v0.9.0")] public async Task>> GetSeriesForLibrary(int libraryId, [FromQuery] UserParams userParams, [FromBody] FilterDto filterDto) { var userId = UserId; var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Gets all recently added series. Obsolete, use recently-added-v2 /// /// /// /// /// [ResponseCache(CacheProfileName = "Instant")] [HttpPost("series/recently-added")] [Obsolete("use recently-added-v2. Will be removed in v0.9.0")] public async Task>> GetRecentlyAdded(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) { var userId = UserId; var series = await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, userId, userParams, filterDto); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Returns all series for the library. Obsolete, use all-v2 /// /// /// /// /// [HttpPost("series/all")] [Obsolete("Use all-v2. Will be removed in v0.9.0")] public async Task>> GetAllSeries(FilterDto filterDto, [FromQuery] UserParams userParams, [FromQuery] int libraryId = 0) { var userId = UserId; var series = await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, userId, userParams, filterDto); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Replaces chapter cover image and locks it with a base64 encoded image. This will update the parent volume's cover image. /// /// Does not use Url property /// [Authorize(Policy = PolicyGroups.AdminPolicy)] [HttpPost("upload/reset-chapter-lock")] [Obsolete("Use LockCover in UploadFileDto, will be removed in v0.9.0")] public async Task ResetChapterLock(UploadFileDto uploadFileDto) { try { var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(uploadFileDto.Id); if (chapter == null) return BadRequest(await _localizationService.Translate(UserId, "chapter-doesnt-exist")); var originalFile = chapter.CoverImage; chapter.CoverImage = string.Empty; chapter.CoverImageLocked = false; _unitOfWork.ChapterRepository.Update(chapter); var volume = (await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(chapter.VolumeId))!; volume.CoverImage = chapter.CoverImage; _unitOfWork.VolumeRepository.Update(volume); var series = (await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(volume.SeriesId))!; if (_unitOfWork.HasChanges()) { await _unitOfWork.CommitAsync(); if (originalFile != null) System.IO.File.Delete(originalFile); await _taskScheduler.RefreshSeriesMetadata(series.LibraryId, series.Id, true); return Ok(); } } catch (Exception e) { _logger.LogError(e, "There was an issue resetting cover lock for Chapter {Id}", uploadFileDto.Id); await _unitOfWork.RollbackAsync(); } return BadRequest(await _localizationService.Translate(UserId, "reset-chapter-lock")); } [HttpGet("stats/user/reading-history")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetReadingHistory(int userId) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(Username!); var isAdmin = User.IsInRole(PolicyConstants.AdminRole); if (!isAdmin && userId != user!.Id) return BadRequest(); return Ok(await _statService.GetReadingHistory(userId)); } [Authorize(PolicyGroups.AdminPolicy)] [HttpGet("stats/server/top/years")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)] [Obsolete("Will be removed in v0.9.0")] public async Task>>> GetTopYears() { return Ok(await _statService.GetTopYears()); } /// /// Returns reading history events for a give or all users, broken up by day, and format /// /// If 0, defaults to all users, else just userId /// If 0, defaults to all time, else just those days asked for /// [HttpGet("stats/reading-count-by-day")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)] [Obsolete("Will be removed in v0.9.0")] public async Task>>> ReadCountByDay(int userId = 0, int days = 0) { var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(Username!); var isAdmin = User.IsInRole(PolicyConstants.AdminRole); if (!isAdmin && userId != user!.Id) return BadRequest(); return Ok(await _statService.ReadCountByDay(userId, days)); } [Authorize(PolicyGroups.AdminPolicy)] [HttpGet("server/count/year")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)] [Obsolete("Will be removed in v0.9.0")] public async Task>>> GetYearStatistics() { return Ok(await _statService.GetYearCount()); } /// /// Returns users with the top reads in the server /// /// /// [Authorize(PolicyGroups.AdminPolicy)] [HttpGet("stats/server/top/users")] [ResponseCache(CacheProfileName = ResponseCacheProfiles.Statistics)] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetTopReads(int days = 0) { return Ok(await _statService.GetTopUsers(days)); } /// /// Get all progress events for a given chapter /// /// /// [HttpGet("reader/all-chapter-progress")] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetProgressForChapter(int chapterId) { var userId = User.IsInRole(PolicyConstants.AdminRole) ? 0 : UserId; return Ok(await _unitOfWork.AppUserProgressRepository.GetUserProgressForChapter(chapterId, userId)); } /// /// Quick Reads are series that should be readable in less than 10 in total and are not Ongoing in release. /// /// Library to restrict series to /// Pagination /// [HttpGet("recommended/quick-reads")] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetQuickReads(int libraryId, [FromQuery] UserParams? userParams) { userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetQuickReads(UserId, libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Quick Catchup Reads are series that should be readable in less than 10 in total and are Ongoing in release. /// /// Library to restrict series to /// /// [HttpGet("recommended/quick-catchup-reads")] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetQuickCatchupReads(int libraryId, [FromQuery] UserParams? userParams) { userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetQuickCatchupReads(UserId, libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Highly Rated based on other users ratings. Will pull series with ratings > 4.0, weighted by count of other users. /// /// Library to restrict series to /// Pagination /// [HttpGet("recommended/highly-rated")] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetHighlyRated(int libraryId, [FromQuery] UserParams? userParams) { var userId = UserId; userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetHighlyRated(userId, libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Chooses a random genre and shows series that are in that without reading progress /// /// Library to restrict series to /// Genre Id /// Pagination /// [HttpGet("recommended/more-in")] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetMoreIn(int libraryId, int genreId, [FromQuery] UserParams? userParams) { var userId = UserId; userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetMoreIn(userId, libraryId, genreId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } /// /// Series that are fully read by the user in no particular order /// /// Library to restrict series to /// Pagination /// [HttpGet("recommended/rediscover")] [Obsolete("Will be removed in v0.9.0")] public async Task>> GetRediscover(int libraryId, [FromQuery] UserParams? userParams) { userParams ??= UserParams.Default; var series = await _unitOfWork.SeriesRepository.GetRediscover(UserId, libraryId, userParams); Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages); return Ok(series); } [Obsolete("Will be removed in v0.9.0")] [HttpGet("users/myself")] public async Task>> GetMyself() { var users = await _unitOfWork.UserRepository.GetAllUsersAsync(); return Ok(users.Where(u => u.UserName == Username!).DefaultIfEmpty().Select(u => _mapper.Map(u)).SingleOrDefault()); } }