diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index c9dbde067..00a97f766 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -67,21 +67,27 @@ namespace Ombi.Core.Engine $"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}"; var userDetails = await GetUser(); - var canRequestOnBehalf = false; + var canRequestOnBehalf = model.RequestOnBehalf.HasValue(); - if (model.RequestOnBehalf.HasValue()) + var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); + if (model.RequestOnBehalf.HasValue() && !isAdmin) { - canRequestOnBehalf = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); + return new RequestEngineResult + { + Result = false, + Message = "You do not have the correct permissions to request on behalf of users!", + ErrorMessage = $"You do not have the correct permissions to request on behalf of users!" + }; + } - if (!canRequestOnBehalf) + if ((model.RootFolderOverride.HasValue || model.QualityPathOverride.HasValue) && !isAdmin) + { + return new RequestEngineResult { - return new RequestEngineResult - { - Result = false, - Message = "You do not have the correct permissions to request on behalf of users!", - ErrorMessage = $"You do not have the correct permissions to request on behalf of users!" - }; - } + Result = false, + Message = "You do not have the correct permissions!", + ErrorMessage = $"You do not have the correct permissions!" + }; } var requestModel = new MovieRequests @@ -101,7 +107,9 @@ namespace Ombi.Core.Engine RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id, Background = movieInfo.BackdropPath, LangCode = model.LanguageCode, - RequestedByAlias = model.RequestedByAlias + RequestedByAlias = model.RequestedByAlias, + RootPathOverride = model.RootFolderOverride.GetValueOrDefault(), + QualityOverride = model.QualityPathOverride.GetValueOrDefault() }; var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 30d6c4e79..9a2ccbf1e 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -140,7 +140,7 @@ namespace Ombi.Core.Engine ErrorMessage = "This has already been requested" }; } - return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf); + return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault()); } // This is a new request @@ -151,21 +151,27 @@ namespace Ombi.Core.Engine public async Task RequestTvShow(TvRequestViewModelV2 tv) { var user = await GetUser(); - var canRequestOnBehalf = false; + var canRequestOnBehalf = tv.RequestOnBehalf.HasValue(); - if (tv.RequestOnBehalf.HasValue()) + var isAdmin = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin); + if (tv.RequestOnBehalf.HasValue() && !isAdmin) { - canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin); + return new RequestEngineResult + { + Result = false, + Message = "You do not have the correct permissions to request on behalf of users!", + ErrorMessage = $"You do not have the correct permissions to request on behalf of users!" + }; + } - if (!canRequestOnBehalf) + if ((tv.RootFolderOverride.HasValue || tv.QualityPathOverride.HasValue) && !isAdmin) + { + return new RequestEngineResult { - return new RequestEngineResult - { - Result = false, - Message = "You do not have the correct permissions to request on behalf of users!", - ErrorMessage = $"You do not have the correct permissions to request on behalf of users!" - }; - } + Result = false, + Message = "You do not have the correct permissions!", + ErrorMessage = $"You do not have the correct permissions!" + }; } var tvBuilder = new TvShowRequestBuilderV2(MovieDbApi); @@ -240,11 +246,11 @@ namespace Ombi.Core.Engine ErrorMessage = "This has already been requested" }; } - return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf); + return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault()); } // This is a new request - var newRequest = tvBuilder.CreateNewRequest(tv); + var newRequest = tvBuilder.CreateNewRequest(tv, tv.RootFolderOverride.GetValueOrDefault(), tv.QualityPathOverride.GetValueOrDefault()); return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf); } @@ -852,10 +858,18 @@ namespace Ombi.Core.Engine } } - private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf) + private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf, int rootFolder, int qualityProfile) { // Add the child existingRequest.ChildRequests.Add(newRequest); + if (qualityProfile > 0) + { + existingRequest.QualityOverride = qualityProfile; + } + if (rootFolder > 0) + { + existingRequest.RootFolder = rootFolder; + } await TvRepository.Update(existingRequest); diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs index e6724b482..02150cc4d 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs @@ -217,7 +217,7 @@ namespace Ombi.Core.Helpers } - public TvShowRequestBuilderV2 CreateNewRequest(TvRequestViewModelV2 tv) + public TvShowRequestBuilderV2 CreateNewRequest(TvRequestViewModelV2 tv, int rootPathOverride, int qualityOverride) { int.TryParse(TheMovieDbRecord.ExternalIds?.TvDbId, out var tvdbId); NewRequest = new TvRequests @@ -232,7 +232,9 @@ namespace Ombi.Core.Helpers TvDbId = tvdbId, ChildRequests = new List(), TotalSeasons = tv.Seasons.Count(), - Background = BackdropPath + Background = BackdropPath, + RootFolder = rootPathOverride, + QualityOverride = qualityOverride }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Core/Models/Requests/BaseRequestOptions.cs b/src/Ombi.Core/Models/Requests/BaseRequestOptions.cs new file mode 100644 index 000000000..55868df44 --- /dev/null +++ b/src/Ombi.Core/Models/Requests/BaseRequestOptions.cs @@ -0,0 +1,39 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2018 Jamie Rees +// File: MovieRequestViewModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using Newtonsoft.Json; + +namespace Ombi.Core.Models.Requests +{ + + public class BaseRequestOptions + { + public string RequestOnBehalf { get; set; } + public int? RootFolderOverride { get; set; } + public int? QualityPathOverride { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs index 22d1cc449..05eec3e3f 100644 --- a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs @@ -29,11 +29,10 @@ using Newtonsoft.Json; namespace Ombi.Core.Models.Requests { - public class MovieRequestViewModel + public class MovieRequestViewModel : BaseRequestOptions { public int TheMovieDbId { get; set; } public string LanguageCode { get; set; } = "en"; - public string RequestOnBehalf { get; set; } /// /// This is only set from a HTTP Header diff --git a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs index 95eb77735..960b30ab9 100644 --- a/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/TvRequestViewModel.cs @@ -20,7 +20,7 @@ namespace Ombi.Core.Models.Requests } - public class TvRequestViewModelBase + public class TvRequestViewModelBase : BaseRequestOptions { public bool RequestAll { get; set; } public bool LatestSeason { get; set; } @@ -28,7 +28,5 @@ namespace Ombi.Core.Models.Requests public List Seasons { get; set; } = new List(); [JsonIgnore] public string RequestedByAlias { get; set; } - - public string RequestOnBehalf { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs index ee1d94509..47088b894 100644 --- a/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs +++ b/src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs @@ -17,7 +17,7 @@ namespace Ombi.Core.Rule.Rules.Search { // If we have all the episodes for this season, then this season is available if (season.Episodes.All(x => x.Available)) - {yarn + { season.SeasonAvailable = true; } } @@ -25,11 +25,12 @@ namespace Ombi.Core.Rule.Rules.Search { search.FullyAvailable = true; } - else if (search.SeasonRequests.Any(x => x.Episodes.Any(e => e.Available))) + else if (search.SeasonRequests.Any(x => x.Episodes.Any(e => e.Available))) { search.PartlyAvailable = true; - } - else + } + + if (!search.FullyAvailable) { var airedButNotAvailable = search.SeasonRequests.Any(x => x.Episodes.Any(c => !c.Available && c.AirDate <= DateTime.Now.Date && c.AirDate != DateTime.MinValue)); diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 5d5c48555..9cb98a40b 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -208,6 +208,10 @@ namespace Ombi.Core.Senders { qualityToUse = model.ParentRequest.QualityOverride.Value; } + if (model.ParentRequest.RootFolder.HasValue) + { + rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder.Value, s); + } // Are we using v3 sonarr? var sonarrV3 = s.V3; diff --git a/src/Ombi.Helpers/CacheKeys.cs b/src/Ombi.Helpers/CacheKeys.cs index f7a40d321..89faead8a 100644 --- a/src/Ombi.Helpers/CacheKeys.cs +++ b/src/Ombi.Helpers/CacheKeys.cs @@ -21,5 +21,6 @@ namespace Ombi.Helpers public const string LidarrRootFolders = nameof(LidarrRootFolders); public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles); public const string FanartTv = nameof(FanartTv); + public const string UsersDropdown = nameof(UsersDropdown); } } diff --git a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html index 1f17faf59..a7f6a7847 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.html @@ -5,7 +5,7 @@
- +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts index 83d4d364c..8cdb90436 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/actor/discover-actor.component.ts @@ -1,25 +1,29 @@ -import { Component, AfterViewInit } from "@angular/core"; +import { Component } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { SearchV2Service } from "../../../services"; import { IActorCredits } from "../../../interfaces/ISearchTvResultV2"; import { IDiscoverCardResult } from "../../interfaces"; import { RequestType } from "../../../interfaces"; +import { AuthService } from "../../../auth/auth.service"; @Component({ templateUrl: "./discover-actor.component.html", styleUrls: ["./discover-actor.component.scss"], }) -export class DiscoverActorComponent implements AfterViewInit { +export class DiscoverActorComponent { public actorId: number; public actorCredits: IActorCredits; public loadingFlag: boolean; + public isAdmin: boolean; public discoverResults: IDiscoverCardResult[] = []; constructor(private searchService: SearchV2Service, - private route: ActivatedRoute) { + private route: ActivatedRoute, + private auth: AuthService) { this.route.params.subscribe((params: any) => { this.actorId = params.actorId; + this.isAdmin = this.auth.isAdmin(); this.loading(); this.searchService.getMoviesByActor(this.actorId).subscribe(res => { this.actorCredits = res; @@ -28,18 +32,6 @@ export class DiscoverActorComponent implements AfterViewInit { }); } - public async ngAfterViewInit() { - // this.discoverResults.forEach((result) => { - // this.searchService.getFullMovieDetails(result.id).subscribe(x => { - // result.available = x.available; - // result.approved = x.approved; - // result.rating = x.voteAverage; - // result.requested = x.requested; - // result.url = x.homepage; - // }); - // }); - } - private createModel() { this.finishLoading(); this.discoverResults = []; diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.html b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.html deleted file mode 100644 index 3ac12d570..000000000 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.html +++ /dev/null @@ -1,171 +0,0 @@ -
- -
-
- -
-
- - {{data.title}} - -
-
-
- - - -
-
-
-

{{data.title}}

-
- -
-
-
- {{'Discovery.CardDetails.Availability' | translate}}: - - - -
-
- {{'Discovery.CardDetails.Studio' | translate}}: - {{movie.productionCompanies[0].name}} - {{'Discovery.CardDetails.Network' | translate}}: - {{tv.network.name}} - {{'Discovery.CardDetails.UnknownNetwork' | translate}} -
-
- {{'Discovery.CardDetails.RequestStatus' | translate}}: - - - - - -
-
- {{'Discovery.CardDetails.Director' | translate}}: - {{movie.credits.crew[0].name}} - Director: - {{tvCreator}} -
-
- {{'Discovery.CardDetails.InCinemas' | translate}}: - {{movie.releaseDate | amLocal | amDateFormat: 'LL'}} - {{'Discovery.CardDetails.FirstAired' | translate}}: - {{tv.firstAired | amLocal | amDateFormat: 'LL'}} -
-
- {{'Discovery.CardDetails.Writer' | translate}}: - {{movie.credits.crew[1].name}} - {{'Discovery.CardDetails.ExecProducer' | translate}}: - {{tvProducer}} -
-
- -
-
- {{data.overview}} -
-
- - -
- -
-
-
-
-
- - - - - - - - - - -
- -
- - - - - - - - {{'Search.ViewOnPlex' | - translate}} - {{'Search.ViewOnEmby' | - translate}} - {{'Search.ViewOnJellyfin' | - translate}} - - -
-
- -
diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.scss b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.scss deleted file mode 100644 index e7fe00c60..000000000 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.scss +++ /dev/null @@ -1,45 +0,0 @@ -@import "~styles/variables.scss"; -.poster { - max-width: 100%; - border-radius: 2%; -} - -.details { - padding: 2%; - border-radius: 10px; - background: $backgroundTint; - div.dark & { - background: $backgroundTint-dark; - } -} - -.details strong { - font-weight: bold; -} - - -h3 strong { - font-weight: bold; -} - -.action-buttons-right { - width: 100%; - text-align: right; -} - -.btn-spacing { - margin-right: 1%; -} - -.media-icons { - color: $primary; - padding: 2%; - div.dark & { - color: $warn-dark; - } -} - -.overview { - height:300px; - overflow-y: auto; -} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts deleted file mode 100644 index 804e0e9ec..000000000 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card-details.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, Inject, OnInit, ViewEncapsulation } from "@angular/core"; -import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from "@angular/material/dialog"; -import { IDiscoverCardResult } from "../../interfaces"; -import { SearchV2Service, RequestService, MessageService } from "../../../services"; -import { RequestType } from "../../../interfaces"; -import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; -import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2"; -import { Router } from "@angular/router"; -import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component"; - -@Component({ - selector: "discover-card-details", - templateUrl: "./discover-card-details.component.html", - styleUrls: ["./discover-card-details.component.scss"], - encapsulation: ViewEncapsulation.None, -}) -export class DiscoverCardDetailsComponent implements OnInit { - - public movie: ISearchMovieResultV2; - public tv: ISearchTvResultV2; - public tvCreator: string; - public tvProducer: string; - public loading: boolean; - public RequestType = RequestType; - - constructor( - public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: IDiscoverCardResult, private searchService: SearchV2Service, private dialog: MatDialog, - private requestService: RequestService, public messageService: MessageService, private router: Router) { } - - public async ngOnInit() { - this.loading = true; - if (this.data.type === RequestType.movie) { - this.movie = await this.searchService.getFullMovieDetailsPromise(+this.data.id); - } else if (this.data.type === RequestType.tvShow) { - this.tv = await this.searchService.getTvInfo(+this.data.id); - const creator = this.tv.crew.filter(tv => { - return tv.type === "Creator"; - })[0]; - if (creator && creator.person) { - this.tvCreator = creator.person.name; - } - const crewResult = this.tv.crew.filter(tv => { - return tv.type === "Executive Producer"; - })[0] - if (crewResult && crewResult.person) { - this.tvProducer = crewResult.person.name; - } - } - this.loading = false; - } - - public close(): void { - this.dialogRef.close(); - } - - public openDetails() { - if (this.data.type === RequestType.movie) { - this.router.navigate(['/details/movie/', this.data.id]); - } else if (this.data.type === RequestType.tvShow) { - this.router.navigate(['/details/tv/', this.data.id]); - } - - this.dialogRef.close(); - } - - public async request() { - this.loading = true; - if (this.data.type === RequestType.movie) { - const result = await this.requestService.requestMovie({ theMovieDbId: +this.data.id, languageCode: "", requestOnBehalf: null }).toPromise(); - this.loading = false; - - if (result.result) { - this.movie.requested = true; - this.messageService.send(result.message, "Ok"); - } else { - this.messageService.send(result.errorMessage, "Ok"); - } - } else if (this.data.type === RequestType.tvShow) { - this.dialog.open(EpisodeRequestComponent, { width: "700px", data: {series: this.tv }, panelClass: 'modal-panel' }) - } - this.loading = false; - - this.dialogRef.close(); - } -} diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html index 9a7ba1acc..2f5f00703 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html @@ -21,7 +21,7 @@
- diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts index 9ca9cfae6..17d3ad776 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts @@ -3,10 +3,11 @@ import { IDiscoverCardResult } from "../../interfaces"; import { RequestType } from "../../../interfaces"; import { MessageService, RequestService, SearchV2Service } from "../../../services"; import { MatDialog } from "@angular/material/dialog"; -import { DiscoverCardDetailsComponent } from "./discover-card-details.component"; import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2"; import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component"; +import { AdminRequestDialogComponent } from "../../../shared/admin-request-dialog/admin-request-dialog.component"; +import { DiscoverType } from "../carousel-list/carousel-list.component"; @Component({ selector: "discover-card", @@ -15,7 +16,9 @@ import { EpisodeRequestComponent } from "../../../shared/episode-request/episode }) export class DiscoverCardComponent implements OnInit { + @Input() public discoverType: DiscoverType; @Input() public result: IDiscoverCardResult; + @Input() public isAdmin: boolean; public RequestType = RequestType; public hide: boolean; public fullyLoaded = false; @@ -40,10 +43,6 @@ export class DiscoverCardComponent implements OnInit { } } - public openDetails(details: IDiscoverCardResult) { - this.dialog.open(DiscoverCardDetailsComponent, { width: "700px", data: details, panelClass: 'modal-panel' }) - } - public async getExtraTvInfo() { // if (this.result.tvMovieDb) { this.tvSearchResult = await this.searchService.getTvInfoWithMovieDbId(+this.result.id); @@ -121,11 +120,30 @@ export class DiscoverCardComponent implements OnInit { this.loading = true; switch (this.result.type) { case RequestType.tvShow: - const dia = this.dialog.open(EpisodeRequestComponent, { width: "700px", data: { series: this.tvSearchResult }, panelClass: 'modal-panel' }); + const dia = this.dialog.open(EpisodeRequestComponent, { width: "700px", data: { series: this.tvSearchResult, isAdmin: this.isAdmin }, panelClass: 'modal-panel' }); dia.afterClosed().subscribe(x => this.loading = false); return; case RequestType.movie: - this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: null, requestOnBehalf: null }).subscribe(x => { + if (this.isAdmin) { + const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id }, panelClass: 'modal-panel' }); + dialog.afterClosed().subscribe((result) => { + if (result) { + this.requestService.requestMovie({ theMovieDbId: +this.result.id, + languageCode: null, + qualityPathOverride: result.radarrPathId, + requestOnBehalf: result.username?.id, + rootFolderOverride: result.radarrFolderId, }).subscribe(x => { + if (x.result) { + this.result.requested = true; + this.messageService.send(x.message, "Ok"); + } else { + this.messageService.send(x.errorMessage, "Ok"); + } + }); + } + }); + } else { + this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: null, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null }).subscribe(x => { if (x.result) { this.result.requested = true; this.messageService.send(x.message, "Ok"); @@ -135,6 +153,7 @@ export class DiscoverCardComponent implements OnInit { this.loading = false; }); return; + } } } diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html index 71fa3ea63..73e68336e 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.html @@ -8,6 +8,6 @@ - + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts index ca8dfd44f..cfe3ef6dd 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts @@ -22,6 +22,7 @@ export class CarouselListComponent implements OnInit { @Input() public discoverType: DiscoverType; @Input() public id: string; + @Input() public isAdmin: boolean; @ViewChild('carousel', {static: false}) carousel: Carousel; public DiscoverOption = DiscoverOption; diff --git a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html index c78378a2d..4bc510b20 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html @@ -15,7 +15,7 @@
- +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts index dd634ef92..82593bab1 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.ts @@ -4,6 +4,7 @@ import { SearchV2Service, RequestService, MessageService } from "../../../servic import { IMovieCollectionsViewModel } from "../../../interfaces/ISearchTvResultV2"; import { IDiscoverCardResult } from "../../interfaces"; import { RequestType } from "../../../interfaces"; +import { AuthService } from "../../../auth/auth.service"; @Component({ templateUrl: "./discover-collections.component.html", @@ -14,13 +15,15 @@ export class DiscoverCollectionsComponent implements OnInit { public collectionId: number; public collection: IMovieCollectionsViewModel; public loadingFlag: boolean; - + public isAdmin: boolean; + public discoverResults: IDiscoverCardResult[] = []; constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private requestService: RequestService, - private messageService: MessageService) { + private messageService: MessageService, + private auth: AuthService) { this.route.params.subscribe((params: any) => { this.collectionId = params.collectionId; }); @@ -28,13 +31,14 @@ export class DiscoverCollectionsComponent implements OnInit { public async ngOnInit() { this.loadingFlag = true; + this.isAdmin = this.auth.isAdmin(); this.collection = await this.searchService.getMovieCollections(this.collectionId); this.createModel(); } public async requestCollection() { await this.collection.collection.forEach(async (movie) => { - await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null, requestOnBehalf: null}).toPromise(); + await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null}).toPromise(); }); this.messageService.send("Requested Collection"); } diff --git a/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html b/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html index fd114128e..701a6b204 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/discover/discover.component.html @@ -2,7 +2,7 @@

{{'Discovery.PopularTab' | translate}}

- +
@@ -10,7 +10,7 @@

{{'Discovery.TrendingTab' | translate}}

- +
@@ -18,7 +18,7 @@

{{'Discovery.UpcomingTab' | translate}}

- +
- - -
- -
-
- {{result.title}} -
-
-
-

{{result.title}}

-
-
- - - {{'Common.Available' | translate}} - - - - {{'Common.ProcessingRequest' | translate}} - - - - {{'Common.RequestDenied' | translate}} - - - - {{'Common.PendingApproval' | translate}} - - - - play_circle_outline - - - - play_circle_outline - - - - play_circle_outline - - - - - play_circle_outline - - - - play_circle_outline - - - - play_circle_outline - - -
-
- - - - {{'Discovery.CardDetails.Studio' | translate}}: {{movie.productionCompanies[0].name}} - - - {{'Discovery.CardDetails.Network' | translate}}: - {{tv.network.name}} - - - {{'Discovery.CardDetails.Director' | translate}}: {{movie.credits.crew[0].name}} - - Director: {{tvCreator}} - - {{'Discovery.CardDetails.InCinemas' | translate}}: - {{movie.releaseDate | amLocal | amDateFormat: 'LL'}} - - {{'Discovery.CardDetails.FirstAired' | translate}}: - {{tv.firstAired | amLocal | amDateFormat: 'LL'}} - - - {{'Discovery.CardDetails.Writer' | translate}}: {{movie.credits.crew[1].name}} - - {{'Discovery.CardDetails.ExecProducer' | translate}}: {{tvProducer}} - - - - - - -
-
-

{{result.overview}}

-
-
- -
-
- -
- - - - - - - - - - -
- -
- - - - - - - - {{'Search.ViewOnPlex' | - translate}} - {{'Search.ViewOnEmby' | - translate}} - {{'Search.ViewOnJellyfin' | - translate}} - -
-
-
-
-
-
diff --git a/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.scss b/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.scss deleted file mode 100644 index caecaddb0..000000000 --- a/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.scss +++ /dev/null @@ -1,137 +0,0 @@ -$ombi-primary:#3f3f3f; -$card-background: #2b2b2b; - -$blue: #1976D2; -$pink: #C2185B; -$green:#1DE9B6; -$orange:#F57C00; - -.btn-blue { - background-color: $blue; -} - -.btn-pink { - background-color: $pink; -} - -.btn-green { - background-color: $green; -} - -.btn-orange { - background-color: $orange; -} - -.btn-spacing { - margin-top:10%; -} - -#cardImage { - border-radius: 5px 5px 0px 0px; - height: 75%; -} - -.dark-card { - border-radius: 8px; -} - -// Changed height to 100% to make all cards the same height -.top-spacing { - margin-top: 1%; -} - -.card-poster { - width: 100%; - border-radius: 8px 0px 0px 8px; - margin-top: -6.5%; - margin-bottom: -6.6%; -} - -.main-container { - margin-left: -2%; -} - - -.rating { - position: absolute; - font-weight: bold; -} - -$border-width: 3px; - -.available { - background-color: #1DE9B6 !important; - color: black !important; -} - -.approved { - background-color: #ff5722 !important; -} - -.requested { - background-color: #ffd740 !important; - color: black !important; -} - -.denied { - background-color: #C2185B !important; -} - -.notrequested { - background-color: #303030 !important; -} - -.expand { - text-align: center; -} - -@media (min-width: 1025px) { - - // Changed height to 100% to make all cards the same height - .grow { - transition: all .2s ease-in-out; - height: 100%; - } - - .grow:hover { - transform: scale(1.1); - } -} - -::ng-deep mat-dialog-container.mat-dialog-container { - // background-color: $ombi-primary; - // color: white; - border-radius: 2% -} - - -/* Title adjust for the Discover page */ -.mat-card-content h6 { - overflow: hidden; - white-space: nowrap; - font-weight: 400; - font-size: 1.1rem; -} - -/* Summary adjust for Discover page */ -.small, -small { - font-size: 0.8rem; -} - -@media (min-width: 2000px) { - #cardImage { - height: 80%; - object-fit: cover; - display: block; - } -} - -.overview { - font-size: 1.2em; -} - -.backdrop { - background-position: 50% 33%; - background-size: cover; -} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts b/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts deleted file mode 100644 index 51ea7a85f..000000000 --- a/src/Ombi/ClientApp/src/app/discover/components/grid/discover-grid.component.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Component, OnInit, Input } from "@angular/core"; -import { IDiscoverCardResult } from "../../interfaces"; -import { RequestType, ISearchTvResult, ISearchMovieResult, ISearchMovieResultContainer } from "../../../interfaces"; -import { ImageService, RequestService, SearchV2Service } from "../../../services"; -import { MatDialog } from "@angular/material/dialog"; -import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2"; -import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; -import { EpisodeRequestComponent, EpisodeRequestData } from "../../../shared/episode-request/episode-request.component"; -import { MatSnackBar } from "@angular/material/snack-bar"; -import { Router } from "@angular/router"; -import { DomSanitizer } from "@angular/platform-browser"; - -@Component({ - selector: "discover-grid", - templateUrl: "./discover-grid.component.html", - styleUrls: ["./discover-grid.component.scss"], -}) -export class DiscoverGridComponent implements OnInit { - - @Input() public result: IDiscoverCardResult; - public RequestType = RequestType; - public requesting: boolean; - - public tv: ISearchTvResultV2; - public tvCreator: string; - public tvProducer: string; - public movie: ISearchMovieResultV2; - - constructor(private searchService: SearchV2Service, private dialog: MatDialog, - private requestService: RequestService, private notification: MatSnackBar, - private router: Router, private sanitizer: DomSanitizer, private imageService: ImageService) { } - - public ngOnInit() { - if (this.result.type == RequestType.tvShow) { - this.getExtraTvInfo(); - } - if (this.result.type == RequestType.movie) { - this.getExtraMovieInfo(); - } - } - - public async getExtraTvInfo() { - this.tv = await this.searchService.getTvInfo(+this.result.id); - this.setTvDefaults(this.tv); - this.updateTvItem(this.tv); - const creator = this.tv.crew.filter(tv => { - return tv.type === "Creator"; - })[0]; - if (creator && creator.person) { - this.tvCreator = creator.person.name; - } - const crewResult = this.tv.crew.filter(tv => { - return tv.type === "Executive Producer"; - })[0] - if (crewResult && crewResult.person) { - this.tvProducer = crewResult.person.name; - } - this.setTvBackground(); - } - - public openDetails() { - if (this.result.type === RequestType.movie) { - this.router.navigate(['/details/movie/', this.result.id]); - } else if (this.result.type === RequestType.tvShow) { - this.router.navigate(['/details/tv/', this.result.id]); - } - } - - public getStatusClass(): string { - if (this.result.available) { - return "available"; - } - if (this.result.approved) { - return "approved"; - } - if (this.result.requested) { - return "requested"; - } - return "notrequested"; - } - - private getExtraMovieInfo() { - this.searchService.getFullMovieDetails(+this.result.id) - .subscribe(m => { - this.movie = m; - this.updateMovieItem(m); - }); - - this.setMovieBackground() - } - - private setMovieBackground(): void { - this.result.background = this.sanitizer.bypassSecurityTrustStyle - ("linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(" + "https://image.tmdb.org/t/p/original" + this.result.background + ")"); - } - - private setTvBackground(): void { - if (this.result.background != null) { - this.result.background = this.sanitizer.bypassSecurityTrustStyle - ("linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(https://image.tmdb.org/t/p/original" + this.result.background + ")"); - } else { - this.imageService.getTvBanner(+this.result.id).subscribe(x => { - if (x) { - this.result.background = this.sanitizer.bypassSecurityTrustStyle - ("linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5) ), url(" + x + ")"); - } - }); - } - } - - private updateMovieItem(updated: ISearchMovieResultV2) { - this.result.url = "http://www.imdb.com/title/" + updated.imdbId + "/"; - this.result.available = updated.available; - this.result.requested = updated.requested; - this.result.requested = updated.requestProcessing; - this.result.rating = updated.voteAverage; - } - - - private setTvDefaults(x: ISearchTvResultV2) { - if (!x.imdbId) { - x.imdbId = "https://www.tvmaze.com/shows/" + x.seriesId; - } else { - x.imdbId = "http://www.imdb.com/title/" + x.imdbId + "/"; - } - } - - private updateTvItem(updated: ISearchTvResultV2) { - this.result.title = updated.title; - this.result.id = updated.id; - this.result.available = updated.fullyAvailable; - this.result.posterPath = updated.banner; - this.result.requested = updated.requested; - this.result.url = updated.imdbId; - } - - public async request() { - this.requesting = true; - if (this.result.type === RequestType.movie) { - const result = await this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: "", requestOnBehalf: null }).toPromise(); - - if (result.result) { - this.result.requested = true; - this.notification.open(result.message, "Ok"); - } else { - this.notification.open(result.errorMessage, "Ok"); - } - } else if (this.result.type === RequestType.tvShow) { - this.dialog.open(EpisodeRequestComponent, { width: "700px", data: { series: this.tv, requestOnBehalf: null }, panelClass: 'modal-panel' }) - } - this.requesting = false; - } - -} diff --git a/src/Ombi/ClientApp/src/app/discover/components/index.ts b/src/Ombi/ClientApp/src/app/discover/components/index.ts index e3d59fe7b..2d399cd76 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/index.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/index.ts @@ -1,13 +1,11 @@ import { DiscoverComponent } from "./discover/discover.component"; -import { DiscoverCardDetailsComponent } from "./card/discover-card-details.component"; import { DiscoverCollectionsComponent } from "./collections/discover-collections.component"; import { DiscoverActorComponent } from "./actor/discover-actor.component"; import { DiscoverCardComponent } from "./card/discover-card.component"; import { Routes } from "@angular/router"; import { AuthGuard } from "../../auth/auth.guard"; -import { SearchService, RequestService } from "../../services"; +import { SearchService, RequestService, SonarrService, RadarrService } from "../../services"; import { MatDialog } from "@angular/material/dialog"; -import { DiscoverGridComponent } from "./grid/discover-grid.component"; import { DiscoverSearchResultsComponent } from "./search-results/search-results.component"; import { CarouselListComponent } from "./carousel-list/carousel-list.component"; import { RequestServiceV2 } from "../../services/requestV2.service"; @@ -16,10 +14,8 @@ import { RequestServiceV2 } from "../../services/requestV2.service"; export const components: any[] = [ DiscoverComponent, DiscoverCardComponent, - DiscoverCardDetailsComponent, DiscoverCollectionsComponent, DiscoverActorComponent, - DiscoverGridComponent, DiscoverSearchResultsComponent, CarouselListComponent, ]; @@ -29,6 +25,8 @@ export const providers: any[] = [ MatDialog, RequestService, RequestServiceV2, + SonarrService, + RadarrService, ]; export const routes: Routes = [ diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html index fb836a105..29a583890 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html @@ -4,7 +4,7 @@
- +
diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts index 6637b9e07..d9e046da9 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts @@ -8,6 +8,7 @@ import { SearchFilter } from "../../../my-nav/SearchFilter"; import { StorageService } from "../../../shared/storage/storage-service"; import { isEqual } from "lodash"; +import { AuthService } from "../../../auth/auth.service"; @Component({ templateUrl: "./search-results.component.html", @@ -18,6 +19,7 @@ export class DiscoverSearchResultsComponent implements OnInit { public loadingFlag: boolean; public searchTerm: string; public results: IMultiSearchResult[]; + public isAdmin: boolean; public discoverResults: IDiscoverCardResult[] = []; @@ -26,7 +28,8 @@ export class DiscoverSearchResultsComponent implements OnInit { constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private filterService: FilterService, - private store: StorageService) { + private store: StorageService, + private authService: AuthService) { this.route.params.subscribe((params: any) => { this.searchTerm = params.searchTerm; this.clear(); @@ -36,6 +39,7 @@ export class DiscoverSearchResultsComponent implements OnInit { public async ngOnInit() { this.loadingFlag = true; + this.isAdmin = this.authService.isAdmin(); this.filterService.onFilterChange.subscribe(async x => { if (!isEqual(this.filter, x)) { diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRequestEngineResult.ts b/src/Ombi/ClientApp/src/app/interfaces/IRequestEngineResult.ts index 1d902261f..87ed1bf61 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRequestEngineResult.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRequestEngineResult.ts @@ -2,4 +2,5 @@ result: boolean; message: string; errorMessage: string; + requestId: number | undefined; } diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts index 495563c36..4f72b0631 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts @@ -168,10 +168,9 @@ export interface IEpisodesRequests { selected: boolean; // This is for the UI only } -export interface IMovieRequestModel { +export interface IMovieRequestModel extends BaseRequestOptions { theMovieDbId: number; languageCode: string | undefined; - requestOnBehalf: string | undefined; } export interface IFilter { @@ -187,3 +186,9 @@ export enum FilterType { Processing = 4, PendingApproval = 5, } + +export class BaseRequestOptions { + requestOnBehalf: string | undefined; + rootFolderOverride: number | undefined; + qualityPathOverride: number | undefined; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts b/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts index 2a5b3742e..b59ad3780 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts @@ -1,4 +1,4 @@ -import { INewSeasonRequests } from "./IRequestModel"; +import { BaseRequestOptions, INewSeasonRequests } from "./IRequestModel"; export interface ISearchTvResult { id: number; @@ -47,12 +47,11 @@ export interface ITvRequestViewModelV2 extends ITvRequestViewModelBase { } -export interface ITvRequestViewModelBase { +export interface ITvRequestViewModelBase extends BaseRequestOptions { requestAll: boolean; firstSeason: boolean; latestSeason: boolean; seasons: ISeasonsViewModel[]; - requestOnBehalf: string | undefined; } export interface ISeasonsViewModel { diff --git a/src/Ombi/ClientApp/src/app/interfaces/IUser.ts b/src/Ombi/ClientApp/src/app/interfaces/IUser.ts index f28075ac0..aad167a84 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IUser.ts @@ -9,6 +9,7 @@ export interface IUser { emailAddress: string; password: string; userType: UserType; + userAlias: string; lastLoggedIn: Date; hasLoggedIn: boolean; movieRequestLimit: number; diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index 92eb37af4..cad326d10 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -20,11 +20,9 @@ [embyUrl]="movie.embyUrl" [jellyfinUrl]="movie.jellyfinUrl" [isAdmin]="isAdmin" - [canRequestOnBehalf]="!hasRequest && !movie.available" [canShowAdvanced]="showAdvanced && movieRequest" [type]="requestType" (openTrailer)="openDialog()" - (onRequestBehalf)="openRequestOnBehalf()" (onAdvancedOptions)="openAdvancedOptions()" > @@ -75,7 +73,7 @@ - - + + +
+ +
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html index dbc380aaa..60cad7bb0 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/social-icons/social-icons.component.html @@ -30,10 +30,6 @@ -
- - + +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.ts index d6096d6cd..701aa26e9 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-information-panel/tv-information-panel.component.ts @@ -1,4 +1,5 @@ -import { Component, ViewEncapsulation, Input, OnInit } from "@angular/core"; +import { APP_BASE_HREF } from "@angular/common"; +import { Component, ViewEncapsulation, Input, OnInit, Inject } from "@angular/core"; import { ITvRequests } from "../../../../../interfaces"; import { ITvRatings } from "../../../../../interfaces/IRatings"; import { ISearchTvResultV2 } from "../../../../../interfaces/ISearchTvResultV2"; @@ -13,7 +14,7 @@ import { SearchV2Service } from "../../../../../services"; }) export class TvInformationPanelComponent implements OnInit { - constructor(private searchService: SearchV2Service) { } + constructor(private searchService: SearchV2Service, @Inject(APP_BASE_HREF) public internalBaseUrl: string) { } @Input() public tv: ISearchTvResultV2; @Input() public request: ITvRequests; @@ -24,8 +25,12 @@ export class TvInformationPanelComponent implements OnInit { public seasonCount: number; public totalEpisodes: number = 0; public nextEpisode: any; + public baseUrl: string; public ngOnInit(): void { + if (this.internalBaseUrl.length > 1) { + this.baseUrl = this.internalBaseUrl; + } this.searchService.getRottenTvRatings(this.tv.title, +this.tv.firstAired.toString().substring(0,4)) .subscribe(x => this.ratings = x); diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts index d67073069..8c3633cf6 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/panels/tv-request-grid/tv-request-grid.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from "@angular/core"; -import { IChildRequests, IEpisodesRequests, INewSeasonRequests, ISeasonsViewModel, ITvRequestViewModelV2, RequestType } from "../../../../../interfaces"; +import { IChildRequests, IEpisodesRequests, INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModelV2, RequestType } from "../../../../../interfaces"; import { RequestService } from "../../../../../services/request.service"; import { MessageService } from "../../../../../services"; import { DenyDialogComponent } from "../../../shared/deny-dialog/deny-dialog.component"; @@ -7,6 +7,7 @@ import { ISearchTvResultV2 } from "../../../../../interfaces/ISearchTvResultV2"; import { MatDialog } from "@angular/material/dialog"; import { SelectionModel } from "@angular/cdk/collections"; import { RequestServiceV2 } from "../../../../../services/requestV2.service"; +import { AdminRequestDialogComponent } from "../../../../../shared/admin-request-dialog/admin-request-dialog.component"; @Component({ templateUrl: "./tv-request-grid.component.html", @@ -59,38 +60,21 @@ export class TvRequestGridComponent { viewModel.seasons.push(seasonsViewModel); }); - const requestResult = await this.requestServiceV2.requestTv(viewModel).toPromise(); - - if (requestResult.result) { - this.notificationService.send( - `Request for ${this.tv.title} has been added successfully`); - - debugger; - this.selection.clear(); - - if (this.tv.firstSeason) { - this.tv.seasonRequests[0].episodes.forEach(ep => { - ep.requested = true; - ep.requestStatus = "Common.PendingApproval"; - }); - } - if (this.tv.requestAll) { - this.tv.seasonRequests.forEach(season => { - season.episodes.forEach(ep => { - ep.requested = true; - ep.requestStatus = "Common.PendingApproval"; - }); - }); - } - if (this.tv.latestSeason) { - this.tv.seasonRequests[this.tv.seasonRequests.length - 1].episodes.forEach(ep => { - ep.requested = true; - ep.requestStatus = "Common.PendingApproval"; - }); - } + if (this.isAdmin) { + const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.tv.id }, panelClass: 'modal-panel' }); + dialog.afterClosed().subscribe(async (result) => { + if (result) { + viewModel.requestOnBehalf = result.username?.id; + viewModel.qualityPathOverride = result?.sonarrPathId; + viewModel.rootFolderOverride = result?.sonarrFolderId; + const requestResult = await this.requestServiceV2.requestTv(viewModel).toPromise(); + this.postRequest(requestResult); + } + }); } else { - this.notificationService.send(requestResult.errorMessage ? requestResult.errorMessage : requestResult.message); + const requestResult = await this.requestServiceV2.requestTv(viewModel).toPromise(); + this.postRequest(requestResult); } } @@ -236,4 +220,37 @@ export class TvRequestGridComponent { } return ""; } + + private postRequest(requestResult: IRequestEngineResult) { + if (requestResult.result) { + this.notificationService.send( + `Request for ${this.tv.title} has been added successfully`); + + this.selection.clear(); + + if (this.tv.firstSeason) { + this.tv.seasonRequests[0].episodes.forEach(ep => { + ep.requested = true; + ep.requestStatus = "Common.PendingApproval"; + }); + } + if (this.tv.requestAll) { + this.tv.seasonRequests.forEach(season => { + season.episodes.forEach(ep => { + ep.requested = true; + ep.requestStatus = "Common.PendingApproval"; + }); + }); + } + if (this.tv.latestSeason) { + this.tv.seasonRequests[this.tv.seasonRequests.length - 1].episodes.forEach(ep => { + ep.requested = true; + ep.requestStatus = "Common.PendingApproval"; + }); + } + + } else { + this.notificationService.send(requestResult.errorMessage ? requestResult.errorMessage : requestResult.message); + } + } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html index e186340be..62c1fd804 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html @@ -31,10 +31,8 @@ [embyUrl]="tv.embyUrl" [jellyfinUrl]="tv.jellyfinUrl" [isAdmin]="isAdmin" - [canRequestOnBehalf]="!showRequest" [canShowAdvanced]="showAdvanced && showRequest" [type]="requestType" - (onRequestBehalf)="openRequestOnBehalf()" (onAdvancedOptions)="openAdvancedOptions()" > @@ -53,9 +51,10 @@ (click)="request()"> {{ 'Common.Request' | translate }} - + diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts index e132cabb5..13989fcdf 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts @@ -76,7 +76,7 @@ export class TvDetailsComponent implements OnInit { } public async request(userId: string) { - this.dialog.open(EpisodeRequestComponent, { width: "800px", data: { series: this.tv, requestOnBehalf: userId }, panelClass: 'modal-panel' }) + this.dialog.open(EpisodeRequestComponent, { width: "800px", data: { series: this.tv, requestOnBehalf: userId, isAdmin: this.isAdmin }, panelClass: 'modal-panel' }) } public async issue() { @@ -108,15 +108,6 @@ export class TvDetailsComponent implements OnInit { }); } - public async openRequestOnBehalf() { - const dialog = this.dialog.open(RequestBehalfComponent, { width: "700px", panelClass: 'modal-panel' }) - await dialog.afterClosed().subscribe(async result => { - if (result) { - await this.request(result.id); - } - }); - } - public setAdvancedOptions(data: IAdvancedData) { this.advancedOptions = data; console.log(this.advancedOptions); diff --git a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html index 21e3feb46..bd6949ebb 100644 --- a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.html @@ -1,14 +1,14 @@  - - - - - - + + + + + + - + @@ -39,27 +39,27 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + - - + + diff --git a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.scss b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.scss new file mode 100644 index 000000000..05f44d1a8 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.scss @@ -0,0 +1,3 @@ +.icon-spacing { + padding-right: 5%; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.ts b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.ts index eb4cafe16..964e0dfe3 100644 --- a/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/settingsmenu.component.ts @@ -2,6 +2,7 @@ @Component({ selector: "settings-menu", templateUrl: "./settingsmenu.component.html", + styleUrls: ["./settingsmenu.component.scss"] }) export class SettingsMenuComponent { public ignore(event: any): void { diff --git a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.html b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.html new file mode 100644 index 000000000..452dd43d2 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.html @@ -0,0 +1,86 @@ + + +
+

{{'MediaDetails.AdvancedOptions' | translate }}

+
+ + +
+ +
+ + +

{{'MediaDetails.RequestOnBehalf' | translate }}

+ + {{ 'MediaDetails.PleaseSelectUser' | translate}} + + + + {{displayFn(option)}} + + + + + + + + + +

+
+

Sonarr Overrides

+ + {{'MediaDetails.QualityProfilesSelect' | translate }} + + {{profile.name}} + + +
+
+ + {{'MediaDetails.RootFolderSelect' | translate }} + + {{profile.path}} + + +
+
+ + + +

+
+

Radarr Overrides

+ + {{'MediaDetails.QualityProfilesSelect' | translate }} + + {{profile.name}} + + +
+
+ + {{'MediaDetails.RootFolderSelect' | translate }} + + {{profile.path}} + + +
+
+ + + +
+ + +
+
+ diff --git a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.scss b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.scss new file mode 100644 index 000000000..8d701c21f --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.scss @@ -0,0 +1,8 @@ + +@import "~styles/variables.scss"; + +.alert-info { + background: $accent; + border-color: $ombi-background-primary; + color:white; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts new file mode 100644 index 000000000..8e16dd87a --- /dev/null +++ b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts @@ -0,0 +1,111 @@ +import { Component, Inject, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup } from "@angular/forms"; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { Observable } from "rxjs"; +import { startWith, map } from "rxjs/operators"; +import { IRadarrProfile, IRadarrRootFolder, ISonarrProfile, ISonarrRootFolder, IUserDropdown, RequestType } from "../../interfaces"; +import { IdentityService, MessageService, RadarrService, RequestService, SonarrService } from "../../services"; +import { RequestServiceV2 } from "../../services/requestV2.service"; + +export interface IAdminRequestDialogData { + type: RequestType, + id: number +} + +@Component({ + selector: "admin-request-dialog", + templateUrl: "admin-request-dialog.component.html", + styleUrls: [ "admin-request-dialog.component.scss" ] +}) +export class AdminRequestDialogComponent implements OnInit { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: IAdminRequestDialogData, + private identityService: IdentityService, + private sonarrService: SonarrService, + private radarrService: RadarrService, + private fb: FormBuilder + ) {} + + public form: FormGroup; + public RequestType = RequestType; + + public options: IUserDropdown[]; + public filteredOptions: Observable; + public userId: string; + + public radarrEnabled: boolean; + public sonarrEnabled: boolean; + + public sonarrProfiles: ISonarrProfile[]; + public sonarrRootFolders: ISonarrRootFolder[]; + public radarrProfiles: IRadarrProfile[]; + public radarrRootFolders: IRadarrRootFolder[]; + + public async ngOnInit() { + + this.form = this.fb.group({ + username: [null], + sonarrPathId: [null], + sonarrFolderId: [null], + radarrPathId: [null], + radarrFolderId: [null] + }) + + this.options = await this.identityService.getUsersDropdown().toPromise(); + + this.filteredOptions = this.form.controls['username'].valueChanges.pipe( + startWith(""), + map((value) => this._filter(value)) + ); + + if (this.data.type === RequestType.tvShow) { + this.sonarrEnabled = await this.sonarrService.isEnabled(); + if (this.sonarrEnabled) { + this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => { + this.sonarrProfiles = c; + }); + this.sonarrService.getRootFoldersWithoutSettings().subscribe(c => { + this.sonarrRootFolders = c; + }); + } + } + if (this.data.type === RequestType.movie) { + this.radarrEnabled = await this.radarrService.isRadarrEnabled(); + if (this.radarrEnabled) { + this.radarrService.getQualityProfilesFromSettings().subscribe(c => { + this.radarrProfiles = c; + }); + this.radarrService.getRootFoldersFromSettings().subscribe(c => { + this.radarrRootFolders = c; + }); + } + } + } + + public displayFn(user: IUserDropdown): string { + const username = user?.username ? user.username : ""; + const email = user?.email ? `(${user.email})` : ""; + return `${username} ${email}`; + } + + private _filter(value: string | IUserDropdown): IUserDropdown[] { + const filterValue = + typeof value === "string" + ? value.toLowerCase() + : value.username.toLowerCase(); + + return this.options.filter((option) => + option.username.toLowerCase().includes(filterValue) + ); + } + + public async submitRequest() { + const model = this.form.value; + model.radarrQualityOverrideTitle = this.radarrProfiles?.filter(x => x.id == model.radarrPathId)[0]?.name; + model.radarrRootFolderTitle = this.radarrRootFolders?.filter(x => x.id == model.radarrFolderId)[0]?.path; + model.sonarrRootFolderTitle = this.sonarrRootFolders?.filter(x => x.id == model.sonarrFolderId)[0]?.path; + model.sonarrQualityOverrideTitle = this.sonarrProfiles?.filter(x => x.id == model.sonarrPathId)[0]?.name; + this.dialogRef.close(model); + } +} diff --git a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts index 4959aa88a..f6d7a6e92 100644 --- a/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts +++ b/src/Ombi/ClientApp/src/app/shared/episode-request/episode-request.component.ts @@ -1,13 +1,15 @@ import { Component, Inject } from "@angular/core"; import { MatCheckboxChange } from "@angular/material/checkbox"; -import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; +import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material/dialog"; import { ISearchTvResultV2 } from "../../interfaces/ISearchTvResultV2"; import { MessageService } from "../../services"; -import { ISeasonsViewModel, IEpisodesRequests, INewSeasonRequests, ITvRequestViewModelV2 } from "../../interfaces"; +import { ISeasonsViewModel, IEpisodesRequests, INewSeasonRequests, ITvRequestViewModelV2, IRequestEngineResult, RequestType } from "../../interfaces"; import { RequestServiceV2 } from "../../services/requestV2.service"; +import { AdminRequestDialogComponent } from "../admin-request-dialog/admin-request-dialog.component"; export interface EpisodeRequestData { series: ISearchTvResultV2; + isAdmin: boolean; requestOnBehalf: string | undefined; } @Component({ @@ -21,7 +23,7 @@ export class EpisodeRequestComponent { } constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: EpisodeRequestData, - private requestService: RequestServiceV2, private notificationService: MessageService) { } + private requestService: RequestServiceV2, private notificationService: MessageService, private dialog: MatDialog) { } public async submitRequests() { @@ -57,21 +59,23 @@ export class EpisodeRequestComponent { viewModel.seasons.push(seasonsViewModel); }); - const requestResult = await this.requestService.requestTv(viewModel).toPromise(); - - if (requestResult.result) { - this.notificationService.send( - `Request for ${this.data.series.title} has been added successfully`); - - this.data.series.seasonRequests.forEach((season) => { - season.episodes.forEach((ep) => { - ep.selected = false; - }); + if (this.data.isAdmin) { + const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.tvShow, id: this.data.series.id }, panelClass: 'modal-panel' }); + dialog.afterClosed().subscribe(async (result) => { + if (result) { + viewModel.requestOnBehalf = result.username?.id; + viewModel.qualityPathOverride = result?.sonarrPathId; + viewModel.rootFolderOverride = result?.sonarrFolderId; + + const requestResult = await this.requestService.requestTv(viewModel).toPromise(); + this.postRequest(requestResult); + } }); - } else { - this.notificationService.send(requestResult.errorMessage ? requestResult.errorMessage : requestResult.message); + const requestResult = await this.requestService.requestTv(viewModel).toPromise(); + this.postRequest(requestResult); } + this.dialogRef.close(); } @@ -114,4 +118,20 @@ export class EpisodeRequestComponent { this.data.series.latestSeason = true; await this.submitRequests(); } + + private postRequest(requestResult: IRequestEngineResult) { + if (requestResult.result) { + this.notificationService.send( + `Request for ${this.data.series.title} has been added successfully`); + + this.data.series.seasonRequests.forEach((season) => { + season.episodes.forEach((ep) => { + ep.selected = false; + }); + }); + + } else { + this.notificationService.send(requestResult.errorMessage ? requestResult.errorMessage : requestResult.message); + } + } } diff --git a/src/Ombi/ClientApp/src/app/shared/shared.module.ts b/src/Ombi/ClientApp/src/app/shared/shared.module.ts index 634561b7f..1189320dd 100644 --- a/src/Ombi/ClientApp/src/app/shared/shared.module.ts +++ b/src/Ombi/ClientApp/src/app/shared/shared.module.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { TranslateModule } from "@ngx-translate/core"; import { TruncateModule } from "@yellowspot/ng-truncate"; import { MomentModule } from "ngx-moment"; @@ -37,15 +37,18 @@ import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { MatTabsModule } from "@angular/material/tabs"; import { EpisodeRequestComponent } from "./episode-request/episode-request.component"; import { DetailsGroupComponent } from "../issues/components/details-group/details-group.component"; +import { AdminRequestDialogComponent } from "./admin-request-dialog/admin-request-dialog.component"; @NgModule({ declarations: [ IssuesReportComponent, EpisodeRequestComponent, DetailsGroupComponent, + AdminRequestDialogComponent, ], imports: [ SidebarModule, + ReactiveFormsModule, FormsModule, CommonModule, InputSwitchModule, @@ -85,6 +88,7 @@ import { DetailsGroupComponent } from "../issues/components/details-group/detail MatProgressSpinnerModule, IssuesReportComponent, EpisodeRequestComponent, + AdminRequestDialogComponent, DetailsGroupComponent, TruncateModule, InputSwitchModule, diff --git a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html index 2a3203f68..e4c7758f6 100644 --- a/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html +++ b/src/Ombi/ClientApp/src/app/user-preferences/components/user-preference/user-preference.component.html @@ -114,7 +114,7 @@ - Coming Soon... + diff --git a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts index 486f456d0..bfbabd0a3 100644 --- a/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts +++ b/src/Ombi/ClientApp/src/app/usermanagement/usermanagement-user.component.ts @@ -78,6 +78,7 @@ export class UserManagementUserComponent implements OnInit { episodeRequestQuota: null, movieRequestQuota: null, language: null, + userAlias: "", streamingCountry: "US", userQualityProfiles: { radarrQualityProfile: 0, diff --git a/src/Ombi/ClientApp/src/styles/Styles.scss b/src/Ombi/ClientApp/src/styles/Styles.scss index 55bc8657c..115555410 100644 --- a/src/Ombi/ClientApp/src/styles/Styles.scss +++ b/src/Ombi/ClientApp/src/styles/Styles.scss @@ -142,8 +142,8 @@ background-color: $ombi-active; } - hr{ - border-top: 1px solid $ombi-background-primary; + hr { + border-top: 1px solid $accent-dark; } .form-control{ @@ -157,3 +157,20 @@ color:#FFF; border: 1px solid $ombi-active; } + + + .alert .glyphicon{ + display: table-cell; + vertical-align: middle; + padding-right: 1%; + } + + .alert div, + .alert span{ + padding-left: 1%; + display:table-cell; + } + + .right-buttons { + float:right; + } \ No newline at end of file diff --git a/src/Ombi/Controllers/V1/IdentityController.cs b/src/Ombi/Controllers/V1/IdentityController.cs index b57a6e890..bf1173d70 100644 --- a/src/Ombi/Controllers/V1/IdentityController.cs +++ b/src/Ombi/Controllers/V1/IdentityController.cs @@ -67,7 +67,8 @@ namespace Ombi.Controllers.V1 IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, IMusicRequestEngine musicEngine, - IUserDeletionEngine deletionEngine) + IUserDeletionEngine deletionEngine, + ICacheService cacheService) { UserManager = user; Mapper = mapper; @@ -95,10 +96,13 @@ namespace Ombi.Controllers.V1 _userQualityProfiles = userProfiles; MusicRequestEngine = musicEngine; _deletionEngine = deletionEngine; + _cacheService = cacheService; } private OmbiUserManager UserManager { get; } private readonly IUserDeletionEngine _deletionEngine; + private readonly ICacheService _cacheService; + private RoleManager RoleManager { get; } private IMapper Mapper { get; } private IEmailProvider EmailProvider { get; } @@ -289,8 +293,8 @@ namespace Ombi.Controllers.V1 [PowerUser] public async Task> GetAllUsersDropdown() { - var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser) - .ToListAsync(); + var users = await _cacheService.GetOrAdd(CacheKeys.UsersDropdown, + async () => await UserManager.Users.Where(x => x.UserType != UserType.SystemUser).ToListAsync()); var model = new List(); diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 1f44a9eec..28565d0f7 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -252,6 +252,8 @@ "ViewCollection":"View Collection", "NotEnoughInfo": "Unfortunately there is not enough information about this show yet!", "AdvancedOptions":"Advanced Options", + "AutoApproveOptions":"You can configure the request here, once requested it will be send to your DVR application and will be auto approved!", + "AutoApproveOptionsTv":"You can configure the request here, once requested it will be send to your DVR application and will be auto approved! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it!", "QualityProfilesSelect":"Select A Quality Profile", "RootFolderSelect":"Select A Root Folder", "Status":"Status", diff --git a/tests/cypress/integration/page-objects/details/movies/moviedetails.page.ts b/tests/cypress/integration/page-objects/details/movies/moviedetails.page.ts index d22d1b7ac..ce1c7bcfb 100644 --- a/tests/cypress/integration/page-objects/details/movies/moviedetails.page.ts +++ b/tests/cypress/integration/page-objects/details/movies/moviedetails.page.ts @@ -1,10 +1,15 @@ import { BasePage } from "../../base.page"; +import { AdminRequestDialog } from "../../shared/AdminRequestDialog"; class MovieInformationPanel { get denyReason(): Cypress.Chainable { return cy.get('#deniedReasonInfo'); } + + get requestedBy(): Cypress.Chainable { + return cy.get('#requestedByInfo'); + } } class DenyModal { @@ -74,6 +79,7 @@ class MovieDetailsPage extends BasePage { denyModal = new DenyModal(); informationPanel = new MovieInformationPanel(); + adminOptionsDialog = new AdminRequestDialog(); constructor() { super(); diff --git a/tests/cypress/integration/page-objects/details/tv/tvdetails.page.ts b/tests/cypress/integration/page-objects/details/tv/tvdetails.page.ts index 4d81051bb..81de64e4e 100644 --- a/tests/cypress/integration/page-objects/details/tv/tvdetails.page.ts +++ b/tests/cypress/integration/page-objects/details/tv/tvdetails.page.ts @@ -1,4 +1,5 @@ import { BasePage } from "../../base.page"; +import { AdminRequestDialog } from "../../shared/AdminRequestDialog"; class TvRequestPanel { @@ -82,6 +83,7 @@ class TvDetailsPage extends BasePage { informationPanel = new TvDetailsInformationPanel(); requestFabButton = new RequestFabButton(); requestPanel = new TvRequestPanel(); + adminOptionsDialog = new AdminRequestDialog(); constructor() { super(); diff --git a/tests/cypress/integration/page-objects/discover/discover.page.ts b/tests/cypress/integration/page-objects/discover/discover.page.ts index 3a4636d58..df644e830 100644 --- a/tests/cypress/integration/page-objects/discover/discover.page.ts +++ b/tests/cypress/integration/page-objects/discover/discover.page.ts @@ -1,5 +1,6 @@ import { BasePage } from "../base.page"; -import { DiscoverCard } from "../shared/DiscoverCard"; +import { AdminRequestDialog } from "../shared/AdminRequestDialog"; +import { DiscoverCard, DiscoverType } from "../shared/DiscoverCard"; class CarouselComponent { private type: string; @@ -16,8 +17,8 @@ class CarouselComponent { return cy.get(`#${this.type}Tv-button`); } - getCard(id: string, movie: boolean): DiscoverCard { - return new DiscoverCard(id, movie); + getCard(id: string, movie: boolean, type?: DiscoverType): DiscoverCard { + return new DiscoverCard(id, movie, type); } constructor(id: string) { @@ -27,6 +28,7 @@ class CarouselComponent { class DiscoverPage extends BasePage { popularCarousel = new CarouselComponent("popular"); + adminOptionsDialog = new AdminRequestDialog(); constructor() { super(); diff --git a/tests/cypress/integration/page-objects/shared/AdminRequestDialog.ts b/tests/cypress/integration/page-objects/shared/AdminRequestDialog.ts new file mode 100644 index 000000000..c684b5d61 --- /dev/null +++ b/tests/cypress/integration/page-objects/shared/AdminRequestDialog.ts @@ -0,0 +1,58 @@ + +export class AdminRequestDialog { + + isOpen(): Cypress.Chainable { + return cy.waitUntil(x => { + return this.title.should('exist'); + }); + } + + get title(): Cypress.Chainable { + return cy.get(`#advancedOptionsTitle`); + } + + get requestOnBehalfUserInput(): Cypress.Chainable { + return cy.get(`#requestOnBehalfUserInput`); + } + + get sonarrQualitySelect(): Cypress.Chainable { + return cy.get(`#sonarrQualitySelect`); + } + + selectSonarrQuality(id: number): Cypress.Chainable { + return cy.get(`#sonarrQualitySelect${id}`); + } + + get sonarrFolderSelect(): Cypress.Chainable { + return cy.get(`#sonarrFolderSelect`); + } + + selectSonarrFolder(id: number): Cypress.Chainable { + return cy.get(`#sonarrFolderSelect${id}`); + } + + get radarrQualitySelect(): Cypress.Chainable { + return cy.get(`#radarrQualitySelect`); + } + + selectradarrQuality(id: number): Cypress.Chainable { + return cy.get(`#radarrQualitySelect${id}`); + } + + get radarrFolderSelect(): Cypress.Chainable { + return cy.get(`#radarrFolderSelect`); + } + + selectradarrFolder(id: number): Cypress.Chainable { + return cy.get(`#radarrFolderSelect${id}`); + } + + + get cancelButton(): Cypress.Chainable { + return cy.get(`#cancelButton`); + } + + get requestButton(): Cypress.Chainable { + return cy.get(`#requestButton`); + } +} diff --git a/tests/cypress/integration/page-objects/shared/DiscoverCard.ts b/tests/cypress/integration/page-objects/shared/DiscoverCard.ts index be126cd27..619a0c048 100644 --- a/tests/cypress/integration/page-objects/shared/DiscoverCard.ts +++ b/tests/cypress/integration/page-objects/shared/DiscoverCard.ts @@ -1,13 +1,22 @@ import { EpisodeRequestModal } from "./EpisodeRequestModal"; +export enum DiscoverType { + Upcoming, + Trending, + Popular, + RecentlyRequested, +} + export class DiscoverCard { private id: string; private movie: boolean; + private type: DiscoverType; episodeRequestModal = new EpisodeRequestModal(); - constructor(id: string, movie: boolean) { + constructor(id: string, movie: boolean, type?: DiscoverType) { this.id = id; this.movie = movie; + this.type = type; } get topLevelCard(): Cypress.Chainable { @@ -35,6 +44,10 @@ export class DiscoverCard { } get requestButton(): Cypress.Chainable { + if (this.type) { + return cy.get(`#requestButton${this.id}${this.movie ? '1' : '0'}${this.type}`); + } + return cy.get(`#requestButton${this.id}${this.movie ? '1' : '0'}`); } diff --git a/tests/cypress/tests/details/movie/moviedetails-buttons.spec.ts b/tests/cypress/tests/details/movie/moviedetails-buttons.spec.ts index 5482ecfbc..a125ffee6 100644 --- a/tests/cypress/tests/details/movie/moviedetails-buttons.spec.ts +++ b/tests/cypress/tests/details/movie/moviedetails-buttons.spec.ts @@ -6,6 +6,10 @@ describe("Movie Details Buttons", () => { Page.visit("587807"); Page.requestButton.click(); + Page.adminOptionsDialog.isOpen(); + + Page.adminOptionsDialog.requestButton.click(); + cy.verifyNotification("Tom & Jerry (2021) has been successfully added"); Page.requestedButton.should("be.visible"); @@ -80,11 +84,13 @@ describe("Movie Details Buttons", () => { it("Movie Requested, mark as available", () => { cy.login(); - Page.visit("399566"); + Page.visit("12444"); Page.requestButton.click(); + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); cy.verifyNotification( - "Godzilla vs. Kong (2021) has been successfully added" + "Harry Potter and the Deathly Hallows: Part 1 (2010) has been successfully added" ); cy.reload(); @@ -102,6 +108,8 @@ describe("Movie Details Buttons", () => { Page.visit("671"); Page.requestButton.click(); + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); cy.verifyNotification( "Harry Potter and the Philosopher's Stone (2001) has been successfully added" ); @@ -112,6 +120,7 @@ describe("Movie Details Buttons", () => { Page.denyButton.click(); Page.denyModal.denyReason.type("Automation Tests"); + cy.wait(500); Page.denyModal.denyButton.click(); Page.deniedButton.should('exist'); diff --git a/tests/cypress/tests/details/tv/tvdetails-requests-grid.spec.ts b/tests/cypress/tests/details/tv/tvdetails-requests-grid.spec.ts index deff3491c..f0274283f 100644 --- a/tests/cypress/tests/details/tv/tvdetails-requests-grid.spec.ts +++ b/tests/cypress/tests/details/tv/tvdetails-requests-grid.spec.ts @@ -137,6 +137,9 @@ describe("TV Requests Grid", function () { Page.requestFabButton.fab.click(); Page.requestFabButton.requestSelected.click(); + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); + cy.verifyNotification('Request for Game of Thrones has been added successfully'); Page.requestPanel.getEpisodeStatus(2,1) @@ -157,6 +160,9 @@ describe("TV Requests Grid", function () { Page.requestFabButton.fab.click(); Page.requestFabButton.requestFirst.click(); + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); + cy.verifyNotification('Request for Game of Thrones has been added successfully'); Page.requestPanel.getEpisodeStatus(1) @@ -170,6 +176,9 @@ describe("TV Requests Grid", function () { Page.requestFabButton.fab.click(); Page.requestFabButton.requestLatest.click(); + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); + cy.verifyNotification('Request for Game of Thrones has been added successfully'); Page.requestPanel.seasonTab(8) diff --git a/tests/cypress/tests/discover/discover-cards-requests.spec.ts b/tests/cypress/tests/discover/discover-cards-requests.spec.ts index 9a6c16e1b..3688d524b 100644 --- a/tests/cypress/tests/discover/discover-cards-requests.spec.ts +++ b/tests/cypress/tests/discover/discover-cards-requests.spec.ts @@ -1,11 +1,12 @@ import { discoverPage as Page } from "@/integration/page-objects"; +import { DiscoverType } from "@/integration/page-objects/shared/DiscoverCard"; describe("Discover Cards Requests Tests", () => { beforeEach(() => { cy.login(); }); - it("Not requested movie allows us to request", () => { + it("Not requested movie allows admin to request", () => { window.localStorage.setItem("DiscoverOptions2", "2"); cy.intercept("GET", "**/search/Movie/Popular/**", (req) => { req.reply((res) => { @@ -27,22 +28,76 @@ describe("Discover Cards Requests Tests", () => { var expectedId = body[0].id; var title = body[0].title; - const card = Page.popularCarousel.getCard(expectedId, true); + const card = Page.popularCarousel.getCard(expectedId, true, DiscoverType.Popular); card.verifyTitle(title); card.requestButton.should("exist"); // Not visible until hover card.requestButton.should("not.be.visible"); - cy.wait(500) + cy.wait(500); card.topLevelCard.realHover(); card.requestButton.should("be.visible"); card.requestButton.click(); + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); + cy.verifyNotification("has been successfully added!"); - card.requestButton.should("not.be.visible"); - card.availabilityText.should('have.text','Pending'); - card.statusClass.should('have.class','requested'); + card.requestButton.should("not.exist"); + card.availabilityText.should("have.text", "Pending"); + card.statusClass.should("have.class", "requested"); + }); + }); + + it("Not requested movie allows non-admin to request", () => { + cy.generateUniqueId().then((id) => { + cy.login(); + const roles = []; + roles.push({ value: "RequestMovie", enabled: true }); + cy.createUser(id, "a", roles).then(() => { + cy.removeLogin(); + cy.loginWithCreds(id, "a"); + + window.localStorage.setItem("DiscoverOptions2", "2"); + cy.intercept("GET", "**/search/Movie/Popular/**", (req) => { + req.reply((res) => { + const body = res.body; + const movie = body[6]; + movie.available = false; + movie.approved = false; + movie.requested = false; + + body[6] = movie; + res.send(body); + }); + }).as("cardsResponse"); + + Page.visit(); + + cy.wait("@cardsResponse").then((res) => { + const body = JSON.parse(res.response.body); + var expectedId = body[6].id; + var title = body[6].title; + + const card = Page.popularCarousel.getCard(expectedId, true, DiscoverType.Popular); + card.verifyTitle(title); + card.requestButton.should("exist"); + // Not visible until hover + card.requestButton.should("not.be.visible"); + cy.wait(500); + card.topLevelCard.realHover(); + + card.requestButton.should("be.visible"); + card.requestButton.click(); + + cy.verifyNotification("has been successfully added!"); + + card.requestButton.should("not.exist"); + card.availabilityText.should("have.text", "Pending"); + card.statusClass.should("have.class", "requested"); + }); + }); }); }); @@ -68,13 +123,13 @@ describe("Discover Cards Requests Tests", () => { var expectedId = body[1].id; var title = body[1].title; - const card = Page.popularCarousel.getCard(expectedId, true); + const card = Page.popularCarousel.getCard(expectedId, true, DiscoverType.Popular); card.verifyTitle(title); card.topLevelCard.realHover(); card.requestButton.should("not.exist"); - card.availabilityText.should('have.text','Available'); - card.statusClass.should('have.class','available'); + card.availabilityText.should("have.text", "Available"); + card.statusClass.should("have.class", "available"); }); }); @@ -100,14 +155,13 @@ describe("Discover Cards Requests Tests", () => { var expectedId = body[1].id; var title = body[1].title; - const card = Page.popularCarousel.getCard(expectedId, true); - card.verifyTitle(title); - - card.topLevelCard.realHover(); + const card = Page.popularCarousel.getCard(expectedId, true, DiscoverType.Popular); + card.title.realHover(); + card.verifyTitle(title); card.requestButton.should("not.exist"); - card.availabilityText.should('have.text','Pending'); - card.statusClass.should('have.class','requested'); + card.availabilityText.should("have.text", "Pending"); + card.statusClass.should("have.class", "requested"); }); }); @@ -133,13 +187,13 @@ describe("Discover Cards Requests Tests", () => { var expectedId = body[1].id; var title = body[1].title; - const card = Page.popularCarousel.getCard(expectedId, true); - card.verifyTitle(title); - card.topLevelCard.realHover(); + const card = Page.popularCarousel.getCard(expectedId, true, DiscoverType.Popular); + card.title.realHover(); + card.verifyTitle(title); card.requestButton.should("not.exist"); - card.availabilityText.should('have.text','Approved'); - card.statusClass.should('have.class','approved'); + card.availabilityText.should("have.text", "Approved"); + card.statusClass.should("have.class", "approved"); }); }); @@ -163,17 +217,17 @@ describe("Discover Cards Requests Tests", () => { var expectedId = body[1].id; var title = body[1].title; - const card = Page.popularCarousel.getCard(expectedId, true); - card.verifyTitle(title); - card.topLevelCard.realHover(); + const card = Page.popularCarousel.getCard(expectedId, true, DiscoverType.Popular); + card.title.realHover(); + card.verifyTitle(title); card.requestButton.should("not.exist"); - card.availabilityText.should('have.text','Available'); - card.statusClass.should('have.class','available'); + card.availabilityText.should("have.text", "Available"); + card.statusClass.should("have.class", "available"); }); }); - it.only("Not available TV does not allow us to request", () => { + it("Not available TV allow admin to request", () => { cy.intercept("GET", "**/search/Tv/popular/**", (req) => { req.reply((res) => { const body = res.body; @@ -184,25 +238,77 @@ describe("Discover Cards Requests Tests", () => { res.send(body); }); }).as("cardsResponse"); + cy.intercept("GET", "**/search/Tv/**").as("otherResponses"); window.localStorage.setItem("DiscoverOptions2", "3"); Page.visit(); + cy.wait("@otherResponses"); cy.wait("@cardsResponse").then((res) => { const body = JSON.parse(res.response.body); var expectedId = body[3].id; var title = body[3].title; - const card = Page.popularCarousel.getCard(expectedId, false); - card.verifyTitle(title); - card.topLevelCard.realHover(); + const card = Page.popularCarousel.getCard(expectedId, false, DiscoverType.Popular); + card.title.realHover(); + card.verifyTitle(title); card.requestButton.should("be.visible"); card.requestButton.click(); const modal = card.episodeRequestModal; modal.latestSeasonButton.click(); - cy.verifyNotification("has been added successfully") + + Page.adminOptionsDialog.isOpen(); + Page.adminOptionsDialog.requestButton.click(); + + cy.verifyNotification("has been added successfully"); + }); + }); + + it("Not available TV allow non-admin to request", () => { + cy.generateUniqueId().then((id) => { + cy.login(); + const roles = []; + roles.push({ value: "RequestTv", enabled: true }); + cy.createUser(id, "a", roles).then(() => { + cy.removeLogin(); + cy.loginWithCreds(id, "a"); + + cy.intercept("GET", "**/search/Tv/popular/**", (req) => { + req.reply((res) => { + const body = res.body; + const tv = body[5]; + tv.fullyAvailable = false; + + body[5] = tv; + res.send(body); + }); + }).as("cardsResponse"); + cy.intercept("GET", "**/search/Tv/**").as("otherResponses"); + window.localStorage.setItem("DiscoverOptions2", "3"); + + Page.visit(); + + cy.wait("@otherResponses"); + cy.wait("@cardsResponse").then((res) => { + const body = JSON.parse(res.response.body); + var expectedId = body[5].id; + var title = body[5].title; + + const card = Page.popularCarousel.getCard(expectedId, false, DiscoverType.Popular); + card.title.realHover(); + + card.verifyTitle(title); + card.requestButton.should("be.visible"); + card.requestButton.click(); + const modal = card.episodeRequestModal; + + modal.latestSeasonButton.click(); + + cy.verifyNotification("has been added successfully"); + }); + }); }); }); });