From ff04d875343604c77c391bf55d0968977e480281 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 14 Sep 2022 20:39:48 +0100 Subject: [PATCH] feat: Recently requested improvements (#4755) * feat(discover): :sparkles: Admins can now approve the Recently Requested list * feat(discover): :zap: Images for the recently requested area are now loading faster and just better all around * test: :white_check_mark: Added automation for the new feature --- .../Services/RecentlyRequestedServiceTests.cs | 9 +-- .../Models/Requests/RecentlyRequestedModel.cs | 3 + .../Services/RecentlyRequestedService.cs | 47 ++++++++++-- src/Ombi.TheMovieDbApi/IMovieDbApi.cs | 3 +- src/Ombi.TheMovieDbApi/Models/TvImages.cs | 2 +- src/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 14 +++- .../components/button/button.component.scss | 18 ----- .../app/components/button/button.component.ts | 3 +- .../detailed-card.component.html | 9 ++- .../detailed-card.component.scss | 11 ++- .../detailed-card.component.stories.ts | 57 +++++++++++++++ .../detailed-card/detailed-card.component.ts | 45 +++++++----- .../recently-requested-list.component.html | 4 +- .../recently-requested-list.component.ts | 70 ++++++++++++------ .../src/app/interfaces/IRecentlyRequested.ts | 1 + src/Ombi/databasea.json | 14 ---- .../page-objects/discover/discover.page.ts | 6 +- .../discover-recently-requested.spec.ts | 71 +++++++++++++++++++ 18 files changed, 292 insertions(+), 95 deletions(-) delete mode 100644 src/Ombi/ClientApp/src/app/components/button/button.component.scss delete mode 100644 src/Ombi/databasea.json diff --git a/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs b/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs index c3a9cbcdd..fd67e4e33 100644 --- a/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs +++ b/src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs @@ -37,6 +37,9 @@ namespace Ombi.Core.Tests.Services .ForEach(b => _fixture.Behaviors.Remove(b)); _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); _mocker = new AutoMocker(); + + _mocker.Setup>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias", Language = "en" }); + _mocker.Setup(x => x.Username).Returns("test"); _subject = _mocker.CreateInstance(); } @@ -70,8 +73,6 @@ namespace Ombi.Core.Tests.Services _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); - _mocker.Setup>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias" }); - _mocker.Setup(x => x.Username).Returns("test"); var result = await _subject.GetRecentlyRequested(CancellationToken.None); @@ -134,8 +135,6 @@ namespace Ombi.Core.Tests.Services _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); - _mocker.Setup>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias" }); - _mocker.Setup(x => x.Username).Returns("test"); var result = await _subject.GetRecentlyRequested(CancellationToken.None); @@ -167,8 +166,6 @@ namespace Ombi.Core.Tests.Services _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); - _mocker.Setup>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias" }); - _mocker.Setup(x => x.Username).Returns("test"); var result = await _subject.GetRecentlyRequested(CancellationToken.None); diff --git a/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs b/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs index cf5716436..4296c2cc1 100644 --- a/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs +++ b/src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs @@ -17,5 +17,8 @@ namespace Ombi.Core.Models.Requests public DateTime ReleaseDate { get; set; } public bool Approved { get; set; } public string MediaId { get; set; } + + public string PosterPath { get; set; } + public string Background { get; set; } } } diff --git a/src/Ombi.Core/Services/RecentlyRequestedService.cs b/src/Ombi.Core/Services/RecentlyRequestedService.cs index 2c5b2f43b..1c67472d4 100644 --- a/src/Ombi.Core/Services/RecentlyRequestedService.cs +++ b/src/Ombi.Core/Services/RecentlyRequestedService.cs @@ -1,4 +1,5 @@ using Microsoft.EntityFrameworkCore; +using Ombi.Api.TheMovieDb; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Helpers; @@ -12,7 +13,6 @@ using Ombi.Store.Repository.Requests; using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using static Ombi.Core.Engine.BaseMediaEngine; @@ -26,7 +26,8 @@ namespace Ombi.Core.Services private readonly IMusicRequestRepository _musicRequestRepository; private readonly ISettingsService _customizationSettings; private readonly ISettingsService _ombiSettings; - + private readonly IMovieDbApi _movieDbApi; + private readonly ICacheService _cache; private const int AmountToTake = 7; public RecentlyRequestedService( @@ -37,13 +38,17 @@ namespace Ombi.Core.Services ISettingsService ombiSettings, ICurrentUser user, OmbiUserManager um, - IRuleEvaluator rules) : base(user, um, rules) + IRuleEvaluator rules, + IMovieDbApi movieDbApi, + ICacheService cache) : base(user, um, rules) { _movieRequestRepository = movieRequestRepository; _tvRequestRepository = tvRequestRepository; _musicRequestRepository = musicRequestRepository; _customizationSettings = customizationSettings; _ombiSettings = ombiSettings; + _movieDbApi = movieDbApi; + _cache = cache; } public async Task> GetRecentlyRequested(CancellationToken cancellationToken) @@ -65,8 +70,10 @@ namespace Ombi.Core.Services var model = new List(); + var lang = await DefaultLanguageCode(); foreach (var item in await recentMovieRequests.ToListAsync(cancellationToken)) { + var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}movie{item.TheMovieDbId}", () => _movieDbApi.GetMovieImages(item.TheMovieDbId.ToString(), cancellationToken), DateTimeOffset.Now.AddDays(1)); model.Add(new RecentlyRequestedModel { RequestId = item.Id, @@ -80,6 +87,8 @@ namespace Ombi.Core.Services UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId, Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias, MediaId = item.TheMovieDbId.ToString(), + PosterPath = images?.posters?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(), + Background = images?.backdrops?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(), }); } @@ -103,6 +112,9 @@ namespace Ombi.Core.Services foreach (var item in await recentTvRequests.ToListAsync(cancellationToken)) { + var providerId = item.ParentRequest.ExternalProviderId.ToString(); + var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}tv{providerId}", () => _movieDbApi.GetTvImages(providerId.ToString(), cancellationToken), DateTimeOffset.Now.AddDays(1)); + var partialAvailability = item.SeasonRequests.SelectMany(x => x.Episodes).Any(e => e.Available); model.Add(new RecentlyRequestedModel { @@ -117,7 +129,9 @@ namespace Ombi.Core.Services Type = RequestType.TvShow, UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId, Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias, - MediaId = item.ParentRequest.ExternalProviderId.ToString() + MediaId = providerId.ToString(), + PosterPath = images?.posters?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(), + Background = images?.backdrops?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(), }); } @@ -134,7 +148,7 @@ namespace Ombi.Core.Services UserId = user.Id }; } - var settings = await _ombiSettings.GetSettingsAsync(); + var settings = await GetOmbiSettings(); var result = new HideResult { Hide = settings.HideRequestsUsers, @@ -142,5 +156,28 @@ namespace Ombi.Core.Services }; return result; } + protected async Task DefaultLanguageCode() + { + var user = await GetUser(); + if (user == null) + { + return "en"; + } + + if (string.IsNullOrEmpty(user.Language)) + { + var s = await GetOmbiSettings(); + return s.DefaultLanguageCode; + } + + return user.Language; + } + + + private OmbiSettings ombiSettings; + protected async Task GetOmbiSettings() + { + return ombiSettings ??= await _ombiSettings.GetSettingsAsync(); + } } } diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index 44dea2656..74f420d39 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -46,6 +46,7 @@ namespace Ombi.Api.TheMovieDb Task> GetLanguages(CancellationToken cancellationToken); Task> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken); Task> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken); - Task GetTvImages(string theMovieDbId, CancellationToken token); + Task GetTvImages(string theMovieDbId, CancellationToken token); + Task GetMovieImages(string theMovieDbId, CancellationToken token); } } diff --git a/src/Ombi.TheMovieDbApi/Models/TvImages.cs b/src/Ombi.TheMovieDbApi/Models/TvImages.cs index 2a235d10d..c87c94ce8 100644 --- a/src/Ombi.TheMovieDbApi/Models/TvImages.cs +++ b/src/Ombi.TheMovieDbApi/Models/TvImages.cs @@ -1,6 +1,6 @@ namespace Ombi.Api.TheMovieDb.Models { - public class TvImages + public class MovieDbImages { public Backdrop[] backdrops { get; set; } public int id { get; set; } diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 0db03fef3..b54057c4a 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -514,12 +514,20 @@ namespace Ombi.Api.TheMovieDb return Api.Request(request, token); } - public Task GetTvImages(string theMovieDbId, CancellationToken token) + public Task GetTvImages(string theMovieDbId, CancellationToken token) { var request = new Request($"tv/{theMovieDbId}/images", BaseUri, HttpMethod.Get); request.AddQueryString("api_key", ApiToken); - - return Api.Request(request, token); + + return Api.Request(request, token); + } + + public Task GetMovieImages(string theMovieDbId, CancellationToken token) + { + var request = new Request($"movie/{theMovieDbId}/images", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + + return Api.Request(request, token); } private async Task AddDiscoverSettings(Request request) diff --git a/src/Ombi/ClientApp/src/app/components/button/button.component.scss b/src/Ombi/ClientApp/src/app/components/button/button.component.scss deleted file mode 100644 index bb360e287..000000000 --- a/src/Ombi/ClientApp/src/app/components/button/button.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -.detailed-container { - width: 400px; - - - - - ::ng-deep .poster { - border-radius: 10px; - opacity: 1; - display: block; - width: 100%; - height: auto; - transition: .5s ease; - backface-visibility: hidden; - } - -} - diff --git a/src/Ombi/ClientApp/src/app/components/button/button.component.ts b/src/Ombi/ClientApp/src/app/components/button/button.component.ts index 3e14f6aa6..7af30e6b6 100644 --- a/src/Ombi/ClientApp/src/app/components/button/button.component.ts +++ b/src/Ombi/ClientApp/src/app/components/button/button.component.ts @@ -7,7 +7,6 @@ import { MatButtonModule } from "@angular/material/button"; selector: 'ombi-button', imports: [...OmbiCommonModules, MatButtonModule], changeDetection: ChangeDetectionStrategy.OnPush, - styleUrls: ['./button.component.scss'], template: ` ` @@ -17,7 +16,7 @@ import { MatButtonModule } from "@angular/material/button"; @Input() public text: string; @Input() public id: string; - @Input() public type: string; + @Input() public type: string = "primary"; @Input() public class: string; @Input('data-toggle') public dataToggle: string; @Input('data-target') public dataTarget: string; diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html index 9cbfb09a8..454874490 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html @@ -1,7 +1,7 @@ -
+
- +
@@ -20,6 +20,11 @@

{{'MediaDetails.Status' | translate}} {{getStatus(request) | translate}}

+
+
+ +
+
diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss index 3dd9685e8..ce1d35bfa 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss @@ -2,7 +2,7 @@ .detailed-container { width: 400px; - height: auto; + height: 250px; margin: 10px; padding: 10px; border-radius: 10px; @@ -10,6 +10,7 @@ @media (max-width:768px) { width: 200px; + height: auto; } background-color: $ombi-background-accent; @@ -36,12 +37,18 @@ border-radius: 10px; opacity: 1; display: block; + height: 225px; width: 100%; - height: 200px; transition: .5s ease; backface-visibility: hidden; border: 1px solid #35465c; } + .action-items { + @media (min-width:768px) { + bottom: 0; + position: absolute; + } + } } diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts index 239c5ab33..cf1d1db5a 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts @@ -67,6 +67,7 @@ NewMovieRequest.args = { overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.', releaseDate: new Date(2020, 1, 1), } as IRecentlyRequested, + isAdmin: false, }; export const MovieNoUsername = Template.bind({}); @@ -206,4 +207,60 @@ TvNoUsername.args = { mediaId: '603', releaseDate: new Date(2020, 1, 1), } as IRecentlyRequested, +}; + +export const AdminNewMovie = Template.bind({}); +// More on args: https://storybook.js.org/docs/angular/writing-stories/args +AdminNewMovie.args = { + request: { + title: 'The Matrix', + approved: false, + available: false, + tvPartiallyAvailable: false, + requestDate: new Date(2022, 1, 1), + username: 'John Doe', + userId: '12345', + type: RequestType.movie, + mediaId: '603', + overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.', + releaseDate: new Date(2020, 1, 1), + } as IRecentlyRequested, + isAdmin: true, +}; + +export const AdminTvShow = Template.bind({}); +// More on args: https://storybook.js.org/docs/angular/writing-stories/args +AdminTvShow.args = { + request: { + title: 'For All Mankind', + approved: false, + available: false, + tvPartiallyAvailable: true, + requestDate: new Date(2022, 1, 1), + userId: '12345', + type: RequestType.tvShow, + mediaId: '603', + username: 'John Doe', + releaseDate: new Date(2020, 1, 1), + } as IRecentlyRequested, + isAdmin: true, +}; + +export const AdminApprovedMovie = Template.bind({}); +// More on args: https://storybook.js.org/docs/angular/writing-stories/args +AdminApprovedMovie.args = { + request: { + title: 'The Matrix', + approved: true, + available: false, + tvPartiallyAvailable: false, + requestDate: new Date(2022, 1, 1), + username: 'John Doe', + userId: '12345', + type: RequestType.movie, + mediaId: '603', + overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.', + releaseDate: new Date(2020, 1, 1), + } as IRecentlyRequested, + isAdmin: true, }; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts index e4f1a092d..49f7603a8 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { IRecentlyRequested, RequestType } from "../../interfaces"; import { ImageService } from "app/services"; import { Subject, takeUntil } from "rxjs"; @@ -7,14 +7,14 @@ import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; @Component({ standalone: false, selector: 'ombi-detailed-card', - changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './detailed-card.component.html', styleUrls: ['./detailed-card.component.scss'] }) export class DetailedCardComponent implements OnInit, OnDestroy { @Input() public request: IRecentlyRequested; - + @Input() public isAdmin: boolean = false; @Output() public onClick: EventEmitter = new EventEmitter(); + @Output() public onApprove: EventEmitter = new EventEmitter(); public RequestType = RequestType; public loading: false; @@ -27,20 +27,10 @@ import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; ngOnInit(): void { if (!this.request.posterPath) { - switch (this.request.type) { - case RequestType.movie: - this.imageService.getMoviePoster(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = x); - this.imageService.getMovieBackground(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => { - this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(" + x + ")"); - }); - break; - case RequestType.tvShow: - this.imageService.getTmdbTvPoster(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = `https://image.tmdb.org/t/p/w300${x}`); - this.imageService.getTmdbTvBackground(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => { - this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + x + ")"); - }); - break; - } + this.loadImages(); + } else { + this.request.posterPath = `https://image.tmdb.org/t/p/w300${this.request.posterPath}`; + this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + this.request.background + ")"); } } @@ -62,6 +52,10 @@ import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; this.onClick.emit(); } + public approve() { + this.onApprove.emit(); + } + public getClass(request: IRecentlyRequested) { if (request.available || request.tvPartiallyAvailable) { return "success"; @@ -78,4 +72,21 @@ import { DomSanitizer, SafeStyle } from "@angular/platform-browser"; this.$imageSub.complete(); } + private loadImages() { + switch (this.request.type) { + case RequestType.movie: + this.imageService.getMoviePoster(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = x); + this.imageService.getMovieBackground(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => { + this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(" + x + ")"); + }); + break; + case RequestType.tvShow: + this.imageService.getTmdbTvPoster(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = `https://image.tmdb.org/t/p/w300${x}`); + this.imageService.getTmdbTvBackground(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => { + this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + x + ")"); + }); + break; + } + } + } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html index 561eec552..f1b2a8057 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html @@ -1,5 +1,5 @@ - + - + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts index 866284cb2..e869abc13 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts @@ -1,15 +1,13 @@ -import { Component, OnInit, Input, ViewChild, Output, EventEmitter, OnDestroy } from "@angular/core"; -import { DiscoverOption, IDiscoverCardResult } from "../../interfaces"; -import { IRecentlyRequested, ISearchMovieResult, ISearchTvResult, RequestType } from "../../../interfaces"; -import { SearchV2Service } from "../../../services"; -import { StorageService } from "../../../shared/storage/storage-service"; -import { MatButtonToggleChange } from '@angular/material/button-toggle'; +import { Component, OnInit, Input, ViewChild, OnDestroy } from "@angular/core"; +import { IRecentlyRequested, IRequestEngineResult, RequestType } from "../../../interfaces"; import { Carousel } from 'primeng/carousel'; -import { FeaturesFacade } from "../../../state/features/features.facade"; import { ResponsiveOptions } from "../carousel.options"; import { RequestServiceV2 } from "app/services/requestV2.service"; -import { Subject, takeUntil } from "rxjs"; +import { finalize, map, Observable, Subject, takeUntil, tap } from "rxjs"; import { Router } from "@angular/router"; +import { AuthService } from "app/auth/auth.service"; +import { NotificationService, RequestService } from "app/services"; +import { TranslateService } from "@ngx-translate/core"; export enum DiscoverType { Upcoming, @@ -30,21 +28,22 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy { @Input() public isAdmin: boolean; @ViewChild('carousel', {static: false}) carousel: Carousel; - - public requests: IRecentlyRequested[]; + public requests$: Observable; public responsiveOptions: any; public RequestType = RequestType; public loadingFlag: boolean; public DiscoverType = DiscoverType; - public is4kEnabled = false; private $loadSub = new Subject(); - constructor(private requestService: RequestServiceV2, - private featureFacade: FeaturesFacade, - private router: Router) { - Carousel.prototype.onTouchMove = () => {}, + constructor(private requestServiceV2: RequestServiceV2, + private requestService: RequestService, + private router: Router, + private authService: AuthService, + private notificationService: NotificationService, + private translateService: TranslateService) { + Carousel.prototype.onTouchMove = () => {}; this.responsiveOptions = ResponsiveOptions; } @@ -54,14 +53,43 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy { } public ngOnInit() { - this.loading(); this.loadData(); + this.isAdmin = this.authService.isAdmin(); } public navigate(request: IRecentlyRequested) { this.router.navigate([this.generateDetailsLink(request), request.mediaId]); } + public approve(request: IRecentlyRequested) { + switch(request.type) { + case RequestType.movie: + this.requestService.approveMovie({id: request.requestId, is4K: false}).pipe( + map((res) => this.handleApproval(res, request)) + ).subscribe(); + break; + case RequestType.tvShow: + this.requestService.approveChild({id: request.requestId}).pipe( + tap((res) => this.handleApproval(res, request)) + ).subscribe(); + break; + case RequestType.album: + this.requestService.approveAlbum({id: request.requestId}).pipe( + tap((res) => this.handleApproval(res, request)) + ).subscribe(); + break; + } + } + + private handleApproval(result: IRequestEngineResult, request: IRecentlyRequested) { + if (result.result) { + this.notificationService.success(this.translateService.instant("Requests.SuccessfullyApproved")); + request.approved = true; + } else { + this.notificationService.error(result.errorMessage); + } + } + private generateDetailsLink(request: IRecentlyRequested): string { switch (request.type) { case RequestType.movie: @@ -74,13 +102,13 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy { } private loadData() { - this.requestService.getRecentlyRequested().pipe(takeUntil(this.$loadSub)).subscribe(x => { - this.requests = x; - this.finishLoading(); - }); + this.requests$ = this.requestServiceV2.getRecentlyRequested().pipe( + tap(() => this.loading()), + takeUntil(this.$loadSub), + finalize(() => this.finishLoading()) + ); } - private loading() { this.loadingFlag = true; } diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts b/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts index 420c8c6a7..9cd5dc76e 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts @@ -15,4 +15,5 @@ export interface IRecentlyRequested { type: RequestType; posterPath: string; + background: string; } \ No newline at end of file diff --git a/src/Ombi/databasea.json b/src/Ombi/databasea.json deleted file mode 100644 index c849cd56a..000000000 --- a/src/Ombi/databasea.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "OmbiDatabase": { - "Type": "MySQL", - "ConnectionString": "Server=192.168.68.118;Port=3306;Database=Ombi;User=ombi;" - }, - "SettingsDatabase": { - "Type": "MySQL", - "ConnectionString": "Server=192.168.68.118;Port=3306;Database=Ombi;User=ombi;" - }, - "ExternalDatabase": { - "Type": "MySQL", - "ConnectionString": "Server=192.168.68.118;Port=3306;Database=Ombi;User=ombi;" - } -} diff --git a/tests/cypress/integration/page-objects/discover/discover.page.ts b/tests/cypress/integration/page-objects/discover/discover.page.ts index ef15f0d29..3f1456817 100644 --- a/tests/cypress/integration/page-objects/discover/discover.page.ts +++ b/tests/cypress/integration/page-objects/discover/discover.page.ts @@ -43,9 +43,13 @@ class DetailedCard { return cy.get(`#detailed-request-status-${this.id}`); } + get approveButton(): Cypress.Chainable { + return cy.get(`#detailed-request-approve-${this.id}`); + } + verifyTitle(expected: string): Cypress.Chainable { return this.title.should('have.text',expected); -} + } constructor(id: string) { this.id = id; diff --git a/tests/cypress/tests/discover/discover-recently-requested.spec.ts b/tests/cypress/tests/discover/discover-recently-requested.spec.ts index 9eff61475..b8bceb088 100644 --- a/tests/cypress/tests/discover/discover-recently-requested.spec.ts +++ b/tests/cypress/tests/discover/discover-recently-requested.spec.ts @@ -44,6 +44,7 @@ describe("Discover Recently Requested Tests", () => { const card = Page.recentlyRequested.getRequest("626735"); card.verifyTitle("Dog"); card.status.should('contain.text', 'Pending'); + card.approveButton.should('be.visible'); }); }); @@ -69,6 +70,7 @@ describe("Discover Recently Requested Tests", () => { const card = Page.recentlyRequested.getRequest("675353"); card.verifyTitle("Sonic the Hedgehog 2"); card.status.should('contain.text', 'Available'); // Because admin auto request + card.approveButton.should('not.exist'); }); }); @@ -94,6 +96,7 @@ describe("Discover Recently Requested Tests", () => { const card = Page.recentlyRequested.getRequest("135647"); card.verifyTitle("2 Good 2 Be True"); card.status.should('contain.text', 'Available'); + card.approveButton.should('not.exist'); }); }); @@ -119,6 +122,7 @@ describe("Discover Recently Requested Tests", () => { const card = Page.recentlyRequested.getRequest("158415"); card.verifyTitle("Pantanal"); card.status.should('contain.text', 'Partially Available'); + card.approveButton.should('not.exist'); }); }); @@ -143,6 +147,7 @@ describe("Discover Recently Requested Tests", () => { const card = Page.recentlyRequested.getRequest("60574"); card.verifyTitle("Peaky Blinders"); card.status.should('contain.text', 'Pending'); + card.approveButton.should('be.visible'); }); }); @@ -161,4 +166,70 @@ describe("Discover Recently Requested Tests", () => { }); }); + it("Approve Requested Movie", () => { + + cy.requestMovie(55341); + + cy.intercept("GET", "**/v2/Requests/recentlyRequested", (req) => { + req.reply((res) => { + const body = res.body; + const movie = body[0]; + movie.available = false; + movie.approved = false; + + body[0] = movie; + res.send(body); + }); + }).as("response"); + + cy.intercept("POST", "**/v1/Request/Movie/Approve").as("approveCall"); + + Page.visit(); + + cy.wait("@response").then((_) => { + + const card = Page.recentlyRequested.getRequest("55341"); + card.approveButton.should('be.visible'); + card.approveButton.click(); + + cy.wait("@approveCall").then((_) => { + card.status.should('contain.text', 'Approved'); + }); + + }); + }); + + it.only("Approve Requested Tv Show", () => { + + cy.requestAllTv(71712); + + cy.intercept("GET", "**/v2/Requests/recentlyRequested", (req) => { + req.reply((res) => { + const body = res.body; + const movie = body[0]; + movie.available = false; + movie.approved = false; + + body[0] = movie; + res.send(body); + }); + }).as("response"); + + cy.intercept("POST", "**/v1/Request/tv/approve").as("approveCall"); + + Page.visit(); + + cy.wait("@response").then((_) => { + + const card = Page.recentlyRequested.getRequest("71712"); + card.approveButton.should('be.visible'); + card.approveButton.click(); + + cy.wait("@approveCall").then((_) => { + card.status.should('contain.text', 'Approved'); + }); + + }); + }); + });