diff --git a/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs b/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs index b922a21c0..0a85fd058 100644 --- a/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/CalendarEngineTests.cs @@ -10,6 +10,7 @@ using Ombi.Core.Authentication; using Ombi.Core.Engine.V2; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository.Requests; +using Ombi.Core.Helpers; namespace Ombi.Core.Tests.Engine { @@ -25,7 +26,7 @@ namespace Ombi.Core.Tests.Engine { MovieRepo = new Mock(); TvRepo = new Mock(); - var principle = new Mock(); + var principle = new Mock(); var identity = new Mock(); identity.Setup(x => x.Name).Returns("UnitTest"); principle.Setup(x => x.Identity).Returns(identity.Object); diff --git a/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs b/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs index 8c37aaa9a..f8ced161f 100644 --- a/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/MovieRequestEngineTests.cs @@ -4,6 +4,7 @@ using Moq.AutoMock; using NUnit.Framework; using Ombi.Core.Authentication; using Ombi.Core.Engine; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; @@ -35,12 +36,17 @@ namespace Ombi.Core.Tests.Engine var identity = new Mock(); identity.Setup(x => x.Name).Returns("Test"); principle.Setup(x => x.Identity).Returns(identity.Object); + var currentUser = new Mock(); + currentUser.Setup(x => x.Identity).Returns(identity.Object); + currentUser.Setup(x => x.Username).Returns("Test"); + currentUser.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { NormalizedUserName = "TEST", Id = "a" }); _repoMock = new Mock(); var requestServiceMock = new Mock(); requestServiceMock.Setup(x => x.MovieRequestService).Returns(_repoMock.Object); _mocker.Use(principle.Object); + _mocker.Use(currentUser.Object); _mocker.Use(userManager.Object); _mocker.Use(requestServiceMock); diff --git a/src/Ombi.Core.Tests/Engine/MovieRequestLimitsTests.cs b/src/Ombi.Core.Tests/Engine/MovieRequestLimitsTests.cs index f1af160d6..88e1c75d2 100644 --- a/src/Ombi.Core.Tests/Engine/MovieRequestLimitsTests.cs +++ b/src/Ombi.Core.Tests/Engine/MovieRequestLimitsTests.cs @@ -4,6 +4,7 @@ using Moq.AutoMock; using NUnit.Framework; using Ombi.Core.Authentication; using Ombi.Core.Engine; +using Ombi.Core.Helpers; using Ombi.Core.Models; using Ombi.Core.Services; using Ombi.Helpers; @@ -36,6 +37,13 @@ namespace Ombi.Core.Tests.Engine var identityMock = new Mock(); identityMock.SetupGet(x => x.Name).Returns("Test"); principleMock.SetupGet(x => x.Identity).Returns(identityMock.Object); + + var currentUser = new Mock(); + currentUser.Setup(x => x.Identity).Returns(identityMock.Object); + currentUser.Setup(x => x.Username).Returns("Test"); + currentUser.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "Test", NormalizedUserName = "TEST", Id = "a" }); + + _mocker.Use(currentUser.Object); _mocker.Use(principleMock.Object); _subject = _mocker.CreateInstance(); diff --git a/src/Ombi.Core.Tests/Engine/MusicRequestLimitTests.cs b/src/Ombi.Core.Tests/Engine/MusicRequestLimitTests.cs index f7291409b..93dd5050d 100644 --- a/src/Ombi.Core.Tests/Engine/MusicRequestLimitTests.cs +++ b/src/Ombi.Core.Tests/Engine/MusicRequestLimitTests.cs @@ -4,6 +4,7 @@ using Moq.AutoMock; using NUnit.Framework; using Ombi.Core.Authentication; using Ombi.Core.Engine; +using Ombi.Core.Helpers; using Ombi.Core.Models; using Ombi.Core.Services; using Ombi.Helpers; @@ -36,7 +37,12 @@ namespace Ombi.Core.Tests.Engine var identityMock = new Mock(); identityMock.SetupGet(x => x.Name).Returns("Test"); principleMock.SetupGet(x => x.Identity).Returns(identityMock.Object); + var currentUser = new Mock(); + currentUser.Setup(x => x.Identity).Returns(identityMock.Object); + currentUser.Setup(x => x.Username).Returns("Test"); + currentUser.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "Test", NormalizedUserName = "TEST", Id = "a" }); _mocker.Use(principleMock.Object); + _mocker.Use(currentUser.Object); _subject = _mocker.CreateInstance(); } diff --git a/src/Ombi.Core.Tests/Engine/TvRequestLimitsTests.cs b/src/Ombi.Core.Tests/Engine/TvRequestLimitsTests.cs index e601ab84d..ba383da6c 100644 --- a/src/Ombi.Core.Tests/Engine/TvRequestLimitsTests.cs +++ b/src/Ombi.Core.Tests/Engine/TvRequestLimitsTests.cs @@ -4,6 +4,7 @@ using Moq.AutoMock; using NUnit.Framework; using Ombi.Core.Authentication; using Ombi.Core.Engine; +using Ombi.Core.Helpers; using Ombi.Core.Models; using Ombi.Core.Services; using Ombi.Helpers; @@ -33,7 +34,12 @@ namespace Ombi.Core.Tests.Engine var identityMock = new Mock(); identityMock.SetupGet(x => x.Name).Returns("Test"); principleMock.SetupGet(x => x.Identity).Returns(identityMock.Object); + var currentUser = new Mock(); + currentUser.Setup(x => x.Identity).Returns(identityMock.Object); + currentUser.Setup(x => x.Username).Returns("Test"); + currentUser.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "Test", NormalizedUserName = "TEST", Id = "a" }); _mocker.Use(principleMock.Object); + _mocker.Use(currentUser.Object); _subject = _mocker.CreateInstance(); } diff --git a/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs b/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs index d9f5b11be..16b7cb811 100644 --- a/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/V2/MovieRequestEngineTests.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using Ombi.Api.TheMovieDb; using Ombi.Core.Engine; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Interfaces; using Ombi.Core.Services; @@ -33,7 +34,7 @@ namespace Ombi.Core.Tests.Engine.V2 var requestService = new Mock(); _movieRequestRepository = new Mock(); requestService.Setup(x => x.MovieRequestService).Returns(_movieRequestRepository.Object); - var user = new Mock(); + var user = new Mock(); var notificationHelper = new Mock(); var rules = new Mock(); var movieSender = new Mock(); diff --git a/src/Ombi.Core.Tests/Engine/V2/MusicSearchEngineV2Tests.cs b/src/Ombi.Core.Tests/Engine/V2/MusicSearchEngineV2Tests.cs index 50ab63346..1b773f060 100644 --- a/src/Ombi.Core.Tests/Engine/V2/MusicSearchEngineV2Tests.cs +++ b/src/Ombi.Core.Tests/Engine/V2/MusicSearchEngineV2Tests.cs @@ -23,6 +23,7 @@ using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Test.Common; using Artist = Hqub.MusicBrainz.API.Entities.Artist; +using Ombi.Core.Helpers; namespace Ombi.Core.Tests.Engine.V2 { @@ -45,7 +46,7 @@ namespace Ombi.Core.Tests.Engine.V2 .ForEach(b => F.Behaviors.Remove(b)); F.Behaviors.Add(new OmitOnRecursionBehavior()); - var principle = new Mock(); + var principle = new Mock(); var requestService = new Mock(); var ruleEval = new Mock(); var um = MockHelper.MockUserManager(new List()); diff --git a/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs index 1269cd2bc..4194ae0ea 100644 --- a/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs +++ b/src/Ombi.Core.Tests/Engine/VoteEngineTests.cs @@ -9,6 +9,7 @@ using NUnit.Framework; using Ombi.Core.Authentication; using Ombi.Core.Engine; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Helpers; using Ombi.Core.Models; using Ombi.Core.Rule.Interfaces; using Ombi.Core.Settings; @@ -32,8 +33,9 @@ namespace Ombi.Core.Tests.Engine TvRequestEngine = new Mock(); MovieRequestEngine = new Mock(); MovieRequestEngine = new Mock(); - User = new Mock(); - User.Setup(x => x.Identity.Name).Returns("abc"); + User = new Mock(); + User.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "abc", NormalizedUserName = "ABC", Id = "abc" }); + UserManager = MockHelper.MockUserManager(new List { new OmbiUser { Id = "abc", UserName = "abc", NormalizedUserName = "ABC" } }); Rule = new Mock(); Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object, @@ -48,7 +50,7 @@ namespace Ombi.Core.Tests.Engine public Fixture F { get; set; } public VoteEngine Engine { get; set; } - public Mock User { get; set; } + public Mock User { get; set; } public Mock UserManager { get; set; } public Mock Rule { get; set; } public Mock> VoteRepository { get; set; } diff --git a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs index 9633249d4..c73237097 100644 --- a/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/AutoApproveRuleTests.cs @@ -11,6 +11,7 @@ using System.Collections.Generic; using Ombi.Store.Entities; using System; using Ombi.Core.Services; +using Ombi.Core.Helpers; namespace Ombi.Core.Tests.Rule.Request { @@ -27,17 +28,18 @@ namespace Ombi.Core.Tests.Rule.Request public void Setup() { - PrincipalMock = new Mock(); - PrincipalMock.Setup(x => x.Identity.Name).Returns("abc"); FeatureService = new Mock(); + PrincipalMock = new Mock(); + PrincipalMock.Setup(x => x.Username).Returns("abc"); + PrincipalMock.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "abc", NormalizedUserName = "ABC", Id = "a" }); + UserManager = MockHelper.MockUserManager(_users); Rule = new AutoApproveRule(PrincipalMock.Object, UserManager.Object, FeatureService.Object); } - private AutoApproveRule Rule { get; set; } - private Mock PrincipalMock { get; set; } + private Mock PrincipalMock { get; set; } private Mock UserManager { get; set; } private Mock FeatureService { get; set; } @@ -99,7 +101,8 @@ namespace Ombi.Core.Tests.Rule.Request [Test] public async Task Should_ReturnSuccess_WhenSystemUserAndRequestTV() { - PrincipalMock.Setup(x => x.Identity.Name).Returns("sys"); + PrincipalMock.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "sys", NormalizedUserName = "SYS", Id = "a" }); + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.AutoApproveTv)).ReturnsAsync(false); var request = new BaseRequest() { RequestType = Store.Entities.RequestType.TvShow }; var result = await Rule.Execute(request); diff --git a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs index f601689dc..9d8db25d1 100644 --- a/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Request/CanRequestRuleTests.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using Moq; using NUnit.Framework; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Rule.Rules; using Ombi.Core.Rule.Rules.Request; using Ombi.Helpers; @@ -26,8 +27,9 @@ namespace Ombi.Core.Tests.Rule.Request public void Setup() { - PrincipalMock = new Mock(); - PrincipalMock.Setup(x => x.Identity.Name).Returns("abc"); + PrincipalMock = new Mock(); + PrincipalMock.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "abc", NormalizedUserName = "ABC", Id = "a" }); + UserManager = MockHelper.MockUserManager(_users); Rule = new CanRequestRule(PrincipalMock.Object, UserManager.Object); @@ -35,7 +37,7 @@ namespace Ombi.Core.Tests.Rule.Request private CanRequestRule Rule { get; set; } - private Mock PrincipalMock { get; set; } + private Mock PrincipalMock { get; set; } private Mock UserManager { get; set; } [Test] @@ -107,7 +109,8 @@ namespace Ombi.Core.Tests.Rule.Request [Test] public async Task Should_ReturnSuccess_WhenRequestingMovieWithSystemRole() { - PrincipalMock.Setup(x => x.Identity.Name).Returns("sys"); + PrincipalMock.Setup(x => x.GetUser()).ReturnsAsync(new OmbiUser { UserName = "sys", NormalizedUserName = "SYS", Id = "a" }); + UserManager.Setup(x => x.IsInRoleAsync(It.IsAny(), OmbiRoles.Admin)).ReturnsAsync(false); var request = new BaseRequest() { RequestType = Store.Entities.RequestType.Movie }; var result = await Rule.Execute(request); diff --git a/src/Ombi.Core/Authentication/OmbiUserManager.cs b/src/Ombi.Core/Authentication/OmbiUserManager.cs index 87f82c1de..124ff1aeb 100644 --- a/src/Ombi.Core/Authentication/OmbiUserManager.cs +++ b/src/Ombi.Core/Authentication/OmbiUserManager.cs @@ -116,16 +116,25 @@ namespace Ombi.Core.Authentication public async Task GetOmbiUserFromPlexToken(string plexToken) { var plexAccount = await _plexApi.GetAccount(plexToken); - + // Check for a ombi user - if (plexAccount?.user != null) + if (plexAccount?.user == null) + { + return null; + } + + var potentialOmbiUser = await Users.FirstOrDefaultAsync(x => + x.ProviderUserId == plexAccount.user.id); + // Update ombi user with the token + + if (potentialOmbiUser != null) { - var potentialOmbiUser = await Users.FirstOrDefaultAsync(x => - x.ProviderUserId == plexAccount.user.id); - return potentialOmbiUser; + potentialOmbiUser.MediaServerToken = plexAccount.user.authentication_token; + await UpdateAsync(potentialOmbiUser); } - return null; + return potentialOmbiUser; + } @@ -142,6 +151,10 @@ namespace Ombi.Core.Authentication var result = await _plexApi.SignIn(new UserRequest { password = password, login = login }); if (result.user?.authentication_token != null) { + // Update ombi user with the token + user.MediaServerToken = result.user?.authentication_token; + await UpdateAsync(user); + return true; } return false; diff --git a/src/Ombi.Core/Engine/BaseMediaEngine.cs b/src/Ombi.Core/Engine/BaseMediaEngine.cs index 277144ab1..ee1ce55a8 100644 --- a/src/Ombi.Core/Engine/BaseMediaEngine.cs +++ b/src/Ombi.Core/Engine/BaseMediaEngine.cs @@ -26,7 +26,7 @@ namespace Ombi.Core.Engine private Dictionary _dbMovies; private Dictionary _dbTv; - protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService, + protected BaseMediaEngine(ICurrentUser identity, IRequestServiceMain requestService, IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService ombiSettings, IRepository sub) : base(identity, um, rules) { RequestService = requestService; diff --git a/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs b/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs index f392bf7ad..af88813d9 100644 --- a/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs @@ -11,6 +11,7 @@ using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Config; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; @@ -24,7 +25,7 @@ namespace Ombi.Core.Engine.Demo { public class DemoMovieSearchEngine : MovieSearchEngine, IDemoMovieSearchEngine { - public DemoMovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, + public DemoMovieSearchEngine(ICurrentUser identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub, IOptions lists) : base(identity, service, movApi, mapper, logger, r, um, mem, s, sub) diff --git a/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs b/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs index 2c91a458f..706a5337d 100644 --- a/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs +++ b/src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs @@ -4,6 +4,7 @@ using Ombi.Api.Trakt; using Ombi.Api.TvMaze; using Ombi.Config; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; @@ -24,7 +25,7 @@ namespace Ombi.Core.Engine.Demo public class DemoTvSearchEngine : TvSearchEngine, IDemoTvSearchEngine { - public DemoTvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, + public DemoTvSearchEngine(ICurrentUser identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, ISettingsService s, IRepository sub, IOptions lists, IImageService imageService, ISettingsService custom) diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 42aff9a61..2c68ea371 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -1,42 +1,35 @@ using System; using Ombi.Core.Rule; using System.Collections.Generic; -using System.Security.Principal; using System.Threading.Tasks; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; using Ombi.Store.Entities.Requests; using Ombi.Store.Entities; -using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; -using Ombi.Helpers; +using Ombi.Core.Helpers; namespace Ombi.Core.Engine.Interfaces { public abstract class BaseEngine { - protected BaseEngine(IPrincipal user, OmbiUserManager um, IRuleEvaluator rules) + protected BaseEngine(ICurrentUser user, OmbiUserManager um, IRuleEvaluator rules) { - UserPrinciple = user; + CurrentUser = user; Rules = rules; UserManager = um; } - protected IPrincipal UserPrinciple { get; } + protected ICurrentUser CurrentUser { get; } protected IRuleEvaluator Rules { get; } - protected OmbiUserManager UserManager { get; } - protected string Username => UserPrinciple.Identity.Name; + protected OmbiUserManager UserManager { get; } + protected string Username => CurrentUser.Username; + protected Task GetUser() => CurrentUser.GetUser(); - private OmbiUser _user; - protected async Task GetUser() - { - if(!Username.HasValue()) - { - return null; - } - var username = Username.ToUpper(); - return _user ??= await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username); - } + /// + /// Only used for background tasks + /// + public void SetUser(OmbiUser user) => CurrentUser.SetUser(user); protected async Task UserAlias() { @@ -52,7 +45,7 @@ namespace Ombi.Core.Engine.Interfaces var user = await GetUser(); return await UserManager.IsInRoleAsync(user, roleName); } - + public async Task> RunRequestRules(BaseRequest model) { var ruleResults = await Rules.StartRequestRules(model); diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index d0fc04b10..01300287e 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -25,5 +25,6 @@ namespace Ombi.Core.Engine.Interfaces Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); Task ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken); + void SetUser(OmbiUser user); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 498cf3e0b..64b5418dd 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -23,12 +23,13 @@ using Ombi.Store.Repository; using Ombi.Core.Models; using System.Threading; using Ombi.Core.Services; +using Ombi.Core.Helpers; namespace Ombi.Core.Engine { public class MovieRequestEngine : BaseMediaEngine, IMovieRequestEngine { - public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user, + public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, ICurrentUser user, INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger log, OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings, IRepository sub, IMediaCacheService mediaCacheService, diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 7d84a0b40..682a999eb 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Core.Rule.Interfaces; @@ -22,7 +23,7 @@ namespace Ombi.Core.Engine { public class MovieSearchEngine : BaseMediaEngine, IMovieEngine { - public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, + public MovieSearchEngine(ICurrentUser identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub) : base(identity, service, r, um, mem, s, sub) { diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index b9aa183d2..640d2ade3 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -24,12 +24,13 @@ using Ombi.Settings.Settings.Models.External; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; using System.ComponentModel; +using Ombi.Core.Helpers; namespace Ombi.Core.Engine { public class MusicRequestEngine : BaseMediaEngine, IMusicRequestEngine { - public MusicRequestEngine(IRequestServiceMain requestService, IPrincipal user, + public MusicRequestEngine(IRequestServiceMain requestService, ICurrentUser user, INotificationHelper helper, IRuleEvaluator r, ILogger log, OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings, IRepository sub, ILidarrApi lidarr, diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index 89bae7069..2602bf71e 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -27,7 +27,7 @@ namespace Ombi.Core.Engine { public class MusicSearchEngine : BaseMediaEngine, IMusicSearchEngine { - public MusicSearchEngine(IPrincipal identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper, + public MusicSearchEngine(ICurrentUser identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper, ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub, ISettingsService lidarrSettings) : base(identity, service, r, um, mem, s, sub) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 4b46e8151..c3828eae3 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -32,7 +32,7 @@ namespace Ombi.Core.Engine { public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine { - public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, + public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, ICurrentUser user, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, ILogger logger, ITvSender sender, IRepository rl, ISettingsService settings, ICacheService cache, IRepository sub, IMediaCacheService mediaCacheService) : base(user, requestService, rule, manager, cache, settings, sub) diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 518b97720..2747f262a 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -23,6 +23,7 @@ using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using System.Threading; using TraktSharp.Entities; +using Ombi.Core.Helpers; namespace Ombi.Core.Engine { @@ -32,7 +33,7 @@ namespace Ombi.Core.Engine private readonly IImageService _imageService; private readonly IMovieDbApi _theMovieDbApi; - public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, + public TvSearchEngine(ICurrentUser identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ISettingsService customizationSettings, ICacheService memCache, ISettingsService s, IRepository sub, IImageService imageService, IMovieDbApi theMovieDbApi) diff --git a/src/Ombi.Core/Engine/V2/CalendarEngine.cs b/src/Ombi.Core/Engine/V2/CalendarEngine.cs index b7ecc8ec6..8347141bb 100644 --- a/src/Ombi.Core/Engine/V2/CalendarEngine.cs +++ b/src/Ombi.Core/Engine/V2/CalendarEngine.cs @@ -5,6 +5,7 @@ using System.Security.Principal; using System.Threading.Tasks; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Helpers; using Ombi.Core.Models.Search.V2; using Ombi.Core.Rule.Interfaces; using Ombi.Store.Entities; @@ -17,7 +18,7 @@ namespace Ombi.Core.Engine.V2 { public DateTime DaysAgo => DateTime.Now.AddDays(-90); public DateTime DaysAhead => DateTime.Now.AddDays(90); - public CalendarEngine(IPrincipal user, OmbiUserManager um, IRuleEvaluator rules, IMovieRequestRepository movieRepo, + public CalendarEngine(ICurrentUser user, OmbiUserManager um, IRuleEvaluator rules, IMovieRequestRepository movieRepo, ITvRequestRepository tvRequestRepo) : base(user, um, rules) { _movieRepo = movieRepo; diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index d160435be..8c612b698 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -5,6 +5,7 @@ using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Core.Models.Search.V2; @@ -28,7 +29,7 @@ namespace Ombi.Core.Engine.V2 { public class MovieSearchEngineV2 : BaseMediaEngine, IMovieEngineV2 { - public MovieSearchEngineV2(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, + public MovieSearchEngineV2(ICurrentUser identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub, ISettingsService customizationSettings, IMovieRequestEngine movieRequestEngine, IHttpClientFactory httpClientFactory) : base(identity, service, r, um, mem, s, sub) diff --git a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs index 44f792415..362a79e43 100644 --- a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs +++ b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs @@ -7,6 +7,7 @@ using Ombi.Api.MusicBrainz; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search.V2; using Ombi.Core.Rule.Interfaces; @@ -25,7 +26,7 @@ namespace Ombi.Core.Engine.V2 { public class MultiSearchEngine : BaseMediaEngine, IMultiSearchEngine { - public MultiSearchEngine(IPrincipal identity, IRequestServiceMain requestService, IRuleEvaluator rules, + public MultiSearchEngine(ICurrentUser identity, IRequestServiceMain requestService, IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService ombiSettings, IRepository sub, IMovieDbApi movieDbApi, ISettingsService lidarrSettings, IMusicBrainzApi musicApi) : base(identity, requestService, rules, um, cache, ombiSettings, sub) diff --git a/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs index b7d9575db..fc3fcf40e 100644 --- a/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MusicSearchEngineV2.cs @@ -11,6 +11,7 @@ using Ombi.Api.Lidarr.Models; using Ombi.Api.MusicBrainz; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search.V2.Music; using Ombi.Core.Rule.Interfaces; @@ -31,7 +32,7 @@ namespace Ombi.Core.Engine.V2 private readonly ISettingsService _lidarrSettings; private readonly ILidarrApi _lidarrApi; - public MusicSearchEngineV2(IPrincipal identity, IRequestServiceMain requestService, IRuleEvaluator rules, + public MusicSearchEngineV2(ICurrentUser identity, IRequestServiceMain requestService, IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService ombiSettings, IRepository sub, IMusicBrainzApi musicBrainzApi, ISettingsService lidarrSettings, ILidarrApi lidarrApi) diff --git a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs index 07f8ce981..46cd44bf1 100644 --- a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs @@ -25,6 +25,7 @@ using Ombi.Api.TheMovieDb.Models; using System.Diagnostics; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Models.UI; +using Ombi.Core.Helpers; namespace Ombi.Core.Engine.V2 { @@ -37,7 +38,7 @@ namespace Ombi.Core.Engine.V2 private readonly ISettingsService _customization; private readonly ITvRequestEngine _requestEngine; - public TvSearchEngineV2(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, + public TvSearchEngineV2(ICurrentUser identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, ISettingsService s, IRepository sub, IMovieDbApi movieApi, ISettingsService customization, ITvRequestEngine requestEngine) : base(identity, service, r, um, memCache, s, sub) diff --git a/src/Ombi.Core/Engine/VoteEngine.cs b/src/Ombi.Core/Engine/VoteEngine.cs index a63ba1604..3e7f6f542 100644 --- a/src/Ombi.Core/Engine/VoteEngine.cs +++ b/src/Ombi.Core/Engine/VoteEngine.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Helpers; using Ombi.Core.Models; using Ombi.Core.Models.UI; using Ombi.Core.Rule.Interfaces; @@ -20,7 +21,7 @@ namespace Ombi.Core.Engine { public class VoteEngine : BaseEngine, IVoteEngine { - public VoteEngine(IRepository votes, IPrincipal user, OmbiUserManager um, IRuleEvaluator r, ISettingsService voteSettings, + public VoteEngine(IRepository votes, ICurrentUser user, OmbiUserManager um, IRuleEvaluator r, ISettingsService voteSettings, IMusicRequestEngine musicRequestEngine, ITvRequestEngine tvRequestEngine, IMovieRequestEngine movieRequestEngine) : base(user, um, r) { _voteRepository = votes; diff --git a/src/Ombi.Core/Helpers/CurrentUser.cs b/src/Ombi.Core/Helpers/CurrentUser.cs new file mode 100644 index 000000000..d69d0a56d --- /dev/null +++ b/src/Ombi.Core/Helpers/CurrentUser.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore; +using Ombi.Core.Authentication; +using Ombi.Helpers; +using Ombi.Store.Entities; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Ombi.Core.Helpers +{ + public class CurrentUser : ICurrentUser + { + private readonly IPrincipal _principle; + private readonly OmbiUserManager _userManager; + private OmbiUser _user; + public IIdentity Identity { get; set; } + + public CurrentUser(IPrincipal principle, OmbiUserManager userManager) + { + _principle = principle; + _userManager = userManager; + Identity = _principle?.Identity; + } + + public void SetUser(OmbiUser user) + { + _user = user; + } + + public string Username => Identity.Name; + public async Task GetUser() + { + if (!Username.HasValue() && _user == null) + { + return null; + } + + if (_user != null) + { + return _user; + } + + var username = Username.ToUpper(); + return _user ??= await _userManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username); + } + + } +} diff --git a/src/Ombi.Core/Helpers/ICurrentUser.cs b/src/Ombi.Core/Helpers/ICurrentUser.cs new file mode 100644 index 000000000..1cce973f3 --- /dev/null +++ b/src/Ombi.Core/Helpers/ICurrentUser.cs @@ -0,0 +1,15 @@ +using Ombi.Store.Entities; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Ombi.Core.Helpers +{ + public interface ICurrentUser + { + string Username { get; } + + Task GetUser(); + void SetUser(OmbiUser user); + IIdentity Identity { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs index 39a5b6c2a..18125ed71 100644 --- a/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs +++ b/src/Ombi.Core/Models/Requests/MovieRequestViewModel.cs @@ -1,31 +1,5 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2018 Jamie Rees -// File: MovieRequestViewModel.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using Newtonsoft.Json; +using Newtonsoft.Json; +using Ombi.Store.Entities.Requests; namespace Ombi.Core.Models.Requests { @@ -41,5 +15,11 @@ namespace Ombi.Core.Models.Requests /// [JsonIgnore] public string RequestedByAlias { get; set; } + + /// + /// Only set via list imports + /// + [JsonIgnore] + public RequestSource Source { get; set; } = RequestSource.Ombi; } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs index 92c1b11a7..23d26625b 100644 --- a/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/AutoApproveRule.cs @@ -3,6 +3,7 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Models.Requests; using Ombi.Core.Rule.Interfaces; using Ombi.Core.Services; @@ -15,20 +16,21 @@ namespace Ombi.Core.Rule.Rules.Request { public class AutoApproveRule : BaseRequestRule, IRules { - public AutoApproveRule(IPrincipal principal, OmbiUserManager um, IFeatureService featureService) + public AutoApproveRule(ICurrentUser principal, OmbiUserManager um, IFeatureService featureService) { User = principal; _manager = um; _featureService = featureService; } - private IPrincipal User { get; } + private ICurrentUser User { get; } private readonly OmbiUserManager _manager; private readonly IFeatureService _featureService; public async Task Execute(BaseRequest obj) { - var username = User.Identity.Name.ToUpper(); + var currentUser = await User.GetUser(); + var username = currentUser.UserName.ToUpper(); var user = await _manager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username); if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser) { diff --git a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs index 2546e1f29..e2e81e612 100644 --- a/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/CanRequestRule.cs @@ -10,23 +10,25 @@ using Ombi.Core.Engine; using Ombi.Core.Rule.Interfaces; using Ombi.Helpers; using Ombi.Store.Entities.Requests; +using Ombi.Core.Helpers; namespace Ombi.Core.Rule.Rules.Request { public class CanRequestRule : BaseRequestRule, IRules { - public CanRequestRule(IPrincipal principal, OmbiUserManager manager) + public CanRequestRule(ICurrentUser principal, OmbiUserManager manager) { User = principal; _manager = manager; } - private IPrincipal User { get; } + private ICurrentUser User { get; } private readonly OmbiUserManager _manager; public async Task Execute(BaseRequest obj) { - var username = User.Identity.Name.ToUpper(); + var currentUser = await User.GetUser(); + var username = currentUser.UserName.ToUpper(); var user = await _manager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username); if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin) || user.IsSystemUser) return Success(); diff --git a/src/Ombi.Core/Services/RequestLimitService.cs b/src/Ombi.Core/Services/RequestLimitService.cs index 83a87c7e7..d90e0d77c 100644 --- a/src/Ombi.Core/Services/RequestLimitService.cs +++ b/src/Ombi.Core/Services/RequestLimitService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Ombi.Core.Authentication; +using Ombi.Core.Helpers; using Ombi.Core.Models; using Ombi.Helpers; using Ombi.Store.Entities; @@ -20,11 +21,11 @@ namespace Ombi.Core.Services } public class RequestLimitService : IRequestLimitService { - private readonly IPrincipal _user; + private readonly ICurrentUser _user; private readonly OmbiUserManager _userManager; private readonly IRepository _requestLog; - public RequestLimitService(IPrincipal user, OmbiUserManager userManager, IRepository rl) + public RequestLimitService(ICurrentUser user, OmbiUserManager userManager, IRepository rl) { _user = user; _userManager = userManager; @@ -141,7 +142,8 @@ namespace Ombi.Core.Services private async Task GetUser() { - var username = _user.Identity.Name.ToUpper(); + var currentUser = await _user.GetUser(); + var username = currentUser.UserName.ToUpper(); return await _userManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username); } diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index d22990f39..691aca237 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -70,6 +70,7 @@ using Ombi.Api.RottenTomatoes; using System.Net.Http; using Microsoft.Extensions.Logging; using Ombi.Core.Services; +using Ombi.Core.Helpers; namespace Ombi.DependencyInjection { @@ -124,6 +125,8 @@ namespace Ombi.DependencyInjection var runtimeVersion = AssemblyHelper.GetRuntimeVersion(); services.AddSingleton(); services.AddScoped(sp => sp.GetService().HttpContext.User); + // HttpContext User is null for background jobs + services.AddScoped(sp => new CurrentUser(sp.GetService()?.HttpContext?.User ?? null, sp.GetService())); services.AddHttpClient("OmbiClient", client => { client.DefaultRequestHeaders.Add("User-Agent", $"Ombi/{runtimeVersion} (https://ombi.io/)"); diff --git a/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs b/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs index e0c6ef589..7f3e0ccbd 100644 --- a/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs +++ b/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs @@ -3,18 +3,16 @@ using Moq.AutoMock; using NUnit.Framework; using Ombi.Api.Plex; using Ombi.Api.Plex.Models; -using Ombi.Core.Authentication; +using Ombi.Core.Engine; +using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models.Requests; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Schedule.Jobs.Plex; using Ombi.Store.Entities; using Ombi.Test.Common; using Quartz; -using System; using System.Collections.Generic; -using System.Linq; -using System.Security.Principal; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -27,11 +25,11 @@ namespace Ombi.Schedule.Tests private PlexWatchlistImport _subject; private AutoMocker _mocker; private Mock _context; - + [SetUp] public void Setup() { - _mocker = new AutoMocker(); + _mocker = new AutoMocker(); var um = MockHelper.MockUserManager(new List { new OmbiUser { Id = "abc", UserType = UserType.PlexUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" } }); _mocker.Use(um); _context = _mocker.GetMock(); @@ -42,16 +40,194 @@ namespace Ombi.Schedule.Tests [Test] public async Task TerminatesWhenPlexIsNotEnabled() { - _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = false }); + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = false, EnableWatchlistImport = true }); await _subject.Execute(null); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + _mocker.Verify(x => x.GetWatchlist(It.IsAny(), It.IsAny()), Times.Never); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); } - + [Test] + public async Task TerminatesWhenWatchlistIsNotEnabled() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = false }); + await _subject.Execute(null); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + _mocker.Verify(x => x.GetWatchlist(It.IsAny(), It.IsAny()), Times.Never); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + } + [Test] public async Task EmptyWatchList() { - _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true }); - _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlist()); + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); + _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlistContainer()); + await _subject.Execute(_context.Object); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + _mocker.Verify(x => x.GetWatchlist(It.IsAny(), It.IsAny()), Times.Once); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + } + + [Test] + public async Task NoPlexUsersWithToken() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); + var um = MockHelper.MockUserManager(new List + { + new OmbiUser { Id = "abc", UserType = UserType.EmbyUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" }, + new OmbiUser { Id = "abc", UserType = UserType.LocalUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" }, + new OmbiUser { Id = "abc", UserType = UserType.SystemUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" }, + new OmbiUser { Id = "abc", UserType = UserType.JellyfinUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" }, + new OmbiUser { Id = "abc", UserType = UserType.EmbyConnectUser, MediaServerToken = "abc", UserName = "abc", NormalizedUserName = "ABC" }, + new OmbiUser { Id = "abc", UserType = UserType.PlexUser, UserName = "abc", NormalizedUserName = "ABC" }, + }); + _mocker.Use(um); + _subject = _mocker.CreateInstance(); + + await _subject.Execute(_context.Object); + _mocker.Verify(x => x.GetWatchlist(It.IsAny(), It.IsAny()), Times.Never); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + } + + + [Test] + public async Task MovieRequestFromWatchList_NoGuid() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); + _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlistContainer + { + MediaContainer = new PlexWatchlist + { + Metadata = new List + { + new Metadata + { + type = "movie", + ratingKey = "abc" + } + } + } + }); + _mocker.Setup>(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny())) + .ReturnsAsync(new PlexWatchlistMetadataContainer + { + MediaContainer = new PlexWatchlistMetadata + { + Metadata = new WatchlistMetadata[] + { + new WatchlistMetadata + { + Guid = new List + { + new PlexGuids + { + Id = "tmdb://123" + } + } + } + } + + } + }); + _mocker.Setup>(x => x.RequestMovie(It.IsAny())) + .ReturnsAsync(new RequestEngineResult { RequestId = 1 }); + await _subject.Execute(_context.Object); + _mocker.Verify(x => x.RequestMovie(It.Is(x => x.TheMovieDbId == 123)), Times.Once); + _mocker.Verify(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny()), Times.Once); + _mocker.Verify(x => x.SetUser(It.Is(x => x.Id == "abc")), Times.Once); + } + + [Test] + public async Task MovieRequestFromWatchList_AlreadyRequested() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); + _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlistContainer + { + MediaContainer = new PlexWatchlist + { + Metadata = new List + { + new Metadata + { + type = "movie", + ratingKey = "abc" + } + } + } + }); + _mocker.Setup>(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny())) + .ReturnsAsync(new PlexWatchlistMetadataContainer + { + MediaContainer = new PlexWatchlistMetadata + { + Metadata = new WatchlistMetadata[] + { + new WatchlistMetadata + { + Guid = new List + { + new PlexGuids + { + Id = "tmdb://123" + } + } + } + } + + } + }); + _mocker.Setup>(x => x.RequestMovie(It.IsAny())) + .ReturnsAsync(new RequestEngineResult { ErrorCode = ErrorCode.AlreadyRequested, ErrorMessage = "Requested" }); + await _subject.Execute(_context.Object); + _mocker.Verify(x => x.RequestMovie(It.Is(x => x.TheMovieDbId == 123)), Times.Once); + _mocker.Verify(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny()), Times.Once); + _mocker.Verify(x => x.SetUser(It.Is(x => x.Id == "abc")), Times.Once); + } + + [Test] + public async Task MovieRequestFromWatchList_NoTmdbGuid() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); + _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlistContainer + { + MediaContainer = new PlexWatchlist + { + Metadata = new List + { + new Metadata + { + type = "movie", + ratingKey = "abc" + } + } + } + }); + _mocker.Setup>(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny())) + .ReturnsAsync(new PlexWatchlistMetadataContainer + { + MediaContainer = new PlexWatchlistMetadata + { + Metadata = new WatchlistMetadata[] + { + new WatchlistMetadata + { + Guid = new List + { + new PlexGuids + { + Id = "imdb://123" + } + } + } + } + + } + }); + _mocker.Setup>(x => x.RequestMovie(It.IsAny())) + .ReturnsAsync(new RequestEngineResult { RequestId = 1 }); await _subject.Execute(_context.Object); + _mocker.Verify(x => x.RequestMovie(It.IsAny()), Times.Never); + _mocker.Verify(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny()), Times.Once); + _mocker.Verify(x => x.SetUser(It.Is(x => x.Id == "abc")), Times.Never); } } } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs b/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs index 4fefdf2aa..b07e95da0 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs @@ -1,17 +1,17 @@ -using Ombi.Api.Plex; +using Microsoft.Extensions.Logging; +using Ombi.Api.Plex; using Ombi.Api.Plex.Models; using Ombi.Core.Authentication; +using Ombi.Core.Engine; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Store.Entities; -using Ombi.Store.Repository.Requests; +using Ombi.Store.Entities.Requests; using Quartz; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -22,34 +22,32 @@ namespace Ombi.Schedule.Jobs.Plex private readonly IPlexApi _plexApi; private readonly ISettingsService _settings; private readonly OmbiUserManager _ombiUserManager; - private readonly IMovieRequestRepository _movieRequestRepository; - private readonly ITvRequestRepository _tvRequestRepository; private readonly IMovieRequestEngine _movieRequestEngine; + private readonly ILogger _logger; public PlexWatchlistImport(IPlexApi plexApi, ISettingsService settings, OmbiUserManager ombiUserManager, - IMovieRequestRepository movieRequestRepository, ITvRequestRepository tvRequestRepository, IMovieRequestEngine movieRequestEngine) + IMovieRequestEngine movieRequestEngine, + ILogger logger) { _plexApi = plexApi; _settings = settings; _ombiUserManager = ombiUserManager; - _movieRequestRepository = movieRequestRepository; - _tvRequestRepository = tvRequestRepository; _movieRequestEngine = movieRequestEngine; + _logger = logger; } public async Task Execute(IJobExecutionContext context) { - var settings = await _settings.GetSettingsAsync(); - if (!settings.Enable) + if (!settings.Enable || !settings.EnableWatchlistImport) { return; } var plexUsersWithTokens = _ombiUserManager.Users.Where(x => x.UserType == UserType.PlexUser && x.MediaServerToken != null).ToList(); - //foreach (var user in plexUsersWithTokens) - //{ - var watchlist = await _plexApi.GetWatchlist(token, context?.CancellationToken ?? CancellationToken.None); + foreach (var user in plexUsersWithTokens) + { + var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None); if (watchlist == null || !(watchlist.MediaContainer?.Metadata?.Any() ?? false)) { return; @@ -64,27 +62,38 @@ namespace Ombi.Schedule.Jobs.Plex await ProcessShow(item); break; case "movie": - await ProcessMovie(token, item, null, context?.CancellationToken ?? CancellationToken.None); + await ProcessMovie(user.MediaServerToken, item, user, context?.CancellationToken ?? CancellationToken.None); break; } } - - - //} + } } - private async Task ProcessMovie(string authToken, Metadata movie, PlexServers servers, CancellationToken cancellationToken) + private async Task ProcessMovie(string authToken, Metadata movie, OmbiUser user, CancellationToken cancellationToken) { - var providerIds = await GetProviderIds(authToken, movie, servers, cancellationToken); + var providerIds = await GetProviderIds(authToken, movie, cancellationToken); if (!providerIds.TheMovieDb.HasValue()) { // We need a MovieDbId to support this; return; } - //_movieRequestEngine.RequestMovie(new() { TheMovieDbId = }); + _movieRequestEngine.SetUser(user); + var response = await _movieRequestEngine.RequestMovie(new() { TheMovieDbId = int.Parse(providerIds.TheMovieDb), Source = RequestSource.PlexWatchlist}); + if (response.IsError) + { + if (response.ErrorCode == ErrorCode.AlreadyRequested) + { + return; + } + _logger.LogInformation($"Error adding title from PlexWatchlist for user '{user.UserName}'. Message: '{response.ErrorMessage}'"); + } + else + { + _logger.LogInformation($"Added title from PlexWatchlist for user '{user.UserName}'. {response.Message}"); + } } - private async Task GetProviderIds(string authToken, Metadata movie, PlexServers servers, CancellationToken cancellationToken) + private async Task GetProviderIds(string authToken, Metadata movie, CancellationToken cancellationToken) { var guids = new List(); if (!movie.Guid.Any()) diff --git a/src/Ombi.Schedule/OmbiScheduler.cs b/src/Ombi.Schedule/OmbiScheduler.cs index 8e45be57c..41602f641 100644 --- a/src/Ombi.Schedule/OmbiScheduler.cs +++ b/src/Ombi.Schedule/OmbiScheduler.cs @@ -91,6 +91,7 @@ namespace Ombi.Schedule await OmbiQuartz.Instance.AddJob(nameof(IPlexUserImporter), "Plex", JobSettingsHelper.UserImporter(s)); await OmbiQuartz.Instance.AddJob(nameof(IPlexEpisodeSync), "Plex", null); await OmbiQuartz.Instance.AddJob(nameof(IPlexAvailabilityChecker), "Plex", null); + await OmbiQuartz.Instance.AddJob(nameof(IPlexWatchlistImport), "Plex", JobSettingsHelper.PlexWatchlistImport(s)); } private static async Task AddEmby(JobSettings s) diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 6cf021f7c..5917e2592 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -7,6 +7,7 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } + public bool EnableWatchlistImport { get; set; } /// /// This is the ClientId for OAuth /// diff --git a/src/Ombi.Settings/Settings/Models/JobSettings.cs b/src/Ombi.Settings/Settings/Models/JobSettings.cs index ac7ab3bdc..cc4ed2e23 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettings.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettings.cs @@ -19,5 +19,6 @@ public string RetryRequests { get; set; } public string MediaDatabaseRefresh { get; set; } public string AutoDeleteRequests { get; set; } + public string PlexWatchlistImport { get; set; } } } diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index e580e1977..b0839cce6 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -54,6 +54,11 @@ namespace Ombi.Settings.Settings.Models { return ValidateCron(Get(s.UserImporter, Cron.Daily())); } + + public static string PlexWatchlistImport(JobSettings s) + { + return ValidateCron(Get(s.PlexWatchlistImport, Cron.Daily())); + } public static string Newsletter(JobSettings s) { diff --git a/src/Ombi.Store/Entities/Requests/BaseRequest.cs b/src/Ombi.Store/Entities/Requests/BaseRequest.cs index f26bcc8ba..9c564376b 100644 --- a/src/Ombi.Store/Entities/Requests/BaseRequest.cs +++ b/src/Ombi.Store/Entities/Requests/BaseRequest.cs @@ -22,6 +22,8 @@ namespace Ombi.Store.Entities.Requests [ForeignKey(nameof(RequestedUserId))] public OmbiUser RequestedUser { get; set; } + public RequestSource Source { get; set; } = RequestSource.Ombi; + [NotMapped] public virtual bool CanApprove => !Approved && !Available; diff --git a/src/Ombi.Store/Entities/Requests/RequestSource.cs b/src/Ombi.Store/Entities/Requests/RequestSource.cs new file mode 100644 index 000000000..44ccc20d8 --- /dev/null +++ b/src/Ombi.Store/Entities/Requests/RequestSource.cs @@ -0,0 +1,8 @@ +namespace Ombi.Store.Entities.Requests +{ + public enum RequestSource + { + Ombi = 0, + PlexWatchlist = 1 + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220407072656_RequestSource.Designer.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220407072656_RequestSource.Designer.cs new file mode 100644 index 000000000..32f92ff6c --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220407072656_RequestSource.Designer.cs @@ -0,0 +1,1283 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context.Sqlite; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + [DbContext(typeof(OmbiSqliteContext))] + [Migration("20220407072656_RequestSource")] + partial class RequestSource + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuditArea") + .HasColumnType("INTEGER"); + + b.Property("AuditType") + .HasColumnType("INTEGER"); + + b.Property("DateTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("User") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("MobileDevices"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("Message") + .HasColumnType("TEXT"); + + b.Property("NotificationType") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("Alias") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("EpisodeRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LastLoggedIn") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MediaServerToken") + .HasColumnType("TEXT"); + + b.Property("MovieRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MovieRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimit") + .HasColumnType("INTEGER"); + + b.Property("MusicRequestLimitType") + .HasColumnType("INTEGER"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("ProviderUserId") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("StreamingCountry") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserAccessToken") + .HasColumnType("TEXT"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UserType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddedAt") + .HasColumnType("TEXT"); + + b.Property("AlbumId") + .HasColumnType("TEXT"); + + b.Property("ContentId") + .HasColumnType("INTEGER"); + + b.Property("ContentType") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestQueue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Completed") + .HasColumnType("TEXT"); + + b.Property("Dts") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RetryCount") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RequestQueue"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("ArtistName") + .HasColumnType("TEXT"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Cover") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("Disk") + .HasColumnType("TEXT"); + + b.Property("ForeignAlbumId") + .HasColumnType("TEXT"); + + b.Property("ForeignArtistId") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("ParentRequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("SeriesType") + .HasColumnType("INTEGER"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("IssuesId") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IssueCategoryId") + .HasColumnType("INTEGER"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("ProviderId") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("ResovledDate") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("UserReportedId") + .HasColumnType("TEXT"); + + 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() + .HasColumnType("INTEGER"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Approved4K") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("Available4K") + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("Denied") + .HasColumnType("INTEGER"); + + b.Property("Denied4K") + .HasColumnType("INTEGER"); + + b.Property("DeniedReason") + .HasColumnType("TEXT"); + + b.Property("DeniedReason4K") + .HasColumnType("TEXT"); + + b.Property("DigitalReleaseDate") + .HasColumnType("TEXT"); + + b.Property("Has4KRequest") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("IssueId") + .HasColumnType("INTEGER"); + + b.Property("LangCode") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved") + .HasColumnType("TEXT"); + + b.Property("MarkedAsApproved4K") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable") + .HasColumnType("TEXT"); + + b.Property("MarkedAsAvailable4K") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied") + .HasColumnType("TEXT"); + + b.Property("MarkedAsDenied4K") + .HasColumnType("TEXT"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("RequestedByAlias") + .HasColumnType("TEXT"); + + b.Property("RequestedDate") + .HasColumnType("TEXT"); + + b.Property("RequestedDate4k") + .HasColumnType("TEXT"); + + b.Property("RequestedUserId") + .HasColumnType("TEXT"); + + b.Property("RootPathOverride") + .HasColumnType("INTEGER"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("TheMovieDbId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EpisodeCount") + .HasColumnType("INTEGER"); + + b.Property("RequestDate") + .HasColumnType("TEXT"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Background") + .HasColumnType("TEXT"); + + b.Property("ExternalProviderId") + .HasColumnType("INTEGER"); + + b.Property("ImdbId") + .HasColumnType("TEXT"); + + b.Property("LanguageProfile") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("PosterPath") + .HasColumnType("TEXT"); + + b.Property("QualityOverride") + .HasColumnType("INTEGER"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("RootFolder") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSeasons") + .HasColumnType("INTEGER"); + + b.Property("TvDbId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Token") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Agent") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RadarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("RadarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfile") + .HasColumnType("INTEGER"); + + b.Property("SonarrQualityProfileAnime") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPath") + .HasColumnType("INTEGER"); + + b.Property("SonarrRootPathAnime") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserQualityProfiles"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("Deleted") + .HasColumnType("INTEGER"); + + b.Property("RequestId") + .HasColumnType("INTEGER"); + + b.Property("RequestType") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("VoteType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Votes"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AirDate") + .HasColumnType("TEXT"); + + b.Property("Approved") + .HasColumnType("INTEGER"); + + b.Property("Available") + .HasColumnType("INTEGER"); + + b.Property("EpisodeNumber") + .HasColumnType("INTEGER"); + + b.Property("Requested") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChildRequestId") + .HasColumnType("INTEGER"); + + b.Property("Overview") + .HasColumnType("TEXT"); + + b.Property("SeasonNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Ombi.Store.Entities.MobileDevices", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("ParentRequest"); + + b.Navigation("RequestedUser"); + }); + + 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"); + + b.Navigation("Issues"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests", null) + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + + b.Navigation("IssueCategory"); + + b.Navigation("UserReported"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + + b.Navigation("RequestedUser"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserQualityProfiles", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Votes", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ChildRequest"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Navigation("NotificationUserIds"); + + b.Navigation("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Navigation("Issues"); + + b.Navigation("SeasonRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Navigation("Comments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Navigation("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Navigation("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Navigation("Episodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/20220407072656_RequestSource.cs b/src/Ombi.Store/Migrations/OmbiSqlite/20220407072656_RequestSource.cs new file mode 100644 index 000000000..c1a2400d0 --- /dev/null +++ b/src/Ombi.Store/Migrations/OmbiSqlite/20220407072656_RequestSource.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Ombi.Store.Migrations.OmbiSqlite +{ + public partial class RequestSource : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Source", + table: "MovieRequests", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "Source", + table: "ChildRequests", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "Source", + table: "AlbumRequests", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Source", + table: "MovieRequests"); + + migrationBuilder.DropColumn( + name: "Source", + table: "ChildRequests"); + + migrationBuilder.DropColumn( + name: "Source", + table: "AlbumRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs index 8062087df..f9ce86bae 100644 --- a/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiSqlite/OmbiSqliteContextModelSnapshot.cs @@ -469,6 +469,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("RequestedUserId") .HasColumnType("TEXT"); + b.Property("Source") + .HasColumnType("INTEGER"); + b.Property("Title") .HasColumnType("TEXT"); @@ -527,6 +530,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("SeriesType") .HasColumnType("INTEGER"); + b.Property("Source") + .HasColumnType("INTEGER"); + b.Property("Title") .HasColumnType("TEXT"); @@ -729,6 +735,9 @@ namespace Ombi.Store.Migrations.OmbiSqlite b.Property("RootPathOverride") .HasColumnType("INTEGER"); + b.Property("Source") + .HasColumnType("INTEGER"); + b.Property("Status") .HasColumnType("TEXT"); diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index 1dc94f261..95b1fba00 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -112,6 +112,7 @@ export interface IPublicInfo { export interface IPlexSettings extends ISettings { enable: boolean; + enableWatchlistImport: boolean; servers: IPlexServer[]; } @@ -219,6 +220,7 @@ export interface IJobSettings { mediaDatabaseRefresh: string; autoDeleteRequests: string; embyRecentlyAddedSync: string; + plexWatchlistImport: string; } export interface IIssueSettings extends ISettings { diff --git a/src/Ombi/ClientApp/src/app/services/job.service.ts b/src/Ombi/ClientApp/src/app/services/job.service.ts index 8bd08c4f5..6cece68d9 100644 --- a/src/Ombi/ClientApp/src/app/services/job.service.ts +++ b/src/Ombi/ClientApp/src/app/services/job.service.ts @@ -27,6 +27,10 @@ export class JobService extends ServiceHelpers { return this.http.post(`${this.url}plexUserImporter/`, {headers: this.headers}); } + public runPlexWatchlistImport(): Observable { + return this.http.post(`${this.url}plexwatchlist/`, {headers: this.headers}); + } + public runEmbyImporter(): Observable { return this.http.post(`${this.url}embyUserImporter/`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.html index e15a88b7f..47e7ef1cd 100644 --- a/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.html @@ -77,6 +77,13 @@ The Plex Sync is required +
+ + Plex Watchlist Import + + The Plex Watchlist Import is required + +
diff --git a/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.ts index 47d05776b..89131bc42 100644 --- a/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/jobs/jobs.component.ts @@ -37,6 +37,7 @@ export class JobsComponent implements OnInit { mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required], autoDeleteRequests: [x.autoDeleteRequests, Validators.required], embyRecentlyAddedSync: [x.embyRecentlyAddedSync, Validators.required], + plexWatchlistImport: [x.plexWatchlistImport, Validators.required], }); }); } diff --git a/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html b/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html index f84395043..858a94358 100644 --- a/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html +++ b/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html @@ -9,6 +9,12 @@ Enable
+
+ Enable User Watchlist Requests + +

When a Plex User adds something to their watchlist in Plex, it will turn up in Ombi as a Request if enabled. This only applies to users that are logging in with their Plex Account

+

Request limits if set are all still applied etc.

+
Advanced
@@ -183,6 +189,12 @@ Clear Data And Resync +
+ +
diff --git a/src/Ombi/ClientApp/src/app/settings/plex/plex.component.ts b/src/Ombi/ClientApp/src/app/settings/plex/plex.component.ts index 8d42419fe..d7c977b7b 100644 --- a/src/Ombi/ClientApp/src/app/settings/plex/plex.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/plex/plex.component.ts @@ -172,6 +172,14 @@ export class PlexComponent implements OnInit, OnDestroy { }); } + public runWatchlistImport(): void { + this.jobService.runPlexWatchlistImport().subscribe(x => { + if (x) { + this.notificationService.success("Triggered the Watchlist Import"); + } + }); + } + public ngOnDestroy() { this.subscriptions.next(); this.subscriptions.complete(); diff --git a/src/Ombi/Controllers/V1/JobController.cs b/src/Ombi/Controllers/V1/JobController.cs index 442866762..57ec060a2 100644 --- a/src/Ombi/Controllers/V1/JobController.cs +++ b/src/Ombi/Controllers/V1/JobController.cs @@ -91,6 +91,17 @@ namespace Ombi.Controllers.V1 return true; } + /// + /// Runs the Plex Watchlist Importer + /// + /// + [HttpPost("plexwatchlist")] + public async Task PlexWatchlistImport() + { + await OmbiQuartz.TriggerJob(nameof(IPlexWatchlistImport), "Plex"); + return true; + } + /// /// Runs the Emby User importer /// diff --git a/src/Ombi/Controllers/V1/SettingsController.cs b/src/Ombi/Controllers/V1/SettingsController.cs index ff4dea913..0892b1dda 100644 --- a/src/Ombi/Controllers/V1/SettingsController.cs +++ b/src/Ombi/Controllers/V1/SettingsController.cs @@ -626,6 +626,7 @@ namespace Ombi.Controllers.V1 j.MediaDatabaseRefresh = j.MediaDatabaseRefresh.HasValue() ? j.MediaDatabaseRefresh : JobSettingsHelper.MediaDatabaseRefresh(j); j.AutoDeleteRequests = j.AutoDeleteRequests.HasValue() ? j.AutoDeleteRequests : JobSettingsHelper.AutoDeleteRequests(j); j.EmbyRecentlyAddedSync = j.EmbyRecentlyAddedSync.HasValue() ? j.EmbyRecentlyAddedSync : JobSettingsHelper.EmbyRecentlyAddedSync(j); + j.PlexWatchlistImport = j.PlexWatchlistImport.HasValue() ? j.PlexWatchlistImport : JobSettingsHelper.PlexWatchlistImport(j); return j; } diff --git a/src/Ombi/Controllers/V1/TokenController.cs b/src/Ombi/Controllers/V1/TokenController.cs index bd0d7da52..588c4f606 100644 --- a/src/Ombi/Controllers/V1/TokenController.cs +++ b/src/Ombi/Controllers/V1/TokenController.cs @@ -6,19 +6,16 @@ using System.Security.Claims; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using Ombi.Core.Authentication; using Ombi.Helpers; using Ombi.Models; using Ombi.Models.External; -using Ombi.Models.Identity; using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; -using Ombi.Schedule.Jobs.Plex; namespace Ombi.Controllers.V1 { @@ -27,26 +24,21 @@ namespace Ombi.Controllers.V1 [ApiController] public class TokenController : ControllerBase { - public TokenController(OmbiUserManager um, IOptions ta, ITokenRepository token, - IPlexOAuthManager oAuthManager, ILogger logger, ISettingsService auth, - IPlexWatchlistImport import) + public TokenController(OmbiUserManager um, ITokenRepository token, + IPlexOAuthManager oAuthManager, ILogger logger, ISettingsService auth) { _userManager = um; - _tokenAuthenticationOptions = ta.Value; _token = token; _plexOAuthManager = oAuthManager; _log = logger; _authSettings = auth; - _import = import; } - private readonly TokenAuthentication _tokenAuthenticationOptions; private readonly ITokenRepository _token; private readonly OmbiUserManager _userManager; private readonly IPlexOAuthManager _plexOAuthManager; private readonly ILogger _log; private readonly ISettingsService _authSettings; - private readonly IPlexWatchlistImport _import; /// /// Gets the token. @@ -57,7 +49,6 @@ namespace Ombi.Controllers.V1 [ProducesResponseType(401)] public async Task GetToken([FromBody] UserAuthModel model) { - await _import.Execute(null); if (!model.UsePlexOAuth) { var user = await _userManager.FindByNameAsync(model.Username); @@ -122,6 +113,7 @@ namespace Ombi.Controllers.V1 { return Unauthorized(); } + return await CreateToken(true, user); }