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.Filtering; using API.DTOs.Metadata; using API.DTOs.Progress; using API.DTOs.Statistics; using API.DTOs.Uploads; using API.Entities; using API.Entities.Enums; using API.Extensions; using API.Helpers; using API.Middleware; using API.Services; using API.Services.Tasks; 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; public DeprecatedController(IUnitOfWork unitOfWork, ILocalizationService localizationService, ITaskScheduler taskScheduler, ILogger logger, IStatisticService statService) { _unitOfWork = unitOfWork; _localizationService = localizationService; _taskScheduler = taskScheduler; _logger = logger; _statService = statService; } /// /// 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); await _unitOfWork.SeriesRepository.AddSeriesModifiers(UserId, pagedList); 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); // Apply progress/rating information (I can't work out how to do this in initial query) if (series == null) return BadRequest(await _localizationService.Translate(UserId, "no-series")); await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); 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); // Apply progress/rating information (I can't work out how to do this in initial query) if (series == null) return BadRequest(await _localizationService.Translate(UserId, "no-series")); await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); 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); // Apply progress/rating information (I can't work out how to do this in initial query) if (series == null) return BadRequest(await _localizationService.Translate(UserId, "no-series")); await _unitOfWork.SeriesRepository.AddSeriesModifiers(userId, series); 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)] 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)] 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)] 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)] 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)] 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")] public async Task>> GetProgressForChapter(int chapterId) { var userId = User.IsInRole(PolicyConstants.AdminRole) ? 0 : UserId; return Ok(await _unitOfWork.AppUserProgressRepository.GetUserProgressForChapter(chapterId, userId)); } }