diff --git a/PlexRequests.Api.Interfaces/ICouchPotatoApi.cs b/PlexRequests.Api.Interfaces/ICouchPotatoApi.cs new file mode 100644 index 000000000..a7b17d61c --- /dev/null +++ b/PlexRequests.Api.Interfaces/ICouchPotatoApi.cs @@ -0,0 +1,37 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: ICouchPotatoApi.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; + +namespace PlexRequests.Api.Interfaces +{ + public interface ICouchPotatoApi + { + bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl); + + } +} \ No newline at end of file diff --git a/PlexRequests.Api.Interfaces/IPlexApi.cs b/PlexRequests.Api.Interfaces/IPlexApi.cs index 3ce0ee7d0..59fffb5d5 100644 --- a/PlexRequests.Api.Interfaces/IPlexApi.cs +++ b/PlexRequests.Api.Interfaces/IPlexApi.cs @@ -27,6 +27,7 @@ using System; using PlexRequests.Api.Models; +using PlexRequests.Api.Models.Plex; namespace PlexRequests.Api.Interfaces { diff --git a/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj b/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj index 005a42c47..45ff3157f 100644 --- a/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj +++ b/PlexRequests.Api.Interfaces/PlexRequests.Api.Interfaces.csproj @@ -46,6 +46,7 @@ + diff --git a/PlexRequests.Api.Models/Plex/PlexAuthentication.cs b/PlexRequests.Api.Models/Plex/PlexAuthentication.cs index 36f65c1b8..7d4a1a0be 100644 --- a/PlexRequests.Api.Models/Plex/PlexAuthentication.cs +++ b/PlexRequests.Api.Models/Plex/PlexAuthentication.cs @@ -24,9 +24,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + using System.Collections.Generic; -namespace PlexRequests.Api.Models +namespace PlexRequests.Api.Models.Plex { public class PlexAuthentication { diff --git a/PlexRequests.Api.Models/Plex/PlexError.cs b/PlexRequests.Api.Models/Plex/PlexError.cs index 7dc696fc9..f62a0a7f0 100644 --- a/PlexRequests.Api.Models/Plex/PlexError.cs +++ b/PlexRequests.Api.Models/Plex/PlexError.cs @@ -24,9 +24,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + using System.Xml.Serialization; -namespace PlexRequests.Api.Models +namespace PlexRequests.Api.Models.Plex { [XmlRoot(ElementName = "errors")] public class PlexError diff --git a/PlexRequests.Api.Models/Plex/PlexFriends.cs b/PlexRequests.Api.Models/Plex/PlexFriends.cs index db3523057..886b12dbf 100644 --- a/PlexRequests.Api.Models/Plex/PlexFriends.cs +++ b/PlexRequests.Api.Models/Plex/PlexFriends.cs @@ -24,10 +24,10 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion -using System.Collections.Generic; + using System.Xml.Serialization; -namespace PlexRequests.Api.Models +namespace PlexRequests.Api.Models.Plex { [XmlRoot(ElementName = "Server")] public class Server diff --git a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj index 6025b2705..21d6ccefc 100644 --- a/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj +++ b/PlexRequests.Api.Models/PlexRequests.Api.Models.csproj @@ -9,8 +9,9 @@ Properties PlexRequests.Api.Models PlexRequests.Api.Models - v4.5.2 + v4.6 512 + true diff --git a/PlexRequests.Api/ApiRequest.cs b/PlexRequests.Api/ApiRequest.cs index 205089a34..f90aec7cf 100644 --- a/PlexRequests.Api/ApiRequest.cs +++ b/PlexRequests.Api/ApiRequest.cs @@ -36,7 +36,6 @@ using Newtonsoft.Json.Linq; using NLog; using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models; using RestSharp; diff --git a/PlexRequests.Api/CouchPotatoApi.cs b/PlexRequests.Api/CouchPotatoApi.cs index cf9fd7d04..0f8e91fcd 100644 --- a/PlexRequests.Api/CouchPotatoApi.cs +++ b/PlexRequests.Api/CouchPotatoApi.cs @@ -29,12 +29,12 @@ using System; using Newtonsoft.Json.Linq; using NLog; - +using PlexRequests.Api.Interfaces; using RestSharp; namespace PlexRequests.Api { - public class CouchPotatoApi + public class CouchPotatoApi : ICouchPotatoApi { public CouchPotatoApi() { diff --git a/PlexRequests.Api/PlexApi.cs b/PlexRequests.Api/PlexApi.cs index 9fc190e6b..cf31ce53e 100644 --- a/PlexRequests.Api/PlexApi.cs +++ b/PlexRequests.Api/PlexApi.cs @@ -28,6 +28,7 @@ using System; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models; +using PlexRequests.Api.Models.Plex; using PlexRequests.Helpers; using RestSharp; diff --git a/PlexRequests.UI.Tests/UserLoginModuleTests.cs b/PlexRequests.UI.Tests/UserLoginModuleTests.cs index 444f679be..6b4e0b4d3 100644 --- a/PlexRequests.UI.Tests/UserLoginModuleTests.cs +++ b/PlexRequests.UI.Tests/UserLoginModuleTests.cs @@ -37,6 +37,7 @@ using NUnit.Framework; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models; +using PlexRequests.Api.Models.Plex; using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.UI.Models; @@ -91,6 +92,39 @@ namespace PlexRequests.UI.Tests PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); } + [Test] + public void LoginWithoutAuthenticationWithEmptyUsername() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + var bootstrapper = new ConfigurableBootstrapper(with => + { + with.Module(); + with.Dependency(AuthMock.Object); + with.Dependency(PlexMock.Object); + with.RootPathProvider(); + }); + + bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", string.Empty); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + AuthMock.Verify(x => x.GetSettings(), Times.Never); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + [Test] public void LoginWithUsernameSuccessfully() { diff --git a/PlexRequests.UI/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index f27f9a0ac..7d9a09bc3 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -76,6 +76,10 @@ namespace PlexRequests.UI container.Register(); container.Register(); + container.Register(); + + + container.Register(); base.ConfigureRequestContainer(container, context); diff --git a/PlexRequests.UI/Content/requests.js b/PlexRequests.UI/Content/requests.js index e216b0ee5..f3651efac 100644 --- a/PlexRequests.UI/Content/requests.js +++ b/PlexRequests.UI/Content/requests.js @@ -13,6 +13,7 @@ var tvimer = 0; movieLoad(); tvLoad(); +// Approve all $('#approveAll').click(function () { $.ajax({ type: 'post', @@ -127,6 +128,34 @@ $(document).on("click", ".delete", function (e) { }); +// Approve single request +$(document).on("click", ".approve", function (e) { + e.preventDefault(); + var buttonId = e.target.id; + var $form = $('#approve' + buttonId); + + $.ajax({ + type: $form.prop('method'), + url: $form.prop('action'), + data: $form.serialize(), + dataType: "json", + success: function (response) { + + if (checkJsonResponse(response)) { + generateNotify("Success! Request Approved.", "success"); + + $("button[custom-button='" + buttonId + "']").remove(); + $("#" + buttonId + "notapproved").prop("class", "fa fa-check"); + } + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); + +}); + // Clear issues $(document).on("click", ".clear", function (e) { e.preventDefault(); diff --git a/PlexRequests.UI/Content/site.js b/PlexRequests.UI/Content/site.js index 4f8efeac6..d16a70def 100644 --- a/PlexRequests.UI/Content/site.js +++ b/PlexRequests.UI/Content/site.js @@ -5,7 +5,13 @@ message: message }, { // settings - type: type + type: type, + animate: { + enter: 'animated bounceInDown', + exit: 'animated bounceOutUp' + }, + newest_on_top: true + }); } diff --git a/PlexRequests.UI/Modules/ApprovalModule.cs b/PlexRequests.UI/Modules/ApprovalModule.cs index 4128fdab9..2c11b46b5 100644 --- a/PlexRequests.UI/Modules/ApprovalModule.cs +++ b/PlexRequests.UI/Modules/ApprovalModule.cs @@ -32,7 +32,10 @@ using Nancy; using Nancy.Security; using NLog; - +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; using PlexRequests.Store; using PlexRequests.UI.Models; @@ -41,11 +44,13 @@ namespace PlexRequests.UI.Modules public class ApprovalModule : BaseModule { - public ApprovalModule(IRepository service) : base("approval") + public ApprovalModule(IRepository service, ISettingsService cpService, ICouchPotatoApi cpApi) : base("approval") { this.RequiresAuthentication(); Service = service; + CpService = cpService; + CpApi = cpApi; Post["/approve"] = parameters => Approve((int)Request.Form.requestid); Post["/approveall"] = x => ApproveAll(); @@ -54,6 +59,8 @@ namespace PlexRequests.UI.Modules private IRepository Service { get; set; } private static Logger Log = LogManager.GetCurrentClassLogger(); + private ISettingsService CpService { get; } + private ICouchPotatoApi CpApi { get; } /// /// Approves the specified request identifier. @@ -62,6 +69,10 @@ namespace PlexRequests.UI.Modules /// private Response Approve(int requestId) { + if (!Context.CurrentUser.IsAuthenticated()) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." }); + } // Get the request from the DB var request = Service.Get(requestId); @@ -71,15 +82,59 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." }); } - // Approve it - request.Approved = true; + switch (request.Type) + { + case RequestType.Movie: + return RequestMovieAndUpdateStatus(request); + case RequestType.TvShow: + return RequestTvAndUpdateStatus(request); + default: + throw new ArgumentOutOfRangeException(nameof(request)); + } + } - // Update the record - var result = Service.Update(request); + private Response RequestTvAndUpdateStatus(RequestedModel request) + { + // TODO + return Response.AsJson(new JsonResponseModel()); + } - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "We could not approve this request. Please try again or check the logs." }); + private Response RequestMovieAndUpdateStatus(RequestedModel request) + { + if (!Context.CurrentUser.IsAuthenticated()) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." }); + } + + var cpSettings = CpService.GetSettings(); + var cp = new CouchPotatoApi(); + Log.Info("Adding movie to CP : {0}", request.Title); + var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri); + Log.Trace("Adding movie to CP result {0}", result); + if (result) + { + // Approve it + request.Approved = true; + + // Update the record + var inserted = Service.Update(request); + + return Response.AsJson(inserted + ? new JsonResponseModel {Result = true} + : new JsonResponseModel + { + Result = false, + Message = "We could not approve this request. Please try again or check the logs." + }); + } + return + Response.AsJson( + new + { + Result = false, + Message = + "Something went wrong adding the movie to CouchPotato! Please check your settings." + }); } /// @@ -88,18 +143,36 @@ namespace PlexRequests.UI.Modules /// private Response ApproveAll() { - var requests = Service.GetAll(); + var requests = Service.GetAll().Where(x => x.Approved == false); var requestedModels = requests as RequestedModel[] ?? requests.ToArray(); if (!requestedModels.Any()) { return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." }); } + var cpSettings = CpService.GetSettings(); + + var updatedRequests = new List(); foreach (var r in requestedModels) { - r.Approved = true; - updatedRequests.Add(r); + if (r.Type == RequestType.Movie) + { + var result = SendMovie(cpSettings, r, CpApi); + if (result) + { + r.Approved = true; + updatedRequests.Add(r); + } + else + { + Log.Error("Could not approve send the movie {0} to couch potato!", r.Title); + } + } + if (r.Type == RequestType.TvShow) + { + // TODO + } } try { @@ -116,5 +189,14 @@ namespace PlexRequests.UI.Modules } } + + + private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp) + { + Log.Info("Adding movie to CP : {0}", r.Title); + var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri); + Log.Trace("Adding movie to CP result {0}", result); + return result; + } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index fba37f859..e8f6d214e 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -32,6 +32,7 @@ using Nancy.Responses.Negotiation; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models; +using PlexRequests.Api.Models.Plex; using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.UI.Models; @@ -61,10 +62,17 @@ namespace PlexRequests.UI.Modules private Response LoginUser() { + var username = Request.Form.username.Value; + + if (string.IsNullOrWhiteSpace(username)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); + } + var authenticated = false; var settings = AuthService.GetSettings(); - var username = Request.Form.username.Value; + if (IsUserInDeniedList(username, settings)) { diff --git a/PlexRequests.UI/Views/Requests/Index.cshtml b/PlexRequests.UI/Views/Requests/Index.cshtml index f2bce55b3..586ce2406 100644 --- a/PlexRequests.UI/Views/Requests/Index.cshtml +++ b/PlexRequests.UI/Views/Requests/Index.cshtml @@ -4,7 +4,9 @@

Below you can see yours and all other requests, as well as their download and approval status.

@if (Context.CurrentUser.IsAuthenticated()) { - + +
+
}