Got TV working nicely

This commit is contained in:
tidusjar 2021-03-24 20:57:35 +00:00
parent 62cceb803d
commit 5e6edc2ad8
17 changed files with 176 additions and 123 deletions

View file

@ -67,7 +67,7 @@ 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();
var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
if (model.RequestOnBehalf.HasValue() && !isAdmin)

View file

@ -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,13 +151,10 @@ namespace Ombi.Core.Engine
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModelV2 tv)
{
var user = await GetUser();
var canRequestOnBehalf = false;
var canRequestOnBehalf = tv.RequestOnBehalf.HasValue();
if (tv.RequestOnBehalf.HasValue())
{
canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
if (!canRequestOnBehalf)
var isAdmin = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
if (tv.RequestOnBehalf.HasValue() && !isAdmin)
{
return new RequestEngineResult
{
@ -166,6 +163,15 @@ namespace Ombi.Core.Engine
ErrorMessage = $"You do not have the correct permissions to request on behalf of users!"
};
}
if ((tv.RootFolderOverride.HasValue || tv.QualityPathOverride.HasValue) && !isAdmin)
{
return new RequestEngineResult
{
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<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf)
private async Task<RequestEngineResult> 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);

View file

@ -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<ChildRequests>(),
TotalSeasons = tv.Seasons.Count(),
Background = BackdropPath
Background = BackdropPath,
RootFolder = rootPathOverride,
QualityOverride = qualityOverride
};
NewRequest.ChildRequests.Add(ChildRequest);

View file

@ -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;

View file

@ -5,7 +5,7 @@
</div>
<div *ngIf="discoverResults" class="row full-height">
<div class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults">
<discover-card [result]="result"></discover-card>
<discover-card [isAdmin]="isAdmin" [result]="result"></discover-card>
</div>
</div>
</div>

View file

@ -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 = [];

View file

@ -118,12 +118,17 @@ 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:
if (this.isAdmin) {
this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id }, panelClass: 'modal-panel' });
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.result.requested = true;
}
});
} else {
this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: null, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null }).subscribe(x => {
if (x.result) {

View file

@ -15,7 +15,7 @@
</div>
<div *ngIf="discoverResults" class="row full-height">
<div class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults">
<discover-card [result]="result"></discover-card>
<discover-card [isAdmin]="isAdmins" [result]="result"></discover-card>
</div>
</div>
</div>

View file

@ -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,6 +31,7 @@ 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();
}

View file

@ -89,15 +89,27 @@ export class MovieDetailsComponent {
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id }, panelClass: 'modal-panel' });
dialog.afterClosed().subscribe(async (result) => {
if (result) {
const requestResult = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId,
languageCode: null,
qualityPathOverride: result.radarrPathId,
requestOnBehalf: result.username?.id,
rootFolderOverride: result.radarrFolderId, }).toPromise();
if (requestResult.result) {
this.movie.requested = true;
this.movie.requestId = result.requestId;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.messageService.send(requestResult.message, "Ok");
} else {
this.messageService.send(requestResult.errorMessage, "Ok");
}
}
});
} else {
const result = await this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: null, requestOnBehalf: userId, qualityPathOverride: undefined, rootFolderOverride: undefined }).toPromise();
if (result.result) {
this.movie.requested = true;
this.movie.requestId = result.requestId;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.messageService.send(result.message, "Ok");
} else {
this.messageService.send(result.errorMessage, "Ok");

View file

@ -45,7 +45,7 @@
<div *ngIf="request">
<span class="label">{{'Requests.RequestedBy' | translate }}:</span>
{{request.requestedUser.userAlias}}
<span id="requestedByInfo">{{request.requestedUser.userAlias}}</span>
</div>
<div *ngIf="request">

View file

@ -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);
});
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();
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";
});
this.postRequest(requestResult);
}
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);
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);
}
}
}

View file

@ -76,7 +76,7 @@ export class TvDetailsComponent implements OnInit {
}
public async request(userId: string) {
this.dialog.open(EpisodeRequestComponent, { width: "800px", data: <EpisodeRequestData> { series: this.tv, requestOnBehalf: userId }, panelClass: 'modal-panel' })
this.dialog.open(EpisodeRequestComponent, { width: "800px", data: <EpisodeRequestData> { series: this.tv, requestOnBehalf: userId, isAdmin: this.isAdmin }, panelClass: 'modal-panel' })
}
public async issue() {

View file

@ -3,7 +3,8 @@
<form [formGroup]="form" *ngIf="form">
<h1 id="advancedOptionsTitle">{{'MediaDetails.AdvancedOptions' | translate }}</h1>
<div class="alert alert-info" role="alert">
{{'MediaDetails.AutoApproveOptions' | translate }}
<span *ngIf="data.type === RequestType.movie">{{'MediaDetails.AutoApproveOptions' | translate }}</span>
<span *ngIf="data.type === RequestType.tvShow">{{'MediaDetails.AutoApproveOptionsTv' | translate }}</span>
</div>
<div style="max-width: 0; max-height: 0; overflow: hidden;">
@ -31,7 +32,7 @@
<hr />
<!-- Sonarr -->
<div *ngIf="data.type === RequestType.tvShow">
<div *ngIf="data.type === RequestType.tvShow && sonarrEnabled">
<div>
<h3>Sonarr Overrides</h3>
<mat-form-field appearance="outline" floatLabel=auto>

View file

@ -21,12 +21,9 @@ export class AdminRequestDialogComponent implements OnInit {
constructor(
public dialogRef: MatDialogRef<AdminRequestDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: IAdminRequestDialogData,
private requestServiceV2: RequestServiceV2,
private notificationService: MessageService,
private identityService: IdentityService,
private sonarrService: SonarrService,
private radarrService: RadarrService,
private requestService: RequestService,
private fb: FormBuilder
) {}
@ -38,7 +35,7 @@ export class AdminRequestDialogComponent implements OnInit {
public userId: string;
public radarrEnabled: boolean;
public sonarrEnabled: boolean;
public sonarrProfiles: ISonarrProfile[];
public sonarrRootFolders: ISonarrRootFolder[];
@ -63,6 +60,8 @@ export class AdminRequestDialogComponent implements OnInit {
);
if (this.data.type === RequestType.tvShow) {
this.sonarrEnabled = await this.sonarrService.isEnabled();
if (this.sonarrEnabled) {
this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => {
this.sonarrProfiles = c;
});
@ -70,6 +69,7 @@ export class AdminRequestDialogComponent implements OnInit {
this.sonarrRootFolders = c;
});
}
}
if (this.data.type === RequestType.movie) {
this.radarrEnabled = await this.radarrService.isRadarrEnabled();
if (this.radarrEnabled) {
@ -102,29 +102,10 @@ export class AdminRequestDialogComponent implements OnInit {
public async submitRequest() {
const model = this.form.value;
if (this.data.type === RequestType.movie) {
this.requestService.requestMovie({
qualityPathOverride: model.radarrPathId,
requestOnBehalf: model.username?.id,
rootFolderOverride: model.radarrFolderId,
theMovieDbId: this.data.id,
languageCode: null
}).subscribe((x) => {
if (x.result) {
this.notificationService.send(x.message, "Ok");
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.requestId = x.requestId;
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);
} else {
this.notificationService.send(x.errorMessage, "Ok");
}
})
}
}
}

View file

@ -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<EpisodeRequestComponent>, @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);
});
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();
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);
this.postRequest(requestResult);
}
});
} else {
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);
}
}
}

View file

@ -253,6 +253,7 @@
"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",