pull/1425/head
Jamie.Rees 7 years ago
parent 5b49d03f85
commit f3d4b8a67c

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
namespace Ombi.Core.Engine
{
public interface ITvRequestEngine
{
Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position);
Task RemoveTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv);
Task<IEnumerable<TvRequestModel>> SearchTvRequest(string search);
Task<TvRequestModel> UpdateTvRequest(TvRequestModel request);
}
}

@ -6,7 +6,7 @@ using Ombi.Store.Entities;
namespace Ombi.Core.Engine
{
public interface IRequestEngine
public interface IMovieRequestEngine
{
Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model);
bool ShouldAutoApprove(RequestType requestType);

@ -17,17 +17,15 @@ using Ombi.Notifications.Models;
namespace Ombi.Core.Engine
{
public class RequestEngine : BaseMediaEngine, IRequestEngine
public class MovieRequestEngine : BaseMediaEngine, IMovieRequestEngine
{
public RequestEngine(IMovieDbApi movieApi, ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
{
MovieApi = movieApi;
TvApi = tvApi;
NotificationService = notificationService;
}
private IMovieDbApi MovieApi { get; }
private INotificationService NotificationService { get; }
private ITvMazeApi TvApi { get; }
public async Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model)
{
var movieInfo = await MovieApi.GetMovieInformation(model.Id);
@ -161,41 +159,7 @@ namespace Ombi.Core.Engine
return null;
}
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
{
var showInfo = await TvApi.ShowLookupByTheTvDbId(tv.Id);
DateTime.TryParse(showInfo.premiered, out DateTime firstAir);
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
// For some reason the poster path is always http
var posterPath = showInfo.image?.medium.Replace("http:", "https:");
var model = new TvRequestModel
{
Type = RequestType.TvShow,
Overview = showInfo.summary.RemoveHtml(),
PosterPath = posterPath,
Title = showInfo.name,
ReleaseDate = firstAir,
Status = showInfo.status,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUsers = new List<string> { Username },
Issues = IssueState.None,
ImdbId = showInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.Id.ToString(),
ProviderId = tv.Id,
SeasonsNumbersRequested = tv.SeasonNumbersRequested,
RequestAll = tv.RequestAll
};
var existingRequest = await TvRequestService.CheckRequestAsync(model.Id);
existingRequest?.ChildRequests.Add(model);
return null;
}
private IEnumerable<EpisodesModel> GetListDifferences(IEnumerable<EpisodesModel> existing, IEnumerable<EpisodesModel> request)
{
var newRequest = request

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Hangfire;
using Ombi.Api.TvMaze;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models;
namespace Ombi.Core.Engine
{
public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine
{
public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, INotificationService notificationService) : base(user, requestService)
{
TvApi = tvApi;
NotificationService = notificationService;
}
private INotificationService NotificationService { get; }
private ITvMazeApi TvApi { get; }
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
{
var showInfo = await TvApi.ShowLookupByTheTvDbId(tv.Id);
DateTime.TryParse(showInfo.premiered, out DateTime firstAir);
// For some reason the poster path is always http
var posterPath = showInfo.image?.medium.Replace("http:", "https:");
var model = new TvRequestModel
{
Type = RequestType.TvShow,
Overview = showInfo.summary.RemoveHtml(),
PosterPath = posterPath,
Title = showInfo.name,
ReleaseDate = firstAir,
Status = showInfo.status,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUsers = new List<string> { Username },
Issues = IssueState.None,
ImdbId = showInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.Id.ToString(),
ProviderId = tv.Id,
SeasonsNumbersRequested = tv.SeasonNumbersRequested,
RequestAll = tv.RequestAll
};
var existingRequest = await TvRequestService.CheckRequestAsync(model.Id);
if (existingRequest != null)
{
return await AddExistingRequest(model, existingRequest);
}
// This is a new request
return await AddRequest(model);
}
public async Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position)
{
var allRequests = await TvRequestService.GetAllAsync(count, position);
return allRequests;
}
public async Task<IEnumerable<TvRequestModel>> SearchTvRequest(string search)
{
var allRequests = await TvRequestService.GetAllAsync();
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase));
return results;
}
public async Task<TvRequestModel> UpdateTvRequest(TvRequestModel request)
{
var allRequests = await TvRequestService.GetAllAsync();
var results = allRequests.FirstOrDefault(x => x.Id == request.Id);
results.Approved = request.Approved;
results.Available = request.Available;
results.Denied = request.Denied;
results.DeniedReason = request.DeniedReason;
results.AdminNote = request.AdminNote;
results.ImdbId = request.ImdbId;
results.IssueId = request.IssueId;
results.Issues = request.Issues;
results.OtherMessage = request.OtherMessage;
results.Overview = request.Overview;
results.PosterPath = request.PosterPath;
results.RequestedUsers = request.RequestedUsers?.ToList() ?? new List<string>();
var model = TvRequestService.UpdateRequest(results);
return model;
}
public async Task RemoveTvRequest(int requestId)
{
await TvRequestService.DeleteRequestAsync(requestId);
}
private async Task<RequestEngineResult> AddExistingRequest(TvRequestModel newRequest, TvRequestModel existingRequest)
{
var episodeDifference = new List<EpisodesModel>();
if (existingRequest.HasChildRequests)
{
// Let's check if this has already been requested as a child!
foreach (var children in existingRequest.ChildRequests)
{
var difference = GetListDifferences(children.Episodes, newRequest.Episodes).ToList();
if (difference.Any())
{
episodeDifference = difference;
}
}
}
if (episodeDifference.Any())
{
// This is where there are some episodes that have been requested, but this list contains the 'new' requests
newRequest.Episodes = episodeDifference;
}
existingRequest.ChildRequests.Add(newRequest);
TvRequestService.UpdateRequest(existingRequest);
if (ShouldAutoApprove(RequestType.TvShow))
{
// TODO Auto Approval Code
}
return await AddRequest(newRequest);
}
private IEnumerable<EpisodesModel> GetListDifferences(IEnumerable<EpisodesModel> existing, IEnumerable<EpisodesModel> request)
{
var newRequest = request
.Select(r =>
new EpisodesModel
{
SeasonNumber = r.SeasonNumber,
EpisodeNumber = r.EpisodeNumber
}).ToList();
return newRequest.Except(existing);
}
private async Task<RequestEngineResult> AddRequest(TvRequestModel model)
{
await TvRequestService.AddRequestAsync(model);
if (ShouldSendNotification(model.Type))
{
var notificationModel = new NotificationModel
{
Title = model.Title,
User = Username,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = model.Type,
ImgSrc = model.PosterPath
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel).Wait());
}
//var limit = await RequestLimitRepo.GetAllAsync();
//var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
//if (usersLimit == null)
//{
// await RequestLimitRepo.InsertAsync(new RequestLimit
// {
// Username = Username,
// RequestType = model.Type,
// FirstRequestDate = DateTime.UtcNow,
// RequestCount = 1
// });
//}
//else
//{
// usersLimit.RequestCount++;
// await RequestLimitRepo.UpdateAsync(usersLimit);
//}
return new RequestEngineResult { RequestAdded = true };
}
}
}

@ -12,7 +12,7 @@ namespace Ombi.Core.Models.Requests
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public bool RequestAll { get; set; }
public bool RequestAll { get; set; }
public List<int> SeasonsNumbersRequested { get; set; }
public List<EpisodesModel> Episodes { get; set; }

@ -46,7 +46,8 @@ namespace Ombi.DependencyInjection
public static IServiceCollection RegisterEngines(this IServiceCollection services)
{
services.AddTransient<IMovieEngine, MovieSearchEngine>();
services.AddTransient<IRequestEngine, RequestEngine>();
services.AddTransient<IMovieRequestEngine, MovieRequestEngine>();
services.AddTransient<ITvRequestEngine, TvRequestEngine>();
services.AddTransient<ITvSearchEngine, TvSearchEngine>();
return services;
}

@ -11,49 +11,77 @@ namespace Ombi.Controllers
[Authorize]
public class RequestController : BaseV1ApiController
{
public RequestController(IRequestEngine engine)
public RequestController(IMovieRequestEngine engine, ITvRequestEngine tvRequestEngine)
{
RequestEngine = engine;
MovieRequestEngine = engine;
TvRequestEngine = tvRequestEngine;
}
private IRequestEngine RequestEngine { get; }
private IMovieRequestEngine MovieRequestEngine { get; }
private ITvRequestEngine TvRequestEngine { get; }
[HttpGet("movie/{count:int}/{position:int}", Name = "GetRequestsByCount")]
[HttpGet("movie/{count:int}/{position:int}")]
public async Task<IEnumerable<MovieRequestModel>> GetRequests(int count, int position)
{
return await RequestEngine.GetMovieRequests(count, position);
return await MovieRequestEngine.GetMovieRequests(count, position);
}
[HttpPost("movie")]
public async Task<RequestEngineResult> RequestMovie([FromBody]SearchMovieViewModel movie)
{
return await RequestEngine.RequestMovie(movie);
return await MovieRequestEngine.RequestMovie(movie);
}
//[HttpPost("tv")]
//public async Task<RequestEngineResult> RequestTv([FromBody]SearchTvShowViewModel tv)
//{
// return await RequestEngine.RequestMovie();
//}
[HttpGet("movie/search/{searchTerm}")]
public async Task<IEnumerable<MovieRequestModel>> Search(string searchTerm)
{
return await RequestEngine.SearchMovieRequest(searchTerm);
return await MovieRequestEngine.SearchMovieRequest(searchTerm);
}
[HttpDelete("movie/{requestId:int}")]
public async Task DeleteRequest(int requestId)
{
await RequestEngine.RemoveMovieRequest(requestId);
await MovieRequestEngine.RemoveMovieRequest(requestId);
}
[HttpPut("movie")]
public async Task<MovieRequestModel> UpdateRequest([FromBody]MovieRequestModel model)
{
return await RequestEngine.UpdateMovieRequest(model);
return await MovieRequestEngine.UpdateMovieRequest(model);
}
[HttpGet("tv/{count:int}/{position:int}")]
public async Task<IEnumerable<TvRequestModel>> GetTvRequests(int count, int position)
{
return await TvRequestEngine.GetTvRequests(count, position);
}
[HttpPost("tv")]
public async Task<RequestEngineResult> RequestTv([FromBody]SearchTvShowViewModel tv)
{
return await TvRequestEngine.RequestTvShow(tv);
}
[HttpGet("tv/search/{searchTerm}")]
public async Task<IEnumerable<TvRequestModel>> SearchTv(string searchTerm)
{
return await TvRequestEngine.SearchTvRequest(searchTerm);
}
[HttpDelete("tv/{requestId:int}")]
public async Task DeleteTvRequest(int requestId)
{
await TvRequestEngine.RemoveTvRequest(requestId);
}
[HttpPut("tv")]
public async Task<TvRequestModel> UpdateRequest([FromBody]TvRequestModel model)
{
return await TvRequestEngine.UpdateTvRequest(model);
}
}
}

@ -55,6 +55,28 @@
<Content Update="wwwroot\app\interfaces\ISearchTvResult.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\requests\movierequests - Copy.component.html">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\requests\tvrequests.component.js">
<DependentUpon>tvrequests.component.ts</DependentUpon>
</Content>
<Content Update="wwwroot\app\requests\movierequests - Copy.component.js">
<DependentUpon>movierequests - Copy.component.ts</DependentUpon>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\requests\tvrequests.component.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\requests\request - Copy.component.js">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\requests\request - Copy.component.js.map">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\requests\request - Copy.component.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Update="wwwroot\app\search\moviesearch - Copy.component.html">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>

@ -11,10 +11,16 @@ import { HttpModule } from '@angular/http';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'
// Components
// Search
import { SearchComponent } from './search/search.component';
import { MovieSearchComponent } from './search/moviesearch.component';
import { TvSearchComponent } from './search/tvsearch.component';
// Request
import { RequestComponent } from './requests/request.component';
import { MovieRequestsComponent } from './requests/movierequests.component';
import { TvRequestsComponent } from './requests/tvrequests.component';
import { LoginComponent } from './login/login.component';
import { LandingPageComponent } from './landingpage/landingpage.component';
import { UserManagementComponent } from './usermanagement/usermanagement.component';
@ -75,7 +81,9 @@ const routes: Routes = [
MovieSearchComponent,
TvSearchComponent,
LandingPageComponent,
UserManagementComponent
UserManagementComponent,
MovieRequestsComponent,
TvRequestsComponent
],
providers: [
SearchService,

@ -0,0 +1,178 @@
<div class="form-group">
<div>
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
</div>
</div>
<br/>
<div infinite-scroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="100"
(scrolled)="loadMore()">
<div *ngFor="let request of movieRequests">
<div class="row">
<div class="col-sm-2">
<img *ngIf="request.type == 1" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
<img *ngIf="request.type == 2" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
</div>
<div class="col-sm-5 ">
<div>
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
</a>
</div>
<br />
<div>
<span>Status: </span>
<span class="label label-success">{{request.status}}</span>
</div>
<div>
<span>Request status: </span>
<span *ngIf="request.available" class="label label-success">Available</span>
<span *ngIf="request.approved && !request.available" class="label label-info">Processing Request</span>
<span *ngIf="request.denied" class="label label-danger">Request Denied</span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" class="label label-warning">Pending Approval</span>
</div>
<div *ngIf="request.denied">
Denied: <i style="color:red;" class="fa fa-check"></i>
</div>
<div>Release Date: {{request.releaseDate | date}}</div>
<br/>
<!--{{#if_eq type "tv"}}
{{#if episodes}}
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
{{else}}
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
{{/if}}
{{/if_eq}}-->
<div *ngIf="request.requestedUsers">Requested By: <span *ngFor="let user of request.requestedUsers">{{user}} </span></div>
<div>Requested Date: {{request.requestedDate | date}}</div>
<!--{{#if admin}}
{{#if currentRootPath}}
<div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
{{/if}}
{{/if}}
<div>
{{#if_eq issueId 0}}
@*Nothing*@
{{else}}
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
{{/if_eq}}
</div>-->
</div>
<div class="col-sm-3 col-sm-push-3">
<div *ngIf="isAdmin">
<div *ngIf="!request.approved">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div *ngIf="request.hasQualities" class="btn-group btn-split">
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<!--<ul class="dropdown-menu">
{{#each qualities}}
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
{{/each}}
</ul>-->
</div>
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
</form>
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
{{#if_eq hasRootFolders true}}
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
{{#each rootFolders}}
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
{{/each}}
</ul>
</div>
{{/if_eq}}
</form>-->
<div *ngIf="!request.denied">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden"/>
<input name="reason" type="text" hidden="hidden"/>
<div class="btn-group btn-split">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="deny-with-reason" id="denyReason{{request.requestId}}" href="#" data-toggle="modal" data-target="#denyReasonModal">Deny with a reason</a></li>
</ul>
</div>
</form>
</div>
</div>
<form>
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> Remove</button>
</form>
<form>
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> Mark Unavailable</button>
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> Mark Available</button>
</form>
</div>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div class="dropdown">
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
<li><a issue-select="3">@UI.Issues_Playback</a></li>
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
</ul>
</div>
</div>
</div>
<hr />
</div>
</div>

@ -0,0 +1,109 @@
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import { RequestService } from '../services/request.service';
import { IdentityService } from '../services/identity.service';
import { IMovieRequestModel } from '../interfaces/IRequestModel';
@Component({
selector: 'movie-requests',
moduleId: module.id,
templateUrl: './movierequests.component.html'
})
export class MovieRequestsComponent implements OnInit {
constructor(private requestService: RequestService, private identityService: IdentityService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.resetSearch();
return;
}
this.requestService.searchMovieRequests(this.searchText).subscribe(m => this.movieRequests = m);
});
}
movieRequests: IMovieRequestModel[];
searchChanged: Subject<string> = new Subject<string>();
searchText: string;
isAdmin : boolean;
private currentlyLoaded: number;
private amountToLoad : number;
ngOnInit() {
this.amountToLoad = 5;
this.currentlyLoaded = 5;
this.loadInit();
}
loadMore() {
this.requestService.getMovieRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => {
this.movieRequests.push.apply(this.movieRequests, x);
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
});
}
search(text: any) {
this.searchChanged.next(text.target.value);
}
removeRequest(request: IMovieRequestModel) {
this.requestService.removeMovieRequest(request);
this.removeRequestFromUi(request);
}
changeAvailability(request: IMovieRequestModel, available: boolean) {
request.available = available;
this.updateRequest(request);
}
approve(request: IMovieRequestModel) {
request.approved = true;
request.denied = false;
this.updateRequest(request);
}
deny(request: IMovieRequestModel) {
request.approved = false;
request.denied = true;
this.updateRequest(request);
}
private updateRequest(request: IMovieRequestModel) {
this.requestService.updateMovieRequest(request).subscribe(x => request = x);
}
private loadInit() {
this.requestService.getMovieRequests(this.amountToLoad, 0).subscribe(x => this.movieRequests = x);
this.isAdmin = this.identityService.hasRole("Admin");
}
private resetSearch() {
this.currentlyLoaded = 5;
this.loadInit();
}
private removeRequestFromUi(key: IMovieRequestModel) {
var index = this.movieRequests.indexOf(key, 0);
if (index > -1) {
this.movieRequests.splice(index, 1);
}
}
}

@ -1,180 +1,25 @@
<h1 id="searchTitle">Requests</h1>
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
<div class="form-group">
<div>
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
</div>
</div>
<br/>
<div infinite-scroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="100"
(scrolled)="loadMore()">
<div *ngFor="let request of movieRequests">
<div class="row">
<div class="col-sm-2">
<img *ngIf="request.type == 1" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
<img *ngIf="request.type == 2" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
</div>
<div class="col-sm-5 ">
<div>
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
</a>
</div>
<br />
<div>
<span>Status: </span>
<span class="label label-success">{{request.status}}</span>
</div>
<div>
<span>Request status: </span>
<span *ngIf="request.available" class="label label-success">Available</span>
<span *ngIf="request.approved && !request.available" class="label label-info">Processing Request</span>
<span *ngIf="request.denied" class="label label-danger">Request Denied</span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" class="label label-warning">Pending Approval</span>
</div>
<div *ngIf="request.denied">
Denied: <i style="color:red;" class="fa fa-check"></i>
</div>
<div>Release Date: {{request.releaseDate | date}}</div>
<br/>
<!--{{#if_eq type "tv"}}
{{#if episodes}}
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
{{else}}
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
{{/if}}
{{/if_eq}}-->
<div *ngIf="request.requestedUsers">Requested By: <span *ngFor="let user of request.requestedUsers">{{user}} </span></div>
<div>Requested Date: {{request.requestedDate | date}}</div>
<!--{{#if admin}}
{{#if currentRootPath}}
<div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
{{/if}}
{{/if}}
<div>
{{#if_eq issueId 0}}
@*Nothing*@
{{else}}
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
{{/if_eq}}
</div>-->
</div>
<div class="col-sm-3 col-sm-push-3">
<div *ngIf="isAdmin">
<div *ngIf="!request.approved">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div *ngIf="request.hasQualities" class="btn-group btn-split">
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<!--<ul class="dropdown-menu">
{{#each qualities}}
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
{{/each}}
</ul>-->
</div>
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
</form>
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
{{#if_eq hasRootFolders true}}
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
{{#each rootFolders}}
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
{{/each}}
</ul>
</div>
{{/if_eq}}
</form>-->
<div *ngIf="!request.denied">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden"/>
<input name="reason" type="text" hidden="hidden"/>
<div class="btn-group btn-split">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="deny-with-reason" id="denyReason{{request.requestId}}" href="#" data-toggle="modal" data-target="#denyReasonModal">Deny with a reason</a></li>
</ul>
</div>
</form>
</div>
</div>
<form>
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> Remove</button>
</form>
<form>
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> Mark Unavailable</button>
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> Mark Available</button>
</form>
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a id="movieTabButton" aria-controls="home" role="tab" data-toggle="tab" (click)="selectTab()"><i class="fa fa-film"></i> Movies</a>
</li>
<li role="presentation">
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTab()"><i class="fa fa-television"></i> TV Shows</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div [hidden]="!showMovie">
<movie-requests></movie-requests>
</div>
<div [hidden]="!showTv">
<tv-requests></tv-requests>
</div>
</div>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div class="dropdown">
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
<li><a issue-select="3">@UI.Issues_Playback</a></li>
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
</ul>
</div>
</div>
</div>
<hr />
</div>
</div>

@ -1,110 +1,18 @@
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import { RequestService } from '../services/request.service';
import { IdentityService } from '../services/identity.service';
import { IMovieRequestModel, ITvRequestModel } from '../interfaces/IRequestModel';
import { Component } from '@angular/core';
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './request.component.html'
})
export class RequestComponent implements OnInit {
constructor(private requestService: RequestService, private identityService: IdentityService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.resetSearch();
return;
}
this.requestService.searchRequests(this.searchText).subscribe(x => this.movieRequests = x);
});
}
movieRequests: IMovieRequestModel[];
tvRequests: ITvRequestModel[];
searchChanged: Subject<string> = new Subject<string>();
searchText: string;
isAdmin : boolean;
private currentlyLoaded: number;
private amountToLoad : number;
ngOnInit() {
this.amountToLoad = 5;
this.currentlyLoaded = 5;
this.loadInit();
}
loadMore() {
this.requestService.getRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => {
this.movieRequests.push.apply(this.movieRequests, x);
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
});
}
export class RequestComponent {
search(text: any) {
this.searchChanged.next(text.target.value);
}
removeRequest(request: IMovieRequestModel) {
this.requestService.removeMovieRequest(request);
this.removeRequestFromUi(request);
}
changeAvailability(request: IMovieRequestModel, available: boolean) {
request.available = available;
this.updateRequest(request);
}
approve(request: IMovieRequestModel) {
request.approved = true;
request.denied = false;
this.updateRequest(request);
}
showMovie = true;
showTv = false;
deny(request: IMovieRequestModel) {
request.approved = false;
request.denied = true;
this.updateRequest(request);
selectTab() {
this.showMovie = !this.showMovie;
this.showTv = !this.showTv;
}
private updateRequest(request: IMovieRequestModel) {
this.requestService.updateRequest(request).subscribe(x => request = x);
}
private loadInit() {
this.requestService.getRequests(this.amountToLoad, 0).subscribe(x => this.movieRequests = x);
this.isAdmin = this.identityService.hasRole("Admin");
}
private resetSearch() {
this.currentlyLoaded = 5;
this.loadInit();
}
private removeRequestFromUi(key: IMovieRequestModel) {
var index = this.movieRequests.indexOf(key, 0);
if (index > -1) {
this.movieRequests.splice(index, 1);
}
}
}

@ -0,0 +1,178 @@
<div class="form-group">
<div>
<input type="text" class="form-control form-control-custom" placeholder="Search" (keyup)="search($event)">
</div>
</div>
<br/>
<div infinite-scroll
[infiniteScrollDistance]="1"
[infiniteScrollThrottle]="100"
(scrolled)="loadMore()">
<div *ngFor="let request of tvRequests">
<div class="row">
<div class="col-sm-2">
<img *ngIf="request.type == 1" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
<img *ngIf="request.type == 2" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
</div>
<div class="col-sm-5 ">
<div>
<a href="http://www.imdb.com/title/{{request.imdbId}}/" target="_blank">
<h4 class="request-title">{{request.title}} ({{request.releaseDate | date: 'yyyy'}})</h4>
</a>
</div>
<br />
<div>
<span>Status: </span>
<span class="label label-success">{{request.status}}</span>
</div>
<div>
<span>Request status: </span>
<span *ngIf="request.available" class="label label-success">Available</span>
<span *ngIf="request.approved && !request.available" class="label label-info">Processing Request</span>
<span *ngIf="request.denied" class="label label-danger">Request Denied</span>
<span *ngIf="request.deniedReason" title="{{request.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!request.approved && !request.availble && !request.denied" class="label label-warning">Pending Approval</span>
</div>
<div *ngIf="request.denied">
Denied: <i style="color:red;" class="fa fa-check"></i>
</div>
<div>Release Date: {{request.releaseDate | date}}</div>
<br/>
<!--{{#if_eq type "tv"}}
{{#if episodes}}
Episodes: <span class="customTooltip" data-tooltip-content="#{{requestId}}toolTipContent"><i class="fa fa-info-circle"></i></span>
{{else}}
<div>@UI.Requests_SeasonsRequested: {{seriesRequested}}</div>
{{/if}}
{{/if_eq}}-->
<div *ngIf="request.requestedUsers">Requested By: <span *ngFor="let user of request.requestedUsers">{{user}} </span></div>
<div>Requested Date: {{request.requestedDate | date}}</div>
<!--{{#if admin}}
{{#if currentRootPath}}
<div class="{{requestId}}rootPathMain">Root Path: <span id="{{requestId}}currentRootPath">{{currentRootPath}}</span></div>
{{/if}}
{{/if}}
<div>
{{#if_eq issueId 0}}
@*Nothing*@
{{else}}
@UI.Issues_Issue: <a href="@formAction/issues/{{issueId}}"><i class="fa fa-check"></i></a>
{{/if_eq}}
</div>-->
</div>
<div class="col-sm-3 col-sm-push-3">
<div *ngIf="isAdmin">
<div *ngIf="!request.approved">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div *ngIf="request.hasQualities" class="btn-group btn-split">
<button type="button" (click)="approve(request)" class="btn btn-sm btn-success-outline approve"><i class="fa fa-plus"></i> Approve</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<!--<ul class="dropdown-menu">
{{#each qualities}}
<li><a href="#" class="approve-with-quality" id="{{id}}">{{name}}</a></li>
{{/each}}
</ul>-->
</div>
<button *ngIf="!request.hasQualities" (click)="approve(request)" style="text-align: right" class="btn btn-sm btn-success-outline approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
</form>
<!--<form method="POST" action="@formAction/requests/changeRootFolder{{#if_eq type "tv"}}tv{{else}}movie{{/if_eq}}" id="changeFolder{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden"/>
{{#if_eq hasRootFolders true}}
<div class="btn-group btn-split">
<button type="button" class="btn btn-sm btn-success-outline" id="changeRootFolderBtn{{requestId}}" custom-button="{{requestId}}">@*<i class="fa fa-plus"></i>*@ Change Root Folder</button>
<button type="button" class="btn btn-success-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
{{#each rootFolders}}
<li><a href="#" class="change-root-folder" id="{{id}}" requestId="{{requestId}}">{{path}}</a></li>
{{/each}}
</ul>
</div>
{{/if_eq}}
</form>-->
<div *ngIf="!request.denied">
<form>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden"/>
<input name="reason" type="text" hidden="hidden"/>
<div class="btn-group btn-split">
<button type="button" (click)="deny(request)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> Deny</button>
<button type="button" class="btn btn-danger-outline dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">@UI.Requests_ToggleDropdown</span>
</button>
<ul class="dropdown-menu">
<li><a class="deny-with-reason" id="denyReason{{request.requestId}}" href="#" data-toggle="modal" data-target="#denyReasonModal">Deny with a reason</a></li>
</ul>
</div>
</form>
</div>
</div>
<form>
<button (click)="removeRequest(request)" style="text-align: right" class="btn btn-sm btn-danger-outline delete"><i class="fa fa-minus"></i> Remove</button>
</form>
<form>
<button *ngIf="request.available" (click)="changeAvailability(request, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> Mark Unavailable</button>
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> Mark Available</button>
</form>
</div>
<input name="requestId" type="text" value="{{request.requestId}}" hidden="hidden" />
<div class="dropdown">
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
<li><a issue-select="3">@UI.Issues_Playback</a></li>
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
</ul>
</div>
</div>
</div>
<hr />
</div>
</div>

@ -0,0 +1,109 @@
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import { RequestService } from '../services/request.service';
import { IdentityService } from '../services/identity.service';
import { ITvRequestModel } from '../interfaces/IRequestModel';
@Component({
selector: 'tv-requests',
moduleId: module.id,
templateUrl: './tvrequests.component.html'
})
export class TvRequestsComponent implements OnInit {
constructor(private requestService: RequestService, private identityService: IdentityService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.resetSearch();
return;
}
this.requestService.searchTvRequests(this.searchText).subscribe(m => this.tvRequests = m);
});
}
tvRequests: ITvRequestModel[];
searchChanged = new Subject<string>();
searchText: string;
isAdmin : boolean;
private currentlyLoaded: number;
private amountToLoad : number;
ngOnInit() {
this.amountToLoad = 5;
this.currentlyLoaded = 5;
this.loadInit();
}
loadMore() {
this.requestService.getTvRequests(this.amountToLoad, this.currentlyLoaded + 1).subscribe(x => {
this.tvRequests.push.apply(this.tvRequests, x);
this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad;
});
}
search(text: any) {
this.searchChanged.next(text.target.value);
}
removeRequest(request: ITvRequestModel) {
this.requestService.removeTvRequest(request);
this.removeRequestFromUi(request);
}
changeAvailability(request: ITvRequestModel, available: boolean) {
request.available = available;
this.updateRequest(request);
}
approve(request: ITvRequestModel) {
request.approved = true;
request.denied = false;
this.updateRequest(request);
}
deny(request: ITvRequestModel) {
request.approved = false;
request.denied = true;
this.updateRequest(request);
}
private updateRequest(request: ITvRequestModel) {
this.requestService.updateTvRequest(request).subscribe(x => request = x);
}
private loadInit() {
this.requestService.getTvRequests(this.amountToLoad, 0).subscribe(x => this.tvRequests = x);
this.isAdmin = this.identityService.hasRole("Admin");
}
private resetSearch() {
this.currentlyLoaded = 5;
this.loadInit();
}
private removeRequestFromUi(key: ITvRequestModel) {
var index = this.tvRequests.indexOf(key, 0);
if (index > -1) {
this.tvRequests.splice(index, 1);
}
}
}

@ -6,7 +6,7 @@ import { ServiceAuthHelpers } from './service.helpers';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
import { ISearchTvResult } from '../interfaces/ISearchTvResult';
import { IMovieRequestModel } from '../interfaces/IRequestModel';
import { IMovieRequestModel, ITvRequestModel } from '../interfaces/IRequestModel';
@Injectable()
export class RequestService extends ServiceAuthHelpers {
@ -22,11 +22,11 @@ export class RequestService extends ServiceAuthHelpers {
return this.http.post(`${this.url}/TV/`, JSON.stringify(tv), { headers: this.headers }).map(this.extractData);
}
getRequests(count: number, position: number): Observable<IMovieRequestModel[]> {
getMovieRequests(count: number, position: number): Observable<IMovieRequestModel[]> {
return this.http.get(`${this.url}/movie/${count}/${position}`).map(this.extractData);
}
searchRequests(search: string): Observable<IMovieRequestModel[]> {
searchMovieRequests(search: string): Observable<IMovieRequestModel[]> {
return this.http.get(`${this.url}/movie/search/${search}`).map(this.extractData);
}
@ -34,7 +34,23 @@ export class RequestService extends ServiceAuthHelpers {
this.http.delete(`${this.url}/movie/${request.id}`).map(this.extractData).subscribe();
}
updateRequest(request: IMovieRequestModel): Observable<IMovieRequestModel> {
updateMovieRequest(request: IMovieRequestModel): Observable<IMovieRequestModel> {
return this.http.post(`${this.url}/movie/`, JSON.stringify(request), { headers: this.headers }).map(this.extractData);
}
getTvRequests(count: number, position: number): Observable<ITvRequestModel[]> {
return this.http.get(`${this.url}/tv/${count}/${position}`).map(this.extractData);
}
searchTvRequests(search: string): Observable<ITvRequestModel[]> {
return this.http.get(`${this.url}/tv/search/${search}`).map(this.extractData);
}
removeTvRequest(request: ITvRequestModel) {
this.http.delete(`${this.url}/tv/${request.id}`).map(this.extractData).subscribe();
}
updateTvRequest(request: ITvRequestModel): Observable<ITvRequestModel> {
return this.http.post(`${this.url}/tv/`, JSON.stringify(request), { headers: this.headers }).map(this.extractData);
}
}
Loading…
Cancel
Save