Added the request limits in the ui for music

This commit is contained in:
Jamie 2018-09-20 22:18:26 +01:00
parent 550028b9eb
commit be2d88c9ea
17 changed files with 107 additions and 9 deletions

View file

@ -1,7 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Models;
using Ombi.Core.Models.Requests; using Ombi.Core.Models.Requests;
using Ombi.Core.Models.UI; using Ombi.Core.Models.UI;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Engine namespace Ombi.Core.Engine
@ -20,5 +22,6 @@ namespace Ombi.Core.Engine
Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model); Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model);
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search); Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
Task<bool> UserHasRequest(string userId); Task<bool> UserHasRequest(string userId);
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null);
} }
} }

View file

@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr; using Ombi.Api.Lidarr;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models;
using Ombi.Core.Models.UI; using Ombi.Core.Models.UI;
using Ombi.Core.Rule.Interfaces; using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Senders; using Ombi.Core.Senders;
@ -409,6 +410,49 @@ namespace Ombi.Core.Engine
Result = true Result = true
}; };
} }
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
{
if (user == null)
{
user = await GetUser();
// If user is still null after attempting to get the logged in user, return null.
if (user == null)
{
return null;
}
}
int limit = user.MusicRequestLimit ?? 0;
if (limit <= 0)
{
return new RequestQuotaCountModel()
{
HasLimit = false,
Limit = 0,
Remaining = 0,
NextRequest = DateTime.Now,
};
}
IQueryable<RequestLog> log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Album);
int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7))
.OrderBy(x => x.RequestDate)
.Select(x => x.RequestDate)
.FirstOrDefaultAsync();
return new RequestQuotaCountModel()
{
HasLimit = true,
Limit = limit,
Remaining = count,
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
};
}
public async Task<RequestEngineResult> MarkAvailable(int modelId) public async Task<RequestEngineResult> MarkAvailable(int modelId)
{ {
@ -453,5 +497,7 @@ namespace Ombi.Core.Engine
return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!" }; return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!" };
} }
} }
} }

View file

@ -19,6 +19,7 @@ namespace Ombi.Core.Models.UI
public int EpisodeRequestLimit { get; set; } public int EpisodeRequestLimit { get; set; }
public RequestQuotaCountModel EpisodeRequestQuota { get; set; } public RequestQuotaCountModel EpisodeRequestQuota { get; set; }
public RequestQuotaCountModel MovieRequestQuota { get; set; } public RequestQuotaCountModel MovieRequestQuota { get; set; }
public RequestQuotaCountModel MusicRequestQuota { get; set; }
public int MusicRequestLimit { get; set; } public int MusicRequestLimit { get; set; }
public UserQualityProfiles UserQualityProfiles { get; set; } public UserQualityProfiles UserQualityProfiles { get; set; }
} }

View file

@ -20,6 +20,7 @@ export interface IUser {
// FOR UI // FOR UI
episodeRequestQuota: IRemainingRequests | null; episodeRequestQuota: IRemainingRequests | null;
movieRequestQuota: IRemainingRequests | null; movieRequestQuota: IRemainingRequests | null;
musicRequestQuota: IRemainingRequests | null;
checked: boolean; checked: boolean;
} }

View file

@ -12,6 +12,8 @@ import { Observable } from "rxjs";
export class RemainingRequestsComponent implements OnInit { export class RemainingRequestsComponent implements OnInit {
public remaining: IRemainingRequests; public remaining: IRemainingRequests;
@Input() public movie: boolean; @Input() public movie: boolean;
@Input() public tv: boolean;
@Input() public music: boolean;
public daysUntil: number; public daysUntil: number;
public hoursUntil: number; public hoursUntil: number;
public minutesUntil: number; public minutesUntil: number;
@ -42,9 +44,13 @@ export class RemainingRequestsComponent implements OnInit {
if (this.movie) { if (this.movie) {
this.requestService.getRemainingMovieRequests().subscribe(callback); this.requestService.getRemainingMovieRequests().subscribe(callback);
} else { }
if(this.tv) {
this.requestService.getRemainingTvRequests().subscribe(callback); this.requestService.getRemainingTvRequests().subscribe(callback);
} }
if(this.music) {
this.requestService.getRemainingMusicRequests().subscribe(callback);
}
} }
private calculateTime(): void { private calculateTime(): void {

View file

@ -89,9 +89,9 @@ export class MovieSearchComponent implements OnInit {
try { try {
this.requestService.requestMovie({ theMovieDbId: searchResult.id }) this.requestService.requestMovie({ theMovieDbId: searchResult.id })
.subscribe(x => { .subscribe(x => {
this.movieRequested.next();
this.result = x; this.result = x;
if (this.result.result) { if (this.result.result) {
this.movieRequested.next();
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
this.notificationService.success(x); this.notificationService.success(x);
searchResult.processed = true; searchResult.processed = true;

View file

@ -1,6 +1,7 @@
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { Subject } from "rxjs";
import { AuthService } from "../../auth/auth.service"; import { AuthService } from "../../auth/auth.service";
import { IIssueCategory, IRequestEngineResult } from "../../interfaces"; import { IIssueCategory, IRequestEngineResult } from "../../interfaces";
import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult"; import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult";
@ -18,6 +19,8 @@ export class AlbumSearchComponent {
@Input() public issueCategories: IIssueCategory[]; @Input() public issueCategories: IIssueCategory[];
@Input() public issuesEnabled: boolean; @Input() public issuesEnabled: boolean;
@Input() public musicRequested: Subject<void>;
public issuesBarVisible = false; public issuesBarVisible = false;
public issueRequestTitle: string; public issueRequestTitle: string;
public issueRequestId: number; public issueRequestId: number;
@ -56,10 +59,12 @@ export class AlbumSearchComponent {
try { try {
this.requestService.requestAlbum({ foreignAlbumId: searchResult.foreignAlbumId }) this.requestService.requestAlbum({ foreignAlbumId: searchResult.foreignAlbumId })
.subscribe(x => { .subscribe(x => {
this.engineResult = x; this.engineResult = x;
if (this.engineResult.result) { if (this.engineResult.result) {
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { this.musicRequested.next();
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
this.notificationService.success(x); this.notificationService.success(x);
searchResult.processed = true; searchResult.processed = true;
}); });

View file

@ -28,6 +28,9 @@
<div class='no-search-results-text' [translate]="'Search.NoResults'"></div> <div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
</div> </div>
<remaining-requests [music]="true" [quotaRefreshEvents]="musicRequested.asObservable()" #remainingAlbums></remaining-requests>
<div *ngFor="let result of artistResult"> <div *ngFor="let result of artistResult">
<artist-search [result]="result" [defaultPoster]="defaultPoster" (viewAlbumsResult)="viewAlbumsForArtist($event)"></artist-search> <artist-search [result]="result" [defaultPoster]="defaultPoster" (viewAlbumsResult)="viewAlbumsForArtist($event)"></artist-search>
<br/> <br/>
@ -35,7 +38,7 @@
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<div *ngFor="let result of albumResult" class="col-md-4"> <div *ngFor="let result of albumResult" class="col-md-4">
<album-search [result]="result" [defaultPoster]="defaultPoster" (setSearch)="setArtistSearch($event)"></album-search> <album-search [musicRequested]="musicRequested" [result]="result" [defaultPoster]="defaultPoster" (setSearch)="setArtistSearch($event)"></album-search>
<br/> <br/>
<br/> <br/>
</div> </div>

View file

@ -24,6 +24,7 @@ export class MusicSearchComponent implements OnInit {
public searchApplied = false; public searchApplied = false;
public searchAlbum: boolean = true; public searchAlbum: boolean = true;
public musicRequested: Subject<void> = new Subject<void>();
@Input() public issueCategories: IIssueCategory[]; @Input() public issueCategories: IIssueCategory[];
@Input() public issuesEnabled: boolean; @Input() public issuesEnabled: boolean;
public issuesBarVisible = false; public issuesBarVisible = false;

View file

@ -27,7 +27,7 @@
</div> </div>
</div> </div>
<remaining-requests [movie]="false" [quotaRefreshEvents]="tvRequested.asObservable()" #remainingTvShows></remaining-requests> <remaining-requests [tv]="true" [quotaRefreshEvents]="tvRequested.asObservable()" #remainingTvShows></remaining-requests>
<!-- Movie content --> <!-- Movie content -->
<div id="actorMovieList"> <div id="actorMovieList">

View file

@ -26,6 +26,10 @@ export class RequestService extends ServiceHelpers {
return this.http.get<IRemainingRequests>(`${this.url}tv/remaining`, {headers: this.headers}); return this.http.get<IRemainingRequests>(`${this.url}tv/remaining`, {headers: this.headers});
} }
public getRemainingMusicRequests(): Observable<IRemainingRequests> {
return this.http.get<IRemainingRequests>(`${this.url}music/remaining`, {headers: this.headers});
}
public requestMovie(movie: IMovieRequestModel): Observable<IRequestEngineResult> { public requestMovie(movie: IMovieRequestModel): Observable<IRequestEngineResult> {
return this.http.post<IRequestEngineResult>(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); return this.http.post<IRequestEngineResult>(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers});
} }

View file

@ -82,6 +82,7 @@ export class UserManagementUserComponent implements OnInit {
sonarrRootPath: 0, sonarrRootPath: 0,
sonarrRootPathAnime: 0, sonarrRootPathAnime: 0,
}, },
musicRequestQuota: null,
}; };
} }
} }

View file

@ -98,6 +98,9 @@
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit"> <div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.hasLimit">
{{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }} {{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }}
</div> </div>
<div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.hasLimit">
{{'UserManagment.MusicRemaining' | translate: {remaining: u.musicRequestQuota.remaining, total: u.musicRequestLimit} }}
</div>
</td> </td>
<td class="td-labelled" data-label="Request Due"> <td class="td-labelled" data-label="Request Due">
<div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit"> <div *ngIf="u.movieRequestQuota != null && u.movieRequestQuota.remaining != u.movieRequestLimit">
@ -106,6 +109,9 @@
<div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit"> <div *ngIf="u.episodeRequestQuota != null && u.episodeRequestQuota.remaining != u.episodeRequestLimit">
{{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | date: 'short')} }} {{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | date: 'short')} }}
</div> </div>
<div *ngIf="u.musicRequestQuota != null && u.musicRequestQuota.remaining != u.musicRequestLimit">
{{'UserManagment.MusicDue' | translate: {date: (u.musicRequestQuota.nextRequest | date: 'short')} }}
</div>
</td> </td>
<td class="td-labelled" data-label="Last Logged In:"> <td class="td-labelled" data-label="Last Logged In:">
{{u.lastLoggedIn | date: 'short'}} {{u.lastLoggedIn | date: 'short'}}

View file

@ -16,6 +16,7 @@ using Ombi.Api.Plex;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Config; using Ombi.Config;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Helpers; using Ombi.Core.Helpers;
using Ombi.Core.Models.UI; using Ombi.Core.Models.UI;
@ -66,7 +67,8 @@ namespace Ombi.Controllers
IRepository<UserQualityProfiles> userProfiles, IRepository<UserQualityProfiles> userProfiles,
IMusicRequestRepository musicRepo, IMusicRequestRepository musicRepo,
IMovieRequestEngine movieRequestEngine, IMovieRequestEngine movieRequestEngine,
ITvRequestEngine tvRequestEngine) ITvRequestEngine tvRequestEngine,
IMusicRequestEngine musicEngine)
{ {
UserManager = user; UserManager = user;
Mapper = mapper; Mapper = mapper;
@ -92,6 +94,7 @@ namespace Ombi.Controllers
MovieRequestEngine = movieRequestEngine; MovieRequestEngine = movieRequestEngine;
_userNotificationPreferences = notificationPreferences; _userNotificationPreferences = notificationPreferences;
_userQualityProfiles = userProfiles; _userQualityProfiles = userProfiles;
MusicRequestEngine = musicEngine;
} }
private OmbiUserManager UserManager { get; } private OmbiUserManager UserManager { get; }
@ -106,6 +109,7 @@ namespace Ombi.Controllers
private IMovieRequestRepository MovieRepo { get; } private IMovieRequestRepository MovieRepo { get; }
private ITvRequestRepository TvRepo { get; } private ITvRequestRepository TvRepo { get; }
private IMovieRequestEngine MovieRequestEngine { get; } private IMovieRequestEngine MovieRequestEngine { get; }
private IMusicRequestEngine MusicRequestEngine { get; }
private ITvRequestEngine TvRequestEngine { get; } private ITvRequestEngine TvRequestEngine { get; }
private IMusicRequestRepository MusicRepo { get; } private IMusicRequestRepository MusicRepo { get; }
private readonly ILogger<IdentityController> _log; private readonly ILogger<IdentityController> _log;
@ -342,6 +346,11 @@ namespace Ombi.Controllers
vm.MovieRequestQuota = await MovieRequestEngine.GetRemainingRequests(user); vm.MovieRequestQuota = await MovieRequestEngine.GetRemainingRequests(user);
} }
if (vm.MusicRequestLimit > 0)
{
vm.MusicRequestQuota = await MusicRequestEngine.GetRemainingRequests(user);
}
// Get the quality profiles // Get the quality profiles
vm.UserQualityProfiles = await _userQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == user.Id); vm.UserQualityProfiles = await _userQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == user.Id);
if (vm.UserQualityProfiles == null) if (vm.UserQualityProfiles == null)

View file

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Models;
using Ombi.Core.Models.UI; using Ombi.Core.Models.UI;
namespace Ombi.Controllers namespace Ombi.Controllers
@ -140,5 +141,14 @@ namespace Ombi.Controllers
{ {
return await _engine.DenyAlbumById(model.Id); return await _engine.DenyAlbumById(model.Id);
} }
/// <summary>
/// Gets model containing remaining number of music requests.
/// </summary>
[HttpGet("remaining")]
public async Task<RequestQuotaCountModel> GetRemainingMusicRequests()
{
return await _engine.GetRemainingRequests();
}
} }
} }

View file

@ -467,7 +467,7 @@ namespace Ombi.Controllers
} }
/// <summary> /// <summary>
/// Gets model containing remaining number of requests. /// Gets model containing remaining number of movie requests.
/// </summary> /// </summary>
[HttpGet("movie/remaining")] [HttpGet("movie/remaining")]
public async Task<RequestQuotaCountModel> GetRemainingMovieRequests() public async Task<RequestQuotaCountModel> GetRemainingMovieRequests()
@ -476,7 +476,7 @@ namespace Ombi.Controllers
} }
/// <summary> /// <summary>
/// Gets model containing remaining number of requests. /// Gets model containing remaining number of tv requests.
/// </summary> /// </summary>
[HttpGet("tv/remaining")] [HttpGet("tv/remaining")]
public async Task<RequestQuotaCountModel> GetRemainingTvRequests() public async Task<RequestQuotaCountModel> GetRemainingTvRequests()

View file

@ -189,7 +189,9 @@
"UserManagment": { "UserManagment": {
"TvRemaining": "TV: {{remaining}}/{{total}} remaining", "TvRemaining": "TV: {{remaining}}/{{total}} remaining",
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining",
"TvDue": "TV: {{date}}", "TvDue": "TV: {{date}}",
"MovieDue": "Movie: {{date}}" "MovieDue": "Movie: {{date}}",
"MusicDue": "Music: {{date}}"
} }
} }