using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.Comparators;
using API.DTOs.ReadingLists;
using API.Entities;
using API.Extensions;
using API.Helpers;
using API.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
public class ReadingListController : BaseApiController
{
private readonly IUnitOfWork _unitOfWork;
private readonly ChapterSortComparerZeroFirst _chapterSortComparerForInChapterSorting = new ChapterSortComparerZeroFirst();
public ReadingListController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
///
/// Fetches a single Reading List
///
///
///
[HttpGet]
public async Task>> GetList(int readingListId)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByIdAsync(readingListId, userId));
}
///
/// Returns reading lists (paginated) for a given user.
///
/// Defaults to true
///
[HttpPost("lists")]
public async Task>> GetListsForUser([FromQuery] UserParams userParams, [FromQuery] bool includePromoted = true)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var items = await _unitOfWork.ReadingListRepository.GetReadingListDtosForUserAsync(userId, includePromoted,
userParams);
Response.AddPaginationHeader(items.CurrentPage, items.PageSize, items.TotalCount, items.TotalPages);
return Ok(items);
}
///
/// Fetches all reading list items for a given list including rich metadata around series, volume, chapters, and progress
///
/// This call is expensive
///
///
[HttpGet("items")]
public async Task>> GetListForUser(int readingListId)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId);
return Ok(await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(userId, items.ToList()));
}
///
/// Updates an items position
///
///
///
[HttpPost("update-position")]
public async Task UpdateListItemPosition(UpdateReadingListPosition dto)
{
// Make sure UI buffers events
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(dto.ReadingListId)).ToList();
var item = items.Find(r => r.Id == dto.ReadingListItemId);
items.Remove(item);
items.Insert(dto.ToPosition, item);
for (var i = 0; i < items.Count; i++)
{
items[i].Order = i;
}
if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
{
return Ok("Updated");
}
return BadRequest("Couldn't update position");
}
///
/// Deletes a list item from the list. Will reorder all item positions afterwards
///
///
///
[HttpPost("delete-item")]
public async Task DeleteListItem(UpdateReadingListPosition dto)
{
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(dto.ReadingListId)).ToList();
var item = items.Find(r => r.Id == dto.ReadingListItemId);
items.Remove(item);
for (var i = 0; i < items.Count; i++)
{
items[i].Order = i;
}
if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
{
return Ok("Updated");
}
return BadRequest("Couldn't delete item");
}
///
/// Removes all entries that are fully read from the reading list
///
///
///
[HttpPost("remove-read")]
public async Task DeleteReadFromList([FromQuery] int readingListId)
{
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
var items = await _unitOfWork.ReadingListRepository.GetReadingListItemDtosByIdAsync(readingListId, userId);
items = await _unitOfWork.ReadingListRepository.AddReadingProgressModifiers(userId, items.ToList());
// Collect all Ids to remove
var itemIdsToRemove = items.Where(item => item.PagesRead == item.PagesTotal).Select(item => item.Id);
try
{
var listItems =
(await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).Where(r =>
itemIdsToRemove.Contains(r.Id));
_unitOfWork.ReadingListRepository.BulkRemove(listItems);
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
}
else
{
return Ok("Nothing to remove");
}
}
catch
{
await _unitOfWork.RollbackAsync();
}
return BadRequest("Could not remove read items");
}
///
/// Deletes a reading list
///
///
///
[HttpDelete]
public async Task DeleteList([FromQuery] int readingListId)
{
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
var readingList = user.ReadingLists.SingleOrDefault(r => r.Id == readingListId);
if (readingList == null)
{
return BadRequest("User is not associated with this reading list");
}
user.ReadingLists.Remove(readingList);
if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
{
return Ok("Deleted");
}
return BadRequest("There was an issue deleting reading list");
}
///
/// Creates a new List with a unique title. Returns the new ReadingList back
///
///
///
[HttpPost("create")]
public async Task> CreateList(CreateReadingListDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
// When creating, we need to make sure Title is unique
var hasExisting = user.ReadingLists.Any(l => l.Title.Equals(dto.Title));
if (hasExisting)
{
return BadRequest("A list of this name already exists");
}
user.ReadingLists.Add(new ReadingList()
{
Promoted = false,
Title = dto.Title,
Summary = string.Empty
});
if (!_unitOfWork.HasChanges()) return BadRequest("There was a problem creating list");
await _unitOfWork.CommitAsync();
return Ok(await _unitOfWork.ReadingListRepository.GetReadingListDtoByTitleAsync(dto.Title));
}
///
/// Update a
///
///
///
[HttpPost("update")]
public async Task UpdateList(UpdateReadingListDto dto)
{
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
if (readingList == null) return BadRequest("List does not exist");
if (!string.IsNullOrEmpty(dto.Title))
{
readingList.Title = dto.Title; // Should I check if this is unique?
}
if (!string.IsNullOrEmpty(dto.Title))
{
readingList.Summary = dto.Summary;
}
readingList.Promoted = dto.Promoted;
_unitOfWork.ReadingListRepository.Update(readingList);
if (await _unitOfWork.CommitAsync())
{
return Ok("Updated");
}
return BadRequest("Could not update reading list");
}
[HttpPost("update-by-series")]
public async Task UpdateListBySeries(UpdateReadingListBySeriesDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
var chapterIdsForSeries =
await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new [] {dto.SeriesId});
// If there are adds, tell tracking this has been modified
if (await AddChaptersToReadingList(dto.SeriesId, chapterIdsForSeries, readingList))
{
_unitOfWork.ReadingListRepository.Update(readingList);
}
try
{
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
}
}
catch
{
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
}
[HttpPost("update-by-volume")]
public async Task UpdateListByVolume(UpdateReadingListByVolumeDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
var chapterIdsForVolume =
(await _unitOfWork.ChapterRepository.GetChaptersAsync(dto.VolumeId)).Select(c => c.Id).ToList();
// If there are adds, tell tracking this has been modified
if (await AddChaptersToReadingList(dto.SeriesId, chapterIdsForVolume, readingList))
{
_unitOfWork.ReadingListRepository.Update(readingList);
}
try
{
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
}
}
catch
{
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
}
[HttpPost("update-by-chapter")]
public async Task UpdateListByChapter(UpdateReadingListByChapterDto dto)
{
var user = await _unitOfWork.UserRepository.GetUserWithReadingListsByUsernameAsync(User.GetUsername());
var readingList = user.ReadingLists.SingleOrDefault(l => l.Id == dto.ReadingListId);
if (readingList == null) return BadRequest("Reading List does not exist");
// If there are adds, tell tracking this has been modified
if (await AddChaptersToReadingList(dto.SeriesId, new List() { dto.ChapterId }, readingList))
{
_unitOfWork.ReadingListRepository.Update(readingList);
}
try
{
if (_unitOfWork.HasChanges())
{
await _unitOfWork.CommitAsync();
return Ok("Updated");
}
}
catch
{
await _unitOfWork.RollbackAsync();
}
return Ok("Nothing to do");
}
///
/// Adds a list of Chapters as reading list items to the passed reading list.
///
///
///
///
/// True if new chapters were added
private async Task AddChaptersToReadingList(int seriesId, IList chapterIds,
ReadingList readingList)
{
readingList.Items ??= new List();
var lastOrder = 0;
if (readingList.Items.Any())
{
lastOrder = readingList.Items.DefaultIfEmpty().Max(rli => rli.Order);
}
var existingChapterExists = readingList.Items.Select(rli => rli.ChapterId).ToHashSet();
var chaptersForSeries = (await _unitOfWork.ChapterRepository.GetChaptersByIdsAsync(chapterIds))
.OrderBy(c => int.Parse(c.Volume.Name))
.ThenBy(x => double.Parse(x.Number), _chapterSortComparerForInChapterSorting);
var index = lastOrder + 1;
foreach (var chapter in chaptersForSeries)
{
if (existingChapterExists.Contains(chapter.Id)) continue;
readingList.Items.Add(new ReadingListItem()
{
Order = index,
ChapterId = chapter.Id,
SeriesId = seriesId,
VolumeId = chapter.VolumeId
});
index += 1;
}
return index > lastOrder + 1;
}
///
/// Returns the next chapter within the reading list
///
///
///
/// Chapter Id for next item, -1 if nothing exists
[HttpGet("next-chapter")]
public async Task> GetNextChapter(int currentChapterId, int readingListId)
{
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).ToList();
var readingListItem = items.SingleOrDefault(rl => rl.ChapterId == currentChapterId);
if (readingListItem == null) return BadRequest("Id does not exist");
var index = items.IndexOf(readingListItem) + 1;
if (items.Count > index)
{
return items[index].ChapterId;
}
return Ok(-1);
}
///
/// Returns the prev chapter within the reading list
///
///
///
/// Chapter Id for next item, -1 if nothing exists
[HttpGet("prev-chapter")]
public async Task> GetPrevChapter(int currentChapterId, int readingListId)
{
var items = (await _unitOfWork.ReadingListRepository.GetReadingListItemsByIdAsync(readingListId)).ToList();
var readingListItem = items.SingleOrDefault(rl => rl.ChapterId == currentChapterId);
if (readingListItem == null) return BadRequest("Id does not exist");
var index = items.IndexOf(readingListItem) - 1;
if (0 <= index)
{
return items[index].ChapterId;
}
return Ok(-1);
}
}
}