Skip to content

Commit

Permalink
feat: Recently requested improvements (#4755)
Browse files Browse the repository at this point in the history
* feat(discover): ✨ Admins can now approve the Recently Requested list

* feat(discover): ⚡ Images for the recently requested area are now loading faster and just better all around

* test: ✅ Added automation for the new feature
  • Loading branch information
tidusjar authored Sep 14, 2022
1 parent b0b1764 commit ff04d87
Show file tree
Hide file tree
Showing 18 changed files with 292 additions and 95 deletions.
9 changes: 3 additions & 6 deletions src/Ombi.Core.Tests/Services/RecentlyRequestedServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public void Setup()
.ForEach(b => _fixture.Behaviors.Remove(b));
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
_mocker = new AutoMocker();

_mocker.Setup<ICurrentUser, Task<OmbiUser>>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias", Language = "en" });
_mocker.Setup<ICurrentUser, string>(x => x.Username).Returns("test");
_subject = _mocker.CreateInstance<RecentlyRequestedService>();
}

Expand Down Expand Up @@ -70,8 +73,6 @@ public async Task GetRecentlyRequested_Movies()
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
_mocker.Setup<ICurrentUser, Task<OmbiUser>>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias" });
_mocker.Setup<ICurrentUser, string>(x => x.Username).Returns("test");

var result = await _subject.GetRecentlyRequested(CancellationToken.None);

Expand Down Expand Up @@ -134,8 +135,6 @@ public async Task GetRecentlyRequested_Movies_HideAvailable()
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
_mocker.Setup<ICurrentUser, Task<OmbiUser>>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias" });
_mocker.Setup<ICurrentUser, string>(x => x.Username).Returns("test");

var result = await _subject.GetRecentlyRequested(CancellationToken.None);

Expand Down Expand Up @@ -167,8 +166,6 @@ public async Task GetRecentlyRequested()
_mocker.Setup<IMovieRequestRepository, IQueryable<MovieRequests>>(x => x.GetAll()).Returns(movies.AsQueryable().BuildMock().Object);
_mocker.Setup<IMusicRequestRepository, IQueryable<AlbumRequest>>(x => x.GetAll()).Returns(albums.AsQueryable().BuildMock().Object);
_mocker.Setup<ITvRequestRepository, IQueryable<ChildRequests>>(x => x.GetChild()).Returns(chilRequests.AsQueryable().BuildMock().Object);
_mocker.Setup<ICurrentUser, Task<OmbiUser>>(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "test", Alias = "alias" });
_mocker.Setup<ICurrentUser, string>(x => x.Username).Returns("test");

var result = await _subject.GetRecentlyRequested(CancellationToken.None);

Expand Down
3 changes: 3 additions & 0 deletions src/Ombi.Core/Models/Requests/RecentlyRequestedModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@ public class RecentlyRequestedModel
public DateTime ReleaseDate { get; set; }
public bool Approved { get; set; }
public string MediaId { get; set; }

public string PosterPath { get; set; }
public string Background { get; set; }
}
}
47 changes: 42 additions & 5 deletions src/Ombi.Core/Services/RecentlyRequestedService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Ombi.Api.TheMovieDb;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Helpers;
Expand All @@ -12,7 +13,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Ombi.Core.Engine.BaseMediaEngine;
Expand All @@ -26,7 +26,8 @@ public class RecentlyRequestedService : BaseEngine, IRecentlyRequestedService
private readonly IMusicRequestRepository _musicRequestRepository;
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
private readonly ISettingsService<OmbiSettings> _ombiSettings;

private readonly IMovieDbApi _movieDbApi;
private readonly ICacheService _cache;
private const int AmountToTake = 7;

public RecentlyRequestedService(
Expand All @@ -37,13 +38,17 @@ public RecentlyRequestedService(
ISettingsService<OmbiSettings> ombiSettings,
ICurrentUser user,
OmbiUserManager um,
IRuleEvaluator rules) : base(user, um, rules)
IRuleEvaluator rules,
IMovieDbApi movieDbApi,
ICacheService cache) : base(user, um, rules)
{
_movieRequestRepository = movieRequestRepository;
_tvRequestRepository = tvRequestRepository;
_musicRequestRepository = musicRequestRepository;
_customizationSettings = customizationSettings;
_ombiSettings = ombiSettings;
_movieDbApi = movieDbApi;
_cache = cache;
}

public async Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(CancellationToken cancellationToken)
Expand All @@ -65,8 +70,10 @@ public async Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(Canc

var model = new List<RecentlyRequestedModel>();

var lang = await DefaultLanguageCode();
foreach (var item in await recentMovieRequests.ToListAsync(cancellationToken))
{
var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}movie{item.TheMovieDbId}", () => _movieDbApi.GetMovieImages(item.TheMovieDbId.ToString(), cancellationToken), DateTimeOffset.Now.AddDays(1));
model.Add(new RecentlyRequestedModel
{
RequestId = item.Id,
Expand All @@ -80,6 +87,8 @@ public async Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(Canc
UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId,
Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias,
MediaId = item.TheMovieDbId.ToString(),
PosterPath = images?.posters?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
Background = images?.backdrops?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
});
}

Expand All @@ -103,6 +112,9 @@ public async Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(Canc

foreach (var item in await recentTvRequests.ToListAsync(cancellationToken))
{
var providerId = item.ParentRequest.ExternalProviderId.ToString();
var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}tv{providerId}", () => _movieDbApi.GetTvImages(providerId.ToString(), cancellationToken), DateTimeOffset.Now.AddDays(1));

var partialAvailability = item.SeasonRequests.SelectMany(x => x.Episodes).Any(e => e.Available);
model.Add(new RecentlyRequestedModel
{
Expand All @@ -117,7 +129,9 @@ public async Task<IEnumerable<RecentlyRequestedModel>> GetRecentlyRequested(Canc
Type = RequestType.TvShow,
UserId = hideUsers.Hide ? string.Empty : item.RequestedUserId,
Username = hideUsers.Hide ? string.Empty : item.RequestedUser.UserAlias,
MediaId = item.ParentRequest.ExternalProviderId.ToString()
MediaId = providerId.ToString(),
PosterPath = images?.posters?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
Background = images?.backdrops?.Where(x => lang.Equals(x?.iso_639_1, StringComparison.InvariantCultureIgnoreCase))?.OrderByDescending(x => x.vote_count)?.Select(x => x.file_path)?.FirstOrDefault(),
});
}

Expand All @@ -134,13 +148,36 @@ private async Task<HideResult> HideFromOtherUsers()
UserId = user.Id
};
}
var settings = await _ombiSettings.GetSettingsAsync();
var settings = await GetOmbiSettings();
var result = new HideResult
{
Hide = settings.HideRequestsUsers,
UserId = user.Id
};
return result;
}
protected async Task<string> DefaultLanguageCode()
{
var user = await GetUser();
if (user == null)
{
return "en";
}

if (string.IsNullOrEmpty(user.Language))
{
var s = await GetOmbiSettings();
return s.DefaultLanguageCode;
}

return user.Language;
}


private OmbiSettings ombiSettings;
protected async Task<OmbiSettings> GetOmbiSettings()
{
return ombiSettings ??= await _ombiSettings.GetSettingsAsync();
}
}
}
3 changes: 2 additions & 1 deletion src/Ombi.TheMovieDbApi/IMovieDbApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public interface IMovieDbApi
Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken);
Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken);
Task<TvImages> GetTvImages(string theMovieDbId, CancellationToken token);
Task<MovieDbImages> GetTvImages(string theMovieDbId, CancellationToken token);
Task<MovieDbImages> GetMovieImages(string theMovieDbId, CancellationToken token);
}
}
2 changes: 1 addition & 1 deletion src/Ombi.TheMovieDbApi/Models/TvImages.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace Ombi.Api.TheMovieDb.Models
{
public class TvImages
public class MovieDbImages
{
public Backdrop[] backdrops { get; set; }
public int id { get; set; }
Expand Down
14 changes: 11 additions & 3 deletions src/Ombi.TheMovieDbApi/TheMovieDbApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -514,12 +514,20 @@ public Task<WatchProviders> GetTvWatchProviders(int theMoviedbId, CancellationTo
return Api.Request<WatchProviders>(request, token);
}

public Task<TvImages> GetTvImages(string theMovieDbId, CancellationToken token)
public Task<MovieDbImages> GetTvImages(string theMovieDbId, CancellationToken token)
{
var request = new Request($"tv/{theMovieDbId}/images", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);

return Api.Request<TvImages>(request, token);

return Api.Request<MovieDbImages>(request, token);
}

public Task<MovieDbImages> GetMovieImages(string theMovieDbId, CancellationToken token)
{
var request = new Request($"movie/{theMovieDbId}/images", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);

return Api.Request<MovieDbImages>(request, token);
}

private async Task AddDiscoverSettings(Request request)
Expand Down
18 changes: 0 additions & 18 deletions src/Ombi/ClientApp/src/app/components/button/button.component.scss

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { MatButtonModule } from "@angular/material/button";
selector: 'ombi-button',
imports: [...OmbiCommonModules, MatButtonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['./button.component.scss'],
template: `
<button [id]="id" [type]="type" [class]="class" [data-toggle]="dataToggle" mat-raised-button [data-target]="dataTarget">{{text}}</button>
`
Expand All @@ -17,7 +16,7 @@ import { MatButtonModule } from "@angular/material/button";
@Input() public text: string;

@Input() public id: string;
@Input() public type: string;
@Input() public type: string = "primary";
@Input() public class: string;
@Input('data-toggle') public dataToggle: string;
@Input('data-target') public dataTarget: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div id="detailed-{{request.mediaId}}" class="detailed-container" (click)="click()" [style.background-image]="background">
<div id="detailed-{{request.mediaId}}" class="detailed-container" [style.background-image]="background">
<div class="row">
<div class="col-xl-5 col-lg-5 col-md-5 col-sm-12 posterColumn">
<ombi-image [src]="request.posterPath" [type]="request.type" class="poster" alt="{{request.title}}">
<ombi-image (click)="click()" [src]="request.posterPath" [type]="request.type" class="poster" alt="{{request.title}}">
</ombi-image>
</div>
<div class="col-xl-7 col-lg-7 col-md-7 col-sm-12">
Expand All @@ -20,6 +20,11 @@ <h3 id="detailed-request-title-{{request.mediaId}}">{{request.title}}</h3>
<p id="detailed-request-status-{{request.mediaId}}">{{'MediaDetails.Status' | translate}} <span class="badge badge-{{getClass(request)}}">{{getStatus(request) | translate}}</span></p>
</div>
</div>
<div class="row action-items">
<div class="col-12" *ngIf="isAdmin">
<button *ngIf="!request.approved" id="detailed-request-approve-{{request.mediaId}}" color="accent" mat-raised-button (click)="approve()">{{'Common.Approve' | translate}}</button>
</div>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

.detailed-container {
width: 400px;
height: auto;
height: 250px;
margin: 10px;
padding: 10px;
border-radius: 10px;
border: 1px solid $ombi-background-primary-accent;

@media (max-width:768px) {
width: 200px;
height: auto;
}

background-color: $ombi-background-accent;
Expand All @@ -36,12 +37,18 @@
border-radius: 10px;
opacity: 1;
display: block;
height: 225px;
width: 100%;
height: 200px;
transition: .5s ease;
backface-visibility: hidden;
border: 1px solid #35465c;
}

.action-items {
@media (min-width:768px) {
bottom: 0;
position: absolute;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ NewMovieRequest.args = {
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: false,
};

export const MovieNoUsername = Template.bind({});
Expand Down Expand Up @@ -206,4 +207,60 @@ TvNoUsername.args = {
mediaId: '603',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
};

export const AdminNewMovie = Template.bind({});
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
AdminNewMovie.args = {
request: {
title: 'The Matrix',
approved: false,
available: false,
tvPartiallyAvailable: false,
requestDate: new Date(2022, 1, 1),
username: 'John Doe',
userId: '12345',
type: RequestType.movie,
mediaId: '603',
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: true,
};

export const AdminTvShow = Template.bind({});
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
AdminTvShow.args = {
request: {
title: 'For All Mankind',
approved: false,
available: false,
tvPartiallyAvailable: true,
requestDate: new Date(2022, 1, 1),
userId: '12345',
type: RequestType.tvShow,
mediaId: '603',
username: 'John Doe',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: true,
};

export const AdminApprovedMovie = Template.bind({});
// More on args: https://storybook.js.org/docs/angular/writing-stories/args
AdminApprovedMovie.args = {
request: {
title: 'The Matrix',
approved: true,
available: false,
tvPartiallyAvailable: false,
requestDate: new Date(2022, 1, 1),
username: 'John Doe',
userId: '12345',
type: RequestType.movie,
mediaId: '603',
overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.',
releaseDate: new Date(2020, 1, 1),
} as IRecentlyRequested,
isAdmin: true,
};
Loading

0 comments on commit ff04d87

Please sign in to comment.