diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 343eaa2d7..c79ec50c9 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -24,7 +24,7 @@ namespace Ombi.Api.Plex Task GetAccount(string authToken); Task GetRecentlyAdded(string authToken, string uri, string sectionId); Task GetPin(int pinId); - Task GetOAuthUrl(int pinId, string code, string applicationUrl); + Task GetOAuthUrl(string code, string applicationUrl); Task AddUser(string emailAddress, string serverId, string authToken, int[] libs); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index f0808622f..fe6ba23e2 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -217,12 +217,11 @@ namespace Ombi.Api.Plex return await Api.Request(request); } - public async Task GetOAuthUrl(int pinId, string code, string applicationUrl) + public async Task 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"); diff --git a/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs new file mode 100644 index 000000000..ad4c33131 --- /dev/null +++ b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs @@ -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>(); + VoteSettings = new Mock>(); + MusicRequestEngine = new Mock(); + TvRequestEngine = new Mock(); + MovieRequestEngine = new Mock(); + MovieRequestEngine = new Mock(); + User = new Mock(); + UserManager = new Mock(); + UserManager.Setup(x => x.Users) + .Returns(new EnumerableQuery(new List {new OmbiUser {Id = "abc"}})); + Rule = new Mock(); + 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 User { get; set; } + public Mock UserManager { get; set; } + public Mock Rule { get; set; } + public Mock> VoteRepository { get; set; } + public Mock> VoteSettings { get; set; } + public Mock MusicRequestEngine { get; set; } + public Mock TvRequestEngine { get; set; } + public Mock 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().ToList(); + votes.Add(new Votes + { + RequestId = 1, + RequestType = RequestType.Movie, + UserId = "abc" + }); + VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery(votes)); + var result = await Engine.UpVote(1, RequestType.Movie); + + Assert.That(result.Result, Is.True); + VoteRepository.Verify(x => x.Add(It.Is(c => c.UserId == "abc" && c.VoteType == VoteType.Upvote)), Times.Once); + VoteRepository.Verify(x => x.Delete(It.IsAny()), Times.Once); + MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj index 30de4b6f0..d20176ec4 100644 --- a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj +++ b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj @@ -5,9 +5,10 @@ - + + - + diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 426037bb7..76b1b5d97 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -36,17 +36,17 @@ namespace Ombi.Core.Authentication return await _api.GetAccount(accessToken); } - public async Task GetOAuthUrl(int pinId, string code, string websiteAddress = null) + public async Task 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 GetWizardOAuthUrl(int pinId, string code, string websiteAddress) + public async Task GetWizardOAuthUrl(string code, string websiteAddress) { - var url = await _api.GetOAuthUrl(pinId, code, websiteAddress); + var url = await _api.GetOAuthUrl(code, websiteAddress); return url; } } diff --git a/src/Ombi.Core/Engine/IVoteEngine.cs b/src/Ombi.Core/Engine/IVoteEngine.cs new file mode 100644 index 000000000..681f333c5 --- /dev/null +++ b/src/Ombi.Core/Engine/IVoteEngine.cs @@ -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 DownVote(int requestId, RequestType requestType); + Task GetVoteForUser(int requestId, string userId); + IQueryable GetVotes(int requestId, RequestType requestType); + Task RemoveCurrentVote(Votes currentVote); + Task UpVote(int requestId, RequestType requestType); + Task> GetMovieViewModel(); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 26bc5969c..c5cb8c45a 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -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 { diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index bb674a35d..c8e958002 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -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> 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> 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> 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> ProcessResults(IEnumerable items) + private IEnumerable ProcessResults(IEnumerable items) { var retVal = new List(); foreach (var tvMazeSearch in items) { - retVal.Add(await ProcessResult(tvMazeSearch)); + retVal.Add(ProcessResult(tvMazeSearch)); } return retVal; } - private async Task ProcessResult(T tvMazeSearch) + private SearchTvShowViewModel ProcessResult(T tvMazeSearch) { return Mapper.Map(tvMazeSearch); } diff --git a/src/Ombi.Core/Engine/VoteEngine.cs b/src/Ombi.Core/Engine/VoteEngine.cs new file mode 100644 index 000000000..2ab35c2fa --- /dev/null +++ b/src/Ombi.Core/Engine/VoteEngine.cs @@ -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, IPrincipal user, OmbiUserManager um, IRuleEvaluator r, ISettingsService voteSettings, + IMusicRequestEngine musicRequestEngine, ITvRequestEngine tvRequestEngine, IMovieRequestEngine movieRequestEngine) : base(user, um, r) + { + _voteRepository = votes; + _voteSettings = voteSettings; + _movieRequestEngine = movieRequestEngine; + _musicRequestEngine = musicRequestEngine; + _tvRequestEngine = tvRequestEngine; + } + + private readonly IRepository _voteRepository; + private readonly ISettingsService _voteSettings; + private readonly IMusicRequestEngine _musicRequestEngine; + private readonly ITvRequestEngine _tvRequestEngine; + private readonly IMovieRequestEngine _movieRequestEngine; + + public async Task> GetMovieViewModel() + { + var vm = new List(); + 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("
"); + } + 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 GetVotes(int requestId, RequestType requestType) + { + return _voteRepository.GetAll().Where(x => x.RequestType == requestType && requestId == x.RequestId); + } + + public Task GetVoteForUser(int requestId, string userId) + { + return _voteRepository.GetAll().FirstOrDefaultAsync(x => x.RequestId == requestId && x.UserId == userId); + } + + public async Task 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 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); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs index a5c0c44ff..7668ee882 100644 --- a/src/Ombi.Core/IPlexOAuthManager.cs +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -7,8 +7,8 @@ namespace Ombi.Core.Authentication public interface IPlexOAuthManager { Task GetAccessTokenFromPin(int pinId); - Task GetOAuthUrl(int pinId, string code, string websiteAddress = null); - Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress); + Task GetOAuthUrl(string code, string websiteAddress = null); + Task GetWizardOAuthUrl(string code, string websiteAddress); Task GetAccount(string accessToken); } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/UI/VoteViewModel.cs b/src/Ombi.Core/Models/UI/VoteViewModel.cs new file mode 100644 index 000000000..f58db3dbc --- /dev/null +++ b/src/Ombi.Core/Models/UI/VoteViewModel.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/VoteEngineResult.cs b/src/Ombi.Core/Models/VoteEngineResult.cs new file mode 100644 index 000000000..7546b0bb2 --- /dev/null +++ b/src/Ombi.Core/Models/VoteEngineResult.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs index 9e2dab1bd..60e4ca6ee 100644 --- a/src/Ombi.Core/Senders/MusicSender.cs +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -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 }; } diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 3af06b476..30ccb6973 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -91,6 +91,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterHttp(this IServiceCollection services) { diff --git a/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj b/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj index de1c55533..5d5d3b9d0 100644 --- a/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj +++ b/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj @@ -5,11 +5,11 @@ - - - + + + - + diff --git a/src/Ombi.Schedule.Tests/NewsletterTests.cs b/src/Ombi.Schedule.Tests/NewsletterTests.cs index b3c2ce98a..146cd97cf 100644 --- a/src/Ombi.Schedule.Tests/NewsletterTests.cs +++ b/src/Ombi.Schedule.Tests/NewsletterTests.cs @@ -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 episodes) { - var emailSettings = new Mock>(); - var customziation = new Mock>(); - var newsletterSettings = new Mock>(); - 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(); foreach (var i in episodes) { ep.Add(i); } - var result = newsletter.BuildEpisodeList(ep); + var result = BuildEpisodeList(ep); return result; } diff --git a/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj b/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj index ea1d17f8c..ca07f635f 100644 --- a/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj +++ b/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj @@ -6,9 +6,9 @@ - + - + diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index f152f6b4b..9f5e58482 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -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 orderedEpisodes) + public static string BuildEpisodeList(IEnumerable orderedEpisodes) { var epSb = new StringBuilder(); var previousEpisodes = new List(); diff --git a/src/Ombi.Settings/Settings/Models/VoteSettings.cs b/src/Ombi.Settings/Settings/Models/VoteSettings.cs new file mode 100644 index 000000000..4d63cfea4 --- /dev/null +++ b/src/Ombi.Settings/Settings/Models/VoteSettings.cs @@ -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; } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Context/IOmbiContext.cs b/src/Ombi.Store/Context/IOmbiContext.cs index 2c4d809b4..77134e1a3 100644 --- a/src/Ombi.Store/Context/IOmbiContext.cs +++ b/src/Ombi.Store/Context/IOmbiContext.cs @@ -25,6 +25,7 @@ namespace Ombi.Store.Context DbSet Set() where TEntity : class; DbSet NotificationTemplates { get; set; } DbSet ApplicationConfigurations { get; set; } + DbSet Votes { get; set; } void Seed(); DbSet Audit { get; set; } DbSet MovieRequests { get; set; } diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index f34b4c57c..66631dfc6 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -40,6 +40,7 @@ namespace Ombi.Store.Context public DbSet IssueComments { get; set; } public DbSet RequestLogs { get; set; } public DbSet RecentlyAddedLogs { get; set; } + public DbSet Votes { get; set; } public DbSet Audit { get; set; } diff --git a/src/Ombi.Store/Entities/Votes.cs b/src/Ombi.Store/Entities/Votes.cs new file mode 100644 index 000000000..61e5651da --- /dev/null +++ b/src/Ombi.Store/Entities/Votes.cs @@ -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 + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs b/src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs new file mode 100644 index 000000000..5d9b47c26 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180928201334_Votes.Designer.cs @@ -0,0 +1,1182 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180928201334_Votes")] + partial class Votes + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RadarrQualityProfile"); + + b.Property("RadarrRootPath"); + + b.Property("SonarrQualityProfile"); + + b.Property("SonarrQualityProfileAnime"); + + b.Property("SonarrRootPath"); + + b.Property("SonarrRootPathAnime"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .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") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180928201334_Votes.cs b/src/Ombi.Store/Migrations/20180928201334_Votes.cs new file mode 100644 index 000000000..1c64a19aa --- /dev/null +++ b/src/Ombi.Store/Migrations/20180928201334_Votes.cs @@ -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(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RequestId = table.Column(nullable: false), + VoteType = table.Column(nullable: false), + RequestType = table.Column(nullable: false), + UserId = table.Column(nullable: true), + Date = table.Column(nullable: false), + Deleted = table.Column(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"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 86694ec23..60927b1ed 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -916,6 +916,30 @@ namespace Ombi.Store.Migrations b.ToTable("UserQualityProfiles"); }); + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Date"); + + b.Property("Deleted"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.Property("VoteType"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => { b.Property("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") diff --git a/src/Ombi.Tests/Ombi.Tests.csproj b/src/Ombi.Tests/Ombi.Tests.csproj index 95eb09353..55c66b80c 100644 --- a/src/Ombi.Tests/Ombi.Tests.csproj +++ b/src/Ombi.Tests/Ombi.Tests.csproj @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/src/Ombi/ClientApp/app/app.component.html b/src/Ombi/ClientApp/app/app.component.html index 24398f088..953e2a9a2 100644 --- a/src/Ombi/ClientApp/app/app.component.html +++ b/src/Ombi/ClientApp/app/app.component.html @@ -54,6 +54,12 @@ {{ 'NavigationBar.UserManagement' | translate }} + diff --git a/src/Ombi/ClientApp/app/settings/vote/vote.component.html b/src/Ombi/ClientApp/app/settings/vote/vote.component.html new file mode 100644 index 000000000..c5877f3d8 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/vote/vote.component.html @@ -0,0 +1,50 @@ + + + +
+ Vote +
+
+
+
+ + +
+
+ +

Vote limits tell Ombi how many votes the request needs before approval.

+

e.g. If the Movie vote limit is 10, it requires 10 Upvotes from 10 different users before it will be approved.

+
+ + + + The limit needs to be greater than or equal to 1 +
+
+ + + The limit needs to be greater than or equal to 1 +
+
+ + + The limit needs to be greater than or equal to 1 +
+ +
+
+ +
+
+
+ +
+ + + + + +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/vote/vote.component.ts b/src/Ombi/ClientApp/app/settings/vote/vote.component.ts new file mode 100644 index 000000000..d99239b96 --- /dev/null +++ b/src/Ombi/ClientApp/app/settings/vote/vote.component.ts @@ -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"); + } + }); + } +} diff --git a/src/Ombi/ClientApp/app/vote/vote.component.html b/src/Ombi/ClientApp/app/vote/vote.component.html new file mode 100644 index 000000000..6d8f8a797 --- /dev/null +++ b/src/Ombi/ClientApp/app/vote/vote.component.html @@ -0,0 +1,92 @@ +

Vote

+ + + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + +
TitleDescription
+ + + poster{{vm.title}}
+
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + +
TitleDescription
+ + + poster{{vm.title}}
+
+ +
+
+ + + + poster + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/vote/vote.component.scss b/src/Ombi/ClientApp/app/vote/vote.component.scss new file mode 100644 index 000000000..a57fb9749 --- /dev/null +++ b/src/Ombi/ClientApp/app/vote/vote.component.scss @@ -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; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/vote/vote.component.ts b/src/Ombi/ClientApp/app/vote/vote.component.ts new file mode 100644 index 000000000..ffab837cd --- /dev/null +++ b/src/Ombi/ClientApp/app/vote/vote.component.ts @@ -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; + }); + } +} diff --git a/src/Ombi/ClientApp/app/vote/vote.module.ts b/src/Ombi/ClientApp/app/vote/vote.module.ts new file mode 100644 index 000000000..ee7532da5 --- /dev/null +++ b/src/Ombi/ClientApp/app/vote/vote.module.ts @@ -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 { } diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index a9f3119fa..98f5e3e99 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -1007,5 +1007,4 @@ a > h4:hover { .album-cover { width:300px; -} - +} \ No newline at end of file diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index d7cefd091..f0492b580 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -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) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 9785af553..b523250a6 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -599,12 +599,11 @@ namespace Ombi.Controllers /// - /// Save the Issues settings. + /// Save the Vote settings. /// /// The settings. /// [HttpPost("Issues")] - [AllowAnonymous] public async Task IssueSettings([FromBody]IssueSettings settings) { return await Save(settings); @@ -629,6 +628,35 @@ namespace Ombi.Controllers return issues.Enabled; } + /// + /// Save the Vote settings. + /// + /// The settings. + /// + [HttpPost("vote")] + public async Task VoteSettings([FromBody]VoteSettings settings) + { + return await Save(settings); + } + + /// + /// Gets the Vote Settings. + /// + /// + [HttpGet("vote")] + public async Task VoteSettings() + { + return await Get(); + } + + [AllowAnonymous] + [HttpGet("voteenabled")] + public async Task VoteEnabled() + { + var vote = await Get(); + return vote.Enabled; + } + /// /// Saves the email notification settings. /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 1314a741a..87136fd00 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -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 diff --git a/src/Ombi/Controllers/VoteController.cs b/src/Ombi/Controllers/VoteController.cs new file mode 100644 index 000000000..70ea0ec8c --- /dev/null +++ b/src/Ombi/Controllers/VoteController.cs @@ -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; + + /// + /// Returns the viewmodel to render on the UI + /// + [HttpGet] + public Task> GetView() + { + return _engine.GetMovieViewModel(); + } + + /// + /// Upvotes a movie + /// + [HttpPost("up/movie/{requestId:int}")] + public Task UpvoteMovie(int requestId) + { + return _engine.UpVote(requestId, RequestType.Movie); + } + + /// + /// Upvotes a tv show + /// + [HttpPost("up/tv/{requestId:int}")] + public Task UpvoteTv(int requestId) + { + return _engine.UpVote(requestId, RequestType.TvShow); + } + + /// + /// Upvotes a album + /// + [HttpPost("up/album/{requestId:int}")] + public Task UpvoteAlbum(int requestId) + { + return _engine.UpVote(requestId, RequestType.Album); + } + + /// + /// Downvotes a movie + /// + [HttpPost("down/movie/{requestId:int}")] + public Task DownvoteMovie(int requestId) + { + return _engine.DownVote(requestId, RequestType.Movie); + } + + /// + /// Downvotes a tv show + /// + [HttpPost("down/tv/{requestId:int}")] + public Task DownvoteTv(int requestId) + { + return _engine.DownVote(requestId, RequestType.TvShow); + } + + /// + /// Downvotes a album + /// + [HttpPost("down/album/{requestId:int}")] + public Task DownvoteAlbum(int requestId) + { + return _engine.DownVote(requestId, RequestType.Album); + } + + /// + /// Get's all the votes for the request id + /// + /// + [HttpGet("movie/{requestId:int}")] + public Task> MovieVotes(int requestId) + { + return _engine.GetVotes(requestId, RequestType.Movie).ToListAsync(); + } + + /// + /// Get's all the votes for the request id + /// + /// + [HttpGet("music/{requestId:int}")] + public Task> MusicVotes(int requestId) + { + return _engine.GetVotes(requestId, RequestType.Album).ToListAsync(); + } + + /// + /// Get's all the votes for the request id + /// + /// + [HttpGet("tv/{requestId:int}")] + public Task> TvVotes(int requestId) + { + return _engine.GetVotes(requestId, RequestType.TvShow).ToListAsync(); + } + } +} \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/da.json b/src/Ombi/wwwroot/translations/da.json index 17c671217..0229b3603 100644 --- a/src/Ombi/wwwroot/translations/da.json +++ b/src/Ombi/wwwroot/translations/da.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index 2da1aab4e..c3646221d 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index cbe2c034e..93f6c39f0 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -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" } } diff --git a/src/Ombi/wwwroot/translations/es.json b/src/Ombi/wwwroot/translations/es.json index a49ea5140..dc7637e79 100644 --- a/src/Ombi/wwwroot/translations/es.json +++ b/src/Ombi/wwwroot/translations/es.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index 0834653f8..c20dffdb0 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/it.json b/src/Ombi/wwwroot/translations/it.json index 25f706303..0ade93919 100644 --- a/src/Ombi/wwwroot/translations/it.json +++ b/src/Ombi/wwwroot/translations/it.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index 60d54fe8c..18a1d319d 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/no.json b/src/Ombi/wwwroot/translations/no.json index 87df0181f..80cd9e31a 100644 --- a/src/Ombi/wwwroot/translations/no.json +++ b/src/Ombi/wwwroot/translations/no.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/pl.json b/src/Ombi/wwwroot/translations/pl.json index 8e180e5bb..3aba4da41 100644 --- a/src/Ombi/wwwroot/translations/pl.json +++ b/src/Ombi/wwwroot/translations/pl.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/pt.json b/src/Ombi/wwwroot/translations/pt.json index e547d0066..eaf45cac9 100644 --- a/src/Ombi/wwwroot/translations/pt.json +++ b/src/Ombi/wwwroot/translations/pt.json @@ -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" } } \ No newline at end of file diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index 26a85fe10..8d2e8374a 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -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" } } \ No newline at end of file