Fixed a bug in the Login and added a unit test to cover that.

Added a button to approve an individual request.
Fixed some minor bugs in the request screen
pull/13/head
Jamie Rees 9 years ago
parent e6bd71a359
commit 452ad07ba0

@ -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);
}
}

@ -27,6 +27,7 @@
using System; using System;
using PlexRequests.Api.Models; using PlexRequests.Api.Models;
using PlexRequests.Api.Models.Plex;
namespace PlexRequests.Api.Interfaces namespace PlexRequests.Api.Interfaces
{ {

@ -46,6 +46,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="IApiRequest.cs" /> <Compile Include="IApiRequest.cs" />
<Compile Include="ICouchPotatoApi.cs" />
<Compile Include="IPlexApi.cs" /> <Compile Include="IPlexApi.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

@ -24,9 +24,10 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System.Collections.Generic; using System.Collections.Generic;
namespace PlexRequests.Api.Models namespace PlexRequests.Api.Models.Plex
{ {
public class PlexAuthentication public class PlexAuthentication
{ {

@ -24,9 +24,10 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System.Xml.Serialization; using System.Xml.Serialization;
namespace PlexRequests.Api.Models namespace PlexRequests.Api.Models.Plex
{ {
[XmlRoot(ElementName = "errors")] [XmlRoot(ElementName = "errors")]
public class PlexError public class PlexError

@ -24,10 +24,10 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
using System.Collections.Generic;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace PlexRequests.Api.Models namespace PlexRequests.Api.Models.Plex
{ {
[XmlRoot(ElementName = "Server")] [XmlRoot(ElementName = "Server")]
public class Server public class Server

@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Api.Models</RootNamespace> <RootNamespace>PlexRequests.Api.Models</RootNamespace>
<AssemblyName>PlexRequests.Api.Models</AssemblyName> <AssemblyName>PlexRequests.Api.Models</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>

@ -36,7 +36,6 @@ using Newtonsoft.Json.Linq;
using NLog; using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models;
using RestSharp; using RestSharp;

@ -29,12 +29,12 @@ using System;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NLog; using NLog;
using PlexRequests.Api.Interfaces;
using RestSharp; using RestSharp;
namespace PlexRequests.Api namespace PlexRequests.Api
{ {
public class CouchPotatoApi public class CouchPotatoApi : ICouchPotatoApi
{ {
public CouchPotatoApi() public CouchPotatoApi()
{ {

@ -28,6 +28,7 @@ using System;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models; using PlexRequests.Api.Models;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using RestSharp; using RestSharp;

@ -37,6 +37,7 @@ using NUnit.Framework;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models; using PlexRequests.Api.Models;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -91,6 +92,39 @@ namespace PlexRequests.UI.Tests
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never); PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), 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<UserLoginModule>();
with.Dependency(AuthMock.Object);
with.Dependency(PlexMock.Object);
with.RootPathProvider<TestRootPathProvider>();
});
bootstrapper.WithSession(new Dictionary<string, object>());
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<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
AuthMock.Verify(x => x.GetSettings(), Times.Never);
PlexMock.Verify(x => x.SignIn(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
}
[Test] [Test]
public void LoginWithUsernameSuccessfully() public void LoginWithUsernameSuccessfully()
{ {

@ -76,6 +76,10 @@ namespace PlexRequests.UI
container.Register<IConfigurationReader, ConfigurationReader>(); container.Register<IConfigurationReader, ConfigurationReader>();
container.Register<IIntervals, UpdateInterval>(); container.Register<IIntervals, UpdateInterval>();
container.Register<ICouchPotatoApi, CouchPotatoApi>();
container.Register<IPlexApi, PlexApi>(); container.Register<IPlexApi, PlexApi>();
base.ConfigureRequestContainer(container, context); base.ConfigureRequestContainer(container, context);

@ -13,6 +13,7 @@ var tvimer = 0;
movieLoad(); movieLoad();
tvLoad(); tvLoad();
// Approve all
$('#approveAll').click(function () { $('#approveAll').click(function () {
$.ajax({ $.ajax({
type: 'post', 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 // Clear issues
$(document).on("click", ".clear", function (e) { $(document).on("click", ".clear", function (e) {
e.preventDefault(); e.preventDefault();

@ -5,7 +5,13 @@
message: message message: message
}, { }, {
// settings // settings
type: type type: type,
animate: {
enter: 'animated bounceInDown',
exit: 'animated bounceOutUp'
},
newest_on_top: true
}); });
} }

@ -32,7 +32,10 @@ using Nancy;
using Nancy.Security; using Nancy.Security;
using NLog; using NLog;
using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -41,11 +44,13 @@ namespace PlexRequests.UI.Modules
public class ApprovalModule : BaseModule public class ApprovalModule : BaseModule
{ {
public ApprovalModule(IRepository<RequestedModel> service) : base("approval") public ApprovalModule(IRepository<RequestedModel> service, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi) : base("approval")
{ {
this.RequiresAuthentication(); this.RequiresAuthentication();
Service = service; Service = service;
CpService = cpService;
CpApi = cpApi;
Post["/approve"] = parameters => Approve((int)Request.Form.requestid); Post["/approve"] = parameters => Approve((int)Request.Form.requestid);
Post["/approveall"] = x => ApproveAll(); Post["/approveall"] = x => ApproveAll();
@ -54,6 +59,8 @@ namespace PlexRequests.UI.Modules
private IRepository<RequestedModel> Service { get; set; } private IRepository<RequestedModel> Service { get; set; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ICouchPotatoApi CpApi { get; }
/// <summary> /// <summary>
/// Approves the specified request identifier. /// Approves the specified request identifier.
@ -62,6 +69,10 @@ namespace PlexRequests.UI.Modules
/// <returns></returns> /// <returns></returns>
private Response Approve(int requestId) 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 // Get the request from the DB
var request = Service.Get(requestId); 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." }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
} }
// Approve it switch (request.Type)
request.Approved = true; {
case RequestType.Movie:
return RequestMovieAndUpdateStatus(request);
case RequestType.TvShow:
return RequestTvAndUpdateStatus(request);
default:
throw new ArgumentOutOfRangeException(nameof(request));
}
}
// Update the record private Response RequestTvAndUpdateStatus(RequestedModel request)
var result = Service.Update(request); {
// TODO
return Response.AsJson(new JsonResponseModel());
}
return Response.AsJson(result private Response RequestMovieAndUpdateStatus(RequestedModel request)
? new JsonResponseModel { Result = true } {
: new JsonResponseModel { Result = false, Message = "We could not approve this request. Please try again or check the logs." }); 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."
});
} }
/// <summary> /// <summary>
@ -88,18 +143,36 @@ namespace PlexRequests.UI.Modules
/// <returns></returns> /// <returns></returns>
private Response ApproveAll() private Response ApproveAll()
{ {
var requests = Service.GetAll(); var requests = Service.GetAll().Where(x => x.Approved == false);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray(); var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any()) if (!requestedModels.Any())
{ {
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
} }
var cpSettings = CpService.GetSettings();
var updatedRequests = new List<RequestedModel>(); var updatedRequests = new List<RequestedModel>();
foreach (var r in requestedModels) foreach (var r in requestedModels)
{ {
r.Approved = true; if (r.Type == RequestType.Movie)
updatedRequests.Add(r); {
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 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;
}
} }
} }

@ -32,6 +32,7 @@ using Nancy.Responses.Negotiation;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models; using PlexRequests.Api.Models;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -61,10 +62,17 @@ namespace PlexRequests.UI.Modules
private Response LoginUser() 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 authenticated = false;
var settings = AuthService.GetSettings(); var settings = AuthService.GetSettings();
var username = Request.Form.username.Value;
if (IsUserInDeniedList(username, settings)) if (IsUserInDeniedList(username, settings))
{ {

@ -4,7 +4,9 @@
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4> <h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
@if (Context.CurrentUser.IsAuthenticated()) @if (Context.CurrentUser.IsAuthenticated())
{ {
<button id="approveAll" class="btn btn-primary" type="submit"><i class="fa fa-plus"></i> Approve All</button> <button id="approveAll" class="btn btn-success" type="submit"><i class="fa fa-plus"></i> Approve All</button>
<br/>
<br/>
} }
<!-- Nav tabs --> <!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist"> <ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@ -50,7 +52,7 @@
<script id="search-template" type="text/x-handlebars-template"> <script id="search-template" type="text/x-handlebars-template">
<div id="{{id}}Template"> <div id="{{requestId}}Template">
<div class="row"> <div class="row">
<div class="col-sm-2"> <div class="col-sm-2">
{{#if_eq type "movie"}} {{#if_eq type "movie"}}
@ -76,7 +78,7 @@
<p> <p>
Approved: Approved:
{{#if_eq approved false}} {{#if_eq approved false}}
<i class="fa fa-times"></i> <i id="{{requestId}}notapproved" class="fa fa-times"></i>
{{/if_eq}} {{/if_eq}}
{{#if_eq approved true}} {{#if_eq approved true}}
<i class="fa fa-check"></i> <i class="fa fa-check"></i>
@ -105,6 +107,12 @@
<br /> <br />
<br /> <br />
{{#if_eq admin true}} {{#if_eq admin true}}
{{#if_eq approved false}}
<form method="POST" action="/approval/approve" id="approve{{requestId}}">
<input name="requestId" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" custom-button="{{requestId}}" style="text-align: right" class="btn btn-success approve" type="submit"><i class="fa fa-plus"></i> Approve</button>
</form>
{{/if_eq}}
<form method="POST" action="/requests/delete" id="delete{{requestId}}"> <form method="POST" action="/requests/delete" id="delete{{requestId}}">
<input name="Id" type="text" value="{{requestId}}" hidden="hidden" /> <input name="Id" type="text" value="{{requestId}}" hidden="hidden" />
<button id="{{requestId}}" style="text-align: right" class="btn btn-danger delete" type="submit"><i class="fa fa-plus"></i> Remove</button> <button id="{{requestId}}" style="text-align: right" class="btn btn-danger delete" type="submit"><i class="fa fa-plus"></i> Remove</button>

Loading…
Cancel
Save