diff --git a/code/backend/Cleanuparr.Domain/Entities/Whisparr/WhisparrV3Command.cs b/code/backend/Cleanuparr.Domain/Entities/Whisparr/WhisparrV3Command.cs deleted file mode 100644 index 4026cd81..00000000 --- a/code/backend/Cleanuparr.Domain/Entities/Whisparr/WhisparrV3Command.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Cleanuparr.Domain.Entities.Whisparr; - -public sealed record WhisparrV3Command -{ - public required string Name { get; init; } - - public required List MovieIds { get; init; } -} \ No newline at end of file diff --git a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Arr/WhisparrV3ClientTests.cs b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Arr/WhisparrV3ClientTests.cs index df006700..d5abc5d3 100644 --- a/code/backend/Cleanuparr.Infrastructure.Tests/Features/Arr/WhisparrV3ClientTests.cs +++ b/code/backend/Cleanuparr.Infrastructure.Tests/Features/Arr/WhisparrV3ClientTests.cs @@ -305,6 +305,24 @@ public class WhisparrV3ClientTests _client.HasContentId(new QueueRecord { DownloadId = "HASH", Title = "movie" }).ShouldBeFalse(); } + [Fact] + public async Task GetAllTagsAsync_ReadsV3TagEndpoint() + { + // Arrange + _httpMessageHandler.SetupResponse((_, _) => Task.FromResult(JsonResponse(new[] + { + new Tag { Id = 1, Label = "skip" }, + }))); + + // Act + List tags = await _client.GetAllTagsAsync(_arrInstance); + + // Assert + tags.ShouldHaveSingleItem().Label.ShouldBe("skip"); + HttpRequestMessage request = _httpMessageHandler.CapturedRequests.ShouldHaveSingleItem(); + request.RequestUri!.AbsolutePath.ShouldBe("/api/v3/tag"); + } + #region Helpers private void RouteResponses(long commandIdForPost) diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IWhisparrV3Client.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IWhisparrV3Client.cs index 8d803f7b..a100c8af 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IWhisparrV3Client.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/Interfaces/IWhisparrV3Client.cs @@ -1,5 +1,5 @@ namespace Cleanuparr.Infrastructure.Features.Arr.Interfaces; -public interface IWhisparrV3Client : IArrClient +public interface IWhisparrV3Client : IRadarrClient { -} \ No newline at end of file +} diff --git a/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs b/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs index 2d3b1d2f..e5deebd4 100644 --- a/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs +++ b/code/backend/Cleanuparr.Infrastructure/Features/Arr/WhisparrV3Client.cs @@ -1,20 +1,11 @@ -using System.Text; -using Cleanuparr.Domain.Entities.Arr; -using Cleanuparr.Domain.Entities.Arr.ManualImport; -using Cleanuparr.Domain.Entities.Arr.Queue; -using Cleanuparr.Domain.Entities.Radarr; -using Cleanuparr.Domain.Entities.Whisparr; using Cleanuparr.Infrastructure.Features.Arr.Interfaces; using Cleanuparr.Infrastructure.Features.ItemStriker; using Cleanuparr.Infrastructure.Interceptors; -using Cleanuparr.Persistence.Models.Configuration.Arr; -using System.Text.Json; -using Cleanuparr.Infrastructure.Json; using Microsoft.Extensions.Logging; namespace Cleanuparr.Infrastructure.Features.Arr; -public class WhisparrV3Client : ArrClient, IWhisparrV3Client +public class WhisparrV3Client : RadarrClient, IWhisparrV3Client { public WhisparrV3Client( ILogger logger, @@ -24,142 +15,4 @@ public class WhisparrV3Client : ArrClient, IWhisparrV3Client ) : base(logger, httpClientFactory, striker, dryRunInterceptor) { } - - protected override string ApiVersion => "v3"; - - protected override string GetSystemStatusUrlPath() - { - return $"/api/{ApiVersion}/system/status"; - } - - protected override string GetQueueUrlPath() - { - return $"/api/{ApiVersion}/queue"; - } - - protected override string GetQueueUrlQuery(int page) - { - return $"page={page}&pageSize={QueuePageSize}&includeUnknownMovieItems=true&includeMovie=true"; - } - - protected override string GetQueueDeleteUrlPath(long recordId) - { - return $"/api/{ApiVersion}/queue/{recordId}"; - } - - public override async Task> SearchItemsAsync(ArrInstance arrInstance, HashSet? items) - { - if (items?.Count is null or 0) - { - return []; - } - - List ids = items.Select(item => item.Id).ToList(); - - UriBuilder uriBuilder = new(arrInstance.Url); - uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}{CommandUrlPath}"; - - WhisparrV3Command command = new() - { - Name = "MoviesSearch", - MovieIds = ids, - }; - - using HttpRequestMessage request = new(HttpMethod.Post, uriBuilder.Uri); - request.Content = new StringContent( - JsonSerializer.Serialize(command, CleanuparrJsonOptions.Outbound), - Encoding.UTF8, - "application/json" - ); - SetApiKey(request, arrInstance.ApiKey); - - string? logContext = await ComputeCommandLogContextAsync(arrInstance, command); - - try - { - using HttpResponseMessage? response = await _dryRunInterceptor.InterceptAsync(() => SendRequestAsync(request)); - - long? commandId = null; - - if (response is not null) - { - commandId = await ReadCommandIdAsync(response); - } - - _logger.LogInformation("{Log}", GetSearchLog(arrInstance.Url, command, true, logContext)); - - return commandId.HasValue ? [commandId.Value] : []; - } - catch - { - _logger.LogError("{Log}", GetSearchLog(arrInstance.Url, command, false, logContext)); - throw; - } - } - - public override bool HasContentId(QueueRecord record) => record.MovieId is not 0; - - /// - public override bool SupportsForceImport => true; - - /// - public override ManualImportFile? MapCandidate(QueueRecord record, ManualImportCandidate candidate) => - candidate.ResolvedMovieId is { } movieId && movieId == record.MovieId - ? ManualImportFile.FromMovieCandidate(candidate, movieId) - : null; - - private static string GetSearchLog(Uri instanceUrl, WhisparrV3Command command, bool success, string? logContext) - { - string status = success ? "triggered" : "failed"; - string message = logContext ?? $"movie ids: {string.Join(',', command.MovieIds)}"; - - return $"movie search {status} | {instanceUrl} | {message}"; - } - - private async Task ComputeCommandLogContextAsync(ArrInstance arrInstance, WhisparrV3Command command) - { - try - { - StringBuilder log = new(); - - foreach (long movieId in command.MovieIds) - { - Movie? movie = await GetMovie(arrInstance, movieId); - - if (movie is null) - { - return null; - } - - log.Append($"[{movie.Title}]"); - } - - return log.ToString(); - } - catch (Exception exception) - { - _logger.LogDebug(exception, "failed to compute log context"); - } - - return null; - } - - private async Task GetMovie(ArrInstance arrInstance, long movieId) - { - UriBuilder uriBuilder = new(arrInstance.Url); - uriBuilder.Path = $"{uriBuilder.Path.TrimEnd('/')}/api/{ApiVersion}/movie/{movieId}"; - - using HttpRequestMessage request = new(HttpMethod.Get, uriBuilder.Uri); - SetApiKey(request, arrInstance.ApiKey); - - using HttpResponseMessage response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - response.EnsureSuccessStatusCode(); - - return await DeserializeStreamAsync(response); - } - - public override async Task> GetAllTagsAsync(ArrInstance arrInstance) - { - throw new NotImplementedException(); - } -} \ No newline at end of file +}