mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-16 02:02:55 -07:00
Merge branch 'develop' of https://github.com/tidusjar/ombi into develop
This commit is contained in:
commit
1de276a425
64 changed files with 2415 additions and 77 deletions
|
@ -24,7 +24,7 @@ namespace Ombi.Api.Plex
|
|||
Task<PlexAccount> GetAccount(string authToken);
|
||||
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
|
||||
Task<OAuthPin> GetPin(int pinId);
|
||||
Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl);
|
||||
Task<Uri> GetOAuthUrl(string code, string applicationUrl);
|
||||
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
|
||||
}
|
||||
}
|
|
@ -217,12 +217,11 @@ namespace Ombi.Api.Plex
|
|||
return await Api.Request<OAuthPin>(request);
|
||||
}
|
||||
|
||||
public async Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl)
|
||||
public async Task<Uri> GetOAuthUrl(string code, string applicationUrl)
|
||||
{
|
||||
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
|
||||
await AddHeaders(request);
|
||||
|
||||
request.AddQueryString("pinID", pinId.ToString());
|
||||
request.AddQueryString("code", code);
|
||||
request.AddQueryString("context[device][product]", ApplicationName);
|
||||
request.AddQueryString("context[device][environment]", "bundled");
|
||||
|
|
73
src/Ombi.Core.Tests/Engine/VoteEngineTests.cs
Normal file
73
src/Ombi.Core.Tests/Engine/VoteEngineTests.cs
Normal file
|
@ -0,0 +1,73 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using AutoFixture;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Engine;
|
||||
using Ombi.Core.Engine.Interfaces;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Tests.Engine
|
||||
{
|
||||
[TestFixture]
|
||||
public class VoteEngineTests
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
F = new Fixture();
|
||||
VoteRepository = new Mock<IRepository<Votes>>();
|
||||
VoteSettings = new Mock<ISettingsService<VoteSettings>>();
|
||||
MusicRequestEngine = new Mock<IMusicRequestEngine>();
|
||||
TvRequestEngine = new Mock<ITvRequestEngine>();
|
||||
MovieRequestEngine = new Mock<IMovieRequestEngine>();
|
||||
MovieRequestEngine = new Mock<IMovieRequestEngine>();
|
||||
User = new Mock<IPrincipal>();
|
||||
UserManager = new Mock<OmbiUserManager>();
|
||||
UserManager.Setup(x => x.Users)
|
||||
.Returns(new EnumerableQuery<OmbiUser>(new List<OmbiUser> {new OmbiUser {Id = "abc"}}));
|
||||
Rule = new Mock<IRuleEvaluator>();
|
||||
Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object,
|
||||
TvRequestEngine.Object, MovieRequestEngine.Object);
|
||||
}
|
||||
|
||||
public Fixture F { get; set; }
|
||||
public VoteEngine Engine { get; set; }
|
||||
public Mock<IPrincipal> User { get; set; }
|
||||
public Mock<OmbiUserManager> UserManager { get; set; }
|
||||
public Mock<IRuleEvaluator> Rule { get; set; }
|
||||
public Mock<IRepository<Votes>> VoteRepository { get; set; }
|
||||
public Mock<ISettingsService<VoteSettings>> VoteSettings { get; set; }
|
||||
public Mock<IMusicRequestEngine> MusicRequestEngine { get; set; }
|
||||
public Mock<ITvRequestEngine> TvRequestEngine { get; set; }
|
||||
public Mock<IMovieRequestEngine> MovieRequestEngine { get; set; }
|
||||
|
||||
[Test]
|
||||
[Ignore("Need to mock the user manager")]
|
||||
public async Task New_Upvote()
|
||||
{
|
||||
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings());
|
||||
var votes = F.CreateMany<Votes>().ToList();
|
||||
votes.Add(new Votes
|
||||
{
|
||||
RequestId = 1,
|
||||
RequestType = RequestType.Movie,
|
||||
UserId = "abc"
|
||||
});
|
||||
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes));
|
||||
var result = await Engine.UpVote(1, RequestType.Movie);
|
||||
|
||||
Assert.That(result.Result, Is.True);
|
||||
VoteRepository.Verify(x => x.Add(It.Is<Votes>(c => c.UserId == "abc" && c.VoteType == VoteType.Upvote)), Times.Once);
|
||||
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Once);
|
||||
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.9.0" />
|
||||
<PackageReference Include="AutoFixture" Version="4.5.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -36,17 +36,17 @@ namespace Ombi.Core.Authentication
|
|||
return await _api.GetAccount(accessToken);
|
||||
}
|
||||
|
||||
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
|
||||
public async Task<Uri> GetOAuthUrl(string code, string websiteAddress = null)
|
||||
{
|
||||
var settings = await _customizationSettingsService.GetSettingsAsync();
|
||||
var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl);
|
||||
var url = await _api.GetOAuthUrl(code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
public async Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
|
||||
public async Task<Uri> GetWizardOAuthUrl(string code, string websiteAddress)
|
||||
{
|
||||
var url = await _api.GetOAuthUrl(pinId, code, websiteAddress);
|
||||
var url = await _api.GetOAuthUrl(code, websiteAddress);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
19
src/Ombi.Core/Engine/IVoteEngine.cs
Normal file
19
src/Ombi.Core/Engine/IVoteEngine.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public interface IVoteEngine
|
||||
{
|
||||
Task<VoteEngineResult> DownVote(int requestId, RequestType requestType);
|
||||
Task<Votes> GetVoteForUser(int requestId, string userId);
|
||||
IQueryable<Votes> GetVotes(int requestId, RequestType requestType);
|
||||
Task RemoveCurrentVote(Votes currentVote);
|
||||
Task<VoteEngineResult> UpVote(int requestId, RequestType requestType);
|
||||
Task<List<VoteViewModel>> GetMovieViewModel();
|
||||
}
|
||||
}
|
|
@ -7,11 +7,8 @@ using Ombi.Core.Models.Search;
|
|||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Entities;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Core.Engine.Interfaces
|
||||
{
|
||||
|
|
|
@ -61,7 +61,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
continue;
|
||||
}
|
||||
retVal.Add(await ProcessResult(tvMazeSearch));
|
||||
retVal.Add(ProcessResult(tvMazeSearch));
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ namespace Ombi.Core.Engine
|
|||
public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
var processed = ProcessResults(result);
|
||||
return processed;
|
||||
}
|
||||
|
||||
|
@ -131,35 +131,35 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
|
||||
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
var processed = ProcessResults(result);
|
||||
return processed;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
var processed = ProcessResults(result);
|
||||
return processed;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
var processed = ProcessResults(result);
|
||||
return processed;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
|
||||
private IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
|
||||
{
|
||||
var retVal = new List<SearchTvShowViewModel>();
|
||||
foreach (var tvMazeSearch in items)
|
||||
{
|
||||
retVal.Add(await ProcessResult(tvMazeSearch));
|
||||
retVal.Add(ProcessResult(tvMazeSearch));
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private async Task<SearchTvShowViewModel> ProcessResult<T>(T tvMazeSearch)
|
||||
private SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
|
||||
{
|
||||
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
|
||||
}
|
||||
|
|
254
src/Ombi.Core/Engine/VoteEngine.cs
Normal file
254
src/Ombi.Core/Engine/VoteEngine.cs
Normal file
|
@ -0,0 +1,254 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Authentication;
|
||||
using Ombi.Core.Engine.Interfaces;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public class VoteEngine : BaseEngine, IVoteEngine
|
||||
{
|
||||
public VoteEngine(IRepository<Votes> votes, IPrincipal user, OmbiUserManager um, IRuleEvaluator r, ISettingsService<VoteSettings> voteSettings,
|
||||
IMusicRequestEngine musicRequestEngine, ITvRequestEngine tvRequestEngine, IMovieRequestEngine movieRequestEngine) : base(user, um, r)
|
||||
{
|
||||
_voteRepository = votes;
|
||||
_voteSettings = voteSettings;
|
||||
_movieRequestEngine = movieRequestEngine;
|
||||
_musicRequestEngine = musicRequestEngine;
|
||||
_tvRequestEngine = tvRequestEngine;
|
||||
}
|
||||
|
||||
private readonly IRepository<Votes> _voteRepository;
|
||||
private readonly ISettingsService<VoteSettings> _voteSettings;
|
||||
private readonly IMusicRequestEngine _musicRequestEngine;
|
||||
private readonly ITvRequestEngine _tvRequestEngine;
|
||||
private readonly IMovieRequestEngine _movieRequestEngine;
|
||||
|
||||
public async Task<List<VoteViewModel>> GetMovieViewModel()
|
||||
{
|
||||
var vm = new List<VoteViewModel>();
|
||||
var movieRequests = await _movieRequestEngine.GetRequests();
|
||||
var tvRequestsTask = _tvRequestEngine.GetRequests();
|
||||
var musicRequestsTask = _musicRequestEngine.GetRequests();
|
||||
var user = await GetUser();
|
||||
foreach (var r in movieRequests)
|
||||
{
|
||||
if (r.Available || r.Approved || (r.Denied ?? false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Make model
|
||||
var votes = GetVotes(r.Id, RequestType.Movie);
|
||||
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
|
||||
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
|
||||
|
||||
vm.Add(new VoteViewModel
|
||||
{
|
||||
Upvotes = upVotes,
|
||||
Downvotes = downVotes,
|
||||
RequestId = r.Id,
|
||||
RequestType = RequestType.Movie,
|
||||
Title = r.Title,
|
||||
Image = $"https://image.tmdb.org/t/p/w500/{r.PosterPath}",
|
||||
Background = $"https://image.tmdb.org/t/p/w1280{r.Background}",
|
||||
Description = r.Overview,
|
||||
AlreadyVoted = myVote != null,
|
||||
MyVote = myVote?.VoteType ?? VoteType.Downvote
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var r in await musicRequestsTask)
|
||||
{
|
||||
if (r.Available || r.Approved || (r.Denied ?? false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Make model
|
||||
var votes = GetVotes(r.Id, RequestType.Album);
|
||||
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
|
||||
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
|
||||
vm.Add(new VoteViewModel
|
||||
{
|
||||
Upvotes = upVotes,
|
||||
Downvotes = downVotes,
|
||||
RequestId = r.Id,
|
||||
RequestType = RequestType.Album,
|
||||
Title = r.Title,
|
||||
Image = r.Cover,
|
||||
Background = r.Cover,
|
||||
Description = r.ArtistName,
|
||||
AlreadyVoted = myVote != null,
|
||||
MyVote = myVote?.VoteType ?? VoteType.Downvote
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var r in await tvRequestsTask)
|
||||
{
|
||||
|
||||
foreach (var childRequests in r.ChildRequests)
|
||||
{
|
||||
var finalsb = new StringBuilder();
|
||||
if (childRequests.Available || childRequests.Approved || (childRequests.Denied ?? false))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var votes = GetVotes(childRequests.Id, RequestType.TvShow);
|
||||
// Make model
|
||||
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
|
||||
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
|
||||
foreach (var epInformation in childRequests.SeasonRequests.OrderBy(x => x.SeasonNumber))
|
||||
{
|
||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var episodeString = NewsletterJob.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||
finalsb.Append("<br />");
|
||||
}
|
||||
vm.Add(new VoteViewModel
|
||||
{
|
||||
Upvotes = upVotes,
|
||||
Downvotes = downVotes,
|
||||
RequestId = childRequests.Id,
|
||||
RequestType = RequestType.TvShow,
|
||||
Title = r.Title,
|
||||
Image = r.PosterPath,
|
||||
Background = r.Background,
|
||||
Description = finalsb.ToString(),
|
||||
AlreadyVoted = myVote != null,
|
||||
MyVote = myVote?.VoteType ?? VoteType.Downvote
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
public IQueryable<Votes> GetVotes(int requestId, RequestType requestType)
|
||||
{
|
||||
return _voteRepository.GetAll().Where(x => x.RequestType == requestType && requestId == x.RequestId);
|
||||
}
|
||||
|
||||
public Task<Votes> GetVoteForUser(int requestId, string userId)
|
||||
{
|
||||
return _voteRepository.GetAll().FirstOrDefaultAsync(x => x.RequestId == requestId && x.UserId == userId);
|
||||
}
|
||||
|
||||
public async Task<VoteEngineResult> UpVote(int requestId, RequestType requestType)
|
||||
{
|
||||
// How many votes does this have?!
|
||||
var currentVotes = GetVotes(requestId, requestType);
|
||||
var voteSettings = await _voteSettings.GetSettingsAsync();
|
||||
|
||||
// Does this user have a downvote? If so we should revert it and make it an upvote
|
||||
var user = await GetUser();
|
||||
|
||||
var currentVote = await GetVoteForUser(requestId, user.Id);
|
||||
if (currentVote != null && currentVote.VoteType == VoteType.Upvote)
|
||||
{
|
||||
return new VoteEngineResult { ErrorMessage = "You have already voted!" };
|
||||
}
|
||||
await RemoveCurrentVote(currentVote);
|
||||
await _movieRequestEngine.SubscribeToRequest(requestId, requestType);
|
||||
|
||||
await _voteRepository.Add(new Votes
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
RequestId = requestId,
|
||||
RequestType = requestType,
|
||||
UserId = user.Id,
|
||||
VoteType = VoteType.Upvote
|
||||
});
|
||||
|
||||
var upVotes = await currentVotes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||
var downVotes = -(await currentVotes.Where(x => x.VoteType == VoteType.Downvote).CountAsync());
|
||||
|
||||
var totalVotes = upVotes + downVotes;
|
||||
RequestEngineResult result = null;
|
||||
switch (requestType)
|
||||
{
|
||||
case RequestType.TvShow:
|
||||
if (totalVotes >= voteSettings.TvShowVoteMax)
|
||||
{
|
||||
result = await _tvRequestEngine.ApproveChildRequest(requestId);
|
||||
}
|
||||
break;
|
||||
case RequestType.Movie:
|
||||
if (totalVotes >= voteSettings.MovieVoteMax)
|
||||
{
|
||||
result = await _movieRequestEngine.ApproveMovieById(requestId);
|
||||
}
|
||||
break;
|
||||
case RequestType.Album:
|
||||
if (totalVotes >= voteSettings.MusicVoteMax)
|
||||
{
|
||||
result = await _musicRequestEngine.ApproveAlbumById(requestId);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
|
||||
}
|
||||
|
||||
if (result != null && !result.Result)
|
||||
{
|
||||
return new VoteEngineResult
|
||||
{
|
||||
ErrorMessage = "Voted succesfully but could not approve movie!"
|
||||
};
|
||||
}
|
||||
|
||||
return new VoteEngineResult
|
||||
{
|
||||
Result = true
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<VoteEngineResult> DownVote(int requestId, RequestType requestType)
|
||||
{
|
||||
var user = await GetUser();
|
||||
var currentVote = await GetVoteForUser(requestId, user.Id);
|
||||
if (currentVote != null && currentVote.VoteType == VoteType.Downvote)
|
||||
{
|
||||
return new VoteEngineResult { ErrorMessage = "You have already voted!" };
|
||||
}
|
||||
await RemoveCurrentVote(currentVote);
|
||||
|
||||
await _movieRequestEngine.UnSubscribeRequest(requestId, requestType);
|
||||
|
||||
await _voteRepository.Add(new Votes
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
RequestId = requestId,
|
||||
RequestType = requestType,
|
||||
UserId = user.Id,
|
||||
VoteType = VoteType.Downvote
|
||||
});
|
||||
|
||||
return new VoteEngineResult
|
||||
{
|
||||
Result = true
|
||||
};
|
||||
}
|
||||
|
||||
public async Task RemoveCurrentVote(Votes currentVote)
|
||||
{
|
||||
if (currentVote != null)
|
||||
{
|
||||
await _voteRepository.Delete(currentVote);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,8 @@ namespace Ombi.Core.Authentication
|
|||
public interface IPlexOAuthManager
|
||||
{
|
||||
Task<string> GetAccessTokenFromPin(int pinId);
|
||||
Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null);
|
||||
Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
|
||||
Task<Uri> GetOAuthUrl(string code, string websiteAddress = null);
|
||||
Task<Uri> GetWizardOAuthUrl(string code, string websiteAddress);
|
||||
Task<PlexAccount> GetAccount(string accessToken);
|
||||
}
|
||||
}
|
18
src/Ombi.Core/Models/UI/VoteViewModel.cs
Normal file
18
src/Ombi.Core/Models/UI/VoteViewModel.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.UI
|
||||
{
|
||||
public class VoteViewModel
|
||||
{
|
||||
public int RequestId { get; set; }
|
||||
public RequestType RequestType { get; set; }
|
||||
public string Image { get; set; }
|
||||
public string Background { get; set; }
|
||||
public int Upvotes { get; set; }
|
||||
public int Downvotes { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Description { get; set; }
|
||||
public bool AlreadyVoted { get; set; }
|
||||
public VoteType MyVote { get; set; }
|
||||
}
|
||||
}
|
10
src/Ombi.Core/Models/VoteEngineResult.cs
Normal file
10
src/Ombi.Core/Models/VoteEngineResult.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ombi.Core.Models
|
||||
{
|
||||
public class VoteEngineResult
|
||||
{
|
||||
public bool Result { get; set; }
|
||||
public string Message { get; set; }
|
||||
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
|
@ -79,7 +79,31 @@ namespace Ombi.Core.Senders
|
|||
// Search for it
|
||||
if (!settings.AddOnly)
|
||||
{
|
||||
await _lidarrApi.AlbumSearch(new[] { result.id }, settings.ApiKey, settings.FullUri);
|
||||
// get the album
|
||||
var album = await _lidarrApi.GetAllAlbumsByArtistId(result.id, settings.ApiKey, settings.FullUri);
|
||||
|
||||
var albumToSearch = album.FirstOrDefault(x =>
|
||||
x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase));
|
||||
var maxRetryCount = 10; // 5 seconds
|
||||
var currentRetry = 0;
|
||||
while (albumToSearch != null)
|
||||
{
|
||||
if (currentRetry >= maxRetryCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
currentRetry++;
|
||||
await Task.Delay(500);
|
||||
album = await _lidarrApi.GetAllAlbumsByArtistId(result.id, settings.ApiKey, settings.FullUri);
|
||||
albumToSearch = album.FirstOrDefault(x =>
|
||||
x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
|
||||
if (albumToSearch != null)
|
||||
{
|
||||
await _lidarrApi.AlbumSearch(new[] {albumToSearch.id}, settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
}
|
||||
return new SenderResult { Message = "Album has been requested!", Sent = true, Success = true };
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMusicSender, MusicSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||
services.AddTransient<IVoteEngine, VoteEngine>();
|
||||
}
|
||||
public static void RegisterHttp(this IServiceCollection services)
|
||||
{
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
using static Ombi.Schedule.Jobs.Ombi.NewsletterJob;
|
||||
|
||||
namespace Ombi.Schedule.Tests
|
||||
{
|
||||
|
@ -15,17 +10,12 @@ namespace Ombi.Schedule.Tests
|
|||
[TestCaseSource(nameof(EpisodeListData))]
|
||||
public string BuildEpisodeListTest(List<int> episodes)
|
||||
{
|
||||
var emailSettings = new Mock<ISettingsService<EmailNotificationSettings>>();
|
||||
var customziation = new Mock<ISettingsService<CustomizationSettings>>();
|
||||
var newsletterSettings = new Mock<ISettingsService<NewsletterSettings>>();
|
||||
var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null, null, null, null);
|
||||
|
||||
var ep = new List<int>();
|
||||
foreach (var i in episodes)
|
||||
{
|
||||
ep.Add(i);
|
||||
}
|
||||
var result = newsletter.BuildEpisodeList(ep);
|
||||
var result = BuildEpisodeList(ep);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.3" />
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -654,7 +654,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
AddInfoTable(sb);
|
||||
|
||||
var title = "";
|
||||
if (!String.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4)
|
||||
if (!string.IsNullOrEmpty(info.premiered) && info.premiered.Length > 4)
|
||||
{
|
||||
title = $"{t.Title} ({info.premiered.Remove(4)})";
|
||||
} else
|
||||
|
@ -715,7 +715,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
public string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
|
||||
public static string BuildEpisodeList(IEnumerable<int> orderedEpisodes)
|
||||
{
|
||||
var epSb = new StringBuilder();
|
||||
var previousEpisodes = new List<int>();
|
||||
|
|
10
src/Ombi.Settings/Settings/Models/VoteSettings.cs
Normal file
10
src/Ombi.Settings/Settings/Models/VoteSettings.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ombi.Settings.Settings.Models
|
||||
{
|
||||
public class VoteSettings : Settings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public int MovieVoteMax { get; set; }
|
||||
public int MusicVoteMax { get; set; }
|
||||
public int TvShowVoteMax { get; set; }
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ namespace Ombi.Store.Context
|
|||
DbSet<TEntity> Set<TEntity>() where TEntity : class;
|
||||
DbSet<NotificationTemplates> NotificationTemplates { get; set; }
|
||||
DbSet<ApplicationConfiguration> ApplicationConfigurations { get; set; }
|
||||
DbSet<Votes> Votes { get; set; }
|
||||
void Seed();
|
||||
DbSet<Audit> Audit { get; set; }
|
||||
DbSet<MovieRequests> MovieRequests { get; set; }
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace Ombi.Store.Context
|
|||
public DbSet<IssueComments> IssueComments { get; set; }
|
||||
public DbSet<RequestLog> RequestLogs { get; set; }
|
||||
public DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
||||
public DbSet<Votes> Votes { get; set; }
|
||||
|
||||
|
||||
public DbSet<Audit> Audit { get; set; }
|
||||
|
|
25
src/Ombi.Store/Entities/Votes.cs
Normal file
25
src/Ombi.Store/Entities/Votes.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities
|
||||
{
|
||||
[Table("Votes")]
|
||||
public class Votes : Entity
|
||||
{
|
||||
public int RequestId { get; set; }
|
||||
public VoteType VoteType { get; set; }
|
||||
public RequestType RequestType { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public bool Deleted { get; set; }
|
||||
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public OmbiUser User { get; set; }
|
||||
}
|
||||
|
||||
public enum VoteType
|
||||
{
|
||||
Upvote = 0,
|
||||
Downvote = 1
|
||||
}
|
||||
}
|
1182
src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs
generated
Normal file
1182
src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
46
src/Ombi.Store/Migrations/20180928201334_Votes.cs
Normal file
46
src/Ombi.Store/Migrations/20180928201334_Votes.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class Votes : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Votes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
RequestId = table.Column<int>(nullable: false),
|
||||
VoteType = table.Column<int>(nullable: false),
|
||||
RequestType = table.Column<int>(nullable: false),
|
||||
UserId = table.Column<string>(nullable: true),
|
||||
Date = table.Column<DateTime>(nullable: false),
|
||||
Deleted = table.Column<bool>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Votes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Votes_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Votes_UserId",
|
||||
table: "Votes",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Votes");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -916,6 +916,30 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("UserQualityProfiles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Votes", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("Date");
|
||||
|
||||
b.Property<bool>("Deleted");
|
||||
|
||||
b.Property<int>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<int>("VoteType");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -1128,6 +1152,13 @@ namespace Ombi.Store.Migrations
|
|||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Votes", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
|
||||
<PackageReference Include="Moq" Version="4.7.99" />
|
||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -54,6 +54,12 @@
|
|||
<i class="fa fa-user"></i> {{ 'NavigationBar.UserManagement' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul *ngIf="voteEnabled" class="nav navbar-nav">
|
||||
<li id="Vote" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/vote']">
|
||||
<i class="fa fa-thumbs-o-up"></i> {{ 'NavigationBar.Vote' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul *ngIf="hasRole('Admin') || hasRole('PowerUser')" class="nav navbar-nav donation">
|
||||
<li>
|
||||
|
|
|
@ -23,6 +23,7 @@ export class AppComponent implements OnInit {
|
|||
public updateAvailable: boolean;
|
||||
public currentUrl: string;
|
||||
public userAccessToken: string;
|
||||
public voteEnabled = false;
|
||||
|
||||
private checkedForUpdate: boolean;
|
||||
|
||||
|
@ -54,6 +55,7 @@ export class AppComponent implements OnInit {
|
|||
|
||||
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
|
||||
this.settingsService.issueEnabled().subscribe(x => this.issuesEnabled = x);
|
||||
this.settingsService.voteEnabled().subscribe(x => this.voteEnabled =x);
|
||||
|
||||
this.router.events.subscribe((event: NavigationStart) => {
|
||||
this.currentUrl = event.url;
|
||||
|
|
|
@ -15,7 +15,7 @@ import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
|||
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
||||
import { CookieService } from "ng2-cookies";
|
||||
import { GrowlModule } from "primeng/components/growl/growl";
|
||||
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule, DialogModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
|
||||
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule, DialogModule, OverlayPanelModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
|
||||
|
||||
// Components
|
||||
import { AppComponent } from "./app.component";
|
||||
|
@ -55,6 +55,7 @@ const routes: Routes = [
|
|||
{ loadChildren: "./requests/requests.module#RequestsModule", path: "requests" },
|
||||
{ loadChildren: "./search/search.module#SearchModule", path: "search" },
|
||||
{ loadChildren: "./recentlyAdded/recentlyAdded.module#RecentlyAddedModule", path: "recentlyadded" },
|
||||
{ loadChildren: "./vote/vote.module#VoteModule", path: "vote" },
|
||||
];
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
|
@ -97,6 +98,7 @@ export function JwtTokenGetter() {
|
|||
CaptchaModule,
|
||||
TooltipModule,
|
||||
ConfirmDialogModule,
|
||||
OverlayPanelModule,
|
||||
CommonModule,
|
||||
JwtModule.forRoot({
|
||||
config: {
|
||||
|
|
|
@ -59,6 +59,7 @@ export interface INewsletterNotificationSettings extends INotificationSettings {
|
|||
notificationTemplate: INotificationTemplates;
|
||||
disableMovies: boolean;
|
||||
disableTv: boolean;
|
||||
disableMusic: boolean;
|
||||
externalEmails: string[];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
export enum RequestType {
|
||||
movie = 1,
|
||||
tvShow = 2,
|
||||
|
||||
}
|
||||
|
||||
// NEW WORLD
|
||||
|
|
|
@ -231,3 +231,10 @@ export interface IJobSettingsViewModel {
|
|||
result: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface IVoteSettings extends ISettings {
|
||||
enabled: boolean;
|
||||
movieVoteMax: number;
|
||||
musicVoteMax: number;
|
||||
tvShowVoteMax: number;
|
||||
}
|
||||
|
|
30
src/Ombi/ClientApp/app/interfaces/IVote.ts
Normal file
30
src/Ombi/ClientApp/app/interfaces/IVote.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
export interface IVoteViewModel {
|
||||
requestId: number;
|
||||
requestType: RequestTypes;
|
||||
image: string;
|
||||
background: string;
|
||||
upvotes: number;
|
||||
downvotes: number;
|
||||
title: string;
|
||||
description: string;
|
||||
alreadyVoted: boolean;
|
||||
myVote: VoteType;
|
||||
}
|
||||
|
||||
export interface IVoteEngineResult {
|
||||
result: boolean;
|
||||
message: string;
|
||||
isError: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export enum RequestTypes {
|
||||
TvShow = 0,
|
||||
Movie = 1,
|
||||
Album = 2,
|
||||
}
|
||||
|
||||
export enum VoteType {
|
||||
Upvote = 0,
|
||||
Downvote = 1,
|
||||
}
|
|
@ -16,3 +16,4 @@ export * from "./IIssues";
|
|||
export * from "./IRecentlyAdded";
|
||||
export * from "./ILidarr";
|
||||
export * from "./ISearchMusicResult";
|
||||
export * from "./IVote";
|
||||
|
|
|
@ -14,3 +14,4 @@ export * from "./issues.service";
|
|||
export * from "./mobile.service";
|
||||
export * from "./notificationMessage.service";
|
||||
export * from "./recentlyAdded.service";
|
||||
export * from "./vote.service";
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
IThemes,
|
||||
IUpdateSettings,
|
||||
IUserManagementSettings,
|
||||
IVoteSettings,
|
||||
} from "../interfaces";
|
||||
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
@ -284,6 +285,18 @@ export class SettingsService extends ServiceHelpers {
|
|||
.post<boolean>(`${this.url}/issues`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getVoteSettings(): Observable<IVoteSettings> {
|
||||
return this.http.get<IVoteSettings>(`${this.url}/vote`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public voteEnabled(): Observable<boolean> {
|
||||
return this.http.get<boolean>(`${this.url}/voteenabled`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveVoteSettings(settings: IVoteSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/vote`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getNewsletterSettings(): Observable<INewsletterNotificationSettings> {
|
||||
return this.http.get<INewsletterNotificationSettings>(`${this.url}/notifications/newsletter`, {headers: this.headers});
|
||||
}
|
||||
|
|
36
src/Ombi/ClientApp/app/services/vote.service.ts
Normal file
36
src/Ombi/ClientApp/app/services/vote.service.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { IVoteEngineResult, IVoteViewModel } from "../interfaces";
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
||||
@Injectable()
|
||||
export class VoteService extends ServiceHelpers {
|
||||
constructor(public http: HttpClient, public platformLocation: PlatformLocation) {
|
||||
super(http, "/api/v1/Vote/", platformLocation);
|
||||
}
|
||||
|
||||
public async getModel(): Promise<IVoteViewModel[]> {
|
||||
return await this.http.get<IVoteViewModel[]>(`${this.url}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
|
||||
public async upvoteMovie(requestId: number): Promise<IVoteEngineResult> {
|
||||
return await this.http.post<IVoteEngineResult>(`${this.url}up/movie/${requestId}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
public async upvoteTv(requestId: number): Promise<IVoteEngineResult> {
|
||||
return await this.http.post<IVoteEngineResult>(`${this.url}up/tv/${requestId}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
public async upvoteAlbum(requestId: number): Promise<IVoteEngineResult> {
|
||||
return await this.http.post<IVoteEngineResult>(`${this.url}up/album/${requestId}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
public async downvoteMovie(requestId: number): Promise<IVoteEngineResult> {
|
||||
return await this.http.post<IVoteEngineResult>(`${this.url}down/movie/${requestId}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
public async downvoteTv(requestId: number): Promise<IVoteEngineResult> {
|
||||
return await this.http.post<IVoteEngineResult>(`${this.url}down/tv/${requestId}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
public async downvoteAlbum(requestId: number): Promise<IVoteEngineResult> {
|
||||
return await this.http.post<IVoteEngineResult>(`${this.url}down/album/${requestId}`, {headers: this.headers}).toPromise();
|
||||
}
|
||||
}
|
|
@ -26,12 +26,13 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="form.controls.deleteIssues.value">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="daysAfterResolvedToDelete" formControlName="daysAfterResolvedToDelete" ng-checked="form.daysAfterResolvedToDelete">
|
||||
<label for="daysAfterResolvedToDelete">Leave the resolved issue for X amount of days before deletion</label>
|
||||
</div>
|
||||
<label for="daysAfterResolvedToDelete" class="control-label">Leave the resolved issue for X amount of
|
||||
days before deletion</label>
|
||||
<input type="number" class="form-control form-control-custom form-small" min="1" max="365" id="daysAfterResolvedToDelete"
|
||||
formControlName="daysAfterResolvedToDelete" ng-checked="form.daysAfterResolvedToDelete">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline ">Submit</button>
|
||||
|
@ -52,8 +53,8 @@
|
|||
<label for="categoryToAdd" class="control-label">Add Category</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" [(ngModel)]="categoryToAdd.value" class="form-control form-control-custom " id="categoryToAdd" name="categoryToAdd"
|
||||
value="{{categoryToAdd.value}}">
|
||||
<input type="text" [(ngModel)]="categoryToAdd.value" class="form-control form-control-custom " id="categoryToAdd"
|
||||
name="categoryToAdd" value="{{categoryToAdd.value}}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-primary-outline" (click)="addCategory()">Add</button>
|
||||
|
|
|
@ -55,6 +55,11 @@ export class IssuesComponent implements OnInit {
|
|||
|
||||
const settings = form.value;
|
||||
|
||||
if(settings.deleteIssues && settings.daysAfterResolvedToDelete <= 0) {
|
||||
this.notificationService.error("You need to enter days greater than 0");
|
||||
return;
|
||||
}
|
||||
|
||||
this.settingsService.saveIssueSettings(settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved the Issue settings");
|
||||
|
|
|
@ -20,6 +20,12 @@
|
|||
<input type="checkbox" id="disableMovies" [(ngModel)]="settings.disableMovies" ng-checked="settings.disableMovies"><label for="disableMovies">Disable Movies</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="disableMusic" [(ngModel)]="settings.disableMusic" ng-checked="settings.disableMusic"><label for="disableMusic">Disable Music</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Subject</label>
|
||||
<div>
|
||||
|
|
|
@ -41,6 +41,7 @@ import { SickRageComponent } from "./sickrage/sickrage.component";
|
|||
import { SonarrComponent } from "./sonarr/sonarr.component";
|
||||
import { UpdateComponent } from "./update/update.component";
|
||||
import { UserManagementComponent } from "./usermanagement/usermanagement.component";
|
||||
import { VoteComponent } from "./vote/vote.component";
|
||||
import { WikiComponent } from "./wiki.component";
|
||||
|
||||
import { SettingsMenuComponent } from "./settingsmenu.component";
|
||||
|
@ -75,6 +76,7 @@ const routes: Routes = [
|
|||
{ path: "MassEmail", component: MassEmailComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Lidarr", component: LidarrComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Vote", component: VoteComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -127,6 +129,7 @@ const routes: Routes = [
|
|||
MassEmailComponent,
|
||||
NewsletterComponent,
|
||||
LidarrComponent,
|
||||
VoteComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Issues']">Issues</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/UserManagement']">User Importer</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Authentication']">Authentication</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Vote']">Vote</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
|
|
50
src/Ombi/ClientApp/app/settings/vote/vote.component.html
Normal file
50
src/Ombi/ClientApp/app/settings/vote/vote.component.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<settings-menu></settings-menu>
|
||||
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Vote-Settings'"></wiki>
|
||||
|
||||
<fieldset>
|
||||
<legend>Vote</legend>
|
||||
<form *ngIf="form" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" formControlName="enabled" ng-checked="form.enabled">
|
||||
<label for="enable">Enable</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Vote limits tell Ombi how many votes the request needs before approval.</p>
|
||||
<p>e.g. If the Movie vote limit is 10, it requires 10 Upvotes from 10 different users before it will be approved.</p>
|
||||
<div class="form-group">
|
||||
<label for="movieVoteMax" class="control-label">Movie Vote Limit</label>
|
||||
<input type="number" class="form-control form-control-custom form-small" min="1" id="movieVoteMax" [ngClass]="{'form-error': form.get('movieVoteMax').hasError('min')}"
|
||||
formControlName="movieVoteMax" ng-checked="form.movieVoteMax">
|
||||
|
||||
<small *ngIf="form.get('movieVoteMax').hasError('min')" class="error-text">The limit needs to be greater than or equal to 1</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="musicVoteMax" class="control-label">Music Vote Limit</label>
|
||||
<input type="number" class="form-control form-control-custom form-small" min="1" id="musicVoteMax" [ngClass]="{'form-error': form.get('movieVoteMax').hasError('min')}"
|
||||
formControlName="musicVoteMax" ng-checked="form.musicVoteMax">
|
||||
<small *ngIf="form.get('movieVoteMax').hasError('min')" class="error-text">The limit needs to be greater than or equal to 1</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tvShowVoteMax" class="control-label">TV Show Vote Limit</label>
|
||||
<input type="number" class="form-control form-control-custom form-small" min="1" id="tvShowVoteMax" [ngClass]="{'form-error': form.get('movieVoteMax').hasError('min')}"
|
||||
formControlName="tvShowVoteMax" ng-checked="form.tvShowVoteMax">
|
||||
<small *ngIf="form.get('movieVoteMax').hasError('min')" class="error-text">The limit needs to be greater than or equal to 1</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline ">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</fieldset>
|
44
src/Ombi/ClientApp/app/settings/vote/vote.component.ts
Normal file
44
src/Ombi/ClientApp/app/settings/vote/vote.component.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
|
||||
import { NotificationService, SettingsService } from "../../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./vote.component.html",
|
||||
})
|
||||
export class VoteComponent implements OnInit {
|
||||
|
||||
public form: FormGroup;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private readonly fb: FormBuilder,
|
||||
private notificationService: NotificationService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getVoteSettings().subscribe(x => {
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
movieVoteMax: [x.movieVoteMax, Validators.min(1)],
|
||||
musicVoteMax: [x.musicVoteMax, Validators.min(1)],
|
||||
tvShowVoteMax: [x.tvShowVoteMax, Validators.min(1)],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = form.value;
|
||||
|
||||
this.settingsService.saveVoteSettings(settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved the Vote settings");
|
||||
} else {
|
||||
this.notificationService.success("There was an error when saving the Vote settings");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
92
src/Ombi/ClientApp/app/vote/vote.component.html
Normal file
92
src/Ombi/ClientApp/app/vote/vote.component.html
Normal file
|
@ -0,0 +1,92 @@
|
|||
<h1>Vote</h1>
|
||||
|
||||
|
||||
|
||||
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
|
||||
|
||||
<li role="presentation" class="active">
|
||||
<a id="currentVotes" href="#currentVotes" aria-controls="home" role="tab" data-toggle="tab" (click)="selectCurrentTab()"><i
|
||||
class="fa fa-meh-o"></i> {{ 'Votes.VotesTab' | translate }}</a>
|
||||
</li>
|
||||
|
||||
<li role="presentation">
|
||||
<a id="completedVotes" href="#completedVotes" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectCompletedVotesTab()"><i
|
||||
class="fa fa-smile-o"></i> {{ 'Votes.CompletedVotesTab' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
|
||||
<div [hidden]="!showCurrent">
|
||||
<div *ngIf="currentVotes">
|
||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 10%"></td>
|
||||
<td></td>
|
||||
<td style="width: 10%">Title</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let vm of currentVotes">
|
||||
<td class="vcenter">
|
||||
<button class="btn btn-info-outline col-md-6" (click)="upvote(vm)"><i class="fa fa-thumbs-o-up"
|
||||
aria-hidden="true"></i></button>
|
||||
<button class="btn btn-info-outline col-md-6" (click)="downvote(vm)" ><i class="fa fa-thumbs-o-down"
|
||||
aria-hidden="true"></i></button>
|
||||
</td>
|
||||
<td style="width: 10%"> <img *ngIf="vm.image" class="img-responsive poster" style="max-width: 100%;
|
||||
height: auto;
|
||||
width: 100%;"
|
||||
(click)="toggle($event, vm.image)" src="{{vm.image}}" alt="poster"></td>
|
||||
<td class="vcenter">{{vm.title}}</td>
|
||||
<td class="vcenter" [innerHTML]="vm.description"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showCompleted">
|
||||
<div *ngIf="completedVotes">
|
||||
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width: 10%"></td>
|
||||
<td></td>
|
||||
<td style="width: 10%">Title</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let vm of completedVotes">
|
||||
<td class="vcenter">
|
||||
<button class="btn btn-info-outline col-md-6" [ngClass]="{'btn-success-outline': vm.myVote == VoteType.Upvote, 'btn-info-outline': vm.myVote != VoteType.Upvote}"
|
||||
(click)="upvote(vm)" [disabled]="vm.myVote == VoteType.Upvote"><i class="fa fa-thumbs-o-up"
|
||||
aria-hidden="true"></i></button>
|
||||
<button class="btn btn-info-outline col-md-6" [ngClass]="{'btn-danger-outline': vm.myVote == VoteType.Downvote, 'btn-info-outline': vm.myVote != VoteType.Downvote}"
|
||||
(click)="downvote(vm)" [disabled]="vm.myVote == VoteType.Downvote"><i class="fa fa-thumbs-o-down"
|
||||
aria-hidden="true"></i></button>
|
||||
</td>
|
||||
<td style="width: 10%"> <img *ngIf="vm.image" class="img-responsive poster" style="max-width: 100%;
|
||||
height: auto;
|
||||
width: 100%;"
|
||||
(click)="toggle($event, vm.image)" src="{{vm.image}}" alt="poster"></td>
|
||||
<td class="vcenter">{{vm.title}}</td>
|
||||
<td class="vcenter" [innerHTML]="vm.description"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p-overlayPanel #op [dismissable]="true" [styleClass]="'hideBackground'">
|
||||
<img class="img-responsive poster" width="70%" src="{{panelImage}}" alt="poster">
|
||||
</p-overlayPanel>
|
12
src/Ombi/ClientApp/app/vote/vote.component.scss
Normal file
12
src/Ombi/ClientApp/app/vote/vote.component.scss
Normal file
|
@ -0,0 +1,12 @@
|
|||
.vcenter {
|
||||
vertical-align: middle;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.hideBackground {
|
||||
border: 0px solid transparent !important;
|
||||
background: transparent !important;
|
||||
-webkit-box-shadow: 0 0px 0px 0 transparent !important;
|
||||
-moz-box-shadow: 0 0px 0px 0 transparent !important;
|
||||
box-shadow: 0 0px 0px 0 transparent !important;
|
||||
}
|
101
src/Ombi/ClientApp/app/vote/vote.component.ts
Normal file
101
src/Ombi/ClientApp/app/vote/vote.component.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
import { Component, OnInit, ViewChild } from "@angular/core";
|
||||
|
||||
import { OverlayPanel } from "primeng/primeng";
|
||||
import { NotificationService, VoteService } from "../services";
|
||||
|
||||
import { IVoteEngineResult, IVoteViewModel, RequestTypes, VoteType } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
templateUrl: "vote.component.html",
|
||||
styleUrls: ["vote.component.scss"],
|
||||
})
|
||||
export class VoteComponent implements OnInit {
|
||||
|
||||
public showCurrent: boolean = true;
|
||||
public showCompleted: boolean;
|
||||
public viewModel: IVoteViewModel[];
|
||||
public currentVotes: IVoteViewModel[];
|
||||
public completedVotes: IVoteViewModel[];
|
||||
public VoteType = VoteType;
|
||||
public panelImage: string;
|
||||
@ViewChild("op") public overlayPanel: OverlayPanel;
|
||||
|
||||
constructor(private voteService: VoteService, private notificationSerivce: NotificationService) { }
|
||||
|
||||
public async ngOnInit() {
|
||||
this.viewModel = await this.voteService.getModel();
|
||||
this.filterLists();
|
||||
}
|
||||
|
||||
public selectCurrentTab() {
|
||||
this.showCurrent = true;
|
||||
this.showCompleted = false;
|
||||
}
|
||||
|
||||
public selectCompletedVotesTab() {
|
||||
this.showCurrent = false;
|
||||
this.showCompleted = true;
|
||||
}
|
||||
|
||||
public toggle(event: any, image: string) {
|
||||
this.panelImage = image;
|
||||
this.overlayPanel.toggle(event);
|
||||
}
|
||||
|
||||
public async upvote(vm: IVoteViewModel) {
|
||||
let result: IVoteEngineResult = {errorMessage:"", isError: false, message:"",result:false};
|
||||
switch(vm.requestType) {
|
||||
case RequestTypes.Album:
|
||||
result = await this.voteService.upvoteAlbum(vm.requestId);
|
||||
break;
|
||||
case RequestTypes.Movie:
|
||||
result = await this.voteService.upvoteMovie(vm.requestId);
|
||||
break;
|
||||
case RequestTypes.TvShow:
|
||||
result = await this.voteService.upvoteTv(vm.requestId);
|
||||
break;
|
||||
}
|
||||
|
||||
if(result.isError) {
|
||||
this.notificationSerivce.error(result.errorMessage);
|
||||
} else {
|
||||
this.notificationSerivce.success("Voted!");
|
||||
vm.alreadyVoted = true;
|
||||
vm.myVote = VoteType.Upvote;
|
||||
this.filterLists();
|
||||
}
|
||||
}
|
||||
|
||||
public async downvote(vm: IVoteViewModel) {
|
||||
let result: IVoteEngineResult = {errorMessage:"", isError: false, message:"",result:false};
|
||||
switch(vm.requestType) {
|
||||
case RequestTypes.Album:
|
||||
result = await this.voteService.downvoteAlbum(vm.requestId);
|
||||
break;
|
||||
case RequestTypes.Movie:
|
||||
result = await this.voteService.downvoteMovie(vm.requestId);
|
||||
break;
|
||||
case RequestTypes.TvShow:
|
||||
result = await this.voteService.downvoteTv(vm.requestId);
|
||||
break;
|
||||
}
|
||||
|
||||
if(result.isError) {
|
||||
this.notificationSerivce.error(result.errorMessage);
|
||||
} else {
|
||||
this.notificationSerivce.success("Voted!");
|
||||
vm.alreadyVoted = true;
|
||||
vm.myVote = VoteType.Downvote;
|
||||
this.filterLists();
|
||||
}
|
||||
}
|
||||
|
||||
private filterLists() {
|
||||
this.completedVotes = this.viewModel.filter(vm => {
|
||||
return vm.alreadyVoted;
|
||||
});
|
||||
this.currentVotes = this.viewModel.filter(vm => {
|
||||
return !vm.alreadyVoted;
|
||||
});
|
||||
}
|
||||
}
|
41
src/Ombi/ClientApp/app/vote/vote.module.ts
Normal file
41
src/Ombi/ClientApp/app/vote/vote.module.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { OrderModule } from "ngx-order-pipe";
|
||||
import { OverlayPanelModule, SharedModule, TabViewModule } from "primeng/primeng";
|
||||
|
||||
import { VoteService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
import { SharedModule as OmbiShared } from "../shared/shared.module";
|
||||
|
||||
import { VoteComponent } from "./vote.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: VoteComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
NgbModule.forRoot(),
|
||||
SharedModule,
|
||||
OrderModule,
|
||||
OmbiShared,
|
||||
TabViewModule,
|
||||
OverlayPanelModule,
|
||||
],
|
||||
declarations: [
|
||||
VoteComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
],
|
||||
providers: [
|
||||
VoteService,
|
||||
],
|
||||
|
||||
})
|
||||
export class VoteModule { }
|
|
@ -1007,5 +1007,4 @@ a > h4:hover {
|
|||
|
||||
.album-cover {
|
||||
width:300px;
|
||||
}
|
||||
|
||||
}
|
|
@ -283,12 +283,12 @@ namespace Ombi.Controllers.External
|
|||
Uri url;
|
||||
if (!wizard.Wizard)
|
||||
{
|
||||
url = await _plexOAuthManager.GetOAuthUrl(wizard.Pin.id, wizard.Pin.code);
|
||||
url = await _plexOAuthManager.GetOAuthUrl(wizard.Pin.code);
|
||||
}
|
||||
else
|
||||
{
|
||||
var websiteAddress =$"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
|
||||
url = await _plexOAuthManager.GetWizardOAuthUrl(wizard.Pin.id, wizard.Pin.code, websiteAddress);
|
||||
url = await _plexOAuthManager.GetWizardOAuthUrl(wizard.Pin.code, websiteAddress);
|
||||
}
|
||||
|
||||
if (url == null)
|
||||
|
|
|
@ -599,12 +599,11 @@ namespace Ombi.Controllers
|
|||
|
||||
|
||||
/// <summary>
|
||||
/// Save the Issues settings.
|
||||
/// Save the Vote settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("Issues")]
|
||||
[AllowAnonymous]
|
||||
public async Task<bool> IssueSettings([FromBody]IssueSettings settings)
|
||||
{
|
||||
return await Save(settings);
|
||||
|
@ -629,6 +628,35 @@ namespace Ombi.Controllers
|
|||
return issues.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the Vote settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("vote")]
|
||||
public async Task<bool> VoteSettings([FromBody]VoteSettings settings)
|
||||
{
|
||||
return await Save(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Vote Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("vote")]
|
||||
public async Task<VoteSettings> VoteSettings()
|
||||
{
|
||||
return await Get<VoteSettings>();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("voteenabled")]
|
||||
public async Task<bool> VoteEnabled()
|
||||
{
|
||||
var vote = await Get<VoteSettings>();
|
||||
return vote.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the email notification settings.
|
||||
/// </summary>
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace Ombi.Controllers
|
|||
|
||||
var websiteAddress = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}";
|
||||
//https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd
|
||||
var url = await _plexOAuthManager.GetOAuthUrl(model.PlexTvPin.id, model.PlexTvPin.code, websiteAddress);
|
||||
var url = await _plexOAuthManager.GetOAuthUrl(model.PlexTvPin.code, websiteAddress);
|
||||
if (url == null)
|
||||
{
|
||||
return new JsonResult(new
|
||||
|
|
118
src/Ombi/Controllers/VoteController.cs
Normal file
118
src/Ombi/Controllers/VoteController.cs
Normal file
|
@ -0,0 +1,118 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Engine;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
[ApiV1]
|
||||
[Authorize]
|
||||
[Produces("application/json")]
|
||||
public class VoteController : Controller
|
||||
{
|
||||
public VoteController(IVoteEngine engine)
|
||||
{
|
||||
_engine = engine;
|
||||
}
|
||||
|
||||
private readonly IVoteEngine _engine;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the viewmodel to render on the UI
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public Task<List<VoteViewModel>> GetView()
|
||||
{
|
||||
return _engine.GetMovieViewModel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upvotes a movie
|
||||
/// </summary>
|
||||
[HttpPost("up/movie/{requestId:int}")]
|
||||
public Task<VoteEngineResult> UpvoteMovie(int requestId)
|
||||
{
|
||||
return _engine.UpVote(requestId, RequestType.Movie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upvotes a tv show
|
||||
/// </summary>
|
||||
[HttpPost("up/tv/{requestId:int}")]
|
||||
public Task<VoteEngineResult> UpvoteTv(int requestId)
|
||||
{
|
||||
return _engine.UpVote(requestId, RequestType.TvShow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upvotes a album
|
||||
/// </summary>
|
||||
[HttpPost("up/album/{requestId:int}")]
|
||||
public Task<VoteEngineResult> UpvoteAlbum(int requestId)
|
||||
{
|
||||
return _engine.UpVote(requestId, RequestType.Album);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downvotes a movie
|
||||
/// </summary>
|
||||
[HttpPost("down/movie/{requestId:int}")]
|
||||
public Task<VoteEngineResult> DownvoteMovie(int requestId)
|
||||
{
|
||||
return _engine.DownVote(requestId, RequestType.Movie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downvotes a tv show
|
||||
/// </summary>
|
||||
[HttpPost("down/tv/{requestId:int}")]
|
||||
public Task<VoteEngineResult> DownvoteTv(int requestId)
|
||||
{
|
||||
return _engine.DownVote(requestId, RequestType.TvShow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downvotes a album
|
||||
/// </summary>
|
||||
[HttpPost("down/album/{requestId:int}")]
|
||||
public Task<VoteEngineResult> DownvoteAlbum(int requestId)
|
||||
{
|
||||
return _engine.DownVote(requestId, RequestType.Album);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's all the votes for the request id
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("movie/{requestId:int}")]
|
||||
public Task<List<Votes>> MovieVotes(int requestId)
|
||||
{
|
||||
return _engine.GetVotes(requestId, RequestType.Movie).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's all the votes for the request id
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("music/{requestId:int}")]
|
||||
public Task<List<Votes>> MusicVotes(int requestId)
|
||||
{
|
||||
return _engine.GetVotes(requestId, RequestType.Album).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's all the votes for the request id
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("tv/{requestId:int}")]
|
||||
public Task<List<Votes>> TvVotes(int requestId)
|
||||
{
|
||||
return _engine.GetVotes(requestId, RequestType.TvShow).ToListAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Anmodninger",
|
||||
"UserManagement": "Brugeradministration",
|
||||
"Issues": "Problemer",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Donér!",
|
||||
"DonateLibraryMaintainer": "Donér til vedligeholder af bibliotek",
|
||||
"DonateTooltip": "Sådan overbeviser jeg min kone om, at jeg skal bruge min fritid på at udvikle Ombi :)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Anfragen",
|
||||
"UserManagement": "Benutzerverwaltung",
|
||||
"Issues": "Probleme",
|
||||
"Vote": "Bewerten",
|
||||
"Donate": "Spenden!",
|
||||
"DonateLibraryMaintainer": "Spende sie an den Bibliotheks Betreuer",
|
||||
"DonateTooltip": "So überzeuge ich meine Frau, meine Freizeit mit der Entwicklung von Ombi zu verbringen ;)",
|
||||
|
@ -65,27 +66,27 @@
|
|||
"Danish": "Dänisch",
|
||||
"Dutch": "Niederländisch",
|
||||
"Norwegian": "Norwegisch",
|
||||
"BrazillianPortuguese": "Brazillian Portuguese",
|
||||
"Polish": "Polish",
|
||||
"Swedish": "Swedish"
|
||||
"BrazillianPortuguese": "Portugiesisch (Brasilien)",
|
||||
"Polish": "Polnisch",
|
||||
"Swedish": "Schwedisch"
|
||||
},
|
||||
"OpenMobileApp": "Mobile App",
|
||||
"RecentlyAdded": "Recently Added"
|
||||
"RecentlyAdded": "Kürzlich hinzugefügt"
|
||||
},
|
||||
"Search": {
|
||||
"Title": "Suche",
|
||||
"Paragraph": "Möchtest du etwas sehen, das nicht verfügbar ist? Kein Problem, benutze einfach die Suchbox und fordere es an!",
|
||||
"MoviesTab": "Filme",
|
||||
"TvTab": "Serien",
|
||||
"MusicTab": "Music",
|
||||
"MusicTab": "Musik",
|
||||
"Suggestions": "Vorschläge",
|
||||
"NoResults": "Es tut uns leid, wir haben keine Ergebnisse gefunden!",
|
||||
"DigitalDate": "Digital Release: {{date}}",
|
||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
||||
"TheatricalRelease": "Kinostart: {{date}}",
|
||||
"ViewOnPlex": "In Plex anschauen",
|
||||
"ViewOnEmby": "In Emby anschauen",
|
||||
"RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt",
|
||||
"Similar": "Similar",
|
||||
"Similar": "Ähnliche",
|
||||
"Movies": {
|
||||
"PopularMovies": "Beliebte Filme",
|
||||
"UpcomingMovies": "Kommende Filme",
|
||||
|
@ -115,14 +116,14 @@
|
|||
"Paragraph": "Unten sehen Sie Ihre und alle anderen Anfragen, sowie deren Download und Genehmigungsstatus.",
|
||||
"MoviesTab": "Filme",
|
||||
"TvTab": "Serien",
|
||||
"MusicTab": "Music",
|
||||
"MusicTab": "Musik",
|
||||
"RequestedBy": "Angefordert von:",
|
||||
"Status": "Status:",
|
||||
"RequestStatus": "Anfrage Status:",
|
||||
"Denied": " Abgelehnt:",
|
||||
"TheatricalRelease": "Theatrical Release: {{date}}",
|
||||
"TheatricalRelease": "Kinostart: {{date}}",
|
||||
"ReleaseDate": "Veröffentlicht: {{date}}",
|
||||
"TheatricalReleaseSort": "Theatrical Release",
|
||||
"TheatricalReleaseSort": "Kinostart",
|
||||
"DigitalRelease": "Digital Release: {{date}}",
|
||||
"RequestDate": "Datum der Anfrage:",
|
||||
"QualityOverride": "Qualitäts Überschreiben:",
|
||||
|
@ -139,10 +140,10 @@
|
|||
"GridStatus": "Status",
|
||||
"ReportIssue": "Problem melden",
|
||||
"Filter": "Filter",
|
||||
"Sort": "Sort",
|
||||
"Sort": "Sortieren",
|
||||
"SeasonNumberHeading": "Staffel: {seasonNumber}",
|
||||
"SortTitleAsc": "Title ▲",
|
||||
"SortTitleDesc": "Title ▼",
|
||||
"SortTitleAsc": "Titel ▲",
|
||||
"SortTitleDesc": "Titel ▼",
|
||||
"SortRequestDateAsc": "Request Date ▲",
|
||||
"SortRequestDateDesc": "Request Date ▼",
|
||||
"SortStatusAsc": "Status ▲",
|
||||
|
@ -179,14 +180,18 @@
|
|||
"FilterHeaderAvailability": "Verfügbarkeit",
|
||||
"FilterHeaderRequestStatus": "Status",
|
||||
"Approved": "Bestätigt",
|
||||
"PendingApproval": "Pending Approval"
|
||||
"PendingApproval": "Genehmigung ausstehend"
|
||||
},
|
||||
"UserManagment": {
|
||||
"TvRemaining": "TV: {{remaining}}/{{total}} remaining",
|
||||
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
|
||||
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining",
|
||||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
"MovieDue": "Film: {{date}}",
|
||||
"MusicDue": "Musik: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Bewertet",
|
||||
"VotesTab": "Erforderliche Bewertungen"
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@
|
|||
"Requests": "Requests",
|
||||
"UserManagement": "User Management",
|
||||
"Issues":"Issues",
|
||||
"Vote":"Vote",
|
||||
"Donate": "Donate!",
|
||||
"DonateLibraryMaintainer": "Donate to Library Maintainer",
|
||||
"DonateTooltip":
|
||||
|
@ -193,5 +194,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Solicitudes",
|
||||
"UserManagement": "Gestión de usuarios",
|
||||
"Issues": "Incidencias",
|
||||
"Vote": "Vote",
|
||||
"Donate": "¡Donar!",
|
||||
"DonateLibraryMaintainer": "Donate to Library Maintainer",
|
||||
"DonateTooltip": "Para que mi esposa me deje desarrollar Ombi ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Demandes",
|
||||
"UserManagement": "Gestion des utilisateurs",
|
||||
"Issues": "Problèmes",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Faire un don !",
|
||||
"DonateLibraryMaintainer": "Faire un don au mainteneur de la bibliothèque",
|
||||
"DonateTooltip": "C’est pour convaincre ma femme de me laisser passer mon temps libre à développer Ombi ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Richieste",
|
||||
"UserManagement": "Gestione degli utenti",
|
||||
"Issues": "Problemi",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Fai una donazione!",
|
||||
"DonateLibraryMaintainer": "Dona al manutentore della libreria",
|
||||
"DonateTooltip": "Questo è come convinco mia moglie a farmi spendere il mio tempo libero nello sviluppo di Ombi ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Verzoeklijst",
|
||||
"UserManagement": "Gebruikersbeheer",
|
||||
"Issues": "Problemen",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Doneer!",
|
||||
"DonateLibraryMaintainer": "Doneren aan bibliotheek beheerder",
|
||||
"DonateTooltip": "Zo heb ik mijn vrouw overtuigd dat ik Ombi mag ontwikkelen ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Forespørsler",
|
||||
"UserManagement": "Brukeradministrasjon",
|
||||
"Issues": "Mangler",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Doner!",
|
||||
"DonateLibraryMaintainer": "Doner til vedlikeholderen av biblioteket",
|
||||
"DonateTooltip": "Dette er hvordan jeg overbevise min kone til å la meg bruke min fritid til å utvikle Ombi ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Zgłoszenia",
|
||||
"UserManagement": "Zarządzanie użytkownikami",
|
||||
"Issues": "Problemy",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Wesprzyj!",
|
||||
"DonateLibraryMaintainer": "Wesprzyj właściciela biblioteki",
|
||||
"DonateTooltip": "W ten sposób przekonuję moją żonę by spędzać mój wolny czas rozwijając Ombi ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Solicitações",
|
||||
"UserManagement": "Gerenciador de Usuário",
|
||||
"Issues": "Problemas",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Fazer uma doação!",
|
||||
"DonateLibraryMaintainer": "Doar para o Dono da Biblioteca",
|
||||
"DonateTooltip": "É assim que eu convenço a minha mulher a deixar-me passar o meu tempo livre desenvolvendo Ombi;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@
|
|||
"Requests": "Förfrågningar",
|
||||
"UserManagement": "Användarhantering",
|
||||
"Issues": "Problem",
|
||||
"Vote": "Vote",
|
||||
"Donate": "Donera!",
|
||||
"DonateLibraryMaintainer": "Donera till bibliotekets utvecklare",
|
||||
"DonateTooltip": "Det är så här jag övertygar min fru att jag vill spendera min fritid att utveckla Ombi ;)",
|
||||
|
@ -188,5 +189,9 @@
|
|||
"TvDue": "TV: {{date}}",
|
||||
"MovieDue": "Movie: {{date}}",
|
||||
"MusicDue": "Music: {{date}}"
|
||||
},
|
||||
"Votes": {
|
||||
"CompletedVotesTab": "Voted",
|
||||
"VotesTab": "Votes Needed"
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue