From 7d3be883d6d34e453a3baae97c291702b9d6f4e2 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 8 Nov 2021 22:44:06 +0000 Subject: [PATCH] feat(discover): :sparkles: Added a new API to query recently requested content --- .../Services/RecentlyRequestedServiceTests.cs | 170 ++++++++++++++++++ .../Models/Requests/RecentlyRequestedModel.cs | 20 +++ .../Services/IRecentlyRequestedService.cs | 12 ++ .../Services/RecentlyRequestedService.cs | 111 ++++++++++++ src/Ombi.DependencyInjection/IocExtensions.cs | 1 + .../Settings/Models/CustomizationSettings.cs | 1 + src/Ombi/Controllers/V2/RequestsController.cs | 12 +- 7 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs create mode 100644 src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs create mode 100644 src/Ombi.Core/Services/IRecentlyRequestedService.cs create mode 100644 src/Ombi.Core/Services/RecentlyRequestedService.cs diff --git a/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs b/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs new file mode 100644 index 000000000..e0b06f541 --- /dev/null +++ b/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs @@ -0,0 +1,170 @@ +using AutoFixture; +using MockQueryable.Moq; +using Moq; +using Moq.AutoMock; +using NUnit.Framework; +using Ombi.Core.Models.Requests; +using Ombi.Core.Services; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.Core.Tests.Services +{ + [TestFixture] + public class RecentlyRequestedServiceTests + { + private AutoMocker _mocker; + private RecentlyRequestedService _subject; + private Fixture _fixture; + + [SetUp] + public void Setup() + { + _fixture = new Fixture(); + + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _mocker = new AutoMocker(); + _subject = _mocker.CreateInstance(); + } + + [Test] + public async Task GetRecentlyRequested_Movies() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new CustomizationSettings()); + var releaseDate = new DateTime(2019, 01, 01); + var requestDate = DateTime.Now; + var movies = new List + { + new MovieRequests + { + Id = 1, + Approved = true, + Available = true, + ReleaseDate = releaseDate, + Title = "title", + Overview = "overview", + RequestedDate = requestDate, + RequestedUser = new Store.Entities.OmbiUser + { + UserName = "a" + }, + RequestedUserId = "b", + } + }; + var albums = new List(); + var chilRequests = new List(); + _mocker.Setup>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object); + _mocker.Setup>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object); + _mocker.Setup>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object); + + var result = await _subject.GetRecentlyRequested(CancellationToken.None); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First(), Is.InstanceOf() + .With.Property(nameof(RecentlyRequestedModel.RequestId)).EqualTo(1) + .With.Property(nameof(RecentlyRequestedModel.Approved)).EqualTo(true) + .With.Property(nameof(RecentlyRequestedModel.Available)).EqualTo(true) + .With.Property(nameof(RecentlyRequestedModel.Title)).EqualTo("title") + .With.Property(nameof(RecentlyRequestedModel.Overview)).EqualTo("overview") + .With.Property(nameof(RecentlyRequestedModel.RequestDate)).EqualTo(requestDate) + .With.Property(nameof(RecentlyRequestedModel.ReleaseDate)).EqualTo(releaseDate) + .With.Property(nameof(RecentlyRequestedModel.Type)).EqualTo(RequestType.Movie) + ); + } + + [Test] + public async Task GetRecentlyRequested_Movies_HideAvailable() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new CustomizationSettings() { HideAvailableRecentlyRequested = true }); + var releaseDate = new DateTime(2019, 01, 01); + var requestDate = DateTime.Now; + var movies = new List + { + new MovieRequests + { + Id = 1, + Approved = true, + Available = true, + ReleaseDate = releaseDate, + Title = "title", + Overview = "overview", + RequestedDate = requestDate, + RequestedUser = new Store.Entities.OmbiUser + { + UserName = "a" + }, + RequestedUserId = "b", + }, + + new MovieRequests + { + Id = 1, + Approved = true, + Available = false, + ReleaseDate = releaseDate, + Title = "title2", + Overview = "overview2", + RequestedDate = requestDate, + RequestedUser = new Store.Entities.OmbiUser + { + UserName = "a" + }, + RequestedUserId = "b", + } + }; + var albums = new List(); + var chilRequests = new List(); + _mocker.Setup>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object); + _mocker.Setup>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object); + _mocker.Setup>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object); + + var result = await _subject.GetRecentlyRequested(CancellationToken.None); + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First(), Is.InstanceOf() + .With.Property(nameof(RecentlyRequestedModel.RequestId)).EqualTo(1) + .With.Property(nameof(RecentlyRequestedModel.Approved)).EqualTo(true) + .With.Property(nameof(RecentlyRequestedModel.Available)).EqualTo(false) + .With.Property(nameof(RecentlyRequestedModel.Title)).EqualTo("title2") + .With.Property(nameof(RecentlyRequestedModel.Overview)).EqualTo("overview2") + .With.Property(nameof(RecentlyRequestedModel.RequestDate)).EqualTo(requestDate) + .With.Property(nameof(RecentlyRequestedModel.ReleaseDate)).EqualTo(releaseDate) + .With.Property(nameof(RecentlyRequestedModel.Type)).EqualTo(RequestType.Movie) + ); + } + + [Test] + public async Task GetRecentlyRequested() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new CustomizationSettings()); + var releaseDate = new DateTime(2019, 01, 01); + var requestDate = DateTime.Now; + + var movies = _fixture.CreateMany(10); + var albums = _fixture.CreateMany(10); + var chilRequests = _fixture.CreateMany(10); + + _mocker.Setup>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object); + _mocker.Setup>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object); + _mocker.Setup>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object); + + var result = await _subject.GetRecentlyRequested(CancellationToken.None); + + Assert.That(result.Count, Is.EqualTo(21)); + } + } +} diff --git a/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs b/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs new file mode 100644 index 000000000..8ab6c6b15 --- /dev/null +++ b/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs @@ -0,0 +1,20 @@ +using Ombi.Store.Entities; +using System; + +namespace Ombi.Core.Models.Requests +{ + public class RecentlyRequestedModel + { + public int RequestId { get; set; } + public RequestType Type { get; set; } + public string UserId { get; set; } + public string Username { get; set; } + public bool Available { get; set; } + public bool TvPartiallyAvailable { get; set; } + public DateTime RequestDate { get; set; } + public string Title { get; set; } + public string Overview { get; set; } + public DateTime ReleaseDate { get; set; } + public bool Approved { get; set; } + } +} diff --git a/src/Ombi.Core/Services/IRecentlyRequestedService.cs b/src/Ombi.Core/Services/IRecentlyRequestedService.cs new file mode 100644 index 000000000..21604c14d --- /dev/null +++ b/src/Ombi.Core/Services/IRecentlyRequestedService.cs @@ -0,0 +1,12 @@ +using Ombi.Core.Models.Requests; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.Core.Services +{ + public interface IRecentlyRequestedService + { + Task> GetRecentlyRequested(CancellationToken cancellationToken); + } +} diff --git a/src/Ombi.Core/Services/RecentlyRequestedService.cs b/src/Ombi.Core/Services/RecentlyRequestedService.cs new file mode 100644 index 000000000..d4c50bc6f --- /dev/null +++ b/src/Ombi.Core/Services/RecentlyRequestedService.cs @@ -0,0 +1,111 @@ +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Models.Requests; +using Ombi.Core.Settings; +using Ombi.Settings.Settings.Models; +using Ombi.Store.Entities; +using Ombi.Store.Repository.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ombi.Core.Services +{ + public class RecentlyRequestedService : IRecentlyRequestedService + { + private readonly IMovieRequestRepository _movieRequestRepository; + private readonly ITvRequestRepository _tvRequestRepository; + private readonly IMusicRequestRepository _musicRequestRepository; + private readonly ISettingsService _customizationSettings; + + private const int AmountToTake = 7; + + public RecentlyRequestedService( + IMovieRequestRepository movieRequestRepository, + ITvRequestRepository tvRequestRepository, + IMusicRequestRepository musicRequestRepository, + ISettingsService customizationSettings) + { + _movieRequestRepository = movieRequestRepository; + _tvRequestRepository = tvRequestRepository; + _musicRequestRepository = musicRequestRepository; + _customizationSettings = customizationSettings; + } + + public async Task> GetRecentlyRequested(CancellationToken cancellationToken) + { + var customizationSettingsTask = _customizationSettings.GetSettingsAsync(); + + var recentMovieRequests = _movieRequestRepository.GetAll().Include(x => x.RequestedUser).OrderByDescending(x => x.RequestedDate).Take(AmountToTake); + var recentTvRequests = _tvRequestRepository.GetChild().Include(x => x.RequestedUser).Include(x => x.ParentRequest).OrderByDescending(x => x.RequestedDate).Take(AmountToTake); + var recentMusicRequests = _musicRequestRepository.GetAll().Include(x => x.RequestedUser).OrderByDescending(x => x.RequestedDate).Take(AmountToTake); + + var settings = await customizationSettingsTask; + if (settings.HideAvailableRecentlyRequested) + { + recentMovieRequests = recentMovieRequests.Where(x => !x.Available); + recentTvRequests = recentTvRequests.Where(x => !x.Available); + recentMusicRequests = recentMusicRequests.Where(x => !x.Available); + } + + var model = new List(); + + foreach (var item in await recentMovieRequests.ToListAsync(cancellationToken)) + { + model.Add(new RecentlyRequestedModel + { + RequestId = item.Id, + Available = item.Available, + Overview = item.Overview, + ReleaseDate = item.ReleaseDate, + RequestDate = item.RequestedDate, + Title = item.Title, + Type = RequestType.Movie, + Approved = item.Approved, + UserId = item.RequestedUserId, + Username = item.RequestedUser.UserAlias + }); + } + + foreach (var item in await recentMusicRequests.ToListAsync(cancellationToken)) + { + model.Add(new RecentlyRequestedModel + { + RequestId = item.Id, + Available = item.Available, + Overview = item.ArtistName, + Approved = item.Approved, + ReleaseDate = item.ReleaseDate, + RequestDate = item.RequestedDate, + Title = item.Title, + Type = RequestType.Album, + UserId = item.RequestedUserId, + Username = item.RequestedUser.UserAlias + }); + } + + foreach (var item in await recentTvRequests.ToListAsync(cancellationToken)) + { + var partialAvailability = item.SeasonRequests.SelectMany(x => x.Episodes).Any(e => e.Available); + model.Add(new RecentlyRequestedModel + { + RequestId = item.Id, + Available = item.Available, + Overview = item.ParentRequest.Overview, + ReleaseDate = item.ParentRequest.ReleaseDate, + Approved = item.Approved, + RequestDate = item.RequestedDate, + TvPartiallyAvailable = partialAvailability, + Title = item.ParentRequest.Title, + Type = RequestType.TvShow, + UserId = item.RequestedUserId, + Username = item.RequestedUser.UserAlias + }); + } + + return model.OrderByDescending(x => x.RequestDate); + } + } +} diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index f81cd05da..ca1fbaba8 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -224,6 +224,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterJobs(this IServiceCollection services) diff --git a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs index 1f2155331..310748039 100644 --- a/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/CustomizationSettings.cs @@ -12,6 +12,7 @@ public bool RecentlyAddedPage { get; set; } public bool UseCustomPage { get; set; } public bool HideAvailableFromDiscover { get; set; } + public bool HideAvailableRecentlyRequested { get; set; } public string AddToUrl(string part) { diff --git a/src/Ombi/Controllers/V2/RequestsController.cs b/src/Ombi/Controllers/V2/RequestsController.cs index 9f26e95bf..0dfe48a20 100644 --- a/src/Ombi/Controllers/V2/RequestsController.cs +++ b/src/Ombi/Controllers/V2/RequestsController.cs @@ -13,6 +13,8 @@ using System.Linq; using Microsoft.Extensions.Logging; using Ombi.Attributes; using Ombi.Helpers; +using Ombi.Core.Services; +using System.Collections.Generic; namespace Ombi.Controllers.V2 { @@ -24,15 +26,17 @@ namespace Ombi.Controllers.V2 private readonly IMusicRequestEngine _musicRequestEngine; private readonly IVoteEngine _voteEngine; private readonly ILogger _logger; + private readonly IRecentlyRequestedService _recentlyRequestedService; public RequestsController(IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IMusicRequestEngine musicRequestEngine, - IVoteEngine voteEngine, ILogger logger) + IVoteEngine voteEngine, ILogger logger, IRecentlyRequestedService recentlyRequestedService) { _movieRequestEngine = movieRequestEngine; _tvRequestEngine = tvRequestEngine; _musicRequestEngine = musicRequestEngine; _voteEngine = voteEngine; _logger = logger; + _recentlyRequestedService = recentlyRequestedService; } /// @@ -223,6 +227,12 @@ namespace Ombi.Controllers.V2 return await _movieRequestEngine.RequestCollection(collectionId, HttpContext.RequestAborted); } + [HttpGet("recentlyRequested")] + public Task> RecentlyRequested() + { + return _recentlyRequestedService.GetRecentlyRequested(CancellationToken); + } + private string GetApiAlias() { // Make sure this only applies when using the API KEY