Skip to content

Commit

Permalink
* Added the ability to request on behalf of a user
Browse files Browse the repository at this point in the history
* Moved the "advanced" section into a small cog icon on the media details page
* Added some more information on the movie panel e.g. Requested By user
  • Loading branch information
tidusjar committed Jan 4, 2021
1 parent 87233a7 commit 8220f41
Show file tree
Hide file tree
Showing 37 changed files with 552 additions and 330 deletions.
32 changes: 24 additions & 8 deletions src/Ombi.Core/Engine/MovieRequestEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
$"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}";

var userDetails = await GetUser();
var canRequestOnBehalf = false;

if (model.RequestOnBehalf.HasValue())
{
canRequestOnBehalf = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);

if (!canRequestOnBehalf)
{
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!"
};
}
}

var requestModel = new MovieRequests
{
Expand All @@ -82,7 +98,7 @@ public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
Status = movieInfo.Status,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUserId = userDetails.Id,
RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id,
Background = movieInfo.BackdropPath,
LangCode = model.LanguageCode,
RequestedByAlias = model.RequestedByAlias
Expand All @@ -103,7 +119,7 @@ public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)

if (requestModel.Approved) // The rules have auto approved this
{
var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName);
var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
if (requestEngineResult.Result)
{
var result = await ApproveMovie(requestModel);
Expand All @@ -124,7 +140,7 @@ public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
// If there are no providers then it's successful but movie has not been sent
}

return await AddMovieRequest(requestModel, fullMovieName);
return await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf);
}


Expand Down Expand Up @@ -270,7 +286,7 @@ public async Task<RequestsViewModel<MovieRequests>> GetRequestsByStatus(int coun
allRequests = allRequests.Where(x => x.Available);
break;
case RequestStatus.Denied:
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
allRequests = allRequests.Where(x => x.Denied.HasValue && x.Denied.Value && !x.Available);
break;
default:
break;
Expand Down Expand Up @@ -429,7 +445,7 @@ public async Task<IEnumerable<MovieRequests>> GetRequests()
public async Task<MovieRequests> GetRequest(int requestId)
{
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
await CheckForSubscription(new HideResult(), new List<MovieRequests>{request });
await CheckForSubscription(new HideResult(), new List<MovieRequests> { request });

return request;
}
Expand Down Expand Up @@ -654,19 +670,19 @@ public async Task<RequestEngineResult> MarkAvailable(int modelId)
};
}

private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName)
private async Task<RequestEngineResult> AddMovieRequest(MovieRequests model, string movieName, string requestOnBehalf)
{
await MovieRepository.Add(model);

var result = await RunSpecificRule(model, SpecificRules.CanSendNotification);
if (result.Success)
{
{
await NotificationHelper.NewRequest(model);
}

await _requestLog.Add(new RequestLog
{
UserId = (await GetUser()).Id,
UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.Movie,
Expand Down
34 changes: 25 additions & 9 deletions src/Ombi.Core/Engine/TvRequestEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,28 @@ public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
{
var user = await GetUser();
var canRequestOnBehalf = false;

if (tv.RequestOnBehalf.HasValue())
{
canRequestOnBehalf = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);

if (!canRequestOnBehalf)
{
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!"
};
}
}

var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi);
(await tvBuilder
.GetShowInfo(tv.TvDbId))
.CreateTvList(tv)
.CreateChild(tv, user.Id);
.CreateChild(tv, canRequestOnBehalf ? tv.RequestOnBehalf : user.Id);

await tvBuilder.BuildEpisodes(tv);

Expand Down Expand Up @@ -124,12 +140,12 @@ public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
ErrorMessage = "This has already been requested"
};
}
return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest);
return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest, tv.RequestOnBehalf);
}

// This is a new request
var newRequest = tvBuilder.CreateNewRequest(tv);
return await AddRequest(newRequest.NewRequest);
return await AddRequest(newRequest.NewRequest, tv.RequestOnBehalf);
}

public async Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, OrderFilterModel type)
Expand Down Expand Up @@ -736,21 +752,21 @@ private async Task CheckForSubscription(HideResult shouldHide, List<ChildRequest
}
}

private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest)
private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest, string requestOnBehalf)
{
// Add the child
existingRequest.ChildRequests.Add(newRequest);

await TvRepository.Update(existingRequest);

return await AfterRequest(newRequest);
return await AfterRequest(newRequest, requestOnBehalf);
}

private async Task<RequestEngineResult> AddRequest(TvRequests model)
private async Task<RequestEngineResult> AddRequest(TvRequests model, string requestOnBehalf)
{
await TvRepository.Add(model);
// This is a new request so we should only have 1 child
return await AfterRequest(model.ChildRequests.FirstOrDefault());
return await AfterRequest(model.ChildRequests.FirstOrDefault(), requestOnBehalf);
}

private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
Expand All @@ -766,7 +782,7 @@ private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
}


private async Task<RequestEngineResult> AfterRequest(ChildRequests model)
private async Task<RequestEngineResult> AfterRequest(ChildRequests model, string requestOnBehalf)
{
var sendRuleResult = await RunSpecificRule(model, SpecificRules.CanSendNotification);
if (sendRuleResult.Success)
Expand All @@ -776,7 +792,7 @@ private async Task<RequestEngineResult> AfterRequest(ChildRequests model)

await _requestLog.Add(new RequestLog
{
UserId = (await GetUser()).Id,
UserId = requestOnBehalf.HasValue() ? requestOnBehalf : (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.TvShow,
Expand Down
1 change: 1 addition & 0 deletions src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class MovieRequestViewModel
{
public int TheMovieDbId { get; set; }
public string LanguageCode { get; set; } = "en";
public string RequestOnBehalf { get; set; }

/// <summary>
/// This is only set from a HTTP Header
Expand Down
2 changes: 2 additions & 0 deletions src/Ombi.Core/Models/Requests/TvRequestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class TvRequestViewModel
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
[JsonIgnore]
public string RequestedByAlias { get; set; }

public string RequestOnBehalf { get; set; }
}

public class SeasonsViewModel
Expand Down
6 changes: 6 additions & 0 deletions src/Ombi.Core/Models/UI/UserViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ public class ClaimCheckboxes
public string Value { get; set; }
public bool Enabled { get; set; }
}

public class UserViewModelDropdown
{
public string Id { get; set; }
public string Username { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class DiscoverCardDetailsComponent implements OnInit {
public async request() {
this.loading = true;
if (this.data.type === RequestType.movie) {
const result = await this.requestService.requestMovie({ theMovieDbId: this.data.id, languageCode: "" }).toPromise();
const result = await this.requestService.requestMovie({ theMovieDbId: this.data.id, languageCode: "", requestOnBehalf: null }).toPromise();
this.loading = false;

if (result.result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class DiscoverCollectionsComponent implements OnInit {

public async requestCollection() {
await this.collection.collection.forEach(async (movie) => {
await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null}).toPromise();
await this.requestService.requestMovie({theMovieDbId: movie.id, languageCode: null, requestOnBehalf: null}).toPromise();
});
this.messageService.send("Requested Collection");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ImageService, RequestService, SearchV2Service } from "../../../services
import { MatDialog } from "@angular/material/dialog";
import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2";
import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2";
import { EpisodeRequestComponent } from "../../../shared/episode-request/episode-request.component";
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";
Expand Down Expand Up @@ -139,7 +139,7 @@ export class DiscoverGridComponent implements OnInit {
public async request() {
this.requesting = true;
if (this.result.type === RequestType.movie) {
const result = await this.requestService.requestMovie({ theMovieDbId: this.result.id, languageCode: "" }).toPromise();
const result = await this.requestService.requestMovie({ theMovieDbId: this.result.id, languageCode: "", requestOnBehalf: null }).toPromise();

if (result.result) {
this.result.requested = true;
Expand All @@ -148,7 +148,7 @@ export class DiscoverGridComponent implements OnInit {
this.notification.open(result.errorMessage, "Ok");
}
} else if (this.result.type === RequestType.tvShow) {
this.dialog.open(EpisodeRequestComponent, { width: "700px", data: this.tv, panelClass: 'modal-panel' })
this.dialog.open(EpisodeRequestComponent, { width: "700px", data: <EpisodeRequestData> { series: this.tv, requestOnBehalf: null }, panelClass: 'modal-panel' })
}
this.requesting = false;
}
Expand Down
7 changes: 6 additions & 1 deletion src/Ombi/ClientApp/src/app/interfaces/IRadarr.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export interface IRadarrRootFolder {
import { IChildRequests, IMovieRequests } from ".";
import { ITvRequests } from "./IRequestModel";

export interface IRadarrRootFolder {
id: number;
path: string;
}
Expand All @@ -24,4 +27,6 @@ export interface IAdvancedData {
rootFolder: IRadarrRootFolder;
rootFolders: IRadarrRootFolder[];
rootFolderId: number;
movieRequest: IMovieRequests;
tvRequest: ITvRequests;
}
1 change: 1 addition & 0 deletions src/Ombi/ClientApp/src/app/interfaces/IRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export interface IEpisodesRequests {
export interface IMovieRequestModel {
theMovieDbId: number;
languageCode: string | undefined;
requestOnBehalf: string | undefined;
}

export interface IFilter {
Expand Down
1 change: 1 addition & 0 deletions src/Ombi/ClientApp/src/app/interfaces/ISearchTvResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface ITvRequestViewModel {
latestSeason: boolean;
tvDbId: number;
seasons: ISeasonsViewModel[];
requestOnBehalf: string | undefined;
}

export interface ISeasonsViewModel {
Expand Down
5 changes: 5 additions & 0 deletions src/Ombi/ClientApp/src/app/interfaces/IUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export interface IUser {
musicRequestQuota: IRemainingRequests | null;
}

export interface IUserDropdown {
username: string;
id: string;
}

export interface IUserQualityProfiles {
sonarrQualityProfileAnime: number;
sonarrRootPathAnime: number;
Expand Down
10 changes: 6 additions & 4 deletions src/Ombi/ClientApp/src/app/media-details/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { MediaPosterComponent } from "./shared/media-poster/media-poster.compone
import { CastCarouselComponent } from "./shared/cast-carousel/cast-carousel.component";
import { DenyDialogComponent } from "./shared/deny-dialog/deny-dialog.component";
import { TvRequestsPanelComponent } from "./tv/panels/tv-requests/tv-requests-panel.component";
import { MovieAdminPanelComponent } from "./movie/panels/movie-admin-panel/movie-admin-panel.component";
import { MovieAdvancedOptionsComponent } from "./movie/panels/movie-advanced-options/movie-advanced-options.component";
import { SearchService, RequestService, RadarrService, IssuesService, SonarrService } from "../../services";
import { RequestServiceV2 } from "../../services/requestV2.service";
Expand All @@ -18,7 +17,8 @@ import { ArtistDetailsComponent } from "./artist/artist-details.component";
import { ArtistInformationPanel } from "./artist/panels/artist-information-panel/artist-information-panel.component";
import { ArtistReleasePanel } from "./artist/panels/artist-release-panel/artist-release-panel.component";
import { IssuesPanelComponent } from "./shared/issues-panel/issues-panel.component";
import { TvAdminPanelComponent } from "./tv/panels/tv-admin-panel/tv-admin-panel.component";
import { TvAdvancedOptionsComponent } from "./tv/panels/tv-advanced-options/tv-advanced-options.component";
import { RequestBehalfComponent } from "./shared/request-behalf/request-behalf.component";

export const components: any[] = [
MovieDetailsComponent,
Expand All @@ -32,21 +32,23 @@ export const components: any[] = [
CastCarouselComponent,
DenyDialogComponent,
TvRequestsPanelComponent,
MovieAdminPanelComponent,
MovieAdvancedOptionsComponent,
TvAdvancedOptionsComponent,
NewIssueComponent,
ArtistDetailsComponent,
ArtistInformationPanel,
ArtistReleasePanel,
RequestBehalfComponent,
IssuesPanelComponent,
TvAdminPanelComponent,
];

export const entryComponents: any[] = [
YoutubeTrailerComponent,
DenyDialogComponent,
MovieAdvancedOptionsComponent,
TvAdvancedOptionsComponent,
NewIssueComponent,
RequestBehalfComponent,
];

export const providers: any[] = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

</div>

<div class="col-12 col-lg-6 col-xl-6 media-row">
<div class="col-12 col-lg-5 col-xl-5 media-row">

<button mat-raised-button class="btn-green btn-spacing" *ngIf="movie.available"> {{
'Common.Available' | translate }}</button>
Expand Down Expand Up @@ -64,23 +64,28 @@
<button mat-raised-button class="btn-spacing" color="danger" (click)="issue()">
<i class="fa fa-exclamation"></i> {{
'Requests.ReportIssue' | translate }}</button>
</div>




<!-- Setting/Configuration admin area -->
<div class="col-12 col-lg-1 col-xl-1 media-row content-end">
<button *ngIf="isAdmin" mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>settings</mat-icon>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="openRequestOnBehalf()" [disabled]="hasRequest || movie.available">
<mat-icon>supervised_user_circle</mat-icon>
<span>{{'MediaDetails.RequestOnBehalf' | translate}}</span>
</button>
<button mat-menu-item [disabled]="!showAdvanced || !movieRequest" (click)="openAdvancedOptions()">
<mat-icon>movie_filter</mat-icon>
<span>{{'MediaDetails.RadarrConfiguration' | translate}}</span>
</button>
</mat-menu>
</div>
</div>

<div class="row">

<div class="col-12 col-md-2">
<mat-card class="mat-elevation-z8 spacing-below" *ngIf="isAdmin && movieRequest" [ngStyle]="{'display': showAdvanced ? '' : 'none' }">
<mat-card-content class="medium-font">
<movie-admin-panel [movie]="movieRequest" (radarrEnabledChange)="showAdvanced = $event" (advancedOptionsChanged)="setAdvancedOptions($event)">
</movie-admin-panel>
</mat-card-content>
</mat-card>

<mat-card class="mat-elevation-z8">
<mat-card-content class="medium-font">
<movie-information-panel [movie]="movie" [request]="movieRequest" [advancedOptions]="showAdvanced"></movie-information-panel>
Expand Down
Loading

0 comments on commit 8220f41

Please sign in to comment.