mirror of
https://github.com/Radarr/Radarr
synced 2026-01-23 07:54:53 +01:00
refactor: extract BaseMediaEditorController for bulk operations (#140)
- Create IEditorResource interface for common editor properties - Create BaseMediaEditorController with common bulk edit/delete logic - Extract tag handling (Add/Remove/Replace) to base class - BookEditorController: 92 -> 36 lines (-56) - AudiobookEditorController: 92 -> 36 lines (-56) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: admin <admin@ardentleatherworks.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2b42b33d3a
commit
cfa42cbfba
6 changed files with 164 additions and 148 deletions
|
|
@ -1,92 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using FluentValidation.Results;
|
||||
using NzbDrone.Core.Audiobooks;
|
||||
using Radarr.Api.V3.MediaItems;
|
||||
using Radarr.Http;
|
||||
|
||||
namespace Radarr.Api.V3.Audiobooks
|
||||
{
|
||||
[V3ApiController("audiobook/editor")]
|
||||
public class AudiobookEditorController : Controller
|
||||
public class AudiobookEditorController : BaseMediaEditorController<Audiobook, AudiobookResource, AudiobookEditorResource>
|
||||
{
|
||||
private readonly IAudiobookService _audiobookService;
|
||||
private readonly AudiobookEditorValidator _audiobookEditorValidator;
|
||||
|
||||
public AudiobookEditorController(IAudiobookService audiobookService,
|
||||
AudiobookEditorValidator audiobookEditorValidator)
|
||||
public AudiobookEditorController(IAudiobookService audiobookService, AudiobookEditorValidator audiobookEditorValidator)
|
||||
{
|
||||
_audiobookService = audiobookService;
|
||||
_audiobookEditorValidator = audiobookEditorValidator;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public IActionResult SaveAll([FromBody] AudiobookEditorResource resource)
|
||||
{
|
||||
var audiobooksToUpdate = _audiobookService.GetAudiobooks(resource.AudiobookIds);
|
||||
protected override List<Audiobook> GetItemsByIds(List<int> ids) => _audiobookService.GetAudiobooks(ids);
|
||||
protected override List<Audiobook> UpdateItems(List<Audiobook> items) => _audiobookService.UpdateAudiobooks(items);
|
||||
protected override void DeleteItems(List<int> ids, bool deleteFiles) => _audiobookService.DeleteAudiobooks(ids, deleteFiles);
|
||||
protected override ValidationResult ValidateItem(Audiobook item) => _audiobookEditorValidator.Validate(item);
|
||||
protected override AudiobookResource ToResource(Audiobook model) => model.ToResource();
|
||||
|
||||
foreach (var audiobook in audiobooksToUpdate)
|
||||
{
|
||||
if (resource.Monitored.HasValue)
|
||||
{
|
||||
audiobook.Monitored = resource.Monitored.Value;
|
||||
}
|
||||
|
||||
if (resource.QualityProfileId.HasValue)
|
||||
{
|
||||
audiobook.QualityProfileId = resource.QualityProfileId.Value;
|
||||
}
|
||||
|
||||
if (resource.RootFolderPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
audiobook.RootFolderPath = resource.RootFolderPath;
|
||||
}
|
||||
|
||||
if (resource.Tags != null)
|
||||
{
|
||||
var newTags = resource.Tags;
|
||||
var applyTags = resource.ApplyTags;
|
||||
|
||||
switch (applyTags)
|
||||
{
|
||||
case ApplyTags.Add:
|
||||
newTags.ForEach(t => audiobook.Tags.Add(t));
|
||||
break;
|
||||
case ApplyTags.Remove:
|
||||
newTags.ForEach(t => audiobook.Tags.Remove(t));
|
||||
break;
|
||||
case ApplyTags.Replace:
|
||||
audiobook.Tags = new HashSet<int>(newTags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var validationResult = _audiobookEditorValidator.Validate(audiobook);
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
var updatedAudiobooks = _audiobookService.UpdateAudiobooks(audiobooksToUpdate);
|
||||
|
||||
var audiobooksResources = new List<AudiobookResource>(updatedAudiobooks.Count);
|
||||
|
||||
foreach (var audiobook in updatedAudiobooks)
|
||||
{
|
||||
audiobooksResources.Add(audiobook.ToResource());
|
||||
}
|
||||
|
||||
return Ok(audiobooksResources);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public object DeleteAudiobooks([FromBody] AudiobookEditorResource resource)
|
||||
{
|
||||
_audiobookService.DeleteAudiobooks(resource.AudiobookIds, resource.DeleteFiles);
|
||||
|
||||
return new { };
|
||||
}
|
||||
protected override bool GetMonitored(Audiobook item) => item.Monitored;
|
||||
protected override void SetMonitored(Audiobook item, bool monitored) => item.Monitored = monitored;
|
||||
protected override int GetQualityProfileId(Audiobook item) => item.QualityProfileId;
|
||||
protected override void SetQualityProfileId(Audiobook item, int qualityProfileId) => item.QualityProfileId = qualityProfileId;
|
||||
protected override string GetRootFolderPath(Audiobook item) => item.RootFolderPath;
|
||||
protected override void SetRootFolderPath(Audiobook item, string rootFolderPath) => item.RootFolderPath = rootFolderPath;
|
||||
protected override HashSet<int> GetTags(Audiobook item) => item.Tags;
|
||||
protected override void SetTags(Audiobook item, HashSet<int> tags) => item.Tags = tags;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using Radarr.Api.V3.MediaItems;
|
||||
|
||||
namespace Radarr.Api.V3.Audiobooks
|
||||
{
|
||||
public class AudiobookEditorResource
|
||||
public class AudiobookEditorResource : IEditorResource
|
||||
{
|
||||
public List<int> AudiobookIds { get; set; }
|
||||
public bool? Monitored { get; set; }
|
||||
|
|
@ -12,5 +13,7 @@ public class AudiobookEditorResource
|
|||
public ApplyTags ApplyTags { get; set; }
|
||||
public bool MoveFiles { get; set; }
|
||||
public bool DeleteFiles { get; set; }
|
||||
|
||||
List<int> IEditorResource.Ids => AudiobookIds;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using FluentValidation.Results;
|
||||
using NzbDrone.Core.Books;
|
||||
using Radarr.Api.V3.MediaItems;
|
||||
using Radarr.Http;
|
||||
|
||||
namespace Radarr.Api.V3.Books
|
||||
{
|
||||
[V3ApiController("book/editor")]
|
||||
public class BookEditorController : Controller
|
||||
public class BookEditorController : BaseMediaEditorController<Book, BookResource, BookEditorResource>
|
||||
{
|
||||
private readonly IBookService _bookService;
|
||||
private readonly BookEditorValidator _bookEditorValidator;
|
||||
|
||||
public BookEditorController(IBookService bookService,
|
||||
BookEditorValidator bookEditorValidator)
|
||||
public BookEditorController(IBookService bookService, BookEditorValidator bookEditorValidator)
|
||||
{
|
||||
_bookService = bookService;
|
||||
_bookEditorValidator = bookEditorValidator;
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public IActionResult SaveAll([FromBody] BookEditorResource resource)
|
||||
{
|
||||
var booksToUpdate = _bookService.GetBooks(resource.BookIds);
|
||||
protected override List<Book> GetItemsByIds(List<int> ids) => _bookService.GetBooks(ids);
|
||||
protected override List<Book> UpdateItems(List<Book> items) => _bookService.UpdateBooks(items);
|
||||
protected override void DeleteItems(List<int> ids, bool deleteFiles) => _bookService.DeleteBooks(ids, deleteFiles);
|
||||
protected override ValidationResult ValidateItem(Book item) => _bookEditorValidator.Validate(item);
|
||||
protected override BookResource ToResource(Book model) => model.ToResource();
|
||||
|
||||
foreach (var book in booksToUpdate)
|
||||
{
|
||||
if (resource.Monitored.HasValue)
|
||||
{
|
||||
book.Monitored = resource.Monitored.Value;
|
||||
}
|
||||
|
||||
if (resource.QualityProfileId.HasValue)
|
||||
{
|
||||
book.QualityProfileId = resource.QualityProfileId.Value;
|
||||
}
|
||||
|
||||
if (resource.RootFolderPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
book.RootFolderPath = resource.RootFolderPath;
|
||||
}
|
||||
|
||||
if (resource.Tags != null)
|
||||
{
|
||||
var newTags = resource.Tags;
|
||||
var applyTags = resource.ApplyTags;
|
||||
|
||||
switch (applyTags)
|
||||
{
|
||||
case ApplyTags.Add:
|
||||
newTags.ForEach(t => book.Tags.Add(t));
|
||||
break;
|
||||
case ApplyTags.Remove:
|
||||
newTags.ForEach(t => book.Tags.Remove(t));
|
||||
break;
|
||||
case ApplyTags.Replace:
|
||||
book.Tags = new HashSet<int>(newTags);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var validationResult = _bookEditorValidator.Validate(book);
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
var updatedBooks = _bookService.UpdateBooks(booksToUpdate);
|
||||
|
||||
var booksResources = new List<BookResource>(updatedBooks.Count);
|
||||
|
||||
foreach (var book in updatedBooks)
|
||||
{
|
||||
booksResources.Add(book.ToResource());
|
||||
}
|
||||
|
||||
return Ok(booksResources);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public object DeleteBooks([FromBody] BookEditorResource resource)
|
||||
{
|
||||
_bookService.DeleteBooks(resource.BookIds, resource.DeleteFiles);
|
||||
|
||||
return new { };
|
||||
}
|
||||
protected override bool GetMonitored(Book item) => item.Monitored;
|
||||
protected override void SetMonitored(Book item, bool monitored) => item.Monitored = monitored;
|
||||
protected override int GetQualityProfileId(Book item) => item.QualityProfileId;
|
||||
protected override void SetQualityProfileId(Book item, int qualityProfileId) => item.QualityProfileId = qualityProfileId;
|
||||
protected override string GetRootFolderPath(Book item) => item.RootFolderPath;
|
||||
protected override void SetRootFolderPath(Book item, string rootFolderPath) => item.RootFolderPath = rootFolderPath;
|
||||
protected override HashSet<int> GetTags(Book item) => item.Tags;
|
||||
protected override void SetTags(Book item, HashSet<int> tags) => item.Tags = tags;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using Radarr.Api.V3.MediaItems;
|
||||
|
||||
namespace Radarr.Api.V3.Books
|
||||
{
|
||||
public class BookEditorResource
|
||||
public class BookEditorResource : IEditorResource
|
||||
{
|
||||
public List<int> BookIds { get; set; }
|
||||
public bool? Monitored { get; set; }
|
||||
|
|
@ -12,5 +13,7 @@ public class BookEditorResource
|
|||
public ApplyTags ApplyTags { get; set; }
|
||||
public bool MoveFiles { get; set; }
|
||||
public bool DeleteFiles { get; set; }
|
||||
|
||||
List<int> IEditorResource.Ids => BookIds;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
107
src/Radarr.Api.V3/MediaItems/BaseMediaEditorController.cs
Normal file
107
src/Radarr.Api.V3/MediaItems/BaseMediaEditorController.cs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
using System.Collections.Generic;
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using Radarr.Http.REST;
|
||||
|
||||
namespace Radarr.Api.V3.MediaItems
|
||||
{
|
||||
public abstract class BaseMediaEditorController<TModel, TResource, TEditorResource> : Controller
|
||||
where TModel : ModelBase, new()
|
||||
where TResource : RestResource, new()
|
||||
where TEditorResource : class, IEditorResource
|
||||
{
|
||||
protected abstract List<TModel> GetItemsByIds(List<int> ids);
|
||||
protected abstract List<TModel> UpdateItems(List<TModel> items);
|
||||
protected abstract void DeleteItems(List<int> ids, bool deleteFiles);
|
||||
protected abstract ValidationResult ValidateItem(TModel item);
|
||||
protected abstract TResource ToResource(TModel model);
|
||||
|
||||
protected abstract bool GetMonitored(TModel item);
|
||||
protected abstract void SetMonitored(TModel item, bool monitored);
|
||||
protected abstract int GetQualityProfileId(TModel item);
|
||||
protected abstract void SetQualityProfileId(TModel item, int qualityProfileId);
|
||||
protected abstract string GetRootFolderPath(TModel item);
|
||||
protected abstract void SetRootFolderPath(TModel item, string rootFolderPath);
|
||||
protected abstract HashSet<int> GetTags(TModel item);
|
||||
protected abstract void SetTags(TModel item, HashSet<int> tags);
|
||||
|
||||
[HttpPut]
|
||||
public virtual IActionResult SaveAll([FromBody] TEditorResource resource)
|
||||
{
|
||||
var itemsToUpdate = GetItemsByIds(resource.Ids);
|
||||
|
||||
foreach (var item in itemsToUpdate)
|
||||
{
|
||||
ApplyEditorChanges(item, resource);
|
||||
|
||||
var validationResult = ValidateItem(item);
|
||||
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
throw new ValidationException(validationResult.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
var updatedItems = UpdateItems(itemsToUpdate);
|
||||
|
||||
var resources = new List<TResource>(updatedItems.Count);
|
||||
foreach (var item in updatedItems)
|
||||
{
|
||||
resources.Add(ToResource(item));
|
||||
}
|
||||
|
||||
return Ok(resources);
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
public virtual IActionResult DeleteAll([FromBody] TEditorResource resource)
|
||||
{
|
||||
DeleteItems(resource.Ids, resource.DeleteFiles);
|
||||
return Ok(new { });
|
||||
}
|
||||
|
||||
protected virtual void ApplyEditorChanges(TModel item, TEditorResource resource)
|
||||
{
|
||||
if (resource.Monitored.HasValue)
|
||||
{
|
||||
SetMonitored(item, resource.Monitored.Value);
|
||||
}
|
||||
|
||||
if (resource.QualityProfileId.HasValue)
|
||||
{
|
||||
SetQualityProfileId(item, resource.QualityProfileId.Value);
|
||||
}
|
||||
|
||||
if (resource.RootFolderPath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
SetRootFolderPath(item, resource.RootFolderPath);
|
||||
}
|
||||
|
||||
if (resource.Tags != null)
|
||||
{
|
||||
ApplyTagChanges(item, resource.Tags, resource.ApplyTags);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ApplyTagChanges(TModel item, List<int> newTags, ApplyTags applyTags)
|
||||
{
|
||||
var currentTags = GetTags(item);
|
||||
|
||||
switch (applyTags)
|
||||
{
|
||||
case ApplyTags.Add:
|
||||
newTags.ForEach(t => currentTags.Add(t));
|
||||
break;
|
||||
case ApplyTags.Remove:
|
||||
newTags.ForEach(t => currentTags.Remove(t));
|
||||
break;
|
||||
case ApplyTags.Replace:
|
||||
SetTags(item, new HashSet<int>(newTags));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/Radarr.Api.V3/MediaItems/IEditorResource.cs
Normal file
15
src/Radarr.Api.V3/MediaItems/IEditorResource.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Radarr.Api.V3.MediaItems
|
||||
{
|
||||
public interface IEditorResource
|
||||
{
|
||||
List<int> Ids { get; }
|
||||
bool? Monitored { get; set; }
|
||||
int? QualityProfileId { get; set; }
|
||||
string RootFolderPath { get; set; }
|
||||
List<int> Tags { get; set; }
|
||||
ApplyTags ApplyTags { get; set; }
|
||||
bool DeleteFiles { get; set; }
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue