From 068f75f5141280819b32c45ced82bf11617a4b5e Mon Sep 17 00:00:00 2001 From: tidusjar Date: Fri, 19 Aug 2016 11:22:17 +0100 Subject: [PATCH 01/19] User work --- PlexRequests.Core/UserMapper.cs | 13 ++++++ PlexRequests.Helpers.Tests/TypeHelperTests.cs | 10 ++++- PlexRequests.Helpers/TypeHelper.cs | 16 +++++++ .../userManagementController.js | 44 ++++++++++++++++--- .../userManagement/userManagementService.js | 18 +++++--- .../Modules/UserManagementModule.cs | 22 ++++++++-- .../Views/UserManagement/Index.cshtml | 13 +++++- 7 files changed, 118 insertions(+), 18 deletions(-) diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs index 5be4a868a..d4eca3e2a 100644 --- a/PlexRequests.Core/UserMapper.cs +++ b/PlexRequests.Core/UserMapper.cs @@ -134,6 +134,17 @@ public bool DoUsersExist() return CreateUser(username, password, new[] { UserClaims.User }, properties); } + public Guid? CreateUser(string username, string password, string[] claims) + { + return CreateUser(username, password, claims, null); + } + + public IEnumerable GetAllClaims() + { + var properties = typeof(UserClaims).GetConstantsValues(); + return properties; + } + public bool UpdatePassword(string username, string oldPassword, string newPassword) { var users = Repo.GetAll(); @@ -175,6 +186,8 @@ public UsersModel GetUser(Guid userId) public interface ICustomUserMapper { + Guid? CreateUser(string username, string password, string[] claims); + IEnumerable GetAllClaims(); IEnumerable GetUsers(); Task> GetUsersAsync(); UsersModel GetUser(Guid userId); diff --git a/PlexRequests.Helpers.Tests/TypeHelperTests.cs b/PlexRequests.Helpers.Tests/TypeHelperTests.cs index 295b955ad..1d93c375f 100644 --- a/PlexRequests.Helpers.Tests/TypeHelperTests.cs +++ b/PlexRequests.Helpers.Tests/TypeHelperTests.cs @@ -26,7 +26,7 @@ #endregion using System; using System.Collections.Generic; - +using System.Linq; using NUnit.Framework; using PlexRequests.Store; @@ -42,6 +42,14 @@ public string[] FirstCharToUpperTest(Type input) return input.GetPropertyNames(); } + [Test] + public void GetConstantsTest() + { + var consts = typeof(UserClaims).GetConstantsValues(); + Assert.That(consts.Contains("Admin"),Is.True); + Assert.That(consts.Contains("PowerUser"),Is.True); + Assert.That(consts.Contains("User"),Is.True); + } private static IEnumerable TypeData { diff --git a/PlexRequests.Helpers/TypeHelper.cs b/PlexRequests.Helpers/TypeHelper.cs index 27a108bef..77eae6360 100644 --- a/PlexRequests.Helpers/TypeHelper.cs +++ b/PlexRequests.Helpers/TypeHelper.cs @@ -25,7 +25,9 @@ // ************************************************************************/ #endregion using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace PlexRequests.Helpers { @@ -35,5 +37,19 @@ public static string[] GetPropertyNames(this Type t) { return t.GetProperties().Select(x => x.Name).ToArray(); } + + public static IEnumerable GetConstants(this Type type) + { + var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); + + return fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly); + } + + public static IEnumerable GetConstantsValues(this Type type) where T : class + { + var fieldInfos = GetConstants(type); + + return fieldInfos.Select(fi => fi.GetRawConstantValue() as T); + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementController.js b/PlexRequests.UI/Content/app/userManagement/userManagementController.js index 8e74d3c2d..a577b1380 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementController.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementController.js @@ -4,22 +4,27 @@ $scope.user = {}; // The local user $scope.users = []; // list of users + $scope.claims = []; // List of claims - $scope.selectedUser = {}; + $scope.selectedUser = {}; // User on the right side + $scope.selectedClaims = {}; - $scope.sortType = 'username'; + $scope.sortType = "username"; $scope.sortReverse = false; - $scope.searchTerm = ''; + $scope.searchTerm = ""; + $scope.error = { error: false, errorMessage: "" }; - $scope.selectUser = function(id) { + // Select a user to populate on the right side + $scope.selectUser = function (id) { $scope.selectedUser = $scope.users.find(x => x.id === id); } + // Get all users in the system $scope.getUsers = function () { $scope.users = userManagementService.getUsers() .then(function (data) { @@ -27,25 +32,50 @@ }); }; + // Get the claims and populate the create dropdown + $scope.getClaims = function () { + userManagementService.getClaims() + .then(function (data) { + $scope.claims = data.data; + }); + } + + // Create a user, do some validation too $scope.addUser = function () { + if (!$scope.user.username || !$scope.user.password) { $scope.error.error = true; $scope.error.errorMessage = "Please provide a correct username and password"; generateNotify($scope.error.errorMessage, 'warning'); return; } - userManagementService.addUser($scope.user).then(function (data) { + + userManagementService.addUser($scope.user, $scope.selectedClaims).then(function (data) { if (data.message) { $scope.error.error = true; $scope.error.errorMessage = data.message; } else { - $scope.users.push(data); + $scope.users.push(data); // Push the new user into the array to update the DOM $scope.user = {}; + $scope.selectedClaims = {}; } }); }; + + $scope.$watch('claims|filter:{selected:true}', function (nv) { + $scope.selectedClaims = nv.map(function (claim) { + return claim.name; + }); + }, true); + + + // On page load + $scope.init = function () { + $scope.getUsers(); + $scope.getClaims(); + return; + } } angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService", controller]); - }()); \ No newline at end of file diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementService.js b/PlexRequests.UI/Content/app/userManagement/userManagementService.js index fe7b7ef07..b70d1fe26 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementService.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementService.js @@ -2,27 +2,35 @@ var userManagementService = function ($http) { - $http.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded'; // Set default headers + //$http.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded'; // Set default headers var getUsers = function () { return $http.get('/usermanagement/users'); }; - var addUser = function (user) { - if (!user) { + var addUser = function (user, claims) { + if (!user || claims.length === 0) { return null; } + var claimJson = angular.toJson(claims); + var objectToSerialize = { 'claims': claimJson }; + var data = $.param(user) +"&"+ $.param(objectToSerialize); return $http({ url: '/usermanagement/createuser', method: "POST", - data: $.param(user) + data: data }); } + var getClaims = function() { + return $http.get('/usermanagement/claims'); + } + return { getUsers: getUsers, - addUser: addUser + addUser: addUser, + getClaims: getClaims }; } diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs index 9667dac6f..f364d9588 100644 --- a/PlexRequests.UI/Modules/UserManagementModule.cs +++ b/PlexRequests.UI/Modules/UserManagementModule.cs @@ -30,9 +30,10 @@ public UserManagementModule(ISettingsService pr, ICustomUse Get["/"] = x => Load(); Get["/users", true] = async (x, ct) => await LoadUsers(); - Post["/createuser"] = x => CreateUser(Request.Form["username"].ToString(), Request.Form["password"].ToString()); + Post["/createuser"] = x => CreateUser(Request.Form["username"].ToString(), Request.Form["password"].ToString(), (string[])Request.Form["claims"]); Get["/local/{id}"] = x => LocalDetails((Guid)x.id); Get["/plex/{id}", true] = async (x,ct) => await PlexDetails(x.id); + Get["/claims"] = x => GetClaims(); } private ICustomUserMapper UserMapper { get; } @@ -91,7 +92,7 @@ private async Task LoadUsers() return Response.AsJson(model); } - private Response CreateUser(string username, string password) + private Response CreateUser(string username, string password, string[] claims) { if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) { @@ -101,7 +102,7 @@ private Response CreateUser(string username, string password) Message = "Please enter in a valid Username and Password" }); } - var user = UserMapper.CreateRegularUser(username, password); + var user = UserMapper.Ce(username, password); if (user.HasValue) { return Response.AsJson(user); @@ -139,6 +140,21 @@ private async Task PlexDetails(string id) return Nancy.Response.NoBody; } + + /// + /// Returns all claims for the users. + /// + /// + private Response GetClaims() + { + var retVal = new List(); + var claims = UserMapper.GetAllClaims(); + foreach (var c in claims) + { + retVal.Add(new {Name = c, Selected = false}); + } + return Response.AsJson(retVal); + } } } diff --git a/PlexRequests.UI/Views/UserManagement/Index.cshtml b/PlexRequests.UI/Views/UserManagement/Index.cshtml index 14a670a91..e04935869 100644 --- a/PlexRequests.UI/Views/UserManagement/Index.cshtml +++ b/PlexRequests.UI/Views/UserManagement/Index.cshtml @@ -3,7 +3,7 @@ @Html.LoadAngularAssets() -
+


@@ -12,15 +12,23 @@

-
+
+ +
+ + +
+
+
@@ -33,6 +41,7 @@
+ From aa95ec935ec67d27b9ac280d9ffe5942c2006b62 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 20 Aug 2016 11:28:26 +0100 Subject: [PATCH 02/19] Fixed an issue where you could set the base url as requests #479 --- .../Validators/PlexRequestsValidator.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/PlexRequests.UI/Validators/PlexRequestsValidator.cs b/PlexRequests.UI/Validators/PlexRequestsValidator.cs index c82817185..7d61eba2c 100644 --- a/PlexRequests.UI/Validators/PlexRequestsValidator.cs +++ b/PlexRequests.UI/Validators/PlexRequestsValidator.cs @@ -24,6 +24,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using System; using FluentValidation; using PlexRequests.Core.SettingModels; @@ -34,18 +36,18 @@ public class PlexRequestsValidator : AbstractValidator { public PlexRequestsValidator() { - RuleFor(x => x.BaseUrl).NotEqual("requests").WithMessage("You cannot use 'requests' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("admin").WithMessage("You cannot use 'admin' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("search").WithMessage("You cannot use 'search' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("issues").WithMessage("You cannot use 'issues' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("userlogin").WithMessage("You cannot use 'userlogin' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("login").WithMessage("You cannot use 'login' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("test").WithMessage("You cannot use 'test' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("approval").WithMessage("You cannot use 'approval' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application."); - RuleFor(x => x.BaseUrl).NotEqual("landing").WithMessage("You cannot use 'landing' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("requests",StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'requests' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("admin", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'admin' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("search", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'search' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("issues", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'issues' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("userlogin", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'userlogin' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("login", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'login' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("test", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'test' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("approval", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'approval' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("updatechecker", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'updatechecker' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("usermanagement", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'usermanagement' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("api", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'api' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("landing", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'landing' as this is reserved by the application."); } } } \ No newline at end of file From 265d1bdd254835721b75c8c61b830f4c2f296670 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sat, 20 Aug 2016 11:33:53 +0100 Subject: [PATCH 03/19] Fixed build --- PlexRequests.UI/Modules/UserManagementModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs index f364d9588..a589b5d98 100644 --- a/PlexRequests.UI/Modules/UserManagementModule.cs +++ b/PlexRequests.UI/Modules/UserManagementModule.cs @@ -102,7 +102,7 @@ private Response CreateUser(string username, string password, string[] claims) Message = "Please enter in a valid Username and Password" }); } - var user = UserMapper.Ce(username, password); + var user = UserMapper.CreateUser(username, password, claims); if (user.HasValue) { return Response.AsJson(user); From 0fe7bba9acb9d1cc7124fc3948adbd9584954a7a Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 21 Aug 2016 19:17:36 +0100 Subject: [PATCH 04/19] Redirect to search if we are already logged in #488 --- PlexRequests.UI/Modules/UserLoginModule.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index 61481faef..97879089e 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -61,7 +61,17 @@ public UserLoginModule(ISettingsService auth, IPlexApi a PlexSettings = plexSettings; Linker = linker; - Get["UserLoginIndex", "/", true] = async (x, ct) => await Index(); + Get["UserLoginIndex", "/", true] = async (x, ct) => + { + if (!string.IsNullOrEmpty(Username) || IsAdmin) + { + var uri = Linker.BuildAbsoluteUri(Context, "SearchIndex"); + return Response.AsRedirect(uri.ToString()); + } + var settings = await AuthService.GetSettingsAsync(); + return View["Index", settings]; + }; + Post["/", true] = async (x, ct) => await LoginUser(); Get["/logout"] = x => Logout(); } @@ -75,12 +85,6 @@ public UserLoginModule(ISettingsService auth, IPlexApi a private static Logger Log = LogManager.GetCurrentClassLogger(); - public async Task Index() - { - var settings = await AuthService.GetSettingsAsync(); - return View["Index", settings]; - } - private async Task LoginUser() { var dateTimeOffset = Request.Form.DateTimeOffset; From 4b5079598dbc3625030434e7c0018cba2aac7b94 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 21 Aug 2016 19:33:19 +0100 Subject: [PATCH 05/19] Change the redirection to use a relative uri redirect #473 --- PlexRequests.UI.Tests/UserLoginModuleTests.cs | 6 +++--- PlexRequests.UI/Modules/IndexModule.cs | 10 +++++----- PlexRequests.UI/Modules/LandingPageModule.cs | 2 +- PlexRequests.UI/Modules/UserLoginModule.cs | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/PlexRequests.UI.Tests/UserLoginModuleTests.cs b/PlexRequests.UI.Tests/UserLoginModuleTests.cs index 16b138eea..22f57750e 100644 --- a/PlexRequests.UI.Tests/UserLoginModuleTests.cs +++ b/PlexRequests.UI.Tests/UserLoginModuleTests.cs @@ -72,9 +72,9 @@ public void Setup() LandingPageMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new LandingPageSettings()); IAnalytics = new Mock(); Linker = new Mock(); - Linker.Setup(x => x.BuildAbsoluteUri(It.IsAny(), "SearchIndex", null)).Returns(new Uri("http://www.searchindex.com")); - Linker.Setup(x => x.BuildAbsoluteUri(It.IsAny(), "LandingPageIndex", null)).Returns(new Uri("http://www.landingpage.com")); - Linker.Setup(x => x.BuildAbsoluteUri(It.IsAny(), "UserLoginIndex", null)).Returns(new Uri("http://www.userloginindex.com")); + Linker.Setup(x => x.BuildRelativeUri(It.IsAny(), "SearchIndex", null)).Returns(new Uri("http://www.searchindex.com")); + Linker.Setup(x => x.BuildRelativeUri(It.IsAny(), "LandingPageIndex", null)).Returns(new Uri("http://www.landingpage.com")); + Linker.Setup(x => x.BuildRelativeUri(It.IsAny(), "UserLoginIndex", null)).Returns(new Uri("http://www.userloginindex.com")); PlexSettingsMock = new Mock>(); PlexSettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings() {PlexAuthToken = "abc"}); Bootstrapper = new ConfigurableBootstrapper(with => diff --git a/PlexRequests.UI/Modules/IndexModule.cs b/PlexRequests.UI/Modules/IndexModule.cs index 3d7023d39..fd13acb35 100644 --- a/PlexRequests.UI/Modules/IndexModule.cs +++ b/PlexRequests.UI/Modules/IndexModule.cs @@ -59,23 +59,23 @@ public async Task Index() if (!string.IsNullOrEmpty(Username)) { // They are not logged in - return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "LandingPageIndex").ToString()); + return Context.GetRedirect(Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString()); } - return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "SearchIndex").ToString()); + return Context.GetRedirect(Linker.BuildRelativeUri(Context, "SearchIndex").ToString()); } // After login if (string.IsNullOrEmpty(Username)) { // Not logged in yet - return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "UserLoginIndex").ToString()); + return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString()); } // Send them to landing - var landingUrl = Linker.BuildAbsoluteUri(Context, "LandingPageIndex").ToString(); + var landingUrl = Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString(); return Context.GetRedirect(landingUrl); } - return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "UserLoginIndex").ToString()); + return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString()); } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/LandingPageModule.cs b/PlexRequests.UI/Modules/LandingPageModule.cs index 92517f2f7..dccf69430 100644 --- a/PlexRequests.UI/Modules/LandingPageModule.cs +++ b/PlexRequests.UI/Modules/LandingPageModule.cs @@ -52,7 +52,7 @@ public LandingPageModule(ISettingsService settingsService, var s = await LandingSettings.GetSettingsAsync(); if (!s.BeforeLogin && string.IsNullOrEmpty(Username)) //We are signed in { - var url = Linker.BuildAbsoluteUri(Context, "SearchIndex").ToString(); + var url = Linker.BuildRelativeUri(Context, "SearchIndex").ToString(); return Response.AsRedirect(url); } diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index 97879089e..e30f4adc2 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -65,7 +65,7 @@ public UserLoginModule(ISettingsService auth, IPlexApi a { if (!string.IsNullOrEmpty(Username) || IsAdmin) { - var uri = Linker.BuildAbsoluteUri(Context, "SearchIndex"); + var uri = Linker.BuildRelativeUri(Context, "SearchIndex"); return Response.AsRedirect(uri.ToString()); } var settings = await AuthService.GetSettingsAsync(); @@ -93,7 +93,7 @@ private async Task LoginUser() if (string.IsNullOrWhiteSpace(username)) { Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass; - var uri = Linker.BuildAbsoluteUri(Context, "UserLoginIndex"); + var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); return Response.AsRedirect(uri.ToString()); // TODO Check this } @@ -106,7 +106,7 @@ private async Task LoginUser() { Log.Debug("User is in denied list, not allowing them to authenticate"); Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass; - var uri = Linker.BuildAbsoluteUri(Context, "UserLoginIndex"); + var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); return Response.AsRedirect(uri.ToString()); // TODO Check this } @@ -165,7 +165,7 @@ private async Task LoginUser() if (!authenticated) { - var uri = Linker.BuildAbsoluteUri(Context, "UserLoginIndex"); + var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex"); Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass; return Response.AsRedirect(uri.ToString()); // TODO Check this } @@ -176,11 +176,11 @@ private async Task LoginUser() { if (!landingSettings.BeforeLogin) { - var uri = Linker.BuildAbsoluteUri(Context, "LandingPageIndex"); + var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex"); return Response.AsRedirect(uri.ToString()); } } - var retVal = Linker.BuildAbsoluteUri(Context, "SearchIndex"); + var retVal = Linker.BuildRelativeUri(Context, "SearchIndex"); return Response.AsRedirect(retVal.ToString()); // TODO Check this } From 185abcb1a58de9320a920dd0bf67daf18f580d07 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 22 Aug 2016 13:11:03 +0100 Subject: [PATCH 06/19] Working on the beta releases page and also the user management --- PlexRequests.Core/PlexRequests.Core.csproj | 1 + .../SettingModels/RequestSettings.cs | 57 +++ PlexRequests.Core/UserMapper.cs | 8 +- .../app/requests/requestsController.js | 51 ++ .../Content/app/requests/requestsService.js | 49 ++ .../userManagement/userManagementService.js | 9 +- .../Models/UserManagementUsersViewModel.cs | 14 + PlexRequests.UI/Modules/RequestsBetaModule.cs | 453 ++++++++++++++++++ .../Modules/UserManagementModule.cs | 25 +- PlexRequests.UI/PlexRequests.UI.csproj | 7 + .../Views/UserManagement/Index.cshtml | 3 + 11 files changed, 657 insertions(+), 20 deletions(-) create mode 100644 PlexRequests.Core/SettingModels/RequestSettings.cs create mode 100644 PlexRequests.UI/Content/app/requests/requestsController.js create mode 100644 PlexRequests.UI/Content/app/requests/requestsService.js create mode 100644 PlexRequests.UI/Modules/RequestsBetaModule.cs diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index e4448d873..2e2d1b3a1 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -86,6 +86,7 @@ + diff --git a/PlexRequests.Core/SettingModels/RequestSettings.cs b/PlexRequests.Core/SettingModels/RequestSettings.cs new file mode 100644 index 000000000..5a9140947 --- /dev/null +++ b/PlexRequests.Core/SettingModels/RequestSettings.cs @@ -0,0 +1,57 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RequestSettings.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 System; +using System.Collections.Generic; + +namespace PlexRequests.Core.SettingModels +{ + public sealed class RequestSettings : Settings + { + public OrderType Order { get; set; } + public List Filters { get; set; } + } + + public enum OrderType + { + NewRequests, + OldRequests, + NewReleases, + OldReleases + } + + public enum FilterType + { + // ALL is not here, it's managed in the angular controller + Approved, + NotApproved, + Available, + NotAvailable, + Released, + NotReleased + } +} \ No newline at end of file diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs index d4eca3e2a..3c15d548e 100644 --- a/PlexRequests.Core/UserMapper.cs +++ b/PlexRequests.Core/UserMapper.cs @@ -99,7 +99,7 @@ public bool DoUsersExist() return users.Any(); } - private Guid? CreateUser(string username, string password, string[] claims = default(string[]), UserProperties properties = null) + public Guid? CreateUser(string username, string password, string[] claims = default(string[]), UserProperties properties = null) { var salt = PasswordHasher.GenerateSalt(); @@ -134,10 +134,6 @@ public bool DoUsersExist() return CreateUser(username, password, new[] { UserClaims.User }, properties); } - public Guid? CreateUser(string username, string password, string[] claims) - { - return CreateUser(username, password, claims, null); - } public IEnumerable GetAllClaims() { @@ -186,7 +182,7 @@ public UsersModel GetUser(Guid userId) public interface ICustomUserMapper { - Guid? CreateUser(string username, string password, string[] claims); + Guid? CreateUser(string username, string password, string[] claims, UserProperties props); IEnumerable GetAllClaims(); IEnumerable GetUsers(); Task> GetUsersAsync(); diff --git a/PlexRequests.UI/Content/app/requests/requestsController.js b/PlexRequests.UI/Content/app/requests/requestsController.js new file mode 100644 index 000000000..204dcf9a2 --- /dev/null +++ b/PlexRequests.UI/Content/app/requests/requestsController.js @@ -0,0 +1,51 @@ +(function () { + var controller = function($scope, requestsService) { + + $scope.requests = []; + $scope.selectedTab = {}; + $scope.currentPage = 1; + $scope.tabs = []; + + $scope.plexSettings = {}; + $scope.requestSettings = {}; + + // Search + $scope.searchTerm = ""; + + + // Called on page load + $scope.init = function() { + // Get the settings + $scope.plexSettings = requestsService.getPlexRequestSettings(getBaseUrl()); + $scope.requestSettings = requestsService.getRequestSettings(getBaseUrl()); + + if ($scope.plexSettings.SearchForMovies) { + $scope.selectedTab = "movies"; + + // Load the movie Requests + $scope.requests = requestsService.getRequests("movie", getBaseUrl()); + } + }; + + + $scope.changeTab = function(tab) { + // load the data from the tab + switch (tab) { + // Set the selected tab and load the appropriate data + } + + }; + + $scope.search = function() { + $scope.requests = requestsService.getRequests + }; + + function getBaseUrl() { + return $('#baseUrl').val(); + } + + + } + + angular.module('PlexRequests').controller('requestsController', ["$scope", "requestsService", controller]); +}()); \ No newline at end of file diff --git a/PlexRequests.UI/Content/app/requests/requestsService.js b/PlexRequests.UI/Content/app/requests/requestsService.js new file mode 100644 index 000000000..b702c0727 --- /dev/null +++ b/PlexRequests.UI/Content/app/requests/requestsService.js @@ -0,0 +1,49 @@ +(function () { + + var requestsService = function ($http) { + + $http.defaults.headers.common['Content-Type'] = 'application/json'; // Set default headers + + var getRequests = function (type, baseUrl) { + switch (type) { + case "movie": + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/movies")); + case "tv": + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/tvshows")); + case "album": + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/albums")); + } + return null; + }; + + var getPlexRequestSettings = function (baseUrl) { + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/plexrequestsettings")); + } + + var getRequestsSettings = function (baseUrl) { + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/requestsettings")); + } + + var getRequestsSearch = function (type, baseUrl, searchTerm) { + switch (type) { + case "movie": + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/movies/"+ searchTerm)); + case "tv": + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/tvshows/" + searchTerm)); + case "album": + return $http.get(createBaseUrl(baseUrl, "/requestsbeta/albums/" + searchTerm)); + } + return null; + }; + + return { + getRequests: getRequests, + getRequestsSearch: getRequestsSearch, + getPlexRequestSettings: getPlexRequestSettings, + getRequestSettings: getRequestsSettings + }; + } + + angular.module('PlexRequests').factory('requestsService', ["$http", requestsService]); + +}()); \ No newline at end of file diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementService.js b/PlexRequests.UI/Content/app/userManagement/userManagementService.js index b70d1fe26..e1640e3ea 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementService.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementService.js @@ -2,7 +2,7 @@ var userManagementService = function ($http) { - //$http.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded'; // Set default headers + $http.defaults.headers.common['Content-Type'] = 'application/json'; // Set default headers var getUsers = function () { return $http.get('/usermanagement/users'); @@ -13,17 +13,14 @@ return null; } - var claimJson = angular.toJson(claims); - var objectToSerialize = { 'claims': claimJson }; - var data = $.param(user) +"&"+ $.param(objectToSerialize); return $http({ url: '/usermanagement/createuser', method: "POST", - data: data + data: { username: user.username, password: user.password, claims: claims, email: user.email } }); } - var getClaims = function() { + var getClaims = function () { return $http.get('/usermanagement/claims'); } diff --git a/PlexRequests.UI/Models/UserManagementUsersViewModel.cs b/PlexRequests.UI/Models/UserManagementUsersViewModel.cs index 57fcd1f5a..3fbc936ec 100644 --- a/PlexRequests.UI/Models/UserManagementUsersViewModel.cs +++ b/PlexRequests.UI/Models/UserManagementUsersViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Newtonsoft.Json; namespace PlexRequests.UI.Models { @@ -42,5 +43,18 @@ public enum UserType PlexUser, LocalUser } + + public class UserManagementCreateModel + { + [JsonProperty("username")] + public string Username { get; set; } + [JsonProperty("password")] + public string Password { get; set; } + [JsonProperty("claims")] + public string[] Claims { get; set; } + + [JsonProperty("email")] + public string EmailAddress { get; set; } + } } diff --git a/PlexRequests.UI/Modules/RequestsBetaModule.cs b/PlexRequests.UI/Modules/RequestsBetaModule.cs new file mode 100644 index 000000000..a8a1a6e3f --- /dev/null +++ b/PlexRequests.UI/Modules/RequestsBetaModule.cs @@ -0,0 +1,453 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RequestsModule.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 System; +using System.Linq; + +using Nancy; +using Nancy.Responses.Negotiation; +using Nancy.Security; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store; +using PlexRequests.UI.Models; +using PlexRequests.Helpers; +using PlexRequests.UI.Helpers; +using System.Collections.Generic; +using PlexRequests.Api.Interfaces; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Core.Models; +using PlexRequests.Helpers.Analytics; + +using Action = PlexRequests.Helpers.Analytics.Action; + +namespace PlexRequests.UI.Modules +{ + public class RequestsBetaModule : BaseAuthModule + { + public RequestsBetaModule( + IRequestService service, + ISettingsService prSettings, + ISettingsService requestSettings, + ISettingsService plex, + INotificationService notify, + ISettingsService sonarrSettings, + ISettingsService sickRageSettings, + ISettingsService cpSettings, + ICouchPotatoApi cpApi, + ISonarrApi sonarrApi, + ISickRageApi sickRageApi, + ICacheProvider cache, + IAnalytics an) : base("requestsbeta", prSettings) + { + Service = service; + PrSettings = prSettings; + PlexSettings = plex; + NotificationService = notify; + SonarrSettings = sonarrSettings; + SickRageSettings = sickRageSettings; + CpSettings = cpSettings; + SonarrApi = sonarrApi; + SickRageApi = sickRageApi; + CpApi = cpApi; + Cache = cache; + Analytics = an; + + Get["/"] = x => LoadRequests(); + Get["/plexrequestsettings", true] = async (x, ct) => await GetPlexRequestSettings(); + Get["/requestsettings", true] = async (x, ct) => await GetRequestSettings(); + Get["/movies", true] = async (x, ct) => await GetMovies(); + Get["/movies/{searchTerm}", true] = async (x, ct) => await GetMovies((string)x.searchTerm); + + + // Everything below is not being used in the beta page + Get["/tvshows", true] = async (c, ct) => await GetTvShows(); + Get["/albums", true] = async (x, ct) => await GetAlbumRequests(); + Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id); + Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null); + Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea); + + Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id); + + Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available); + } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + private IRequestService Service { get; } + private IAnalytics Analytics { get; } + private INotificationService NotificationService { get; } + private ISettingsService PrSettings { get; } + private ISettingsService PlexSettings { get; } + private ISettingsService RequestSettings { get; } + private ISettingsService SonarrSettings { get; } + private ISettingsService SickRageSettings { get; } + private ISettingsService CpSettings { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickRageApi { get; } + private ICouchPotatoApi CpApi { get; } + private ICacheProvider Cache { get; } + + private Negotiator LoadRequests() + { + return View["Index"]; + } + + private async Task GetPlexRequestSettings() + { + return Response.AsJson(await PrSettings.GetSettingsAsync()); + } + + private async Task GetRequestSettings() + { + return Response.AsJson(await RequestSettings.GetSettingsAsync()); + } + + private async Task GetMovies(string searchTerm = null, bool approved = false, bool notApproved = false, + bool available = false, bool notAvailable = false, bool released = false, bool notReleased = false) + { + var dbMovies = await FilterMovies(searchTerm, approved, notApproved, available, notAvailable, released, notReleased); + var qualities = await GetQualityProfiles(); + var model = MapMoviesToView(dbMovies.ToList(), qualities); + + return Response.AsJson(model); + } + + private async Task GetTvShows() + { + var settingsTask = PrSettings.GetSettingsAsync(); + + var requests = await Service.GetAllAsync(); + requests = requests.Where(x => x.Type == RequestType.TvShow); + + var dbTv = requests; + var settings = await settingsTask; + if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) + { + dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList(); + } + + IEnumerable qualities = new List(); + if (IsAdmin) + { + try + { + var sonarrSettings = await SonarrSettings.GetSettingsAsync(); + if (sonarrSettings.Enabled) + { + var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () => + { + return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + qualities = result.Result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList(); + } + else + { + var sickRageSettings = await SickRageSettings.GetSettingsAsync(); + if (sickRageSettings.Enabled) + { + qualities = sickRageSettings.Qualities.Select(x => new QualityModel { Id = x.Key, Name = x.Value }).ToList(); + } + } + } + catch (Exception e) + { + Log.Info(e); + } + + } + + var viewModel = dbTv.Select(tv => new RequestViewModel + { + ProviderId = tv.ProviderId, + Type = tv.Type, + Status = tv.Status, + ImdbId = tv.ImdbId, + Id = tv.Id, + PosterPath = tv.PosterPath, + ReleaseDate = tv.ReleaseDate, + ReleaseDateTicks = tv.ReleaseDate.Ticks, + RequestedDate = tv.RequestedDate, + RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks, + Released = DateTime.Now > tv.ReleaseDate, + Approved = tv.Available || tv.Approved, + Title = tv.Title, + Overview = tv.Overview, + RequestedUsers = IsAdmin ? tv.AllUsers.ToArray() : new string[] { }, + ReleaseYear = tv.ReleaseDate.Year.ToString(), + Available = tv.Available, + Admin = IsAdmin, + IssueId = tv.IssueId, + TvSeriesRequestType = tv.SeasonsRequested, + Qualities = qualities.ToArray(), + Episodes = tv.Episodes.ToArray(), + }).ToList(); + + return Response.AsJson(viewModel); + } + + private async Task GetAlbumRequests() + { + var settings = PrSettings.GetSettings(); + var dbAlbum = await Service.GetAllAsync(); + dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album); + if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) + { + dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username)); + } + + var viewModel = dbAlbum.Select(album => + { + return new RequestViewModel + { + ProviderId = album.ProviderId, + Type = album.Type, + Status = album.Status, + ImdbId = album.ImdbId, + Id = album.Id, + PosterPath = album.PosterPath, + ReleaseDate = album.ReleaseDate, + ReleaseDateTicks = album.ReleaseDate.Ticks, + RequestedDate = album.RequestedDate, + RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks, + Released = DateTime.Now > album.ReleaseDate, + Approved = album.Available || album.Approved, + Title = album.Title, + Overview = album.Overview, + RequestedUsers = IsAdmin ? album.AllUsers.ToArray() : new string[] { }, + ReleaseYear = album.ReleaseDate.Year.ToString(), + Available = album.Available, + Admin = IsAdmin, + IssueId = album.IssueId, + TvSeriesRequestType = album.SeasonsRequested, + MusicBrainzId = album.MusicBrainzId, + ArtistName = album.ArtistName + + }; + }).ToList(); + + return Response.AsJson(viewModel); + } + + private async Task DeleteRequest(int requestid) + { + this.RequiresClaims(UserClaims.Admin); + Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies)); + + var currentEntity = await Service.GetAsync(requestid); + await Service.DeleteRequestAsync(currentEntity); + return Response.AsJson(new JsonResponseModel { Result = true }); + } + + /// + /// Reports the issue. + /// Comment can be null if the IssueState != Other + /// + /// The request identifier. + /// The issue. + /// The comment. + /// + private async Task ReportIssue(int requestId, IssueState issue, string comment) + { + var originalRequest = await Service.GetAsync(requestId); + if (originalRequest == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); + } + originalRequest.Issues = issue; + originalRequest.OtherMessage = !string.IsNullOrEmpty(comment) + ? $"{Username} - {comment}" + : string.Empty; + + + var result = await Service.UpdateRequestAsync(originalRequest); + + var model = new NotificationModel + { + User = Username, + NotificationType = NotificationType.Issue, + Title = originalRequest.Title, + DateTime = DateTime.Now, + Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords() + }; + await NotificationService.Publish(model); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); + } + + private async Task ClearIssue(int requestId) + { + this.RequiresClaims(UserClaims.Admin); + + var originalRequest = await Service.GetAsync(requestId); + if (originalRequest == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" }); + } + originalRequest.Issues = IssueState.None; + originalRequest.OtherMessage = string.Empty; + + var result = await Service.UpdateRequestAsync(originalRequest); + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" }); + } + + private async Task ChangeRequestAvailability(int requestId, bool available) + { + this.RequiresClaims(UserClaims.Admin); + Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var originalRequest = await Service.GetAsync(requestId); + if (originalRequest == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" }); + } + + originalRequest.Available = available; + + var result = await Service.UpdateRequestAsync(originalRequest); + return Response.AsJson(result + ? new { Result = true, Available = available, Message = string.Empty } + : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); + } + + private List MapMoviesToView(List dbMovies, List qualities) + { + return dbMovies.Select(movie => new RequestViewModel + { + ProviderId = movie.ProviderId, + Type = movie.Type, + Status = movie.Status, + ImdbId = movie.ImdbId, + Id = movie.Id, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + ReleaseDateTicks = movie.ReleaseDate.Ticks, + RequestedDate = movie.RequestedDate, + Released = DateTime.Now > movie.ReleaseDate, + RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks, + Approved = movie.Available || movie.Approved, + Title = movie.Title, + Overview = movie.Overview, + RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { }, + ReleaseYear = movie.ReleaseDate.Year.ToString(), + Available = movie.Available, + Admin = IsAdmin, + IssueId = movie.IssueId, + Qualities = qualities.ToArray() + }).ToList(); + } + + private async Task> GetQualityProfiles() + { + var qualities = new List(); + if (IsAdmin) + { + var cpSettings = CpSettings.GetSettings(); + if (cpSettings.Enabled) + { + try + { + var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () => + { + return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false); + }); + if (result != null) + { + qualities = result.list.Select(x => new QualityModel { Id = x._id, Name = x.label }).ToList(); + } + } + catch (Exception e) + { + Log.Info(e); + } + } + } + return qualities; + } + + private async Task> FilterMovies(string searchTerm = null, bool approved = false, bool notApproved = false, + bool available = false, bool notAvailable = false, bool released = false, bool notReleased = false) + { + var settings = PrSettings.GetSettings(); + var allRequests = await Service.GetAllAsync(); + allRequests = allRequests.Where(x => x.Type == RequestType.Movie); + + var dbMovies = allRequests; + + if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) + { + dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)); + } + + // Filter the movies on the search term + if (!string.IsNullOrEmpty(searchTerm)) + { + dbMovies = dbMovies.Where(x => x.Title.Contains(searchTerm)); + } + + if (approved) + { + dbMovies = dbMovies.Where(x => x.Approved); + } + + if (notApproved) + { + dbMovies = dbMovies.Where(x => !x.Approved); + } + + if (available) + { + dbMovies = dbMovies.Where(x => x.Available); + } + + if (notAvailable) + { + dbMovies = dbMovies.Where(x => !x.Available); + } + + if (released) + { + dbMovies = dbMovies.Where(x => DateTime.Now > x.ReleaseDate); + } + + if (notReleased) + { + dbMovies = dbMovies.Where(x => DateTime.Now < x.ReleaseDate); + } + + return dbMovies; + } + } +} diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs index f364d9588..efcaeaa39 100644 --- a/PlexRequests.UI/Modules/UserManagementModule.cs +++ b/PlexRequests.UI/Modules/UserManagementModule.cs @@ -4,9 +4,10 @@ using System.Threading.Tasks; using Nancy; +using Nancy.Extensions; using Nancy.Responses.Negotiation; using Nancy.Security; - +using Newtonsoft.Json; using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.Models; @@ -30,9 +31,9 @@ public UserManagementModule(ISettingsService pr, ICustomUse Get["/"] = x => Load(); Get["/users", true] = async (x, ct) => await LoadUsers(); - Post["/createuser"] = x => CreateUser(Request.Form["username"].ToString(), Request.Form["password"].ToString(), (string[])Request.Form["claims"]); + Post["/createuser"] = x => CreateUser(); Get["/local/{id}"] = x => LocalDetails((Guid)x.id); - Get["/plex/{id}", true] = async (x,ct) => await PlexDetails(x.id); + Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id); Get["/claims"] = x => GetClaims(); } @@ -58,7 +59,7 @@ private async Task LoadUsers() model.Add(new UserManagementUsersViewModel { - Id= user.UserGuid, + Id = user.UserGuid, Claims = claimsString, Username = user.UserName, Type = UserType.LocalUser, @@ -92,9 +93,17 @@ private async Task LoadUsers() return Response.AsJson(model); } - private Response CreateUser(string username, string password, string[] claims) + private Response CreateUser() { - if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) + var body = Request.Body.AsString(); + if (string.IsNullOrEmpty(body)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" }); + } + + var model = JsonConvert.DeserializeObject(body); + + if (string.IsNullOrWhiteSpace(model.Username) || string.IsNullOrWhiteSpace(model.Password)) { return Response.AsJson(new JsonResponseModel { @@ -102,7 +111,7 @@ private Response CreateUser(string username, string password, string[] claims) Message = "Please enter in a valid Username and Password" }); } - var user = UserMapper.Ce(username, password); + var user = UserMapper.CreateUser(model.Username, model.Password, model.Claims, new UserProperties { EmailAddress = model.EmailAddress }); if (user.HasValue) { return Response.AsJson(user); @@ -151,7 +160,7 @@ private Response GetClaims() var claims = UserMapper.GetAllClaims(); foreach (var c in claims) { - retVal.Add(new {Name = c, Selected = false}); + retVal.Add(new { Name = c, Selected = false }); } return Response.AsJson(retVal); } diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index d178bbc92..1779f378b 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -250,6 +250,7 @@ + @@ -282,6 +283,12 @@ Always + + Always + + + Always + PreserveNewest diff --git a/PlexRequests.UI/Views/UserManagement/Index.cshtml b/PlexRequests.UI/Views/UserManagement/Index.cshtml index 561652eb0..8307ef491 100644 --- a/PlexRequests.UI/Views/UserManagement/Index.cshtml +++ b/PlexRequests.UI/Views/UserManagement/Index.cshtml @@ -19,6 +19,9 @@
+
+ +
Date: Tue, 23 Aug 2016 21:47:12 +0100 Subject: [PATCH 07/19] Fixed issue #487 --- PlexRequests.Api/ApiRequest.cs | 1 + PlexRequests.UI/Helpers/TvSender.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/PlexRequests.Api/ApiRequest.cs b/PlexRequests.Api/ApiRequest.cs index 704880f9b..8cb023ea6 100644 --- a/PlexRequests.Api/ApiRequest.cs +++ b/PlexRequests.Api/ApiRequest.cs @@ -63,6 +63,7 @@ public class ApiRequest : IApiRequest Log.Trace("Api Content Response:"); Log.Trace(response.Content); + if (response.ErrorException != null) { var message = "Error retrieving response. Check inner details for more info."; diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs index 13a941512..05b2799ab 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.UI/Helpers/TvSender.cs @@ -131,7 +131,7 @@ public async Task SendToSonarr(SonarrSettings sonarrSettings, R var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, - sonarrSettings.FullUri); + sonarrSettings.FullUri, true, true); return result; } From d89b129afe1dcef10aa73ba875c86901af6d81f0 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 24 Aug 2016 12:26:50 +0100 Subject: [PATCH 08/19] Remove the datetime picker css from the main assets block and only load it on the pages it needs. #493 --- PlexRequests.UI/Helpers/BaseUrlHelper.cs | 12 ++++++++++-- PlexRequests.UI/Views/Admin/LandingPage.cshtml | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index 625d71380..a30984ae4 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -84,7 +84,6 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) $"", $"", $"", - $"", $"", }; @@ -99,7 +98,6 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) $"", $"", $"", - $"", $"" }; @@ -118,6 +116,16 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) return helper.Raw(sb.ToString()); } + public static IHtmlString LoadDateTimePickerAsset(this HtmlHelpers helper) + { + var startUrl = GetBaseUrl(); + + var sb = new StringBuilder(); + sb.AppendLine($""); + sb.AppendLine($""); + + return helper.Raw(sb.ToString()); + } public static IHtmlString LoadAngularAssets(this HtmlHelpers helper) { var sb = new StringBuilder(); diff --git a/PlexRequests.UI/Views/Admin/LandingPage.cshtml b/PlexRequests.UI/Views/Admin/LandingPage.cshtml index 9f6dcec6f..38d3cc6fa 100644 --- a/PlexRequests.UI/Views/Admin/LandingPage.cshtml +++ b/PlexRequests.UI/Views/Admin/LandingPage.cshtml @@ -1,7 +1,7 @@ @using PlexRequests.UI.Helpers @Html.Partial("_Sidebar") @inherits Nancy.ViewEngines.Razor.NancyRazorViewBase - +@Html.LoadDateTimePickerAsset()
From 9a31a515143600f08c95b5858913659eed369680 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 24 Aug 2016 12:34:39 +0100 Subject: [PATCH 09/19] Changed the way we use the setTimeout function. Should fix #403 #491 #492 --- PlexRequests.UI/Content/search.js | 12 +++++++++--- PlexRequests.UI/Content/site.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index 37b41a95e..bf5eea9db 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -56,7 +56,9 @@ $(function () { if (searchTimer) { clearTimeout(searchTimer); } - searchTimer = setTimeout(movieSearch, 800); + searchTimer = setTimeout(function () { + movieSearch(); + }.bind(this), 800); }); @@ -75,7 +77,9 @@ $(function () { if (searchTimer) { clearTimeout(searchTimer); } - searchTimer = setTimeout(tvSearch, 400); + searchTimer = setTimeout(function() { + tvSearch(); + }.bind(this), 800); }); // Click TV dropdown option @@ -116,7 +120,9 @@ $(function () { if (searchTimer) { clearTimeout(searchTimer); } - searchTimer = setTimeout(musicSearch, 400); + searchTimer = setTimeout(function () { + musicSearch(); + }.bind(this), 800); }); diff --git a/PlexRequests.UI/Content/site.js b/PlexRequests.UI/Content/site.js index 85cae41f8..70639cf3e 100644 --- a/PlexRequests.UI/Content/site.js +++ b/PlexRequests.UI/Content/site.js @@ -8,6 +8,21 @@ return s; } +Function.prototype.bind = function (parent) { + var f = this; + var args = []; + + for (var a = 1; a < arguments.length; a++) { + args[args.length] = arguments[a]; + } + + var temp = function () { + return f.apply(parent, args); + } + + return (temp); +} + $(function() { $('[data-toggle="tooltip"]').tooltip(); }); From 6c9093d0c9d57ea5e59277953c8d07d604d9a8a3 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 24 Aug 2016 17:16:10 +0100 Subject: [PATCH 10/19] Append the application version to the end of our JS/CSS files --- PlexRequests.UI/Helpers/BaseUrlHelper.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index a30984ae4..ee821f7ac 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -82,8 +82,8 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) $"", $"", $"", - $"", - $"", + $"", + $"", $"", }; @@ -94,7 +94,7 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) $"", $"", $"", - $"", + $"", $"", $"", $"", @@ -143,7 +143,7 @@ public static IHtmlString LoadAngularAssets(this HtmlHelpers helper) var startUrl = $"{content}/Content"; sb.AppendLine($""); // Load angular first - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -155,7 +155,7 @@ public static IHtmlString LoadSearchAssets(this HtmlHelpers helper) var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -167,7 +167,7 @@ public static IHtmlString LoadRequestAssets(this HtmlHelpers helper) var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -179,7 +179,7 @@ public static IHtmlString LoadIssueAssets(this HtmlHelpers helper) var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -191,7 +191,7 @@ public static IHtmlString LoadWizardAssets(this HtmlHelpers helper) var content = GetContentUrl(assetLocation); - sb.AppendLine($""); + sb.AppendLine($""); return helper.Raw(sb.ToString()); } @@ -201,7 +201,7 @@ public static IHtmlString LoadIssueDetailsAssets(this HtmlHelpers helper) var assetLocation = GetBaseUrl(); var content = GetContentUrl(assetLocation); - var asset = $""; + var asset = $""; return helper.Raw(asset); } @@ -230,7 +230,7 @@ public static IHtmlString LoadAnalytics(this HtmlHelpers helper) var assetLocation = GetBaseUrl(); var content = GetContentUrl(assetLocation); - var asset = $""; + var asset = $""; return helper.Raw(asset); } From dbcbb8ae6c9b389cde759e2a1a4571ef3c611a17 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 25 Aug 2016 21:32:19 +0100 Subject: [PATCH 11/19] Fixed #480 --- .../Notification/EmailMessageNotification.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 3bbbc43cb..07d5d01b8 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -104,7 +104,14 @@ private EmailNotificationSettings GetConfiguration() private bool ValidateConfiguration(EmailNotificationSettings settings) { - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + if (settings.Authentication) + { + if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) + { + return false; + } + } + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) { return false; } From 2aebbe02597ea3c49a92d1e5879aa77d4b653014 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 25 Aug 2016 21:43:49 +0100 Subject: [PATCH 12/19] Fixed an issue where there were some JS errors on the landing page settings and stopped us being redirected to the login sometimes as an admin --- PlexRequests.UI/Helpers/BaseUrlHelper.cs | 3 +- PlexRequests.UI/Modules/BaseModule.cs | 5 +- PlexRequests.UI/Modules/LoginModule.cs | 302 ++++++++++++----------- 3 files changed, 159 insertions(+), 151 deletions(-) diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs index ee821f7ac..ddb8f8ce1 100644 --- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs +++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs @@ -118,9 +118,10 @@ public static IHtmlString LoadAssets(this HtmlHelpers helper) public static IHtmlString LoadDateTimePickerAsset(this HtmlHelpers helper) { - var startUrl = GetBaseUrl(); + var content = GetBaseUrl(); var sb = new StringBuilder(); + var startUrl = $"{content}/Content"; sb.AppendLine($""); sb.AppendLine($""); diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs index 1a205202b..a01253786 100644 --- a/PlexRequests.UI/Modules/BaseModule.cs +++ b/PlexRequests.UI/Modules/BaseModule.cs @@ -122,7 +122,7 @@ protected bool IsAdmin { get { - if (Context?.CurrentUser == null) + if (!LoggedIn) { return false; } @@ -130,6 +130,9 @@ protected bool IsAdmin return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser); } } + + protected bool LoggedIn => Context?.CurrentUser != null; + protected string Culture { get; set; } protected const string CultureCookieName = "_culture"; protected Response SetCookie() diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs index 8e6a9d39c..8c3bd550d 100644 --- a/PlexRequests.UI/Modules/LoginModule.cs +++ b/PlexRequests.UI/Modules/LoginModule.cs @@ -1,150 +1,154 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: LoginModule.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 System; -using System.Dynamic; - -using Nancy; -using Nancy.Authentication.Forms; -using Nancy.Extensions; -using Nancy.Responses.Negotiation; -using Nancy.Security; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.UI.Models; - -namespace PlexRequests.UI.Modules -{ - public class LoginModule : BaseModule - { - public LoginModule(ISettingsService pr, ICustomUserMapper m) : base(pr) - { - UserMapper = m; - Get["/login"] = _ => - { - { - dynamic model = new ExpandoObject(); - model.Redirect = Request.Query.redirect.Value ?? string.Empty; - model.Errored = Request.Query.error.HasValue; - var adminCreated = UserMapper.DoUsersExist(); - model.AdminExists = adminCreated; - return View["Index", model]; - } - - }; - - Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); - - Post["/login"] = x => - { - var username = (string)Request.Form.Username; - var password = (string)Request.Form.Password; - var dtOffset = (int)Request.Form.DateTimeOffset; - var redirect = (string)Request.Form.Redirect; - - var userId = UserMapper.ValidateUser(username, password); - - if (userId == null) - { - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username); - } - DateTime? expiry = null; - if (Request.Form.RememberMe.HasValue) - { - expiry = DateTime.Now.AddDays(7); - } - Session[SessionKeys.UsernameKey] = username; - Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset; - if(redirect.Contains("userlogin")){ - redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search"; - } - return this.LoginAndRedirect(userId.Value, expiry, redirect); - }; - - Get["/register"] = x => - { - { - dynamic model = new ExpandoObject(); - model.Errored = Request.Query.error.HasValue; - - return View["Register", model]; - } - }; - - Post["/register"] = x => - { - var username = (string)Request.Form.Username; - var exists = UserMapper.DoUsersExist(); - if (exists) - { - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true"); - } - var userId = UserMapper.CreateAdmin(username, Request.Form.Password); - Session[SessionKeys.UsernameKey] = username; - return this.LoginAndRedirect((Guid)userId); - }; - - Get["/changepassword"] = _ => ChangePassword(); - Post["/changepassword"] = _ => ChangePasswordPost(); - } - private ICustomUserMapper UserMapper { get; } - - private Negotiator ChangePassword() - { - this.RequiresAuthentication(); - return View["ChangePassword"]; - } - - private Response ChangePasswordPost() - { - var username = Context.CurrentUser.UserName; - var oldPass = Request.Form.OldPassword; - var newPassword = Request.Form.NewPassword; - var newPasswordAgain = Request.Form.NewPasswordAgain; - - if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) || - string.IsNullOrEmpty(newPasswordAgain)) - { - return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false }); - } - - if (!newPassword.Equals(newPasswordAgain)) - { - return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false }); - } - - var result = UserMapper.UpdatePassword(username, oldPass, newPassword); - if (result) - { - return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true }); - } - - return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: LoginModule.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 System; +using System.Dynamic; + +using Nancy; +using Nancy.Authentication.Forms; +using Nancy.Extensions; +using Nancy.Linker; +using Nancy.Responses.Negotiation; +using Nancy.Security; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public class LoginModule : BaseModule + { + public LoginModule(ISettingsService pr, ICustomUserMapper m, IResourceLinker linker) : base(pr) + { + UserMapper = m; + Get["/login"] = _ => + { + if (LoggedIn) + { + var url = linker.BuildRelativeUri(Context, "SearchIndex"); + return Response.AsRedirect(url.ToString()); + } + dynamic model = new ExpandoObject(); + model.Redirect = Request.Query.redirect.Value ?? string.Empty; + model.Errored = Request.Query.error.HasValue; + var adminCreated = UserMapper.DoUsersExist(); + model.AdminExists = adminCreated; + return View["Index", model]; + }; + + Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/"); + + Post["/login"] = x => + { + var username = (string)Request.Form.Username; + var password = (string)Request.Form.Password; + var dtOffset = (int)Request.Form.DateTimeOffset; + var redirect = (string)Request.Form.Redirect; + + var userId = UserMapper.ValidateUser(username, password); + + if (userId == null) + { + return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username); + } + DateTime? expiry = null; + if (Request.Form.RememberMe.HasValue) + { + expiry = DateTime.Now.AddDays(7); + } + Session[SessionKeys.UsernameKey] = username; + Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset; + if (redirect.Contains("userlogin")) + { + redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search"; + } + return this.LoginAndRedirect(userId.Value, expiry, redirect); + }; + + Get["/register"] = x => + { + { + dynamic model = new ExpandoObject(); + model.Errored = Request.Query.error.HasValue; + + return View["Register", model]; + } + }; + + Post["/register"] = x => + { + var username = (string)Request.Form.Username; + var exists = UserMapper.DoUsersExist(); + if (exists) + { + return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true"); + } + var userId = UserMapper.CreateAdmin(username, Request.Form.Password); + Session[SessionKeys.UsernameKey] = username; + return this.LoginAndRedirect((Guid)userId); + }; + + Get["/changepassword"] = _ => ChangePassword(); + Post["/changepassword"] = _ => ChangePasswordPost(); + } + private ICustomUserMapper UserMapper { get; } + + private Negotiator ChangePassword() + { + this.RequiresAuthentication(); + return View["ChangePassword"]; + } + + private Response ChangePasswordPost() + { + var username = Context.CurrentUser.UserName; + var oldPass = Request.Form.OldPassword; + var newPassword = Request.Form.NewPassword; + var newPasswordAgain = Request.Form.NewPasswordAgain; + + if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) || + string.IsNullOrEmpty(newPasswordAgain)) + { + return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false }); + } + + if (!newPassword.Equals(newPasswordAgain)) + { + return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false }); + } + + var result = UserMapper.UpdatePassword(username, oldPass, newPassword); + if (result) + { + return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true }); + } + + return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false }); + } + } } \ No newline at end of file From 7db336e20222adae1c0c18ab4c17d0e61307bf24 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Thu, 25 Aug 2016 22:53:05 +0100 Subject: [PATCH 13/19] Finished #415 --- .../SettingModels/PlexSettings.cs | 4 + PlexRequests.Core/Setup.cs | 31 ++++++++ PlexRequests.Helpers.Tests/PlexHelperTests.cs | 15 ++++ PlexRequests.Helpers/PlexHelper.cs | 7 ++ .../PlexAvailabilityCheckerTests.cs | 2 + .../Interfaces/IAvailabilityChecker.cs | 3 + .../Jobs/PlexAvailabilityChecker.cs | 49 ++++++++++--- PlexRequests.Services/Models/PlexAlbum.cs | 19 ++--- PlexRequests.Services/Models/PlexMovie.cs | 19 ++--- PlexRequests.Services/Models/PlexTvShow.cs | 1 + PlexRequests.UI/Content/search.js | 9 ++- PlexRequests.UI/Models/SearchViewModel.cs | 73 ++++++++++--------- PlexRequests.UI/Models/SessionKeys.cs | 1 + PlexRequests.UI/Modules/AdminModule.cs | 25 ++++--- PlexRequests.UI/Modules/SearchModule.cs | 17 +++-- PlexRequests.UI/Modules/UserWizardModule.cs | 23 +++++- PlexRequests.UI/Resources/UI.resx | 3 + PlexRequests.UI/Resources/UI1.Designer.cs | 9 +++ PlexRequests.UI/Views/Search/Index.cshtml | 8 +- 19 files changed, 229 insertions(+), 89 deletions(-) diff --git a/PlexRequests.Core/SettingModels/PlexSettings.cs b/PlexRequests.Core/SettingModels/PlexSettings.cs index e83e61f4d..09be5fb15 100644 --- a/PlexRequests.Core/SettingModels/PlexSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexSettings.cs @@ -24,6 +24,9 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using Newtonsoft.Json; + namespace PlexRequests.Core.SettingModels { public sealed class PlexSettings : ExternalSettings @@ -36,5 +39,6 @@ public PlexSettings() public bool EnableTvEpisodeSearching { get; set; } public string PlexAuthToken { get; set; } + public string MachineIdentifier { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 50b27dd2d..dac15a731 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -26,6 +26,7 @@ #endregion using System; +using System.Linq; using System.Text.RegularExpressions; using Mono.Data.Sqlite; @@ -66,6 +67,11 @@ public string SetupDb(string urlBase) { MigrateToVersion1900(); } + + if(version > 1899 && version <= 1910) + { + MigrateToVersion1910(); + } } return Db.DbConnection().ConnectionString; @@ -244,5 +250,30 @@ public void MigrateToVersion1900() Log.Error(e); } } + + /// + /// Migrates to version1910. + /// + public void MigrateToVersion1910() + { + try + { + // Get the new machine Identifier + var settings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); + var plex = settings.GetSettings(); + if (!string.IsNullOrEmpty(plex.PlexAuthToken)) + { + var api = new PlexApi(new ApiRequest()); + var server = api.GetServer(plex.PlexAuthToken); // Get the server info + plex.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plex.PlexAuthToken)?.MachineIdentifier; + + settings.SaveSettings(plex); // Save the new settings + } + } + catch (Exception e) + { + Log.Error(e); + } + } } } diff --git a/PlexRequests.Helpers.Tests/PlexHelperTests.cs b/PlexRequests.Helpers.Tests/PlexHelperTests.cs index 6f09d6c39..33a1bf1a3 100644 --- a/PlexRequests.Helpers.Tests/PlexHelperTests.cs +++ b/PlexRequests.Helpers.Tests/PlexHelperTests.cs @@ -61,6 +61,12 @@ public int TitleToSeasonNumber(string title) return PlexHelper.GetSeasonNumberFromTitle(title); } + [TestCaseSource(nameof(MediaUrls))] + public string GetPlexMediaUrlTest(string machineId, string mediaId) + { + return PlexHelper.GetPlexMediaUrl(machineId, mediaId); + } + private static IEnumerable PlexGuids { get @@ -75,6 +81,15 @@ private static IEnumerable PlexGuids } } + private static IEnumerable MediaUrls + { + get + { + yield return new TestCaseData("abcd","99").Returns("https://app.plex.tv/web/app#!/server/abcd/details/%2Flibrary%2Fmetadata%2F99").SetName("Test 1"); + yield return new TestCaseData("a54d1db669799308cd704b791f331eca6648b952", "51").Returns("https://app.plex.tv/web/app#!/server/a54d1db669799308cd704b791f331eca6648b952/details/%2Flibrary%2Fmetadata%2F51").SetName("Test 2"); + } + } + private static IEnumerable SeasonNumbers { get diff --git a/PlexRequests.Helpers/PlexHelper.cs b/PlexRequests.Helpers/PlexHelper.cs index baafcd451..1e186ddba 100644 --- a/PlexRequests.Helpers/PlexHelper.cs +++ b/PlexRequests.Helpers/PlexHelper.cs @@ -95,6 +95,13 @@ public static int GetSeasonNumberFromTitle(string title) return 0; } + + public static string GetPlexMediaUrl(string machineId, string mediaId) + { + var url = + $"https://app.plex.tv/web/app#!/server/{machineId}/details/%2Flibrary%2Fmetadata%2F{mediaId}"; + return url; + } } public class EpisodeModelHelper diff --git a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs index 5b8ba361f..3b152d7ee 100644 --- a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs +++ b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs @@ -242,6 +242,7 @@ public void GetPlexMoviesTests() } }); CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedMovies); + SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create()); var movies = Checker.GetPlexMovies(); Assert.That(movies.Any(x => x.ProviderId == "1212")); @@ -258,6 +259,7 @@ public void GetPlexTvShowsTests() new Directory1 {Type = "show", Title = "title1", Year = "2016", ProviderId = "1212", Seasons = new List()} } }); + SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create()); CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedTv); var movies = Checker.GetPlexTvShows(); diff --git a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs index 8992e6545..d966e2b5f 100644 --- a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs +++ b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs @@ -42,6 +42,9 @@ public interface IAvailabilityChecker List GetPlexAlbums(); bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist); bool IsEpisodeAvailable(string theTvDbId, int season, int episode); + PlexAlbum GetAlbum(PlexAlbum[] plexAlbums, string title, string year, string artist); + PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null); + PlexTvShow GetTvShow(PlexTvShow[] plexShows, string title, string year, string providerId = null, int[] seasons = null); /// /// Gets the episode's stored in the cache. /// diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index e58a18c17..b5c284756 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -158,6 +158,7 @@ public void CheckAndUpdateAll() public List GetPlexMovies() { + var settings = Plex.GetSettings(); var movies = new List(); var libs = Cache.Get>(CacheKeys.PlexLibaries); if (libs != null) @@ -175,6 +176,7 @@ public List GetPlexMovies() ReleaseYear = video.Year, Title = video.Title, ProviderId = video.ProviderId, + Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey) })); } } @@ -182,6 +184,12 @@ public List GetPlexMovies() } public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, string providerId = null) + { + var movie = GetMovie(plexMovies, title, year, providerId); + return movie != null; + } + + public PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null) { var advanced = !string.IsNullOrEmpty(providerId); foreach (var movie in plexMovies) @@ -191,20 +199,21 @@ public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, if (!string.IsNullOrEmpty(movie.ProviderId) && movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { - return true; + return movie; } } if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && movie.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)) { - return true; + return movie; } } - return false; + return null; } public List GetPlexTvShows() { + var settings = Plex.GetSettings(); var shows = new List(); var libs = Cache.Get>(CacheKeys.PlexLibaries); if (libs != null) @@ -224,7 +233,9 @@ public List GetPlexTvShows() Title = x.Title, ReleaseYear = x.Year, ProviderId = x.ProviderId, - Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray() + Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(), + Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey) + })); } } @@ -232,6 +243,14 @@ public List GetPlexTvShows() } public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year, string providerId = null, int[] seasons = null) + { + var show = GetTvShow(plexShows, title, year, providerId, seasons); + return show != null; + } + + + public PlexTvShow GetTvShow(PlexTvShow[] plexShows, string title, string year, string providerId = null, + int[] seasons = null) { var advanced = !string.IsNullOrEmpty(providerId); foreach (var show in plexShows) @@ -242,23 +261,23 @@ public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year, { if (seasons.Any(season => show.Seasons.Contains(season))) { - return true; + return show; } - return false; + return null; } if (!string.IsNullOrEmpty(show.ProviderId) && show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { - return true; + return show; } } if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) && show.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase)) { - return true; + return show; } } - return false; + return null; } public bool IsEpisodeAvailable(string theTvDbId, int season, int episode) @@ -328,6 +347,7 @@ public async Task> GetEpisodes(int theTvDbId) public List GetPlexAlbums() { + var settings = Plex.GetSettings(); var albums = new List(); var libs = Cache.Get>(CacheKeys.PlexLibaries); if (libs != null) @@ -344,7 +364,8 @@ public List GetPlexAlbums() { Title = x.Title, ReleaseYear = x.Year, - Artist = x.ParentTitle + Artist = x.ParentTitle, + Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey) })); } } @@ -355,7 +376,13 @@ public bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, { return plexAlbums.Any(x => x.Title.Contains(title) && - //x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase) && + x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase)); + } + + public PlexAlbum GetAlbum(PlexAlbum[] plexAlbums, string title, string year, string artist) + { + return plexAlbums.FirstOrDefault(x => + x.Title.Contains(title) && x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase)); } diff --git a/PlexRequests.Services/Models/PlexAlbum.cs b/PlexRequests.Services/Models/PlexAlbum.cs index 5d2bd7254..09d4b2638 100644 --- a/PlexRequests.Services/Models/PlexAlbum.cs +++ b/PlexRequests.Services/Models/PlexAlbum.cs @@ -1,9 +1,10 @@ -namespace PlexRequests.Services.Models -{ - public class PlexAlbum - { - public string Title { get; set; } - public string Artist { get; set; } - public string ReleaseYear { get; set; } - } -} +namespace PlexRequests.Services.Models +{ + public class PlexAlbum + { + public string Title { get; set; } + public string Artist { get; set; } + public string ReleaseYear { get; set; } + public string Url { get; set; } + } +} diff --git a/PlexRequests.Services/Models/PlexMovie.cs b/PlexRequests.Services/Models/PlexMovie.cs index 0149698ba..27eca9948 100644 --- a/PlexRequests.Services/Models/PlexMovie.cs +++ b/PlexRequests.Services/Models/PlexMovie.cs @@ -1,9 +1,10 @@ -namespace PlexRequests.Services.Models -{ - public class PlexMovie - { - public string Title { get; set; } - public string ReleaseYear { get; set; } - public string ProviderId { get; set; } - } -} +namespace PlexRequests.Services.Models +{ + public class PlexMovie + { + public string Title { get; set; } + public string ReleaseYear { get; set; } + public string ProviderId { get; set; } + public string Url { get; set; } + } +} diff --git a/PlexRequests.Services/Models/PlexTvShow.cs b/PlexRequests.Services/Models/PlexTvShow.cs index 5ac629132..aecf6f088 100644 --- a/PlexRequests.Services/Models/PlexTvShow.cs +++ b/PlexRequests.Services/Models/PlexTvShow.cs @@ -6,5 +6,6 @@ public class PlexTvShow public string ReleaseYear { get; set; } public string ProviderId { get; set; } public int[] Seasons { get; set; } + public string Url { get; set; } } } diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index bf5eea9db..f720ae6ba 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -429,7 +429,8 @@ $(function () { imdb: result.imdbId, requested: result.requested, approved: result.approved, - available: result.available + available: result.available, + url: result.plexUrl }; return context; @@ -450,7 +451,8 @@ $(function () { approved: result.approved, available: result.available, episodes: result.episodes, - tvFullyAvailable: result.tvFullyAvailable + tvFullyAvailable: result.tvFullyAvailable, + url: result.plexUrl }; return context; } @@ -470,7 +472,8 @@ $(function () { country: result.country, requested: result.requested, approved: result.approved, - available: result.available + available: result.available, + url: result.plexUrl }; return context; diff --git a/PlexRequests.UI/Models/SearchViewModel.cs b/PlexRequests.UI/Models/SearchViewModel.cs index 776b9d2b1..9c11d32ef 100644 --- a/PlexRequests.UI/Models/SearchViewModel.cs +++ b/PlexRequests.UI/Models/SearchViewModel.cs @@ -1,37 +1,38 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SearchTvShowViewModel.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 - - -namespace PlexRequests.UI.Models -{ - public class SearchViewModel - { - public bool Approved { get; set; } - public bool Requested { get; set; } - public bool Available { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SearchTvShowViewModel.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 + + +namespace PlexRequests.UI.Models +{ + public class SearchViewModel + { + public bool Approved { get; set; } + public bool Requested { get; set; } + public bool Available { get; set; } + public string PlexUrl { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Models/SessionKeys.cs b/PlexRequests.UI/Models/SessionKeys.cs index fefc7d8bd..766a84bf6 100644 --- a/PlexRequests.UI/Models/SessionKeys.cs +++ b/PlexRequests.UI/Models/SessionKeys.cs @@ -31,5 +31,6 @@ public class SessionKeys public const string UsernameKey = "Username"; public const string ClientDateTimeOffsetKey = "ClientDateTimeOffset"; public const string UserWizardPlexAuth = nameof(UserWizardPlexAuth); + public const string UserWizardMachineId = nameof(UserWizardMachineId); } } diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 4d6ca7e43..a04edca3c 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -161,7 +161,7 @@ public AdminModule(ISettingsService prService, Post["/couchpotato"] = _ => SaveCouchPotato(); Get["/plex"] = _ => Plex(); - Post["/plex"] = _ => SavePlex(); + Post["/plex", true] = async (x, ct) => await SavePlex(); Get["/sonarr"] = _ => Sonarr(); Post["/sonarr"] = _ => SaveSonarr(); @@ -170,13 +170,13 @@ public AdminModule(ISettingsService prService, Post["/sickrage"] = _ => SaveSickrage(); Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); - Post["/cpprofiles", true] = async (x,ct) => await GetCpProfiles(); + Post["/cpprofiles", true] = async (x, ct) => await GetCpProfiles(); Post["/cpapikey"] = x => GetCpApiKey(); Get["/emailnotification"] = _ => EmailNotifications(); Post["/emailnotification"] = _ => SaveEmailNotifications(); Post["/testemailnotification"] = _ => TestEmailNotifications(); - Get["/status", true] = async (x,ct) => await Status(); + Get["/status", true] = async (x, ct) => await Status(); Get["/pushbulletnotification"] = _ => PushbulletNotifications(); Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); @@ -268,7 +268,7 @@ private async Task SaveAdmin() Analytics.TrackEventAsync(Category.Admin, Action.Save, "CollectAnalyticData turned off", Username, CookieHelper.GetAnalyticClientId(Cookies)); } var result = PrService.SaveSettings(model); - + Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies)); return Response.AsJson(result ? new JsonResponseModel { Result = true } @@ -377,7 +377,7 @@ private Negotiator Plex() return View["Plex", settings]; } - private Response SavePlex() + private async Task SavePlex() { var plexSettings = this.Bind(); var valid = this.Validate(plexSettings); @@ -386,8 +386,11 @@ private Response SavePlex() return Response.AsJson(valid.SendJsonError()); } + //Lookup identifier + var server = PlexApi.GetServer(plexSettings.PlexAuthToken); + plexSettings.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plexSettings.PlexAuthToken)?.MachineIdentifier; - var result = PlexService.SaveSettings(plexSettings); + var result = await PlexService.SaveSettingsAsync(plexSettings); return Response.AsJson(result ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" } @@ -517,7 +520,7 @@ private Response SaveEmailNotifications() { if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword)) { - return Response.AsJson(new JsonResponseModel {Result = false, Message = "SMTP Authentication is enabled, please specify a username and password"}); + return Response.AsJson(new JsonResponseModel { Result = false, Message = "SMTP Authentication is enabled, please specify a username and password" }); } } @@ -542,7 +545,7 @@ private async Task Status() { var checker = new StatusChecker(); var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30); - var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true}); + var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true }); status.ReleaseNotes = md.Transform(status.ReleaseNotes); return View["Status", status]; } @@ -711,7 +714,7 @@ private async Task GetCpProfiles() private Response GetCpApiKey() { var settings = this.Bind(); - + if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password)) { return Response.AsJson(new { Message = "Please enter a username and password to request the Api Key", Result = false }); @@ -938,12 +941,12 @@ private async Task ClearLogs() { await LogsRepo.DeleteAsync(logEntity); } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully."}); + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully." }); } catch (Exception e) { Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message }); + return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message }); } } } diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 5f0fa4ed1..73f016f24 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -110,7 +110,7 @@ public SearchModule(ICacheProvider cache, ISettingsService Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); - Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); + Get["music/{searchTerm}", true] = async (x, ct) => await SearchAlbum((string)x.searchTerm); Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); @@ -252,9 +252,11 @@ private async Task ProcessMovies(MovieSearchType searchType, string se VoteCount = movie.VoteCount }; var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) + var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString()); + if (plexMovie != null) { viewMovie.Available = true; + viewMovie.PlexUrl = plexMovie.Url; } else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db { @@ -343,9 +345,12 @@ private async Task SearchTvShow(string searchTerm) providerId = viewT.Id.ToString(); } - if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) + var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), + providerId); + if (plexShow != null) { viewT.Available = true; + viewT.PlexUrl = plexShow.Url; } else if (t.show?.externals?.thetvdb != null) { @@ -371,7 +376,7 @@ private async Task SearchTvShow(string searchTerm) return Response.AsJson(viewTv); } - private async Task SearchMusic(string searchTerm) + private async Task SearchAlbum(string searchTerm) { Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); var apiAlbums = new List(); @@ -405,9 +410,11 @@ await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => DateTime release; DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) + var plexAlbum = Checker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name); + if (plexAlbum != null) { viewA.Available = true; + viewA.PlexUrl = plexAlbum.Url; } if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) { diff --git a/PlexRequests.UI/Modules/UserWizardModule.cs b/PlexRequests.UI/Modules/UserWizardModule.cs index 253288929..1bfbbd679 100644 --- a/PlexRequests.UI/Modules/UserWizardModule.cs +++ b/PlexRequests.UI/Modules/UserWizardModule.cs @@ -34,7 +34,7 @@ using Nancy.ModelBinding; using Nancy.Responses.Negotiation; using Nancy.Validation; - +using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Core; using PlexRequests.Core.SettingModels; @@ -84,7 +84,9 @@ public UserWizardModule(ISettingsService pr, ISettingsServi private ICustomUserMapper Mapper { get; } private IAnalytics Analytics { get; } - + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private Response PlexAuth() { var user = this.Bind(); @@ -103,9 +105,10 @@ private Response PlexAuth() // Set the auth token in the session so we can use it in the next form Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token; - + var servers = PlexApi.GetServer(model.user.authentication_token); var firstServer = servers.Server.FirstOrDefault(); + return Response.AsJson(new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme }); } @@ -119,6 +122,20 @@ private async Task Plex() } form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); // Set the auth token from the previous form + // Get the machine ID from the settings (This could have changed) + try + { + var servers = PlexApi.GetServer(form.PlexAuthToken); + var firstServer = servers.Server.FirstOrDefault(x => x.AccessToken == form.PlexAuthToken); + + Session[SessionKeys.UserWizardMachineId] = firstServer?.MachineIdentifier; + } + catch (Exception e) + { + // Probably bad settings, just continue + Log.Error(e); + } + var result = await PlexSettings.SaveSettingsAsync(form); if (result) { diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx index 58e8f71c8..4f23fce97 100644 --- a/PlexRequests.UI/Resources/UI.resx +++ b/PlexRequests.UI/Resources/UI.resx @@ -440,4 +440,7 @@ There is no information available for the release date + + View In Plex + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs index 20ab447d7..6cd282c32 100644 --- a/PlexRequests.UI/Resources/UI1.Designer.cs +++ b/PlexRequests.UI/Resources/UI1.Designer.cs @@ -987,6 +987,15 @@ public static string Search_TvShows { } } + /// + /// Looks up a localized string similar to View In Plex. + /// + public static string Search_ViewInPlex { + get { + return ResourceManager.GetString("Search_ViewInPlex", resourceCulture); + } + } + /// /// Looks up a localized string similar to You have reached your weekly request limit for Albums! Please contact your admin.. /// diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 17204abb1..950a7f06d 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -175,6 +175,8 @@ {{#if_eq type "movie"}} {{#if_eq available true}} +
+ @UI.Search_ViewInPlex {{else}} {{#if_eq requested true}} @@ -186,7 +188,8 @@ {{#if_eq type "tv"}} {{#if_eq tvFullyAvailable true}} @*//TODO Not used yet*@ - +
+ @UI.Search_ViewInPlex {{else}}
\ No newline at end of file From d4740aa723da4896e9561bf17d3794c95521fbd4 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 28 Aug 2016 17:09:39 +0100 Subject: [PATCH 16/19] Fixed #491 and added more logging around the email messages under the Info level --- .../Notification/EmailMessageNotification.cs | 2 +- PlexRequests.UI/Content/requests.js | 2 +- PlexRequests.UI/Content/search.js | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 07d5d01b8..a81a15290 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -193,7 +193,7 @@ private async Task Send(MimeMessage message, EmailNotificationSettings settings) { client.Authenticate(settings.EmailUsername, settings.EmailPassword); } - + Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated); await client.SendAsync(message); await client.DisconnectAsync(true); } diff --git a/PlexRequests.UI/Content/requests.js b/PlexRequests.UI/Content/requests.js index 765db5ecb..678fa60fd 100644 --- a/PlexRequests.UI/Content/requests.js +++ b/PlexRequests.UI/Content/requests.js @@ -578,7 +578,7 @@ function tvLoad() { results.forEach(function (result) { var ep = result.episodes; ep.forEach(function (episode) { - var foundItem = tvObject.find(x => x.seasonNumber === episode.seasonNumber); + var foundItem = tvObject.find(function(x) { return x.seasonNumber === episode.seasonNumber }); if (!foundItem) { var obj = { seasonNumber: episode.seasonNumber, episodes: [] } tvObject.push(obj); diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js index f720ae6ba..a6d85af44 100644 --- a/PlexRequests.UI/Content/search.js +++ b/PlexRequests.UI/Content/search.js @@ -77,7 +77,7 @@ $(function () { if (searchTimer) { clearTimeout(searchTimer); } - searchTimer = setTimeout(function() { + searchTimer = setTimeout(function () { tvSearch(); }.bind(this), 800); }); @@ -493,7 +493,7 @@ $(function () { var $content = $("#seasonsBody"); $content.html(""); $('#selectedSeasonsId').val(id); - results.forEach(function(result) { + results.forEach(function (result) { var context = buildSeasonsContext(result); $content.append(seasonsTemplate(context)); }); @@ -512,7 +512,7 @@ $(function () { }; }); - $('#seasonsRequest').click(function(e) { + $('#seasonsRequest').click(function (e) { e.preventDefault(); var tvId = $('#selectedSeasonsId').val(); var url = createBaseUrl(base, '/search/seasons/'); @@ -531,7 +531,7 @@ $(function () { var $checkedSeasons = $('.selectedSeasons:checkbox:checked'); $checkedSeasons.each(function (index, element) { - if (index < $checkedSeasons.length -1) { + if (index < $checkedSeasons.length - 1) { seasonsParam = seasonsParam + element.id + ","; } else { seasonsParam = seasonsParam + element.id; @@ -545,7 +545,7 @@ $(function () { var url = $form.prop('action'); sendRequestAjax(data, type, url, tvId); - + }); $('#episodesModal').on('show.bs.modal', function (event) { @@ -569,7 +569,9 @@ $(function () { results.forEach(function (result) { var episodes = buildEpisodesView(result); - if (!seenSeasons.find(x => x === episodes.season)) { + if (!seenSeasons.find(function(x) { + return x === episodes.season + })) { // Create the seasons heading seenSeasons.push(episodes.season); var context = buildSeasonsCount(result); @@ -595,7 +597,7 @@ $(function () { loadingButton("episodesRequest", "primary"); var tvId = $('#selectedEpisodeId').val(); - + var $form = $('#form' + tvId); var model = []; @@ -632,7 +634,7 @@ $(function () { } }, - error: function(e) { + error: function (e) { console.log(e); generateNotify("Something went wrong!", "danger"); } From 89e8bace80e9702fd76d02c96e24c9caf2a4912c Mon Sep 17 00:00:00 2001 From: tidusjar Date: Sun, 28 Aug 2016 17:13:20 +0100 Subject: [PATCH 17/19] Fixed tests --- .../PlexAvailabilityCheckerTests.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs index 3b152d7ee..801af7509 100644 --- a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs +++ b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs @@ -42,6 +42,7 @@ using PlexRequests.Helpers; using PlexRequests.Services.Jobs; using PlexRequests.Services.Models; +using PlexRequests.Services.Notification; using PlexRequests.Store.Models; using PlexRequests.Store.Repository; @@ -63,6 +64,11 @@ public class PlexAvailabilityCheckerTests private Mock JobRec { get; set; } private Mock> NotifyUsers { get; set; } private Mock> PlexEpisodes { get; set; } + private Mock Engine + { + get; + set; + } [SetUp] public void Setup() @@ -76,7 +82,8 @@ public void Setup() NotifyUsers = new Mock>(); PlexEpisodes = new Mock>(); JobRec = new Mock(); - Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object); + Engine = new Mock(); + Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object, Engine.Object); } @@ -212,8 +219,7 @@ public bool IsEpisodeAvailableTest(string providerId, int season, int episode) new PlexEpisodes {EpisodeNumber = 1, ShowTitle = "The Flash",ProviderId = 23.ToString(), SeasonNumber = 1, EpisodeTitle = "Pilot"} }; PlexEpisodes.Setup(x => x.Custom(It.IsAny>>())).Returns(expected); - Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object); - + var result = Checker.IsEpisodeAvailable(providerId, season, episode); return result; @@ -270,8 +276,6 @@ public void GetPlexTvShowsTests() public async Task GetAllPlexEpisodes() { PlexEpisodes.Setup(x => x.GetAllAsync()).ReturnsAsync(F.CreateMany().ToList()); - Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object); - var episodes = await Checker.GetEpisodes(); Assert.That(episodes.Count(), Is.GreaterThan(0)); From dbae8ecb6a23fd5ea2634e5821df7fe4aa99506c Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 30 Aug 2016 08:59:02 +0100 Subject: [PATCH 18/19] Update appveyor.yml --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 5370cbdcd..1285d7e4b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,9 +3,9 @@ configuration: Release assembly_info: patch: true file: '**\AssemblyInfo.*' - assembly_version: '1.9.0' + assembly_version: '1.9.1' assembly_file_version: '{version}' - assembly_informational_version: '1.9.0' + assembly_informational_version: '1.9.1' before_build: - cmd: appveyor-retry nuget restore build: From e5495a1b3c016d25a4b10fdc854081c057e57f2a Mon Sep 17 00:00:00 2001 From: tidusjar Date: Tue, 30 Aug 2016 09:53:13 +0100 Subject: [PATCH 19/19] Added french to the navbar --- PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml | 1 + 1 file changed, 1 insertion(+) diff --git a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml index 1c274fedd..6692f6f74 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml @@ -78,6 +78,7 @@