Merge pull request #13 from tidusjar/dev

New features added
pull/14/head^2 v1.2.0
Jamie 9 years ago
commit 5d4c1fd4c2

@ -27,11 +27,13 @@
using System; using System;
using PlexRequests.Api.Models.Movie;
namespace PlexRequests.Api.Interfaces namespace PlexRequests.Api.Interfaces
{ {
public interface ICouchPotatoApi public interface ICouchPotatoApi
{ {
bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl); bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl);
CouchPotatoStatus GetStatus(Uri url, string apiKey);
} }
} }

@ -36,6 +36,7 @@ namespace PlexRequests.Api.Interfaces
PlexAuthentication SignIn(string username, string password); PlexAuthentication SignIn(string username, string password);
PlexFriends GetUsers(string authToken); PlexFriends GetUsers(string authToken);
PlexSearch SearchContent(string authToken, string searchTerm, Uri plexFullHost); PlexSearch SearchContent(string authToken, string searchTerm, Uri plexFullHost);
PlexStatus GetStatus(string authToken, Uri uri);
} }
} }

@ -37,5 +37,7 @@ namespace PlexRequests.Api.Interfaces
SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath, SonarrAddSeries AddSeries(int tvdbId, string title, int qualityId, bool seasonFolders, string rootPath,
bool episodes, string apiKey, Uri baseUrl); bool episodes, string apiKey, Uri baseUrl);
SystemStatus SystemStatus(string apiKey, Uri baseUrl);
} }
} }

@ -34,3 +34,5 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PlexRequests.Api.Models.Movie
{
public class CouchPotatoStatus
{
public bool success { get; set; }
}
}

@ -24,11 +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;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace PlexRequests.Api.Models namespace PlexRequests.Api.Models.Plex
{ {
[XmlRoot(ElementName = "Part")] [XmlRoot(ElementName = "Part")]
public class Part public class Part

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace PlexRequests.Api.Models.Plex
{
[XmlRoot(ElementName = "Directory")]
public class Directory
{
[XmlAttribute(AttributeName = "count")]
public string Count { get; set; }
[XmlAttribute(AttributeName = "key")]
public string Key { get; set; }
[XmlAttribute(AttributeName = "title")]
public string Title { get; set; }
}
[XmlRoot(ElementName = "MediaContainer")]
public class PlexStatus
{
[XmlElement(ElementName = "Directory")]
public List<Directory> Directory { get; set; }
[XmlAttribute(AttributeName = "size")]
public string Size { get; set; }
[XmlAttribute(AttributeName = "allowCameraUpload")]
public string AllowCameraUpload { get; set; }
[XmlAttribute(AttributeName = "allowChannelAccess")]
public string AllowChannelAccess { get; set; }
[XmlAttribute(AttributeName = "allowMediaDeletion")]
public string AllowMediaDeletion { get; set; }
[XmlAttribute(AttributeName = "allowSync")]
public string AllowSync { get; set; }
[XmlAttribute(AttributeName = "backgroundProcessing")]
public string BackgroundProcessing { get; set; }
[XmlAttribute(AttributeName = "certificate")]
public string Certificate { get; set; }
[XmlAttribute(AttributeName = "companionProxy")]
public string CompanionProxy { get; set; }
[XmlAttribute(AttributeName = "friendlyName")]
public string FriendlyName { get; set; }
[XmlAttribute(AttributeName = "machineIdentifier")]
public string MachineIdentifier { get; set; }
[XmlAttribute(AttributeName = "multiuser")]
public string Multiuser { get; set; }
[XmlAttribute(AttributeName = "myPlex")]
public string MyPlex { get; set; }
[XmlAttribute(AttributeName = "myPlexMappingState")]
public string MyPlexMappingState { get; set; }
[XmlAttribute(AttributeName = "myPlexSigninState")]
public string MyPlexSigninState { get; set; }
[XmlAttribute(AttributeName = "myPlexSubscription")]
public string MyPlexSubscription { get; set; }
[XmlAttribute(AttributeName = "myPlexUsername")]
public string MyPlexUsername { get; set; }
[XmlAttribute(AttributeName = "platform")]
public string Platform { get; set; }
[XmlAttribute(AttributeName = "platformVersion")]
public string PlatformVersion { get; set; }
[XmlAttribute(AttributeName = "requestParametersInCookie")]
public string RequestParametersInCookie { get; set; }
[XmlAttribute(AttributeName = "sync")]
public string Sync { get; set; }
[XmlAttribute(AttributeName = "transcoderActiveVideoSessions")]
public string TranscoderActiveVideoSessions { get; set; }
[XmlAttribute(AttributeName = "transcoderAudio")]
public string TranscoderAudio { get; set; }
[XmlAttribute(AttributeName = "transcoderLyrics")]
public string TranscoderLyrics { get; set; }
[XmlAttribute(AttributeName = "transcoderPhoto")]
public string TranscoderPhoto { get; set; }
[XmlAttribute(AttributeName = "transcoderSubtitles")]
public string TranscoderSubtitles { get; set; }
[XmlAttribute(AttributeName = "transcoderVideo")]
public string TranscoderVideo { get; set; }
[XmlAttribute(AttributeName = "transcoderVideoBitrates")]
public string TranscoderVideoBitrates { get; set; }
[XmlAttribute(AttributeName = "transcoderVideoQualities")]
public string TranscoderVideoQualities { get; set; }
[XmlAttribute(AttributeName = "transcoderVideoResolutions")]
public string TranscoderVideoResolutions { get; set; }
[XmlAttribute(AttributeName = "updatedAt")]
public string UpdatedAt { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
}

@ -25,7 +25,7 @@
// ************************************************************************/ // ************************************************************************/
#endregion #endregion
namespace PlexRequests.Api.Models namespace PlexRequests.Api.Models.Plex
{ {
public class PlexUserRequest public class PlexUserRequest
{ {

@ -42,14 +42,17 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Movie\CouchPotatoAdd.cs" /> <Compile Include="Movie\CouchPotatoAdd.cs" />
<Compile Include="Movie\CouchPotatoStatus.cs" />
<Compile Include="Plex\PlexAuthentication.cs" /> <Compile Include="Plex\PlexAuthentication.cs" />
<Compile Include="Plex\PlexError.cs" /> <Compile Include="Plex\PlexError.cs" />
<Compile Include="Plex\PlexFriends.cs" /> <Compile Include="Plex\PlexFriends.cs" />
<Compile Include="Plex\PlexSearch.cs" /> <Compile Include="Plex\PlexSearch.cs" />
<Compile Include="Plex\PlexStatus.cs" />
<Compile Include="Plex\PlexUserRequest.cs" /> <Compile Include="Plex\PlexUserRequest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Sonarr\SonarrAddSeries.cs" /> <Compile Include="Sonarr\SonarrAddSeries.cs" />
<Compile Include="Sonarr\SonarrProfile.cs" /> <Compile Include="Sonarr\SonarrProfile.cs" />
<Compile Include="Sonarr\SystemStatus.cs" />
<Compile Include="Tv\Authentication.cs" /> <Compile Include="Tv\Authentication.cs" />
<Compile Include="Tv\TvSearchResult.cs" /> <Compile Include="Tv\TvSearchResult.cs" />
<Compile Include="Tv\TvShow.cs" /> <Compile Include="Tv\TvShow.cs" />

@ -34,3 +34,5 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,51 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SystemStatus.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.Api.Models.Sonarr
{
public class SystemStatus
{
public string version { get; set; }
public string buildTime { get; set; }
public bool isDebug { get; set; }
public bool isProduction { get; set; }
public bool isAdmin { get; set; }
public bool isUserInteractive { get; set; }
public string startupPath { get; set; }
public string appData { get; set; }
public string osVersion { get; set; }
public bool isMonoRuntime { get; set; }
public bool isMono { get; set; }
public bool isLinux { get; set; }
public bool isOsx { get; set; }
public bool isWindows { get; set; }
public string branch { get; set; }
public string authentication { get; set; }
public string sqliteVersion { get; set; }
public string urlBase { get; set; }
public string runtimeVersion { get; set; }
}
}

@ -95,10 +95,17 @@ namespace PlexRequests.Api
throw new ApplicationException(message, response.ErrorException); throw new ApplicationException(message, response.ErrorException);
} }
var json = JsonConvert.DeserializeObject<T>(response.Content); try
{
return json;
var json = JsonConvert.DeserializeObject<T>(response.Content);
return json;
}
catch (Exception e)
{
Log.Fatal(e);
throw;
}
} }
public T DeserializeXml<T>(string input) public T DeserializeXml<T>(string input)

@ -30,6 +30,8 @@ using Newtonsoft.Json.Linq;
using NLog; using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Movie;
using RestSharp; using RestSharp;
namespace PlexRequests.Api namespace PlexRequests.Api
@ -71,5 +73,25 @@ namespace PlexRequests.Api
} }
return false; return false;
} }
/// <summary>
/// Gets the status.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="apiKey">The API key.</param>
/// <returns></returns>
public CouchPotatoStatus GetStatus(Uri url, string apiKey)
{
Log.Trace("Getting CP Status, ApiKey = {0}", apiKey);
var request = new RestRequest
{
Resource = "api/{apikey}/app.available/",
Method = Method.GET
};
request.AddUrlSegment("apikey", apiKey);
return Api.Execute<CouchPotatoStatus>(request,url);
}
} }
} }

@ -50,5 +50,10 @@ namespace PlexRequests.Api.Mocks
var obj = JsonConvert.DeserializeObject<SonarrAddSeries>(json); var obj = JsonConvert.DeserializeObject<SonarrAddSeries>(json);
return obj; return obj;
} }
public SystemStatus SystemStatus(string apiKey, Uri baseUrl)
{
throw new NotImplementedException();
}
} }
} }

@ -115,6 +115,25 @@ namespace PlexRequests.Api
return search; return search;
} }
public PlexStatus GetStatus(string authToken, Uri uri)
{
var request = new RestRequest
{
Method = Method.GET,
};
request.AddHeader("X-Plex-Client-Identifier", "Test213");
request.AddHeader("X-Plex-Product", "Request Plex");
request.AddHeader("X-Plex-Version", Version);
request.AddHeader("X-Plex-Token", authToken);
request.AddHeader("Content-Type", "application/xml");
var api = new ApiRequest();
var users = api.ExecuteXml<PlexStatus>(request, uri);
return users;
}
} }
} }

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -98,5 +98,15 @@ namespace PlexRequests.Api
return obj; return obj;
} }
public SystemStatus SystemStatus(string apiKey, Uri baseUrl)
{
var request = new RestRequest { Resource = "/api/system/status", Method = Method.GET };
request.AddHeader("X-Api-Key", apiKey);
var obj = Api.ExecuteJson<SystemStatus>(request, baseUrl);
return obj;
}
} }
} }

@ -59,6 +59,7 @@
<Otherwise /> <Otherwise />
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="StatusCheckerTests.cs" />
<Compile Include="AuthenticationSettingsTests.cs" /> <Compile Include="AuthenticationSettingsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
@ -71,6 +72,10 @@
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project> <Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name> <Name>PlexRequests.Core</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
</ItemGroup> </ItemGroup>
<Choose> <Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,45 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AuthenticationSettingsTests.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 NUnit.Framework;
namespace PlexRequests.Core.Tests
{
[TestFixture]
public class StatusCheckerTests
{
[Test]
public void CheckStatusTest()
{
var checker = new StatusChecker();
var status = checker.GetStatus();
Assert.That(status, Is.Not.Null);
}
}
}

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/> <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /></startup></configuration>

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StatusModel.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.Core.Models
{
public class StatusModel
{
public string Version { get; set; }
public bool UpdateAvailable { get; set; }
public int ReleasesBehind { get; set; }
public string UpdateUri { get; set; }
}
}

@ -46,6 +46,10 @@
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Octokit, Version=0.19.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Octokit.0.19.0\lib\net45\Octokit.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Omu.ValueInjecter, Version=3.1.1.0, Culture=neutral, PublicKeyToken=c7694541b0ac80e4, processorArchitecture=MSIL"> <Reference Include="Omu.ValueInjecter, Version=3.1.1.0, Culture=neutral, PublicKeyToken=c7694541b0ac80e4, processorArchitecture=MSIL">
<HintPath>..\packages\valueinjecter.3.1.1.2\lib\net40\Omu.ValueInjecter.dll</HintPath> <HintPath>..\packages\valueinjecter.3.1.1.2\lib\net40\Omu.ValueInjecter.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -66,6 +70,7 @@
<Compile Include="CacheKeys.cs" /> <Compile Include="CacheKeys.cs" />
<Compile Include="IRequestService.cs" /> <Compile Include="IRequestService.cs" />
<Compile Include="ISettingsService.cs" /> <Compile Include="ISettingsService.cs" />
<Compile Include="Models\StatusModel.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" /> <Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\EmailNotificationSettings.cs" /> <Compile Include="SettingModels\EmailNotificationSettings.cs" />
<Compile Include="SettingModels\PlexSettings.cs" /> <Compile Include="SettingModels\PlexSettings.cs" />
@ -77,6 +82,7 @@
<Compile Include="RequestService.cs" /> <Compile Include="RequestService.cs" />
<Compile Include="SettingsServiceV2.cs" /> <Compile Include="SettingsServiceV2.cs" />
<Compile Include="Setup.cs" /> <Compile Include="Setup.cs" />
<Compile Include="StatusChecker.cs" />
<Compile Include="UserIdentity.cs" /> <Compile Include="UserIdentity.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UserMapper.cs" /> <Compile Include="UserMapper.cs" />

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,78 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: StatusChecker.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 System.Reflection;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Octokit;
using PlexRequests.Core.Models;
using PlexRequests.Helpers;
namespace PlexRequests.Core
{
public class StatusChecker
{
public StatusChecker()
{
Git = new GitHubClient(new ProductHeaderValue("PlexRequests-StatusChecker"));
}
private IGitHubClient Git { get; }
private const string Owner = "tidusjar";
private const string RepoName = "PlexRequests.Net";
public async Task<Release> GetLatestRelease()
{
var releases = await Git.Repository.Release.GetAll(Owner, RepoName);
return releases.FirstOrDefault();
}
public StatusModel GetStatus()
{
var assemblyVersion = AssemblyHelper.GetProductVersion();
var model = new StatusModel
{
Version = assemblyVersion,
};
var latestRelease = GetLatestRelease();
var latestVersionArray = latestRelease.Result.Name.Split(new[] { 'v' }, StringSplitOptions.RemoveEmptyEntries);
var latestVersion = latestVersionArray.Length > 1 ? latestVersionArray[1] : string.Empty;
if (!latestVersion.Equals(assemblyVersion, StringComparison.InvariantCultureIgnoreCase))
{
model.UpdateAvailable = true;
model.UpdateUri = latestRelease.Result.HtmlUrl;
}
return model;
}
}
}

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<runtime> <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly> <dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/> <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/> <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly> </dependentAssembly>
</assemblyBinding> </assemblyBinding>
</runtime> </runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /></startup></configuration>

@ -3,5 +3,6 @@
<package id="Nancy" version="1.4.3" targetFramework="net452" /> <package id="Nancy" version="1.4.3" targetFramework="net452" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" /> <package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" /> <package id="Newtonsoft.Json" version="8.0.2" targetFramework="net452" />
<package id="Octokit" version="0.19.0" targetFramework="net46" />
<package id="valueinjecter" version="3.1.1.2" targetFramework="net452" /> <package id="valueinjecter" version="3.1.1.2" targetFramework="net452" />
</packages> </packages>

@ -0,0 +1,41 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AssemblyHelperTests.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 NUnit.Framework;
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class AssemblyHelperTests
{
[Test]
public void GetReleaseVersionTest()
{
var result = AssemblyHelper.GetProductVersion();
Assert.That(result, Is.Not.Null);
}
}
}

@ -35,14 +35,6 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Hangfire.Core, Version=1.5.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Hangfire.Core.1.5.3\lib\net45\Hangfire.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Hangfire.SqlServer, Version=1.5.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Hangfire.SqlServer.1.5.3\lib\net45\Hangfire.SqlServer.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -78,6 +70,7 @@
</Otherwise> </Otherwise>
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="AssemblyHelperTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UriHelperTests.cs" /> <Compile Include="UriHelperTests.cs" />
</ItemGroup> </ItemGroup>

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -37,5 +37,13 @@ namespace PlexRequests.Helpers
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location); var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
return fvi.FileVersion; return fvi.FileVersion;
} }
public static string GetProductVersion()
{
var assembly = Assembly.GetExecutingAssembly();
var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
var retVersion = fvi.ProductVersion;
return retVersion;
}
} }
} }

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,103 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexAvailabilityCheckerTests.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;
using Moq;
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.Helpers.Exceptions;
using PlexRequests.Services.Interfaces;
namespace PlexRequests.Services.Tests
{
[TestFixture]
public class PlexAvailabilityCheckerTests
{
public IAvailabilityChecker Checker { get; set; }
[Test]
public void IsAvailableWithEmptySettingsTest()
{
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
var requestMock = new Mock<IRequestService>();
var plexMock = new Mock<IPlexApi>();
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
Assert.Throws<ApplicationSettingsException>(() => Checker.IsAvailable("title"), "We should be throwing an exception since we cannot talk to the services.");
}
[Test]
public void IsAvailableTest()
{
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
var requestMock = new Mock<IRequestService>();
var plexMock = new Mock<IPlexApi>();
var searchResult = new PlexSearch {Video = new List<Video> {new Video {Title = "title" } } };
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
var result = Checker.IsAvailable("title");
Assert.That(result, Is.True);
}
[Test]
public void IsNotAvailableTest()
{
var settingsMock = new Mock<ISettingsService<PlexSettings>>();
var authMock = new Mock<ISettingsService<AuthenticationSettings>>();
var requestMock = new Mock<IRequestService>();
var plexMock = new Mock<IPlexApi>();
var searchResult = new PlexSearch { Video = new List<Video> { new Video { Title = "wrong title" } } };
settingsMock.Setup(x => x.GetSettings()).Returns(new PlexSettings { Ip = "abc" });
authMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings { PlexAuthToken = "abc" });
plexMock.Setup(x => x.SearchContent(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<Uri>())).Returns(searchResult);
Checker = new PlexAvailabilityChecker(settingsMock.Object, authMock.Object, requestMock.Object, plexMock.Object);
var result = Checker.IsAvailable("title");
Assert.That(result, Is.False);
}
}
}

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>PlexRequests.Services.Tests</RootNamespace>
<AssemblyName>PlexRequests.Services.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Moq, Version=4.2.1510.2205, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="PlexAvailabilityCheckerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj">
<Project>{95834072-A675-415D-AA8F-877C91623810}</Project>
<Name>PlexRequests.Api.Interfaces</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Api.Models\PlexRequests.Api.Models.csproj">
<Project>{CB37A5F8-6DFC-4554-99D3-A42B502E4591}</Project>
<Name>PlexRequests.Api.Models</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Core\PlexRequests.Core.csproj">
<Project>{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}</Project>
<Name>PlexRequests.Core</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Helpers\PlexRequests.Helpers.csproj">
<Project>{1252336D-42A3-482A-804C-836E60173DFA}</Project>
<Name>PlexRequests.Helpers</Name>
</ProjectReference>
<ProjectReference Include="..\PlexRequests.Services\PlexRequests.Services.csproj">
<Project>{566EFA49-68F8-4716-9693-A6B3F2624DEA}</Project>
<Name>PlexRequests.Services</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

@ -0,0 +1,37 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("PlexRequests.Services.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("PlexRequests.Services.Tests")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("eaadb4ac-064f-4d3a-aff9-64a33131a9a7")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Moq" version="4.2.1510.2205" targetFramework="net46" />
<package id="NUnit" version="3.2.0" targetFramework="net46" />
</packages>

@ -36,6 +36,7 @@ using Mono.Data.Sqlite;
using NLog; using NLog;
using PlexRequests.Api;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
@ -51,7 +52,7 @@ namespace PlexRequests.Services
{ {
ConfigurationReader = new ConfigurationReader(); ConfigurationReader = new ConfigurationReader();
var repo = new JsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()); var repo = new JsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider());
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new RequestService(new GenericRepository<RequestedModel>(new DbConfiguration(new SqliteFactory()))) ); Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new RequestService(new GenericRepository<RequestedModel>(new DbConfiguration(new SqliteFactory()))), new PlexApi());
HostingEnvironment.RegisterObject(this); HostingEnvironment.RegisterObject(this);
} }

@ -29,5 +29,6 @@ namespace PlexRequests.Services.Interfaces
public interface IAvailabilityChecker public interface IAvailabilityChecker
{ {
void CheckAndUpdateAll(long check); void CheckAndUpdateAll(long check);
bool IsAvailable(string title);
} }
} }

@ -29,9 +29,10 @@ using System.Linq;
using NLog; using NLog;
using PlexRequests.Api; using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers.Exceptions;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Store; using PlexRequests.Store;
@ -39,17 +40,19 @@ namespace PlexRequests.Services
{ {
public class PlexAvailabilityChecker : IAvailabilityChecker public class PlexAvailabilityChecker : IAvailabilityChecker
{ {
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, ISettingsService<AuthenticationSettings> auth, IRequestService request) public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, ISettingsService<AuthenticationSettings> auth, IRequestService request, IPlexApi plex)
{ {
Plex = plexSettings; Plex = plexSettings;
Auth = auth; Auth = auth;
RequestService = request; RequestService = request;
PlexApi = plex;
} }
private ISettingsService<PlexSettings> Plex { get; } private ISettingsService<PlexSettings> Plex { get; }
private ISettingsService<AuthenticationSettings> Auth { get; } private ISettingsService<AuthenticationSettings> Auth { get; }
private IRequestService RequestService { get; } private IRequestService RequestService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
private IPlexApi PlexApi { get; set; }
public void CheckAndUpdateAll(long check) public void CheckAndUpdateAll(long check)
@ -58,23 +61,16 @@ namespace PlexRequests.Services
var authSettings = Auth.GetSettings(); var authSettings = Auth.GetSettings();
var requests = RequestService.GetAll(); var requests = RequestService.GetAll();
if (plexSettings.Ip == null || authSettings.PlexAuthToken == null || requests == null) var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!ValidateSettings(plexSettings, authSettings, requestedModels))
{ {
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return;
}
if (!requests.Any())
{
Log.Info("We have no requests to check if they are available on Plex.");
return; return;
} }
var api = new PlexApi();
var modifiedModel = new List<RequestedModel>(); var modifiedModel = new List<RequestedModel>();
foreach (var r in requests) foreach (var r in requestedModels)
{ {
var results = api.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri); var results = PlexApi.SearchContent(authSettings.PlexAuthToken, r.Title, plexSettings.FullUri);
var result = results.Video.FirstOrDefault(x => x.Title == r.Title); var result = results.Video.FirstOrDefault(x => x.Title == r.Title);
var originalRequest = RequestService.Get(r.Id); var originalRequest = RequestService.Get(r.Id);
@ -84,5 +80,51 @@ namespace PlexRequests.Services
RequestService.BatchUpdate(modifiedModel); RequestService.BatchUpdate(modifiedModel);
} }
/// <summary>
/// Determines whether the specified search term is available.
/// </summary>
/// <param name="title">The search term.</param>
/// <returns></returns>
/// <exception cref="ApplicationSettingsException">The settings are not configured for Plex or Authentication</exception>
public bool IsAvailable(string title)
{
var plexSettings = Plex.GetSettings();
var authSettings = Auth.GetSettings();
if (!ValidateSettings(plexSettings, authSettings))
{
throw new ApplicationSettingsException("The settings are not configured for Plex or Authentication");
}
var results = PlexApi.SearchContent(authSettings.PlexAuthToken, title, plexSettings.FullUri);
var result = results.Video.FirstOrDefault(x => x.Title == title);
return result?.Title != null;
}
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth, IEnumerable<RequestedModel> requests)
{
if (plex.Ip == null || auth.PlexAuthToken == null || requests == null)
{
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return false;
}
if (!requests.Any())
{
Log.Info("We have no requests to check if they are available on Plex.");
return false;
}
return true;
}
private bool ValidateSettings(PlexSettings plex, AuthenticationSettings auth)
{
if (plex?.Ip == null || auth?.PlexAuthToken == null)
{
Log.Warn("A setting is null, Ensure Plex is configured correctly, and we have a Plex Auth token.");
return false;
}
return true;
}
} }
} }

@ -82,6 +82,7 @@
<Compile Include="UpdateInterval.cs" /> <Compile Include="UpdateInterval.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" />
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

@ -34,3 +34,4 @@ using System.Runtime.InteropServices;
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -24,6 +24,7 @@ namespace PlexRequests.Store
public IssueState Issues { get; set; } public IssueState Issues { get; set; }
public string OtherMessage { get; set; } public string OtherMessage { get; set; }
public bool LatestTv { get; set; } public bool LatestTv { get; set; }
public string AdminNote { get; set; }
} }
public enum RequestType public enum RequestType

@ -27,6 +27,7 @@ CREATE TABLE IF NOT EXISTS Requested
PosterPath varchar(50) NOT NULL, PosterPath varchar(50) NOT NULL,
ReleaseDate varchar(50) NOT NULL, ReleaseDate varchar(50) NOT NULL,
Status varchar(50) NOT NULL, Status varchar(50) NOT NULL,
AdminNote varchar(50),
Approved INTEGER NOT NULL, Approved INTEGER NOT NULL,
LatestTv INTEGER NOT NULL, LatestTv INTEGER NOT NULL,
RequestedBy varchar(50), RequestedBy varchar(50),

@ -0,0 +1,300 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModuleTests.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.Collections.Generic;
using Moq;
using Nancy;
using Nancy.Testing;
using Newtonsoft.Json;
using NUnit.Framework;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Plex;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Models;
using PlexRequests.UI.Modules;
namespace PlexRequests.UI.Tests
{
[TestFixture]
public class AdminModuleTests
{
private Mock<ISettingsService<PlexRequestSettings>> PlexRequestMock { get; set; }
private Mock<ISettingsService<CouchPotatoSettings>> CpMock { get; set; }
private Mock<ISettingsService<AuthenticationSettings>> AuthMock { get; set; }
private Mock<ISettingsService<PlexSettings>> PlexSettingsMock { get; set; }
private Mock<ISettingsService<SonarrSettings>> SonarrSettingsMock { get; set; }
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
private Mock<ISonarrApi> SonarrApiMock { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
[SetUp]
public void Setup()
{
AuthMock = new Mock<ISettingsService<AuthenticationSettings>>();
var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" };
AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings);
PlexMock = new Mock<IPlexApi>();
PlexMock.Setup(x => x.SignIn("Username1", "Password1"))
.Returns(new PlexAuthentication { user = new User { authentication_token = "abc", username = "Username1" } });
PlexRequestMock = new Mock<ISettingsService<PlexRequestSettings>>();
CpMock = new Mock<ISettingsService<CouchPotatoSettings>>();
PlexSettingsMock = new Mock<ISettingsService<PlexSettings>>();
SonarrApiMock = new Mock<ISonarrApi>();
SonarrSettingsMock = new Mock<ISettingsService<SonarrSettings>>();
EmailMock = new Mock<ISettingsService<EmailNotificationSettings>>();
Bootstrapper = new ConfigurableBootstrapper(with =>
{
with.Module<AdminModule>();
with.Dependency(AuthMock.Object);
with.Dependency(PlexRequestMock.Object);
with.Dependency(CpMock.Object);
with.Dependency(PlexSettingsMock.Object);
with.Dependency(SonarrApiMock.Object);
with.Dependency(SonarrSettingsMock.Object);
with.Dependency(PlexMock.Object);
with.Dependency(EmailMock.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup((container, pipelines, context) =>
{
context.CurrentUser = new UserIdentity { UserName = "user" };
});
});
Bootstrapper.WithSession(new Dictionary<string, object>());
}
[Test]
public void RequestAuthTokenTestNewSettings()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Once);
}
[Test]
public void RequestAuthTokenTestEmptyCredentials()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", string.Empty);
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Never);
AuthMock.Verify(x => x.GetSettings(), Times.Never);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Never);
}
[Test]
public void RequestAuthTokenTesPlexSignInFail()
{
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Badusername");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(false));
Assert.That(body.Message, Is.Not.Empty);
PlexMock.Verify(x => x.SignIn("Badusername", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Never);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Never);
}
[Test]
public void RequestAuthTokenTestExistingSettings()
{
AuthMock.Setup(x => x.GetSettings()).Returns(() => null);
var browser = new Browser(Bootstrapper);
var result = browser.Post("/admin/requestauth", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<JsonResponseModel>(result.Body.AsString());
Assert.That(body.Result, Is.EqualTo(true));
PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
AuthMock.Verify(x => x.SaveSettings(It.IsAny<AuthenticationSettings>()), Times.Once);
}
[Test]
public void GetUsersSuccessfully()
{
var users = new PlexFriends { User = new[] { new UserFriends { Username = "abc2" }, } };
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = result.Body.AsString();
Assert.That(body, Is.Not.Null);
Assert.That(body, Contains.Substring("abc2"));
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersReturnsNoUsers()
{
var users = new PlexFriends();
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(users);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersReturnsNull()
{
PlexMock.Setup(x => x.GetUsers(It.IsAny<string>())).Returns(() => null);
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Once);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
[Test]
public void GetUsersTokenIsNull()
{
AuthMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings());
var browser = new Browser(Bootstrapper);
var result = browser.Get("/admin/getusers", with =>
{
with.HttpRequest();
with.Header("Accept", "application/json");
with.FormValue("username", "Username1");
with.FormValue("password", "Password1");
});
Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode));
var body = JsonConvert.DeserializeObject<string>(result.Body.AsString());
Assert.That(body, Is.Not.Null);
Assert.That(string.IsNullOrWhiteSpace(body), Is.True);
PlexMock.Verify(x => x.GetUsers(It.IsAny<string>()), Times.Never);
AuthMock.Verify(x => x.GetSettings(), Times.Once);
}
}
}

@ -66,8 +66,8 @@
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath> <HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="nunit.framework, Version=3.0.5813.39031, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>..\packages\NUnit.3.2.0\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Ploeh.AutoFixture, Version=3.40.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL"> <Reference Include="Ploeh.AutoFixture, Version=3.40.0.0, Culture=neutral, PublicKeyToken=b24654c590009d4f, processorArchitecture=MSIL">
@ -94,6 +94,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestRootPathProvider.cs" /> <Compile Include="TestRootPathProvider.cs" />
<Compile Include="UserLoginModuleTests.cs" /> <Compile Include="UserLoginModuleTests.cs" />
<Compile Include="AdminModuleTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />

@ -9,5 +9,5 @@
<package id="Nancy.Testing" version="1.4.1" targetFramework="net46" /> <package id="Nancy.Testing" version="1.4.1" targetFramework="net46" />
<package id="Nancy.Viewengines.Razor" version="1.4.3" targetFramework="net46" /> <package id="Nancy.Viewengines.Razor" version="1.4.3" targetFramework="net46" />
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net46" /> <package id="Newtonsoft.Json" version="8.0.2" targetFramework="net46" />
<package id="NUnit" version="3.0.1" targetFramework="net452" /> <package id="NUnit" version="3.2.0" targetFramework="net46" />
</packages> </packages>

@ -71,6 +71,7 @@ namespace PlexRequests.UI
container.Register<ISettingsService<AuthenticationSettings>, SettingsServiceV2<AuthenticationSettings>>(); container.Register<ISettingsService<AuthenticationSettings>, SettingsServiceV2<AuthenticationSettings>>();
container.Register<ISettingsService<PlexSettings>, SettingsServiceV2<PlexSettings>>(); container.Register<ISettingsService<PlexSettings>, SettingsServiceV2<PlexSettings>>();
container.Register<ISettingsService<SonarrSettings>, SettingsServiceV2<SonarrSettings>>(); container.Register<ISettingsService<SonarrSettings>, SettingsServiceV2<SonarrSettings>>();
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
container.Register<IRepository<RequestedModel>, GenericRepository<RequestedModel>>(); container.Register<IRepository<RequestedModel>, GenericRepository<RequestedModel>>();
container.Register<IRequestService, RequestService>(); container.Register<IRequestService, RequestService>();

@ -0,0 +1,19 @@
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none; }
.pace-inactive {
display: none; }
.pace .pace-progress {
background: #ffa400;
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 2px; }

@ -0,0 +1 @@
.pace{-webkit-pointer-events:none;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;}.pace-inactive{display:none;}.pace .pace-progress{background:#ffa400;position:fixed;z-index:2000;top:0;right:100%;width:100%;height:2px;}

File diff suppressed because one or more lines are too long

@ -0,0 +1,24 @@
$primary-colour: rgb(255, 164, 0);
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.pace-inactive {
display: none;
}
.pace .pace-progress {
background: $primary-colour;
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 2px;
}

@ -90,6 +90,34 @@ $(".theSaveButton").click(function (e) {
}); });
}); });
// Note Modal click
$(".theNoteSaveButton").click(function (e) {
var comment = $("#noteArea").val();
e.preventDefault();
var $form = $("#noteForm");
var data = $form.serialize();
$.ajax({
type: $form.prop("method"),
url: $form.prop("action"),
data: data,
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
generateNotify("Success! Added Note.", "success");
$("#myModal").modal("hide");
$('#adminNotesArea').html("<div>Note from Admin: " + comment + "</div>");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
// Update the modal // Update the modal
$('#myModal').on('show.bs.modal', function (event) { $('#myModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal var button = $(event.relatedTarget); // Button that triggered the modal
@ -101,6 +129,17 @@ $('#myModal').on('show.bs.modal', function (event) {
requestField.val(id); // Add ID to the hidden field requestField.val(id); // Add ID to the hidden field
}); });
// Update the note modal
$('#noteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var id = button.data('identifier'); // Extract info from data-* attributes
var modal = $(this);
modal.find('.theNoteSaveButton').val(id); // Add ID to the button
var requestField = modal.find('.noteId');
requestField.val(id); // Add ID to the hidden field
});
// Delete // Delete
$(document).on("click", ".delete", function (e) { $(document).on("click", ".delete", function (e) {
e.preventDefault(); e.preventDefault();
@ -171,7 +210,7 @@ $(document).on("click", ".clear", function (e) {
if (checkJsonResponse(response)) { if (checkJsonResponse(response)) {
generateNotify("Success! Issues Cleared.", "info"); generateNotify("Success! Issues Cleared.", "info");
$('#issueArea').html("<p>Issue: None</p>"); $('#issueArea').html("<div>Issue: None</div>");
} }
}, },
error: function (e) { error: function (e) {
@ -182,8 +221,6 @@ $(document).on("click", ".clear", function (e) {
}); });
//change
// Change Availability // Change Availability
$(document).on("click", ".change", function (e) { $(document).on("click", ".change", function (e) {
e.preventDefault(); e.preventDefault();
@ -270,7 +307,8 @@ function buildRequestContext(result, type) {
admin: result.admin, admin: result.admin,
issues: result.issues, issues: result.issues,
otherMessage: result.otherMessage, otherMessage: result.otherMessage,
requestId: result.id requestId: result.id,
adminNote: result.adminNotes
}; };
return context; return context;

@ -47,5 +47,6 @@ namespace PlexRequests.UI.Models
public bool Admin { get; set; } public bool Admin { get; set; }
public string Issues { get; set; } public string Issues { get; set; }
public string OtherMessage { get; set; } public string OtherMessage { get; set; }
public string AdminNotes { get; set; }
} }
} }

@ -35,7 +35,6 @@ using Nancy.Security;
using NLog; using NLog;
using PlexRequests.Api;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
@ -46,20 +45,24 @@ namespace PlexRequests.UI.Modules
{ {
public class AdminModule : NancyModule public class AdminModule : NancyModule
{ {
private ISettingsService<PlexRequestSettings> RpService { get; set; } private ISettingsService<PlexRequestSettings> RpService { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; set; } private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<AuthenticationSettings> AuthService { get; set; } private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<PlexSettings> PlexService { get; set; } private ISettingsService<PlexSettings> PlexService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; set; } private ISettingsService<SonarrSettings> SonarrService { get; }
private ISonarrApi SonarrApi { get; set; } private ISettingsService<EmailNotificationSettings> EmailService { get; }
private IPlexApi PlexApi { get; }
private ISonarrApi SonarrApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger(); private static Logger Log = LogManager.GetCurrentClassLogger();
public AdminModule(ISettingsService<PlexRequestSettings> rpService, public AdminModule(ISettingsService<PlexRequestSettings> rpService,
ISettingsService<CouchPotatoSettings> cpService, ISettingsService<CouchPotatoSettings> cpService,
ISettingsService<AuthenticationSettings> auth ISettingsService<AuthenticationSettings> auth,
, ISettingsService<PlexSettings> plex, ISettingsService<PlexSettings> plex,
ISettingsService<SonarrSettings> sonarr, ISettingsService<SonarrSettings> sonarr,
ISonarrApi sonarrApi) : base("admin") ISonarrApi sonarrApi,
ISettingsService<EmailNotificationSettings> email,
IPlexApi plexApi) : base("admin")
{ {
RpService = rpService; RpService = rpService;
CpService = cpService; CpService = cpService;
@ -67,6 +70,8 @@ namespace PlexRequests.UI.Modules
PlexService = plex; PlexService = plex;
SonarrService = sonarr; SonarrService = sonarr;
SonarrApi = sonarrApi; SonarrApi = sonarrApi;
EmailService = email;
PlexApi = plexApi;
#if !DEBUG #if !DEBUG
this.RequiresAuthentication(); this.RequiresAuthentication();
@ -92,6 +97,10 @@ namespace PlexRequests.UI.Modules
Post["/sonarr"] = _ => SaveSonarr(); Post["/sonarr"] = _ => SaveSonarr();
Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles();
Get["/emailnotification"] = _ => EmailNotifications();
Post["/emailnotification"] = _ => SaveEmailNotifications();
Get["/status"] = _ => Status();
} }
private Negotiator Authentication() private Negotiator Authentication()
@ -118,7 +127,7 @@ namespace PlexRequests.UI.Modules
var settings = RpService.GetSettings(); var settings = RpService.GetSettings();
Log.Trace("Getting Settings:"); Log.Trace("Getting Settings:");
Log.Trace(settings.DumpJson()); Log.Trace(settings.DumpJson());
return View["Settings", settings]; return View["Settings", settings];
} }
@ -141,10 +150,9 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new { Result = false, Message = "Please provide a valid username and password" }); return Response.AsJson(new { Result = false, Message = "Please provide a valid username and password" });
} }
var plex = new PlexApi(); var model = PlexApi.SignIn(user.username, user.password);
var model = plex.SignIn(user.username, user.password);
if (model.user == null) if (model?.user == null)
{ {
return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" });
} }
@ -170,15 +178,23 @@ namespace PlexRequests.UI.Modules
private Response GetUsers() private Response GetUsers()
{ {
var token = AuthService.GetSettings().PlexAuthToken; var settings = AuthService.GetSettings();
var token = settings?.PlexAuthToken;
if (token == null) if (token == null)
{ {
return Response.AsJson(string.Empty); return Response.AsJson(string.Empty);
} }
var api = new PlexApi();
var users = api.GetUsers(token); var users = PlexApi.GetUsers(token);
if (users == null) if (users == null)
{ return Response.AsJson(string.Empty); } {
return Response.AsJson(string.Empty);
}
if (users.User == null || users.User?.Length == 0)
{
return Response.AsJson(string.Empty);
}
var usernames = users.User.Select(x => x.Username); var usernames = users.User.Select(x => x.Username);
return Response.AsJson(usernames); return Response.AsJson(usernames);
@ -193,6 +209,8 @@ namespace PlexRequests.UI.Modules
return View["CouchPotato", model]; return View["CouchPotato", model];
} }
private Response SaveCouchPotato() private Response SaveCouchPotato()
{ {
var couchPotatoSettings = this.Bind<CouchPotatoSettings>(); var couchPotatoSettings = this.Bind<CouchPotatoSettings>();
@ -239,5 +257,28 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(profiles); return Response.AsJson(profiles);
} }
private Negotiator EmailNotifications()
{
var settings = EmailService.GetSettings();
return View["EmailNotifications", settings];
}
private Response SaveEmailNotifications()
{
var settings = this.Bind<EmailNotificationSettings>();
Log.Trace(settings.DumpJson());
var result = EmailService.SaveSettings(settings);
Log.Info("Saved email settings, result: {0}", result);
return Context.GetRedirect("~/admin/emailnotification");
}
private Negotiator Status()
{
var checker = new StatusChecker();
var status = checker.GetStatus();
return View["Status", status];
}
} }
} }

@ -0,0 +1,144 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApplicationTesterModule.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 Nancy;
using Nancy.ModelBinding;
using Nancy.Security;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules
{
public class ApplicationTesterModule : BaseModule
{
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,
ISettingsService<AuthenticationSettings> authSettings) : base("test")
{
this.RequiresAuthentication();
CpApi = cpApi;
SonarrApi = sonarrApi;
PlexApi = plexApi;
AuthSettings = authSettings;
Post["/cp"] = _ => CouchPotatoTest();
Post["/sonarr"] = _ => SonarrTest();
Post["/plex"] = _ => PlexTest();
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private ISonarrApi SonarrApi { get; }
private ICouchPotatoApi CpApi { get; }
private IPlexApi PlexApi { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private Response CouchPotatoTest()
{
var couchPotatoSettings = this.Bind<CouchPotatoSettings>();
try
{
var status = CpApi.GetStatus(couchPotatoSettings.FullUri, couchPotatoSettings.ApiKey);
return status.success
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to CouchPotato successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to CouchPotato, please check your settings." });
}
catch (ApplicationException e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get CP's status: ");
Log.Warn(e);
var message = $"Could not connect to CouchPotato, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to CouchPotato, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response SonarrTest()
{
var sonarrSettings = this.Bind<SonarrSettings>();
try
{
var status = SonarrApi.SystemStatus(sonarrSettings.ApiKey, sonarrSettings.FullUri);
return status != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Sonarr successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Sonarr, please check your settings." });
}
catch (ApplicationException e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Sonarr's status: ");
Log.Warn(e);
var message = $"Could not connect to Sonarr, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Sonarr, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response PlexTest()
{
var plexSettings = this.Bind<PlexSettings>();
var settings = AuthSettings.GetSettings();
if (settings?.PlexAuthToken == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Plex is not setup yet, you need to update your Authentication settings" });
}
try
{
var status = PlexApi.GetStatus(settings.PlexAuthToken, plexSettings.FullUri);
return status != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Plex successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Plex, please check your settings." });
}
catch (ApplicationException e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Plex's status: ");
Log.Warn(e);
var message = $"Could not connect to Plex, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Plex, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
}
}

@ -86,12 +86,14 @@ namespace PlexRequests.UI.Modules
Post["/register"] = x => Post["/register"] = x =>
{ {
var username = (string) Request.Form.Username;
var exists = UserMapper.DoUsersExist(); var exists = UserMapper.DoUsersExist();
if (exists) if (exists)
{ {
return Context.GetRedirect("~/register?error=true&username=" + (string)Request.Form.Username); return Context.GetRedirect("~/register?error=true&username=" + username);
} }
var userId = UserMapper.CreateUser(Request.Form.Username, Request.Form.Password); var userId = UserMapper.CreateUser(username, Request.Form.Password);
Session[SessionKeys.UsernameKey] = username;
return this.LoginAndRedirect((Guid)userId); return this.LoginAndRedirect((Guid)userId);
}; };
} }

@ -61,7 +61,9 @@ namespace PlexRequests.UI.Modules
Post["/clearissues"] = _ => ClearIssue((int)Request.Form.Id); Post["/clearissues"] = _ => ClearIssue((int)Request.Form.Id);
Post["/changeavailability"] = _ => ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available); Post["/changeavailability"] = _ => ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
Post["/addnote"] = _ => AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea);
} }
private IRepository<RequestedModel> Service { get; } private IRepository<RequestedModel> Service { get; }
private ISettingsService<PlexRequestSettings> PrSettings { get; } private ISettingsService<PlexRequestSettings> PrSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
@ -94,7 +96,8 @@ namespace PlexRequests.UI.Modules
Available = movie.Available, Available = movie.Available,
Admin = isAdmin, Admin = isAdmin,
Issues = movie.Issues.Humanize(LetterCasing.Title), Issues = movie.Issues.Humanize(LetterCasing.Title),
OtherMessage = movie.OtherMessage OtherMessage = movie.OtherMessage,
AdminNotes = movie.AdminNote
}).ToList(); }).ToList();
return Response.AsJson(viewModel); return Response.AsJson(viewModel);
@ -122,7 +125,8 @@ namespace PlexRequests.UI.Modules
Available = tv.Available, Available = tv.Available,
Admin = isAdmin, Admin = isAdmin,
Issues = tv.Issues.Humanize(LetterCasing.Title), Issues = tv.Issues.Humanize(LetterCasing.Title),
OtherMessage = tv.OtherMessage OtherMessage = tv.OtherMessage,
AdminNotes = tv.AdminNote
}).ToList(); }).ToList();
return Response.AsJson(viewModel); return Response.AsJson(viewModel);
@ -156,14 +160,14 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
} }
originalRequest.Issues = issue; originalRequest.Issues = issue;
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment) originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
? $"{Session[SessionKeys.UsernameKey]} - {comment}" ? $"{Session[SessionKeys.UsernameKey]} - {comment}"
: string.Empty; : string.Empty;
var result = Service.Update(originalRequest); var result = Service.Update(originalRequest);
return Response.AsJson(result return Response.AsJson(result
? new JsonResponseModel { Result = true } ? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); : new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
} }
@ -183,8 +187,8 @@ namespace PlexRequests.UI.Modules
originalRequest.OtherMessage = string.Empty; originalRequest.OtherMessage = string.Empty;
var result = Service.Update(originalRequest); var result = Service.Update(originalRequest);
return Response.AsJson(result return Response.AsJson(result
? new JsonResponseModel { Result = true } ? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" }); : new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
} }
@ -200,8 +204,24 @@ namespace PlexRequests.UI.Modules
var result = Service.Update(originalRequest); var result = Service.Update(originalRequest);
return Response.AsJson(result return Response.AsJson(result
? new {Result = true, Available = available, Message = string.Empty} ? 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" }); : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
}
private Response AddNote(int requestId, string noteArea)
{
var originalRequest = Service.Get(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to add a note!" });
}
originalRequest.AdminNote = noteArea;
var result = Service.Update(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not update the notes, please try again or check the logs" });
} }
} }
} }

@ -38,7 +38,6 @@ using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.UI.Jobs;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
@ -47,7 +46,8 @@ namespace PlexRequests.UI.Modules
{ {
public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings, public SearchModule(ICacheProvider cache, ISettingsService<CouchPotatoSettings> cpSettings,
ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker, ISettingsService<PlexRequestSettings> prSettings, IAvailabilityChecker checker,
IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings) : base("search") IRequestService request, ISonarrApi sonarrApi, ISettingsService<SonarrSettings> sonarrSettings,
ICouchPotatoApi cpApi) : base("search")
{ {
CpService = cpSettings; CpService = cpSettings;
PrService = prSettings; PrService = prSettings;
@ -58,6 +58,7 @@ namespace PlexRequests.UI.Modules
RequestService = request; RequestService = request;
SonarrApi = sonarrApi; SonarrApi = sonarrApi;
SonarrService = sonarrSettings; SonarrService = sonarrSettings;
CouchPotatoApi = cpApi;
Get["/"] = parameters => RequestLoad(); Get["/"] = parameters => RequestLoad();
@ -71,6 +72,7 @@ namespace PlexRequests.UI.Modules
Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (bool)Request.Form.latest); Post["request/tv"] = parameters => RequestTvShow((int)Request.Form.tvId, (bool)Request.Form.latest);
} }
private TheMovieDbApi MovieApi { get; } private TheMovieDbApi MovieApi { get; }
private ICouchPotatoApi CouchPotatoApi { get; }
private ISonarrApi SonarrApi { get; } private ISonarrApi SonarrApi { get; }
private TheTvDbApi TvApi { get; } private TheTvDbApi TvApi { get; }
private IRequestService RequestService { get; } private IRequestService RequestService { get; }
@ -105,6 +107,7 @@ namespace PlexRequests.UI.Modules
if (tvShow?.data == null) if (tvShow?.data == null)
{ {
Log.Trace("TV Show data is null");
return Response.AsJson(""); return Response.AsJson("");
} }
var model = new List<SearchTvShowViewModel>(); var model = new List<SearchTvShowViewModel>();
@ -137,38 +140,47 @@ namespace PlexRequests.UI.Modules
Zap2ItId = t.zap2itId Zap2ItId = t.zap2itId
}); });
} }
Log.Trace("Returning TV Show results: ");
Log.Trace(model.DumpJson());
return Response.AsJson(model); return Response.AsJson(model);
} }
private Response UpcomingMovies() private Response UpcomingMovies() // TODO : Not used
{ {
var movies = MovieApi.GetUpcomingMovies(); var movies = MovieApi.GetUpcomingMovies();
var result = movies.Result; var result = movies.Result;
Log.Trace("Movie Upcoming Results: ");
Log.Trace(result.DumpJson());
return Response.AsJson(result); return Response.AsJson(result);
} }
private Response CurrentlyPlayingMovies() private Response CurrentlyPlayingMovies() // TODO : Not used
{ {
var movies = MovieApi.GetCurrentPlayingMovies(); var movies = MovieApi.GetCurrentPlayingMovies();
var result = movies.Result; var result = movies.Result;
Log.Trace("Movie Currently Playing Results: ");
Log.Trace(result.DumpJson());
return Response.AsJson(result); return Response.AsJson(result);
} }
private Response RequestMovie(int movieId) private Response RequestMovie(int movieId)
{ {
Log.Trace("Requesting movie with id {0}", movieId); Log.Info("Requesting movie with id {0}", movieId);
if (RequestService.CheckRequest(movieId)) if (RequestService.CheckRequest(movieId))
{ {
Log.Trace("movie with id {0} exists", movieId); Log.Trace("movie with id {0} exists", movieId);
return Response.AsJson(new { Result = false, Message = "Movie has already been requested!" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Movie has already been requested!" });
} }
Log.Trace("movie with id {0} doesnt exists", movieId);
Log.Debug("movie with id {0} doesnt exists", movieId);
var cpSettings = CpService.GetSettings(); var cpSettings = CpService.GetSettings();
if (cpSettings.ApiKey == null) if (cpSettings.ApiKey == null)
{ {
Log.Warn("CP apiKey is null"); Log.Warn("CP apiKey is null");
return Response.AsJson(new { Result = false, Message = "CouchPotato is not yet configured, If you are the Admin, please log in." }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is not yet configured, If you are the Admin, please log in." });
} }
Log.Trace("Settings: "); Log.Trace("Settings: ");
Log.Trace(cpSettings.DumpJson); Log.Trace(cpSettings.DumpJson);
@ -177,6 +189,13 @@ namespace PlexRequests.UI.Modules
Log.Trace("Getting movie info from TheMovieDb"); Log.Trace("Getting movie info from TheMovieDb");
Log.Trace(movieInfo.DumpJson); Log.Trace(movieInfo.DumpJson);
#if !DEBUG
if (CheckIfTitleExistsInPlex(movieInfo.Title))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" });
}
#endif
var model = new RequestedModel var model = new RequestedModel
{ {
ProviderId = movieInfo.Id, ProviderId = movieInfo.Id,
@ -195,36 +214,36 @@ namespace PlexRequests.UI.Modules
var settings = PrService.GetSettings(); var settings = PrService.GetSettings();
Log.Trace(settings.DumpJson());
if (!settings.RequireApproval) if (!settings.RequireApproval)
{ {
var cp = new CouchPotatoApi(); Log.Info("Adding movie to CP (No approval required)");
Log.Trace("Adding movie to CP (No approval required)"); var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri);
var result = cp.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri); Log.Debug("Adding movie to CP result {0}", result);
Log.Trace("Adding movie to CP result {0}", result);
if (result) if (result)
{ {
model.Approved = true; model.Approved = true;
Log.Trace("Adding movie to database requests (No approval required)"); Log.Debug("Adding movie to database requests (No approval required)");
RequestService.AddRequest(movieId, model); RequestService.AddRequest(movieId, model);
return Response.AsJson(new { Result = true }); return Response.AsJson(new JsonResponseModel { Result = true });
} }
return Response.AsJson(new { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." });
} }
try try
{ {
Log.Trace("Adding movie to database requests"); Log.Debug("Adding movie to database requests");
var id = RequestService.AddRequest(movieId, model); var id = RequestService.AddRequest(movieId, model);
//BackgroundJob.Enqueue(() => Checker.CheckAndUpdate(model.Title, (int)id)); //BackgroundJob.Enqueue(() => Checker.CheckAndUpdate(model.Title, (int)id));
return Response.AsJson(new { Result = true }); return Response.AsJson(new JsonResponseModel { Result = true });
} }
catch (Exception e) catch (Exception e)
{ {
Log.Fatal(e); Log.Fatal(e);
return Response.AsJson(new { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." });
} }
} }
@ -236,17 +255,23 @@ namespace PlexRequests.UI.Modules
/// <returns></returns> /// <returns></returns>
private Response RequestTvShow(int showId, bool latest) private Response RequestTvShow(int showId, bool latest)
{ {
// Latest send to Sonarr and no need to store in DB
if (RequestService.CheckRequest(showId)) if (RequestService.CheckRequest(showId))
{ {
return Response.AsJson(new { Result = false, Message = "TV Show has already been requested!" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "TV Show has already been requested!" });
} }
var tvApi = new TheTvDbApi(); var tvApi = new TheTvDbApi();
var token = GetAuthToken(tvApi); var token = GetTvDbAuthToken(tvApi);
var showInfo = tvApi.GetInformation(showId, token).data; var showInfo = tvApi.GetInformation(showId, token).data;
#if !DEBUG
if (CheckIfTitleExistsInPlex(showInfo.seriesName))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.seriesName} is already in Plex!" });
}
#endif
DateTime firstAir; DateTime firstAir;
DateTime.TryParse(showInfo.firstAired, out firstAir); DateTime.TryParse(showInfo.firstAired, out firstAir);
@ -263,7 +288,7 @@ namespace PlexRequests.UI.Modules
Approved = false, Approved = false,
RequestedBy = Session[SessionKeys.UsernameKey].ToString(), RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
Issues = IssueState.None, Issues = IssueState.None,
LatestTv = latest LatestTv = latest
}; };
RequestService.AddRequest(showId, model); RequestService.AddRequest(showId, model);
@ -284,9 +309,15 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new { Result = true }); return Response.AsJson(new { Result = true });
} }
private string GetAuthToken(TheTvDbApi api) private string GetTvDbAuthToken(TheTvDbApi api)
{ {
return Cache.GetOrSet(CacheKeys.TvDbToken, api.Authenticate, 50); return Cache.GetOrSet(CacheKeys.TvDbToken, api.Authenticate, 50);
} }
private bool CheckIfTitleExistsInPlex(string title)
{
var result = Checker.IsAvailable(title);
return result;
}
} }
} }

@ -30,16 +30,17 @@ using Nancy;
using Nancy.Extensions; using Nancy.Extensions;
using Nancy.Responses.Negotiation; using Nancy.Responses.Negotiation;
using NLog;
using PlexRequests.Api.Interfaces; using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models;
using PlexRequests.Api.Models.Plex; using PlexRequests.Api.Models.Plex;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
namespace PlexRequests.UI.Modules namespace PlexRequests.UI.Modules
{ {
// TODO: Add ability to logout
public class UserLoginModule : NancyModule public class UserLoginModule : NancyModule
{ {
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api) : base("userlogin") public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api) : base("userlogin")
@ -54,6 +55,8 @@ namespace PlexRequests.UI.Modules
private ISettingsService<AuthenticationSettings> AuthService { get; } private ISettingsService<AuthenticationSettings> AuthService { get; }
private IPlexApi Api { get; } private IPlexApi Api { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public Negotiator Index() public Negotiator Index()
{ {
var settings = AuthService.GetSettings(); var settings = AuthService.GetSettings();
@ -63,7 +66,7 @@ namespace PlexRequests.UI.Modules
private Response LoginUser() private Response LoginUser()
{ {
var username = Request.Form.username.Value; var username = Request.Form.username.Value;
Log.Debug("Username \"{0}\" attempting to login",username);
if (string.IsNullOrWhiteSpace(username)) if (string.IsNullOrWhiteSpace(username))
{ {
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
@ -72,39 +75,49 @@ namespace PlexRequests.UI.Modules
var authenticated = false; var authenticated = false;
var settings = AuthService.GetSettings(); var settings = AuthService.GetSettings();
Log.Debug("Settings: ");
Log.Debug(settings.DumpJson());
if (IsUserInDeniedList(username, settings)) if (IsUserInDeniedList(username, settings))
{ {
Log.Debug("User is in denied list, not allowing them to authenticate");
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" });
} }
var password = string.Empty; var password = string.Empty;
if (settings.UsePassword) if (settings.UsePassword)
{ {
Log.Debug("Using password");
password = Request.Form.password.Value; password = Request.Form.password.Value;
} }
if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex
{ {
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication)Api.SignIn(username, password); var signedIn = (PlexAuthentication)Api.SignIn(username, password);
if (signedIn.user?.authentication_token != null) if (signedIn.user?.authentication_token != null)
{ {
Log.Debug("Correct credentials, checking if the user is in the friends list");
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
} }
} }
else if(settings.UserAuthentication) // Check against the users in Plex else if(settings.UserAuthentication) // Check against the users in Plex
{ {
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
} }
else if(!settings.UserAuthentication) // No auth, let them pass! else if(!settings.UserAuthentication) // No auth, let them pass!
{ {
Log.Debug("No need to auth");
authenticated = true; authenticated = true;
} }
if (authenticated) if (authenticated)
{ {
Log.Debug("We are authenticated! Setting session.");
// Add to the session (Used in the BaseModules) // Add to the session (Used in the BaseModules)
Session[SessionKeys.UsernameKey] = (string)username; Session[SessionKeys.UsernameKey] = (string)username;
} }
@ -116,6 +129,7 @@ namespace PlexRequests.UI.Modules
private Response Logout() private Response Logout()
{ {
Log.Debug("Logging Out");
if (Session[SessionKeys.UsernameKey] != null) if (Session[SessionKeys.UsernameKey] != null)
{ {
Session.Delete(SessionKeys.UsernameKey); Session.Delete(SessionKeys.UsernameKey);
@ -126,6 +140,8 @@ namespace PlexRequests.UI.Modules
private bool CheckIfUserIsInPlexFriends(string username, string authToken) private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{ {
var users = Api.GetUsers(authToken); var users = Api.GetUsers(authToken);
Log.Debug("Plex Users: ");
Log.Debug(users.DumpJson());
return users.User.Any(x => x.Username == username); return users.User.Any(x => x.Username == username);
} }

@ -166,6 +166,7 @@
<Compile Include="Models\SearchTvShowViewModel.cs" /> <Compile Include="Models\SearchTvShowViewModel.cs" />
<Compile Include="Models\SessionKeys.cs" /> <Compile Include="Models\SessionKeys.cs" />
<Compile Include="Modules\AdminModule.cs" /> <Compile Include="Modules\AdminModule.cs" />
<Compile Include="Modules\ApplicationTesterModule.cs" />
<Compile Include="Modules\BaseModule.cs" /> <Compile Include="Modules\BaseModule.cs" />
<Compile Include="Modules\IndexModule.cs" /> <Compile Include="Modules\IndexModule.cs" />
<Compile Include="Modules\ApprovalModule.cs" /> <Compile Include="Modules\ApprovalModule.cs" />
@ -206,6 +207,13 @@
<Content Include="Content\jquery-2.2.1.min.js"> <Content Include="Content\jquery-2.2.1.min.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Content\pace.css">
<DependentUpon>pace.scss</DependentUpon>
</Content>
<Content Include="Content\pace.min.css">
<DependentUpon>pace.css</DependentUpon>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Content\requests.js"> <Content Include="Content\requests.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -222,6 +230,10 @@
<DependentUpon>compilerconfig.json</DependentUpon> <DependentUpon>compilerconfig.json</DependentUpon>
</None> </None>
<None Include="Content\custom.scss" /> <None Include="Content\custom.scss" />
<Content Include="Content\pace.min.js">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Content\pace.scss" />
<None Include="NLog.xsd"> <None Include="NLog.xsd">
<SubType>Designer</SubType> <SubType>Designer</SubType>
</None> </None>
@ -285,6 +297,12 @@
<Content Include="Views\Admin\Sonarr.cshtml"> <Content Include="Views\Admin\Sonarr.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Views\Admin\EmailNotifications.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Admin\Status.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Web.Debug.config"> <None Include="Web.Debug.config">
<DependentUpon>web.config</DependentUpon> <DependentUpon>web.config</DependentUpon>
</None> </None>

@ -62,7 +62,7 @@ namespace PlexRequests.UI
uri = $"http://localhost:{portResult}"; uri = $"http://localhost:{portResult}";
} }
Log.Trace("Getting assembly version"); Log.Trace("Getting product version");
WriteOutVersion(); WriteOutVersion();
var s = new Setup(); var s = new Setup();
@ -81,7 +81,7 @@ namespace PlexRequests.UI
private static void WriteOutVersion() private static void WriteOutVersion()
{ {
var assemblyVer = AssemblyHelper.GetAssemblyVersion(); var assemblyVer = AssemblyHelper.GetProductVersion();
Log.Info($"Version: {assemblyVer}"); Log.Info($"Version: {assemblyVer}");
Console.WriteLine($"Version: {assemblyVer}"); Console.WriteLine($"Version: {assemblyVer}");
} }

@ -33,3 +33,4 @@ using System.Runtime.InteropServices;
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersionAttribute("1.0.0.0")]

@ -26,6 +26,9 @@
#endregion #endregion
using System; using System;
using FluentScheduler; using FluentScheduler;
using NLog;
using Owin; using Owin;
using PlexRequests.UI.Jobs; using PlexRequests.UI.Jobs;
using TaskFactory = FluentScheduler.TaskFactory; using TaskFactory = FluentScheduler.TaskFactory;
@ -34,6 +37,8 @@ namespace PlexRequests.UI
{ {
public class Startup public class Startup
{ {
private static Logger Log = LogManager.GetCurrentClassLogger();
public void Configuration(IAppBuilder app) public void Configuration(IAppBuilder app)
{ {
try try
@ -44,7 +49,7 @@ namespace PlexRequests.UI
} }
catch (Exception exception) catch (Exception exception)
{ {
Console.WriteLine(exception.Message); Log.Fatal(exception);
throw; throw;
} }

@ -29,7 +29,7 @@
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port"> <input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">CouchPotato API Key</label> <label for="ApiKey" class="control-label">CouchPotato API Key</label>
@ -38,7 +38,14 @@
</div> </div>
</div> </div>
<div class="form-group">
<div>
<button id="testCp" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
</div>
</div>
<div class="form-group"> <div class="form-group">
@ -51,3 +58,33 @@
</div> </div>
<script>
$(function() {
$('#testCp').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
url: "/test/cp",
data: $form.serialize(),
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
generateNotify(response.message, "success");
$('#authToken').val(response.authToken);
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -0,0 +1,93 @@
@Html.Partial("_Sidebar")
@{
int port;
if (Model.EmailPort == 0)
{
port = 25;
}
else
{
port = Model.EmailPort;
}
}
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Email Notifications</legend>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Enabled)
{
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
}
else
{
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="EmailHost" class="control-label">SMTP Hostname or IP</label>
<div class="">
<input type="text" class="form-control form-control-custom " id="EmailHost" name="EmailHost" placeholder="localhost" value="@Model.EmailHost">
</div>
</div>
<div class="form-group">
<label for="EmailPort" class="control-label">SMTP Port</label>
<div class="">
<input type="text" class="form-control form-control-custom " id="EmailPort" name="EmailPort" placeholder="Port Number" value="@port">
</div>
</div>
<div class="form-group">
<label for="RecipientEmail" class="control-label">Email Recipient</label>
<div>
<input type="text" class="form-control form-control-custom " id="RecipientEmail" name="RecipientEmail" value="@Model.RecipientEmail">
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.EmailAuthentication)
{
<input type="checkbox" id="EmailAuthentication" name="EmailAuthentication" checked="checked"><text>Authenticate</text>
}
else
{
<input type="checkbox" id="EmailAuthentication" name="EmailAuthentication"><text>Authenticate</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="EmailUsername" class="control-label">Username</label>
<div>
<input type="text" class="form-control form-control-custom " id="EmailUsername" name="EmailUsername" value="@Model.EmailUsername">
</div>
</div>
<div class="form-group">
<label for="EmailPassword" class="control-label">Password</label>
<div>
<input type="password" class="form-control form-control-custom " id="EmailPassword" name="EmailPassword" value="@Model.EmailPassword">
</div>
</div>
<div class="form-group">
<div>
<button type="submit" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>

@ -29,6 +29,11 @@
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port"> <input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div> </div>
</div> </div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
@ -40,3 +45,32 @@
</div> </div>
<script>
$(function() {
$('#testPlex').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
url: "/test/plex",
data: $form.serialize(),
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
generateNotify(response.message, "success");
$('#authToken').val(response.authToken);
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -53,7 +53,7 @@
<div class="form-group"> <div class="form-group">
<label for="RootPath" class="control-label">Root save directory for TV shows</label> <label for="RootPath" class="control-label">Root save directory for TV shows</label>
<div> <div>
<input type="text" class="form-control form-control-custom " id="RootPath" name="RootPath" value="@Model.RootPath"> <input type="text" class="form-control form-control-custom " placeholder="C:\Media\Tv" id="RootPath" name="RootPath" value="@Model.RootPath">
<label>Enter the root folder where tv shows are saved. For example <strong>C:\Media\TV</strong>.</label> <label>Enter the root folder where tv shows are saved. For example <strong>C:\Media\TV</strong>.</label>
</div> </div>
</div> </div>
@ -74,6 +74,12 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group">
<div>
<button id="testSonarr" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div> <div>
@ -183,6 +189,30 @@
}); });
}); });
$('#testSonarr').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
url: "/test/sonarr",
data: $form.serialize(),
dataType: "json",
success: function (response) {
console.log(response);
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
}) })
</script> </script>

@ -0,0 +1,28 @@
@Html.Partial("_Sidebar")
<div class="col-sm-8 col-sm-push-1">
<fieldset>
<legend>Status</legend>
<div class="form-group">
<label class="control-label">Version: </label>
<label class="control-label">@Model.Version</label>
</div>
<div class="form-group">
<label class="control-label">Update Available: </label>
@if (Model.UpdateAvailable)
{
<label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i> Click Here!</a></label>
}
else
{
<label class="control-label"><i class="fa fa-times"></i></label>
}
</div>
</fieldset>
</div>

@ -2,11 +2,11 @@
<div class="list-group table-of-contents"> <div class="list-group table-of-contents">
@if (Context.Request.Path == "/admin") @if (Context.Request.Path == "/admin")
{ {
<a class="list-group-item active" href="/admin">Plex Request Settings</a> <a class="list-group-item active" href="/admin">Plex Request</a>
} }
else else
{ {
<a class="list-group-item" href="/admin">Plex Request Settings</a> <a class="list-group-item" href="/admin">Plex Request</a>
} }
@if (Context.Request.Path == "/admin/authentication") @if (Context.Request.Path == "/admin/authentication")
{ {
@ -21,30 +21,47 @@
@if (Context.Request.Path == "/admin/plex") @if (Context.Request.Path == "/admin/plex")
{ {
<a class="list-group-item active" href="/admin/plex">Plex Settings</a> <a class="list-group-item active" href="/admin/plex">Plex</a>
} }
else else
{ {
<a class="list-group-item" href="/admin/plex">Plex Settings</a> <a class="list-group-item" href="/admin/plex">Plex</a>
} }
@if (Context.Request.Path == "/admin/couchpotato") @if (Context.Request.Path == "/admin/couchpotato")
{ {
<a class="list-group-item active" href="/admin/couchpotato">CouchPotato Settings</a> <a class="list-group-item active" href="/admin/couchpotato">CouchPotato</a>
} }
else else
{ {
<a class="list-group-item" href="/admin/couchpotato">CouchPotato Settings</a> <a class="list-group-item" href="/admin/couchpotato">CouchPotato</a>
} }
@if (Context.Request.Path == "/admin/sonarr") @if (Context.Request.Path == "/admin/sonarr")
{ {
<a class="list-group-item active" href="/admin/sonarr">Sonarr Settings</a> <a class="list-group-item active" href="/admin/sonarr">Sonarr</a>
} }
else else
{ {
<a class="list-group-item" href="/admin/sonarr">Sonarr Settings</a> <a class="list-group-item" href="/admin/sonarr">Sonarr</a>
} }
@*<a class="list-group-item" href="/admin/sickbeard">Sickbeard Settings</a>*@ @*<a class="list-group-item" href="/admin/sickbeard">Sickbeard Settings</a>*@
@if (Context.Request.Path == "/admin/emailnotification")
{
<a class="list-group-item active" href="/admin/emailnotification">Email Notifications</a>
}
else
{
<a class="list-group-item" href="/admin/emailnotification">Email Notifications</a>
}
@if (Context.Request.Path == "/admin/status")
{
<a class="list-group-item active" href="/admin/status">Status</a>
}
else
{
<a class="list-group-item" href="/admin/status">Status</a>
}
</div> </div>
</div> </div>

@ -5,8 +5,8 @@
@if (Context.CurrentUser.IsAuthenticated()) @if (Context.CurrentUser.IsAuthenticated())
{ {
<button id="approveAll" class="btn btn-success-outline" type="submit"><i class="fa fa-plus"></i> Approve All</button> <button id="approveAll" class="btn btn-success-outline" type="submit"><i class="fa fa-plus"></i> Approve All</button>
<br/> <br />
<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">
@ -102,10 +102,13 @@
<div>Issue: {{issues}}</div> <div>Issue: {{issues}}</div>
{{/if}} {{/if}}
</div> </div>
<div id="adminNotesArea">
{{#if adminNote}}
<div>Note from Admin: {{adminNote}}</div>
{{/if}}
</div>
</div> </div>
<div class="col-sm-2 col-sm-push-3"> <div class="col-sm-2 col-sm-push-3">
<br />
<br />
{{#if_eq admin true}} {{#if_eq admin true}}
{{#if_eq approved false}} {{#if_eq approved false}}
<form method="POST" action="/approval/approve" id="approve{{requestId}}"> <form method="POST" action="/approval/approve" id="approve{{requestId}}">
@ -117,7 +120,7 @@
<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-sm btn-danger-outline delete" type="submit"><i class="fa fa-plus"></i> Remove</button> <button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-danger-outline delete" type="submit"><i class="fa fa-plus"></i> Remove</button>
</form> </form>
<form method="POST" action="/requests/clearissues" id="clear{{requestId}}"> <form method="POST" action="/requests/clearissues" id="clear{{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-sm btn-info-outline clear" type="submit"><i class="fa fa-check"></i> Clear Issues</button> <button id="{{requestId}}" style="text-align: right" class="btn btn-sm btn-info-outline clear" type="submit"><i class="fa fa-check"></i> Clear Issues</button>
@ -147,9 +150,14 @@
<li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li> <li><a id="{{requestId}}" issue-select="2" class="dropdownIssue" href="#">Wrong Content</a></li>
<li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li> <li><a id="{{requestId}}" issue-select="3" class="dropdownIssue" href="#">Playback Issues</a></li>
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li> <li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul> </ul>
</div> </div>
</form> </form>
</div> </div>
@* // TODO add Issues to the view *@ @* // TODO add Issues to the view *@
</div> </div>
@ -170,7 +178,7 @@
<textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea> <textarea class="form-control form-control-custom" rows="3" id="commentArea" name="commentArea"></textarea>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">Save changes</button> <button type="button" class="btn btn-primary-outline theSaveButton" data-dismiss="modal">Save changes</button>
</div> </div>
</form> </form>
@ -178,4 +186,25 @@
</div> </div>
</div> </div>
<div class="modal fade" id="noteModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
<h4 class="modal-title">Add a note</h4>
</div>
<form method="POST" action="/requests/addnote" id="noteForm">
<div class="modal-body">
<input name="requestId" class="noteId" type="text" hidden="hidden" value="" />
<textarea class="form-control form-control-custom" rows="3" id="noteArea" name="noteArea"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary-outline theNoteSaveButton" data-dismiss="modal">Save changes</button>
</div>
</form>
</div>
</div>
</div>
<script src="/Content/requests.js" type="text/javascript"></script> <script src="/Content/requests.js" type="text/javascript"></script>

@ -9,6 +9,8 @@
<link rel="stylesheet" href="~/Content/custom.min.css" type="text/css"/> <link rel="stylesheet" href="~/Content/custom.min.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/bootstrap.css" type="text/css"/> <link rel="stylesheet" href="~/Content/bootstrap.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/font-awesome.css" type="text/css"/> <link rel="stylesheet" href="~/Content/font-awesome.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/pace.min.css" type="text/css"/>
<!-- Scripts --> <!-- Scripts -->
<script src="/Content/jquery-2.2.1.min.js"></script> <script src="/Content/jquery-2.2.1.min.js"></script>
@ -16,6 +18,7 @@
<script src="/Content/bootstrap.min.js"></script> <script src="/Content/bootstrap.min.js"></script>
<script src="/Content/bootstrap-notify.min.js"></script> <script src="/Content/bootstrap-notify.min.js"></script>
<script src="/Content/site.js"></script> <script src="/Content/site.js"></script>
<script src="/Content/pace.min.js"></script>
</head> </head>
<body> <body>

@ -2,5 +2,9 @@
{ {
"outputFile": "Content/custom.css", "outputFile": "Content/custom.css",
"inputFile": "Content/custom.scss" "inputFile": "Content/custom.scss"
},
{
"outputFile": "Content/pace.css",
"inputFile": "Content/pace.scss"
} }
] ]

@ -31,6 +31,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services", "Pl
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Models", "PlexRequests.Api.Models\PlexRequests.Api.Models.csproj", "{CB37A5F8-6DFC-4554-99D3-A42B502E4591}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Models", "PlexRequests.Api.Models\PlexRequests.Api.Models.csproj", "{CB37A5F8-6DFC-4554-99D3-A42B502E4591}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services.Tests", "PlexRequests.Services.Tests\PlexRequests.Services.Tests.csproj", "{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -77,6 +81,14 @@ Global
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.Build.0 = Release|Any CPU {CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Release|Any CPU.Build.0 = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAADB4AC-064F-4D3A-AFF9-64A33131A9A7}.Release|Any CPU.Build.0 = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E6395D3-B074-49E8-898D-0EB99E507E0E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

@ -1,11 +1,11 @@
version: 1.1.{build} version: 1.2.{build}
configuration: Release configuration: Release
assembly_info: assembly_info:
patch: true patch: true
file: '**\AssemblyInfo.*' file: '**\AssemblyInfo.*'
assembly_version: '{version}' assembly_version: '1.2.0'
assembly_file_version: '{version}' assembly_file_version: '{version}'
assembly_informational_version: '{version}' assembly_informational_version: '1.2.0'
before_build: before_build:
- cmd: appveyor-retry nuget restore - cmd: appveyor-retry nuget restore
build: build:

Loading…
Cancel
Save