mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Bulk Deletion (#697)
* Implemented bulk deletion of series * Don't show unauthorized exception on UI, just redirect to the login page.
This commit is contained in:
parent
e3b33bcbf9
commit
6d6eee999a
@ -78,8 +78,9 @@ namespace API.Controllers
|
||||
public async Task<ActionResult<bool>> DeleteSeries(int seriesId)
|
||||
{
|
||||
var username = User.GetUsername();
|
||||
var chapterIds = (await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new []{seriesId}));
|
||||
_logger.LogInformation("Series {SeriesId} is being deleted by {UserName}", seriesId, username);
|
||||
|
||||
var chapterIds = (await _unitOfWork.SeriesRepository.GetChapterIdsForSeriesAsync(new []{seriesId}));
|
||||
var result = await _unitOfWork.SeriesRepository.DeleteSeriesAsync(seriesId);
|
||||
|
||||
if (result)
|
||||
@ -92,6 +93,34 @@ namespace API.Controllers
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("delete-multiple")]
|
||||
public async Task<ActionResult> DeleteMultipleSeries(DeleteSeriesDto dto)
|
||||
{
|
||||
var username = User.GetUsername();
|
||||
_logger.LogInformation("Series {SeriesId} is being deleted by {UserName}", dto.SeriesIds, username);
|
||||
|
||||
var chapterMappings =
|
||||
await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync(dto.SeriesIds.ToArray());
|
||||
|
||||
var allChapterIds = new List<int>();
|
||||
foreach (var mapping in chapterMappings)
|
||||
{
|
||||
allChapterIds.AddRange(mapping.Value);
|
||||
}
|
||||
|
||||
var series = await _unitOfWork.SeriesRepository.GetSeriesByIdsAsync(dto.SeriesIds);
|
||||
_unitOfWork.SeriesRepository.Remove(series);
|
||||
|
||||
if (_unitOfWork.HasChanges() && await _unitOfWork.CommitAsync())
|
||||
{
|
||||
await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters();
|
||||
await _unitOfWork.CollectionTagRepository.RemoveTagsWithoutSeries();
|
||||
_taskScheduler.CleanupChapters(allChapterIds.ToArray());
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns All volumes for a series with progress information and Chapters
|
||||
/// </summary>
|
||||
|
9
API/DTOs/DeleteSeriesDto.cs
Normal file
9
API/DTOs/DeleteSeriesDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class DeleteSeriesDto
|
||||
{
|
||||
public IList<int> SeriesIds { get; set; }
|
||||
}
|
||||
}
|
@ -42,6 +42,11 @@ namespace API.Data.Repositories
|
||||
_context.Series.Remove(series);
|
||||
}
|
||||
|
||||
public void Remove(IEnumerable<Series> series)
|
||||
{
|
||||
_context.Series.RemoveRange(series);
|
||||
}
|
||||
|
||||
public async Task<bool> DoesSeriesNameExistInLibrary(string name)
|
||||
{
|
||||
var libraries = _context.Series
|
||||
@ -172,6 +177,21 @@ namespace API.Data.Repositories
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Volumes, Metadata, and Collection Tags
|
||||
/// </summary>
|
||||
/// <param name="seriesIds"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IList<Series>> GetSeriesByIdsAsync(IList<int> seriesIds)
|
||||
{
|
||||
return await _context.Series
|
||||
.Include(s => s.Volumes)
|
||||
.Include(s => s.Metadata)
|
||||
.ThenInclude(m => m.CollectionTags)
|
||||
.Where(s => seriesIds.Contains(s.Id))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int[]> GetChapterIdsForSeriesAsync(int[] seriesIds)
|
||||
{
|
||||
var volumes = await _context.Volume
|
||||
|
@ -13,6 +13,7 @@ namespace API.Interfaces.Repositories
|
||||
void Attach(Series series);
|
||||
void Update(Series series);
|
||||
void Remove(Series series);
|
||||
void Remove(IEnumerable<Series> series);
|
||||
Task<bool> DoesSeriesNameExistInLibrary(string name);
|
||||
/// <summary>
|
||||
/// Adds user information like progress, ratings, etc
|
||||
@ -33,6 +34,7 @@ namespace API.Interfaces.Repositories
|
||||
Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId, int userId);
|
||||
Task<bool> DeleteSeriesAsync(int seriesId);
|
||||
Task<Series> GetSeriesByIdAsync(int seriesId);
|
||||
Task<IList<Series>> GetSeriesByIdsAsync(IList<int> seriesIds);
|
||||
Task<int[]> GetChapterIdsForSeriesAsync(int[] seriesIds);
|
||||
Task<IDictionary<int, IList<int>>> GetChapterIdWithSeriesIdForSeriesAsync(int[] seriesIds);
|
||||
/// <summary>
|
||||
|
@ -111,11 +111,7 @@ export class ErrorInterceptor implements HttpInterceptor {
|
||||
// NOTE: Signin has error.error or error.statusText available.
|
||||
// if statement is due to http/2 spec issue: https://github.com/angular/angular/issues/23334
|
||||
this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
|
||||
if (user) {
|
||||
this.toastr.error(error.statusText === 'OK' ? 'Unauthorized' : error.statusText, error.status);
|
||||
}
|
||||
this.accountService.logout();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -467,4 +467,21 @@ export class ActionService implements OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all chapters and the volumes as Read. All volumes and chapters must belong to a series
|
||||
* @param seriesId Series Id
|
||||
* @param volumes Volumes, should have id, chapters and pagesRead populated
|
||||
* @param chapters? Chapters, should have id
|
||||
* @param callback Optional callback to perform actions after API completes
|
||||
*/
|
||||
deleteMultipleSeries(seriesIds: Array<Series>, callback?: VoidActionCallback) {
|
||||
this.seriesService.deleteMultipleSeries(seriesIds.map(s => s.id)).pipe(take(1)).subscribe(() => {
|
||||
this.toastr.success('Series deleted');
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -80,6 +80,10 @@ export class SeriesService {
|
||||
return this.httpClient.delete<boolean>(this.baseUrl + 'series/' + seriesId);
|
||||
}
|
||||
|
||||
deleteMultipleSeries(seriesIds: Array<number>) {
|
||||
return this.httpClient.post<boolean>(this.baseUrl + 'series/delete-multiple', {seriesIds});
|
||||
}
|
||||
|
||||
updateRating(seriesId: number, userRating: number, userReview: string) {
|
||||
return this.httpClient.post(this.baseUrl + 'series/update-rating', {seriesId, userRating, userReview});
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ export class BulkSelectionService {
|
||||
getActions(callback: (action: Action, data: any) => void) {
|
||||
// checks if series is present. If so, returns only series actions
|
||||
// else returns volume/chapter items
|
||||
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection];
|
||||
const allowedActions = [Action.AddToReadingList, Action.MarkAsRead, Action.MarkAsUnread, Action.AddToCollection, Action.Delete];
|
||||
if (Object.keys(this.selectedCards).filter(item => item === 'series').length > 0) {
|
||||
return this.actionFactory.getSeriesActions(callback).filter(item => allowedActions.includes(item.action));
|
||||
}
|
||||
|
@ -72,6 +72,12 @@ export class CollectionDetailComponent implements OnInit, OnDestroy {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.Delete:
|
||||
this.actionService.deleteMultipleSeries(selectedSeries, () => {
|
||||
this.loadPage();
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,6 @@ export class InProgressComponent implements OnInit {
|
||||
this.loadPage();
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
|
||||
break;
|
||||
case Action.MarkAsUnread:
|
||||
this.actionService.markMultipleSeriesAsUnread(selectedSeries, () => {
|
||||
@ -120,6 +119,12 @@ export class InProgressComponent implements OnInit {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.Delete:
|
||||
this.actionService.deleteMultipleSeries(selectedSeries, () => {
|
||||
this.loadPage();
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,12 @@ export class LibraryDetailComponent implements OnInit, OnDestroy {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.Delete:
|
||||
this.actionService.deleteMultipleSeries(selectedSeries, () => {
|
||||
this.loadPage();
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,12 @@ export class RecentlyAddedComponent implements OnInit, OnDestroy {
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
case Action.Delete:
|
||||
this.actionService.deleteMultipleSeries(selectedSeries, () => {
|
||||
this.loadPage();
|
||||
this.bulkSelectionService.deselectAll();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user