diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..210f21789 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,71 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain + + +PlexRequests.UI/Content/* linguist-vendored +PlexRequests.UI/Content/* linguist-vendored +base.scss linguist-vendored=false +requests-1.7.js linguist-vendored=false +search-1.7.js linguist-vendored=false +site-1.7.js linguist-vendored=false diff --git a/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs b/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs index adad93c74..21c222974 100644 --- a/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs +++ b/PlexRequests.Api.Models/Movie/CouchPotatoAdd.cs @@ -1,100 +1,100 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PlexRequests.Api.Models.Movie -{ - - public class CouchPotatoAdd - { - public Movie movie { get; set; } - public bool success { get; set; } - } - public class Rating - { - public List imdb { get; set; } - } - - - - public class Images - { - public List disc_art { get; set; } - public List poster { get; set; } - public List backdrop { get; set; } - public List extra_thumbs { get; set; } - public List poster_original { get; set; } - public List actors { get; set; } - public List backdrop_original { get; set; } - public List clear_art { get; set; } - public List logo { get; set; } - public List banner { get; set; } - public List landscape { get; set; } - public List extra_fanart { get; set; } - } - - public class Info - { - public Rating rating { get; set; } - public List genres { get; set; } - public int tmdb_id { get; set; } - public string plot { get; set; } - public string tagline { get; set; } - public Release_Date release_date { get; set; } - public int year { get; set; } - public string original_title { get; set; } - public List actor_roles { get; set; } - public bool via_imdb { get; set; } - public Images images { get; set; } - public List directors { get; set; } - public List titles { get; set; } - public string imdb { get; set; } - public string mpaa { get; set; } - public bool via_tmdb { get; set; } - public List actors { get; set; } - public List writers { get; set; } - public int runtime { get; set; } - public string type { get; set; } - public string released { get; set; } - } - - public class Release_Date - { - public int dvd { get; set; } - public int expires { get; set; } - public int theater { get; set; } - public bool bluray { get; set; } - } - - public class Files - { - public List image_poster { get; set; } - } - - public class Identifiers - { - public string imdb { get; set; } - } - - public class Movie - { - public string status { get; set; } - public Info info { get; set; } - public string _t { get; set; } - public List releases { get; set; } - public string title { get; set; } - public string _rev { get; set; } - public string profile_id { get; set; } - public string _id { get; set; } - public List tags { get; set; } - public int last_edit { get; set; } - public object category_id { get; set; } - public string type { get; set; } - public Files files { get; set; } - public Identifiers identifiers { get; set; } - } - - -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PlexRequests.Api.Models.Movie +{ + + public class CouchPotatoAdd + { + public Movie movie { get; set; } + public bool success { get; set; } + } + public class Rating + { + public List imdb { get; set; } + } + + + + public class Images + { + public List disc_art { get; set; } + public List poster { get; set; } + public List backdrop { get; set; } + public List extra_thumbs { get; set; } + public List poster_original { get; set; } + public List actors { get; set; } + public List backdrop_original { get; set; } + public List clear_art { get; set; } + public List logo { get; set; } + public List banner { get; set; } + public List landscape { get; set; } + public List extra_fanart { get; set; } + } + + public class Info + { + public Rating rating { get; set; } + public List genres { get; set; } + public int tmdb_id { get; set; } + public string plot { get; set; } + public string tagline { get; set; } + public Release_Date release_date { get; set; } + public int year { get; set; } + public string original_title { get; set; } + public List actor_roles { get; set; } + public bool via_imdb { get; set; } + public Images images { get; set; } + public List directors { get; set; } + public List titles { get; set; } + public string imdb { get; set; } + public string mpaa { get; set; } + public bool via_tmdb { get; set; } + public List actors { get; set; } + public List writers { get; set; } + public int runtime { get; set; } + public string type { get; set; } + public string released { get; set; } + } + + public class Release_Date + { + public int dvd { get; set; } + public int expires { get; set; } + public int theater { get; set; } + public bool bluray { get; set; } + } + + public class Files + { + public List image_poster { get; set; } + } + + public class Identifiers + { + public string imdb { get; set; } + } + + public class Movie + { + public string status { get; set; } + public Info info { get; set; } + public string _t { get; set; } + public List releases { get; set; } + public string title { get; set; } + public string _rev { get; set; } + public string profile_id { get; set; } + public string _id { get; set; } + public List tags { get; set; } + public int last_edit { get; set; } + public object category_id { get; set; } + public string type { get; set; } + public Files files { get; set; } + public Identifiers identifiers { get; set; } + } + + +} diff --git a/PlexRequests.Api/CouchPotatoApi.cs b/PlexRequests.Api/CouchPotatoApi.cs index 4ec0356c5..b7118185a 100644 --- a/PlexRequests.Api/CouchPotatoApi.cs +++ b/PlexRequests.Api/CouchPotatoApi.cs @@ -1,164 +1,168 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: CouchPotatoApi.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 Newtonsoft.Json.Linq; - -using NLog; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Movie; -using PlexRequests.Helpers.Exceptions; - -using RestSharp; - -namespace PlexRequests.Api -{ - public class CouchPotatoApi : ICouchPotatoApi - { - public CouchPotatoApi() - { - Api = new ApiRequest(); - } - private ApiRequest Api { get; set; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string)) - { - RestRequest request; - request = string.IsNullOrEmpty(profileId) - ? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"} - : new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" }; - - if (!string.IsNullOrEmpty(profileId)) - { - request.AddUrlSegment("profileId", profileId); - } - - request.AddUrlSegment("apikey", apiKey); - request.AddUrlSegment("imdbid", imdbid); - request.AddUrlSegment("title", title); - - var obj = RetryHandler.Execute(() => Api.ExecuteJson (request, baseUrl),new[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10)}, - (exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan)); - - Log.Trace("CP movie Add result count {0}", obj.Count); - - if (obj.Count > 0) - { - try - { - Log.Trace("CP movie obj[\"success\"] = {0}", obj["success"]); - var result = (bool)obj["success"]; - Log.Trace("CP movie Add result {0}", result); - return result; - } - catch (Exception e) - { - Log.Fatal(e); - return false; - } - } - return false; - } - - /// - /// Gets the status. - /// - /// The URL. - /// The API key. - /// - 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); - - - var obj = RetryHandler.Execute(() => Api.Execute (request, url),new TimeSpan[] { - TimeSpan.FromSeconds (2), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10)}, - (exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan)); - - return obj; - } - - public CouchPotatoProfiles GetProfiles(Uri url, string apiKey) - { - Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey); - var request = new RestRequest - { - Resource = "api/{apikey}/profile.list/", - Method = Method.GET - }; - - request.AddUrlSegment("apikey", apiKey); - - var obj = RetryHandler.Execute(() => Api.Execute (request, url),null, - (exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan)); - - return obj; - } - - public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status) - { - var request = new RestRequest - { - Resource = "/api/{apikey}/movie.list?status={status}" - }; - - request.AddUrlSegment("apikey", apiKey); - request.AddUrlSegment("status", string.Join(",", status)); - try - { - var obj = RetryHandler.Execute(() => Api.Execute (request, baseUrl), - new[] { - TimeSpan.FromSeconds (5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }, - (exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan)); - - return obj; - } - catch (Exception e) // Request error is already logged in the ApiRequest class - { - Log.Error("Error when attempting to GetMovies."); - Log.Error (e); - return new CouchPotatoMovies(); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CouchPotatoApi.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 Newtonsoft.Json.Linq; + +using NLog; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Movie; +using PlexRequests.Helpers.Exceptions; + +using RestSharp; + +namespace PlexRequests.Api +{ + public class CouchPotatoApi : ICouchPotatoApi + { + public CouchPotatoApi() + { + Api = new ApiRequest(); + } + private ApiRequest Api { get; set; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public bool AddMovie(string imdbid, string apiKey, string title, Uri baseUrl, string profileId = default(string)) + { + RestRequest request; + request = string.IsNullOrEmpty(profileId) + ? new RestRequest {Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}"} + : new RestRequest { Resource = "/api/{apikey}/movie.add?title={title}&identifier={imdbid}&profile_id={profileId}" }; + + if (!string.IsNullOrEmpty(profileId)) + { + request.AddUrlSegment("profileId", profileId); + } + + request.AddUrlSegment("apikey", apiKey); + request.AddUrlSegment("imdbid", imdbid); + request.AddUrlSegment("title", title); + + var obj = RetryHandler.Execute(() => Api.ExecuteJson (request, baseUrl),new[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10)}, + (exception, timespan) => Log.Error (exception, "Exception when calling AddMovie for CP, Retrying {0}", timespan)); + + Log.Trace("CP movie Add result count {0}", obj.Count); + + if (obj.Count > 0) + { + try + { + Log.Trace("CP movie obj[\"success\"] = {0}", obj["success"]); + var result = (bool)obj["success"]; + Log.Trace("CP movie Add result {0}", result); + return result; + } + catch (Exception e) + { + Log.Fatal(e); + return false; + } + } + return false; + } + + /// + /// Gets the status. + /// + /// The URL. + /// The API key. + /// + 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); + + + var obj = RetryHandler.Execute(() => Api.Execute (request, url),new TimeSpan[] { + TimeSpan.FromSeconds (2), + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10)}, + (exception, timespan) => Log.Error (exception, "Exception when calling GetStatus for CP, Retrying {0}", timespan)); + + return obj; + } + + public CouchPotatoProfiles GetProfiles(Uri url, string apiKey) + { + Log.Trace("Getting CP Profiles, ApiKey = {0}", apiKey); + var request = new RestRequest + { + Resource = "api/{apikey}/profile.list/", + Method = Method.GET + }; + + request.AddUrlSegment("apikey", apiKey); + + var obj = RetryHandler.Execute(() => Api.Execute (request, url),null, + (exception, timespan) => Log.Error (exception, "Exception when calling GetProfiles for CP, Retrying {0}", timespan)); + + return obj; + } + + public CouchPotatoMovies GetMovies(Uri baseUrl, string apiKey, string[] status) + { + var request = new RestRequest + { + Resource = "/api/{apikey}/movie.list?status={status}", + OnBeforeDeserialization = (x => + { + x.Content = x.Content.Replace("[]", "{}"); + }) + }; + + request.AddUrlSegment("apikey", apiKey); + request.AddUrlSegment("status", string.Join(",", status)); + try + { + var obj = RetryHandler.Execute(() => Api.Execute (request, baseUrl), + new[] { + TimeSpan.FromSeconds (5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(30) + }, + (exception, timespan) => Log.Error (exception, "Exception when calling GetMovies for CP, Retrying {0}", timespan)); + + return obj; + } + catch (Exception e) // Request error is already logged in the ApiRequest class + { + Log.Error("Error when attempting to GetMovies."); + Log.Error (e); + return new CouchPotatoMovies(); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Core.Tests/AuthenticationSettingsTests.cs b/PlexRequests.Core.Tests/AuthenticationSettingsTests.cs index 784117cfa..3072ab82a 100644 --- a/PlexRequests.Core.Tests/AuthenticationSettingsTests.cs +++ b/PlexRequests.Core.Tests/AuthenticationSettingsTests.cs @@ -1,59 +1,91 @@ -#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 NUnit.Framework; - -using PlexRequests.Core.SettingModels; - -namespace PlexRequests.Core.Tests -{ - [TestFixture] - public class AuthenticationSettingsTests - { - [Test, TestCaseSource(nameof(UserData))] - public void DeniedUserListTest(string users, string[] expected) - { - var model = new AuthenticationSettings { DeniedUsers = users }; - - var result = model.DeniedUserList; - - Assert.That(result.Count, Is.EqualTo(expected.Length)); - for (var i = 0; i < expected.Length; i++) - { - Assert.That(result[i], Is.EqualTo(expected[i])); - } - } - - static readonly object[] UserData = - { - new object[] { "john", new [] {"john"} }, - new object[] { "john , abc ,", new [] {"john", "abc"} }, - new object[] { "john,, cde", new [] {"john", "cde"} }, - new object[] { "john,,, aaa , baaa , ", new [] {"john","aaa","baaa"} }, - new object[] { "john, aaa , baaa , maaa, caaa", new [] {"john","aaa","baaa", "maaa", "caaa"} }, - }; - } +#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 System.Collections.Generic; + +using NUnit.Framework; + +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Core.Tests +{ + [TestFixture] + public class NotificationMessageResolverTests + { + [TestCaseSource(nameof(MessageResolver))] + public string Resolve(Dictionary message, Dictionary param) + { + var n = new NotificationMessageResolver(); + var s = new NotificationSettings + { + Message = message, + CustomParamaters = param + }; + + return n.ParseMessage(s, NotificationType.NewRequest); + } + + private static IEnumerable MessageResolver + { + get + { + yield return new TestCaseData( + new Dictionary { { NotificationType.NewRequest, "There has been a new request from {Username}, Title: {Title} for {Type}" } }, + new Dictionary{{"Username", "Jamie" },{"Title", "Finding Dory" },{"Type", "Movie" }}) + .Returns("There has been a new request from Jamie, Title: Finding Dory for Movie") + .SetName("FindingDory"); + + yield return new TestCaseData( + new Dictionary { { NotificationType.NewRequest, string.Empty } }, + new Dictionary()) + .Returns(string.Empty) + .SetName("Empty Message"); + + yield return new TestCaseData( + new Dictionary { { NotificationType.NewRequest, "{{Wowwzer}} Damn}{{son}}}}" } }, + new Dictionary { {"son","HEY!"} }) + .Returns("{{Wowwzer}} Damn}{HEY!}}}") + .SetName("Multiple Curlys"); + + + yield return new TestCaseData( + new Dictionary { { NotificationType.NewRequest, "This is a message with no curlys" } }, + new Dictionary { { "son", "HEY!" } }) + .Returns("This is a message with no curlys") + .SetName("No Curlys"); + + yield return new TestCaseData( + new Dictionary { { NotificationType.NewRequest, new string(')', 5000)} }, + new Dictionary { { "son", "HEY!" } }) + .Returns(new string(')', 5000)) + .SetName("Long String"); + } + } + + } } \ No newline at end of file diff --git a/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs b/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs new file mode 100644 index 000000000..62b03470c --- /dev/null +++ b/PlexRequests.Core.Tests/NotificationMessageResolverTests.cs @@ -0,0 +1,59 @@ +#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 NUnit.Framework; + +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Core.Tests +{ + [TestFixture] + public class AuthenticationSettingsTests + { + [Test, TestCaseSource(nameof(UserData))] + public void DeniedUserListTest(string users, string[] expected) + { + var model = new AuthenticationSettings { DeniedUsers = users }; + + var result = model.DeniedUserList; + + Assert.That(result.Count, Is.EqualTo(expected.Length)); + for (var i = 0; i < expected.Length; i++) + { + Assert.That(result[i], Is.EqualTo(expected[i])); + } + } + + static readonly object[] UserData = + { + new object[] { "john", new [] {"john"} }, + new object[] { "john , abc ,", new [] {"john", "abc"} }, + new object[] { "john,, cde", new [] {"john", "cde"} }, + new object[] { "john,,, aaa , baaa , ", new [] {"john","aaa","baaa"} }, + new object[] { "john, aaa , baaa , maaa, caaa", new [] {"john","aaa","baaa", "maaa", "caaa"} }, + }; + } +} \ No newline at end of file diff --git a/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj b/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj index b0d9a25ef..3a456e243 100644 --- a/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj +++ b/PlexRequests.Core.Tests/PlexRequests.Core.Tests.csproj @@ -1,107 +1,108 @@ - - - - Debug - AnyCPU - {FCFECD5D-47F6-454D-8692-E27A921BE655} - Library - Properties - PlexRequests.Core.Tests - PlexRequests.Core.Tests - v4.5.2 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll - True - - - ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll - True - - - ..\packages\AutoFixture.3.40.0\lib\net40\Ploeh.AutoFixture.dll - True - - - - - - - - - - - - - - - - - - - - - - - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} - PlexRequests.Core - - - {1252336D-42A3-482A-804C-836E60173DFA} - PlexRequests.Helpers - - - - - - - False - - - False - - - False - - - False - - - - - - - + + + + Debug + AnyCPU + {FCFECD5D-47F6-454D-8692-E27A921BE655} + Library + Properties + PlexRequests.Core.Tests + PlexRequests.Core.Tests + v4.5.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll + True + + + ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll + True + + + ..\packages\AutoFixture.3.40.0\lib\net40\Ploeh.AutoFixture.dll + True + + + + + + + + + + + + + + + + + + + + + + + + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} + PlexRequests.Core + + + {1252336D-42A3-482A-804C-836E60173DFA} + PlexRequests.Helpers + + + + + + + False + + + False + + + False + + + False + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Core.Tests/app.config b/PlexRequests.Core.Tests/app.config index 44b249bff..afa1b4c43 100644 --- a/PlexRequests.Core.Tests/app.config +++ b/PlexRequests.Core.Tests/app.config @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/PlexRequests.Services/Notification/NotificationType.cs b/PlexRequests.Core/Models/NotificationType.cs similarity index 94% rename from PlexRequests.Services/Notification/NotificationType.cs rename to PlexRequests.Core/Models/NotificationType.cs index 5e7b32370..a01d153dd 100644 --- a/PlexRequests.Services/Notification/NotificationType.cs +++ b/PlexRequests.Core/Models/NotificationType.cs @@ -1,39 +1,39 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: NotificationType.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.Services.Notification -{ - public enum NotificationType - { - NewRequest, - Issue, - RequestAvailable, - RequestApproved, - AdminNote, - Test, - - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NotificationType.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 enum NotificationType + { + NewRequest, + Issue, + RequestAvailable, + RequestApproved, + AdminNote, + Test, + + } +} diff --git a/PlexRequests.Core/NotificationMessageResolver.cs b/PlexRequests.Core/NotificationMessageResolver.cs new file mode 100644 index 000000000..5e26e9300 --- /dev/null +++ b/PlexRequests.Core/NotificationMessageResolver.cs @@ -0,0 +1,102 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CustomNotificationService.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 System.Linq; + +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; + +namespace PlexRequests.Core +{ + public class NotificationMessageResolver + { + public string ParseMessage(T notification, NotificationType type) where T : NotificationSettings + { + var notificationToParse = notification.Message.FirstOrDefault(x => x.Key == type).Value; + if (string.IsNullOrEmpty(notificationToParse)) + return string.Empty; + + return Resolve(notificationToParse, notification.CustomParamaters); + } + + private string Resolve(string message, Dictionary paramaters) + { + var fields = FindCurlyFields(message); + + + foreach (var f in fields) + { + string outString; + if (paramaters.TryGetValue(f, out outString)) + { + message = message.Replace($"{{{f}}}", outString); + } + } + + return message; + } + + private IEnumerable FindCurlyFields(string message) + { + var insideCurly = false; + var fields = new List(); + var currentWord = string.Empty; + var chars = message.ToCharArray(); + + foreach (var c in chars) + { + if (char.IsWhiteSpace(c)) + { + currentWord = string.Empty; + continue; + } + + if (c == 123) // Start of curly '{' + { + insideCurly = true; + continue; + } + + if (c == 125) // End of curly '}' + { + fields.Add(currentWord); // We have finished the curly, add the word into the list + currentWord = string.Empty; + insideCurly = false; + continue; + } + + if (insideCurly) + { + currentWord += c.ToString(); // Add the character onto the word. + } + + } + + return fields; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj index 495a09ed8..964bb6d01 100644 --- a/PlexRequests.Core/PlexRequests.Core.csproj +++ b/PlexRequests.Core/PlexRequests.Core.csproj @@ -1,134 +1,137 @@ - - - - - Debug - AnyCPU - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} - Library - Properties - PlexRequests.Core - PlexRequests.Core - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\Assemblies\Mono.Data.Sqlite.dll - - - ..\packages\NLog.4.3.4\lib\net45\NLog.dll - True - - - - - - - - - - - ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll - - - ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll - - - ..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Octokit.0.19.0\lib\net45\Octokit.dll - - - ..\packages\valueinjecter.3.1.1.2\lib\net40\Omu.ValueInjecter.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {95834072-A675-415D-AA8F-877C91623810} - PlexRequests.Api.Interfaces - - - {CB37A5F8-6DFC-4554-99D3-A42B502E4591} - PlexRequests.Api.Models - - - {8CB8D235-2674-442D-9C6A-35FCAEEB160D} - PlexRequests.Api - - - {1252336D-42A3-482A-804C-836E60173DFA} - PlexRequests.Helpers - - - {92433867-2B7B-477B-A566-96C382427525} - PlexRequests.Store - - - - + + + + + Debug + AnyCPU + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} + Library + Properties + PlexRequests.Core + PlexRequests.Core + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\Assemblies\Mono.Data.Sqlite.dll + + + ..\packages\NLog.4.3.4\lib\net45\NLog.dll + True + + + + + + + + + + + ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + + + ..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Octokit.0.19.0\lib\net45\Octokit.dll + + + ..\packages\valueinjecter.3.1.1.2\lib\net40\Omu.ValueInjecter.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {95834072-A675-415D-AA8F-877C91623810} + PlexRequests.Api.Interfaces + + + {CB37A5F8-6DFC-4554-99D3-A42B502E4591} + PlexRequests.Api.Models + + + {8CB8D235-2674-442D-9C6A-35FCAEEB160D} + PlexRequests.Api + + + {1252336D-42A3-482A-804C-836E60173DFA} + PlexRequests.Helpers + + + {92433867-2B7B-477B-A566-96C382427525} + PlexRequests.Store + + + + \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs b/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs index 2e3db9451..c00342789 100644 --- a/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/EmailNotificationSettings.cs @@ -1,40 +1,40 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: EmailNotificationSettings.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.SettingModels -{ - public class EmailNotificationSettings : Settings - { - public string EmailHost { get; set; } - public string EmailPassword { get; set; } - public int EmailPort { get; set; } - public string EmailSender { get; set; } - public string EmailUsername { get; set; } - public bool Enabled { get; set; } - public bool EnableUserEmailNotifications { get; set; } - public string RecipientEmail { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: EmailNotificationSettings.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.SettingModels +{ + public class EmailNotificationSettings : NotificationSettings + { + public string EmailHost { get; set; } + public string EmailPassword { get; set; } + public int EmailPort { get; set; } + public string EmailSender { get; set; } + public string EmailUsername { get; set; } + public bool Enabled { get; set; } + public bool EnableUserEmailNotifications { get; set; } + public string RecipientEmail { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/ContainerHelper.cs b/PlexRequests.Core/SettingModels/NotificationSettings.cs similarity index 68% rename from PlexRequests.UI/Helpers/ContainerHelper.cs rename to PlexRequests.Core/SettingModels/NotificationSettings.cs index 85c4f7f92..0ba781958 100644 --- a/PlexRequests.UI/Helpers/ContainerHelper.cs +++ b/PlexRequests.Core/SettingModels/NotificationSettings.cs @@ -1,7 +1,7 @@ #region Copyright // /************************************************************************ // Copyright (c) 2016 Jamie Rees -// File: ContainerHelper.cs +// File: NotificationSettings.cs // Created By: Jamie Rees // // Permission is hereby granted, free of charge, to any person obtaining @@ -24,25 +24,15 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion -using Nancy.TinyIoc; +using System.Collections.Generic; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Store; -using PlexRequests.Store.Repository; +using PlexRequests.Core.Models; -namespace PlexRequests.UI.Helpers +namespace PlexRequests.Core.SettingModels { - public static class ContainerHelper + public class NotificationSettings : Settings { - public static void RegisterSetting(this TinyIoCContainer container) where T : Settings, new() - { - container.Register, SettingsServiceV2>(); - } - - public static void RegisterRepo(this TinyIoCContainer container) where T : Entity, new() - { - container.Register, GenericRepository>(); - } + public Dictionary Message { get; set; } + public Dictionary CustomParamaters { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs index 493d8bf39..6e65951d1 100644 --- a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs @@ -1,77 +1,87 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexRequestSettings.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace PlexRequests.Core.SettingModels -{ - public class PlexRequestSettings : Settings - { - public int Port { get; set; } - public string BaseUrl { get; set; } - public bool SearchForMovies { get; set; } - public bool SearchForTvShows { get; set; } - public bool SearchForMusic { get; set; } - public bool RequireMovieApproval { get; set; } - public bool RequireTvShowApproval { get; set; } - public bool RequireMusicApproval { get; set; } - public bool UsersCanViewOnlyOwnRequests { get; set; } - public bool UsersCanViewOnlyOwnIssues { get; set; } - public int WeeklyRequestLimit { get; set; } - public string NoApprovalUsers { get; set; } - public bool CollectAnalyticData { get; set; } - - /// - /// The CSS name of the theme we want - /// - public string ThemeName { get; set; } - - public string ApiKey { get; set; } - - [JsonIgnore] - public List ApprovalWhiteList - { - get - { - var users = new List(); - if (string.IsNullOrEmpty(NoApprovalUsers)) - { - return users; - } - - var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var user in splitUsers) - { - if (!string.IsNullOrWhiteSpace(user)) - users.Add(user.Trim()); - } - return users; - } - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexRequestSettings.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace PlexRequests.Core.SettingModels +{ + public class PlexRequestSettings : Settings + { + public PlexRequestSettings() + { + TvWeeklyRequestLimit = 0; + MovieWeeklyRequestLimit = 0; + AlbumWeeklyRequestLimit = 0; + } + + public int Port { get; set; } + public string BaseUrl { get; set; } + public bool SearchForMovies { get; set; } + public bool SearchForTvShows { get; set; } + public bool SearchForMusic { get; set; } + public bool RequireMovieApproval { get; set; } + public bool RequireTvShowApproval { get; set; } + public bool RequireMusicApproval { get; set; } + public bool UsersCanViewOnlyOwnRequests { get; set; } + public bool UsersCanViewOnlyOwnIssues { get; set; } + public int MovieWeeklyRequestLimit { get; set; } + public int TvWeeklyRequestLimit { get; set; } + public int AlbumWeeklyRequestLimit { get; set; } + public string NoApprovalUsers { get; set; } + public bool CollectAnalyticData { get; set; } + public bool IgnoreNotifyForAutoApprovedRequests { get; set; } + + /// + /// The CSS name of the theme we want + /// + public string ThemeName { get; set; } + + public string ApiKey { get; set; } + + [JsonIgnore] + public List ApprovalWhiteList + { + get + { + var users = new List(); + if (string.IsNullOrEmpty(NoApprovalUsers)) + { + return users; + } + + var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var user in splitUsers) + { + if (!string.IsNullOrWhiteSpace(user)) + users.Add(user.Trim()); + } + return users; + } + } + } +} diff --git a/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs b/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs index 1b3ce26f5..a8cc43ef9 100644 --- a/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/PushBulletNotificationSettings.cs @@ -1,9 +1,9 @@ -namespace PlexRequests.Core.SettingModels -{ - public class PushbulletNotificationSettings : Settings - { - public bool Enabled { get; set; } - public string AccessToken { get; set; } - public string DeviceIdentifier { get; set; } - } +namespace PlexRequests.Core.SettingModels +{ + public class PushbulletNotificationSettings : NotificationSettings + { + public bool Enabled { get; set; } + public string AccessToken { get; set; } + public string DeviceIdentifier { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs b/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs index ac6c4c435..87772ffa2 100644 --- a/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/PushoverNotificationSettings.cs @@ -1,9 +1,9 @@ -namespace PlexRequests.Core.SettingModels -{ - public class PushoverNotificationSettings : Settings - { - public bool Enabled { get; set; } - public string AccessToken { get; set; } - public string UserToken { get; set; } - } +namespace PlexRequests.Core.SettingModels +{ + public class PushoverNotificationSettings : NotificationSettings + { + public bool Enabled { get; set; } + public string AccessToken { get; set; } + public string UserToken { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs index 216f54f88..6d895f6ef 100644 --- a/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs +++ b/PlexRequests.Core/SettingModels/ScheduledJobsSettings.cs @@ -36,6 +36,7 @@ namespace PlexRequests.Core.SettingModels CouchPotatoCacher = 10; StoreBackup = 24; StoreCleanup = 24; + UserRequestLimitResetter = 12; } public int PlexAvailabilityChecker { get; set; } public int SickRageCacher { get; set; } @@ -43,5 +44,6 @@ namespace PlexRequests.Core.SettingModels public int CouchPotatoCacher { get; set; } public int StoreBackup { get; set; } public int StoreCleanup { get; set; } + public int UserRequestLimitResetter { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs b/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs index 2b412ed3e..93562e8a1 100644 --- a/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs +++ b/PlexRequests.Core/SettingModels/SlackNotificationSettings.cs @@ -4,7 +4,7 @@ using Newtonsoft.Json; namespace PlexRequests.Core.SettingModels { - public class SlackNotificationSettings : Settings + public class SlackNotificationSettings : NotificationSettings { public bool Enabled { get; set; } public string WebhookUrl { get; set; } diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 2c015f55c..a3a407aa1 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -1,244 +1,240 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Setup.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.Text.RegularExpressions; - -using Mono.Data.Sqlite; -using NLog; -using PlexRequests.Api; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Store; -using PlexRequests.Store.Repository; -using System.Threading.Tasks; - -namespace PlexRequests.Core -{ - public class Setup - { - private static Logger Log = LogManager.GetCurrentClassLogger(); - private static DbConfiguration Db { get; set; } - public string SetupDb(string urlBase) - { - Db = new DbConfiguration(new SqliteFactory()); - var created = Db.CheckDb(); - TableCreation.CreateTables(Db.DbConnection()); - - if (created) - { - CreateDefaultSettingsPage(urlBase); - } - - var version = CheckSchema(); - if (version > 0) - { - if (version > 1700 && version <= 1799) - { - MigrateToVersion1700(); - } - if (version > 1799 && version <= 1800) - { - MigrateToVersion1800(); - } - } - - return Db.DbConnection().ConnectionString; - } - - public static string ConnectionString => Db.DbConnection().ConnectionString; - - - private int CheckSchema() - { - var productVersion = AssemblyHelper.GetProductVersion(); - var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0'); - var version = int.Parse(trimStatus); - - var connection = Db.DbConnection(); - var schema = connection.GetSchemaVersion(); - if (schema == null) - { - connection.CreateSchema(version); // Set the default. - schema = connection.GetSchemaVersion(); - } - if (version > schema.SchemaVersion) - { - Db.DbConnection().UpdateSchemaVersion(version); - schema = connection.GetSchemaVersion(); - } - version = schema.SchemaVersion; - - return version; - } - - private void CreateDefaultSettingsPage(string baseUrl) - { - var defaultSettings = new PlexRequestSettings - { - RequireTvShowApproval = true, - RequireMovieApproval = true, - SearchForMovies = true, - SearchForTvShows = true, - WeeklyRequestLimit = 0, - BaseUrl = baseUrl ?? string.Empty - }; - var s = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); - s.SaveSettings(defaultSettings); - } - - public void CacheQualityProfiles() - { - var mc = new MemoryCacheProvider(); - - try - { - Task.Run(() => { CacheSonarrQualityProfiles(mc); }); - Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); }); - // we don't need to cache sickrage profiles, those are static - // TODO: cache headphones profiles? - } - catch (Exception) - { - Log.Error("Failed to cache quality profiles on startup!"); - } - } - - private void CacheSonarrQualityProfiles(MemoryCacheProvider cacheProvider) - { - try - { - Log.Info("Executing GetSettings call to Sonarr for quality profiles"); - var sonarrSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); - var sonarrSettings = sonarrSettingsService.GetSettings(); - if (sonarrSettings.Enabled) - { - Log.Info("Begin executing GetProfiles call to Sonarr for quality profiles"); - SonarrApi sonarrApi = new SonarrApi(); - var profiles = sonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri); - cacheProvider.Set(CacheKeys.SonarrQualityProfiles, profiles); - Log.Info("Finished executing GetProfiles call to Sonarr for quality profiles"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Failed to cache Sonarr quality profiles!"); - } - } - - private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider cacheProvider) - { - try - { - Log.Info("Executing GetSettings call to CouchPotato for quality profiles"); - var cpSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); - var cpSettings = cpSettingsService.GetSettings(); - if (cpSettings.Enabled) - { - Log.Info("Begin executing GetProfiles call to CouchPotato for quality profiles"); - CouchPotatoApi cpApi = new CouchPotatoApi(); - var profiles = cpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey); - cacheProvider.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); - Log.Info("Finished executing GetProfiles call to CouchPotato for quality profiles"); - } - } - catch (Exception ex) - { - Log.Error(ex, "Failed to cache CouchPotato quality profiles!"); - } - } - public void MigrateToVersion1700() - { - // Drop old tables - TableCreation.DropTable(Db.DbConnection(), "User"); - TableCreation.DropTable(Db.DbConnection(), "Log"); - } - - /// - /// Migrates to version 1.8. - /// This includes updating the admin account to have all roles. - /// Set the log level to Error - /// Enable Analytics by default - /// - private void MigrateToVersion1800() - { - - // Give admin all roles/claims - try - { - var userMapper = new UserMapper(new UserRepository(Db, new MemoryCacheProvider())); - var users = userMapper.GetUsers(); - - foreach (var u in users) - { - var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser }; - u.Claims = ByteConverterHelper.ReturnBytes(claims); - - userMapper.EditUser(u); - } - } - catch (Exception e) - { - Log.Error(e); - } - - - // Set log level - try - { - var settingsService = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var logSettings = settingsService.GetSettings(); - logSettings.Level = LogLevel.Error.Ordinal; - settingsService.SaveSettings(logSettings); - - LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level)); - - } - catch (Exception e) - { - Log.Error(e); - } - - - // Enable analytics; - try - { - - var prSettings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); - var settings = prSettings.GetSettings(); - settings.CollectAnalyticData = true; - var updated = prSettings.SaveSettings(settings); - - } - catch (Exception e) - { - Log.Error(e); - } - - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Setup.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.Text.RegularExpressions; + +using Mono.Data.Sqlite; +using NLog; +using PlexRequests.Api; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Store; +using PlexRequests.Store.Repository; +using System.Threading.Tasks; + +namespace PlexRequests.Core +{ + public class Setup + { + private static Logger Log = LogManager.GetCurrentClassLogger(); + private static DbConfiguration Db { get; set; } + public string SetupDb(string urlBase) + { + Db = new DbConfiguration(new SqliteFactory()); + var created = Db.CheckDb(); + TableCreation.CreateTables(Db.DbConnection()); + + if (created) + { + CreateDefaultSettingsPage(urlBase); + } + + var version = CheckSchema(); + if (version > 0) + { + if (version > 1799 && version <= 1800) + { + MigrateToVersion1800(); + } + } + + return Db.DbConnection().ConnectionString; + } + + public static string ConnectionString => Db.DbConnection().ConnectionString; + + + private int CheckSchema() + { + var productVersion = AssemblyHelper.GetProductVersion(); + var trimStatus = new Regex("[^0-9]", RegexOptions.Compiled).Replace(productVersion, string.Empty).PadRight(4, '0'); + var version = int.Parse(trimStatus); + + var connection = Db.DbConnection(); + var schema = connection.GetSchemaVersion(); + if (schema == null) + { + connection.CreateSchema(version); // Set the default. + schema = connection.GetSchemaVersion(); + } + if (version > schema.SchemaVersion) + { + Db.DbConnection().UpdateSchemaVersion(version); + schema = connection.GetSchemaVersion(); + } + version = schema.SchemaVersion; + + return version; + } + + private void CreateDefaultSettingsPage(string baseUrl) + { + var defaultSettings = new PlexRequestSettings + { + RequireTvShowApproval = true, + RequireMovieApproval = true, + SearchForMovies = true, + SearchForTvShows = true, + BaseUrl = baseUrl ?? string.Empty, + CollectAnalyticData = true, + }; + var s = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + s.SaveSettings(defaultSettings); + } + + public void CacheQualityProfiles() + { + var mc = new MemoryCacheProvider(); + + try + { + Task.Run(() => { CacheSonarrQualityProfiles(mc); }); + Task.Run(() => { CacheCouchPotatoQualityProfiles(mc); }); + // we don't need to cache sickrage profiles, those are static + // TODO: cache headphones profiles? + } + catch (Exception) + { + Log.Error("Failed to cache quality profiles on startup!"); + } + } + + private void CacheSonarrQualityProfiles(MemoryCacheProvider cacheProvider) + { + try + { + Log.Info("Executing GetSettings call to Sonarr for quality profiles"); + var sonarrSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + var sonarrSettings = sonarrSettingsService.GetSettings(); + if (sonarrSettings.Enabled) + { + Log.Info("Begin executing GetProfiles call to Sonarr for quality profiles"); + SonarrApi sonarrApi = new SonarrApi(); + var profiles = sonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri); + cacheProvider.Set(CacheKeys.SonarrQualityProfiles, profiles); + Log.Info("Finished executing GetProfiles call to Sonarr for quality profiles"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to cache Sonarr quality profiles!"); + } + } + + private void CacheCouchPotatoQualityProfiles(MemoryCacheProvider cacheProvider) + { + try + { + Log.Info("Executing GetSettings call to CouchPotato for quality profiles"); + var cpSettingsService = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + var cpSettings = cpSettingsService.GetSettings(); + if (cpSettings.Enabled) + { + Log.Info("Begin executing GetProfiles call to CouchPotato for quality profiles"); + CouchPotatoApi cpApi = new CouchPotatoApi(); + var profiles = cpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey); + cacheProvider.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); + Log.Info("Finished executing GetProfiles call to CouchPotato for quality profiles"); + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to cache CouchPotato quality profiles!"); + } + } + public void MigrateToVersion1700() + { + // Drop old tables + TableCreation.DropTable(Db.DbConnection(), "User"); + TableCreation.DropTable(Db.DbConnection(), "Log"); + } + + /// + /// Migrates to version 1.8. + /// This includes updating the admin account to have all roles. + /// Set the log level to Error + /// Enable Analytics by default + /// + private void MigrateToVersion1800() + { + + // Give admin all roles/claims + try + { + var userMapper = new UserMapper(new UserRepository(Db, new MemoryCacheProvider())); + var users = userMapper.GetUsers(); + + foreach (var u in users) + { + var claims = new[] { UserClaims.User, UserClaims.Admin, UserClaims.PowerUser }; + u.Claims = ByteConverterHelper.ReturnBytes(claims); + + userMapper.EditUser(u); + } + } + catch (Exception e) + { + Log.Error(e); + } + + + // Set log level + try + { + var settingsService = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); + var logSettings = settingsService.GetSettings(); + logSettings.Level = LogLevel.Error.Ordinal; + settingsService.SaveSettings(logSettings); + + LoggingHelper.ReconfigureLogLevel(LogLevel.FromOrdinal(logSettings.Level)); + + } + catch (Exception e) + { + Log.Error(e); + } + + + // Enable analytics; + try + { + + var prSettings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider())); + var settings = prSettings.GetSettings(); + settings.CollectAnalyticData = true; + var updated = prSettings.SaveSettings(settings); + + } + catch (Exception e) + { + Log.Error(e); + } + + } + } +} diff --git a/PlexRequests.Core/app.config b/PlexRequests.Core/app.config index 44b249bff..afa1b4c43 100644 --- a/PlexRequests.Core/app.config +++ b/PlexRequests.Core/app.config @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/PlexRequests.Core/packages.config b/PlexRequests.Core/packages.config index 41cff5672..9f54b7236 100644 --- a/PlexRequests.Core/packages.config +++ b/PlexRequests.Core/packages.config @@ -1,9 +1,9 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/CookieHelperTests.cs b/PlexRequests.Helpers.Tests/CookieHelperTests.cs new file mode 100644 index 000000000..c9a8205fb --- /dev/null +++ b/PlexRequests.Helpers.Tests/CookieHelperTests.cs @@ -0,0 +1,52 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: DateTimeHelperTests.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 NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class CookieHelperTests + { + [TestCaseSource(nameof(GetAnalyticsClientId))] + public string TestGetAnalyticsClientId(Dictionary cookies) + { + return CookieHelper.GetAnalyticClientId(cookies); + } + + private static IEnumerable GetAnalyticsClientId + { + get + { + yield return new TestCaseData(new Dictionary()).Returns(string.Empty); + yield return new TestCaseData(new Dictionary { { "_ga", "GA1.1.306549087.1464005217" } }).Returns("306549087.1464005217"); + yield return new TestCaseData(new Dictionary { {"_ga", "GA1.1.306549087" } }).Returns(string.Empty); + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj index 308e966e7..0789e013c 100644 --- a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj +++ b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj @@ -1,119 +1,120 @@ - - - - Debug - AnyCPU - {0E6395D3-B074-49E8-898D-0EB99E507E0E} - Library - Properties - PlexRequests.Helpers.Tests - PlexRequests.Helpers.Tests - v4.5 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll - True - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {1252336d-42a3-482a-804c-836e60173dfa} - PlexRequests.Helpers - - - - - - - False - - - False - - - False - - - False - - - - - - - + + + + Debug + AnyCPU + {0E6395D3-B074-49E8-898D-0EB99E507E0E} + Library + Properties + PlexRequests.Helpers.Tests + PlexRequests.Helpers.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {1252336d-42a3-482a-804c-836e60173dfa} + PlexRequests.Helpers + + + + + + + False + + + False + + + False + + + False + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Helpers/Analytics/Action.cs b/PlexRequests.Helpers/Analytics/Action.cs index a353dcc33..d91356747 100644 --- a/PlexRequests.Helpers/Analytics/Action.cs +++ b/PlexRequests.Helpers/Analytics/Action.cs @@ -34,6 +34,12 @@ namespace PlexRequests.Helpers.Analytics Create, Save, Update, - Start + Start, + View, + Movie, + TvShow, + Album, + Request, + Language } } \ No newline at end of file diff --git a/PlexRequests.Helpers/Analytics/Analytics.cs b/PlexRequests.Helpers/Analytics/Analytics.cs index 4b4d74ed9..764295fa3 100644 --- a/PlexRequests.Helpers/Analytics/Analytics.cs +++ b/PlexRequests.Helpers/Analytics/Analytics.cs @@ -47,52 +47,51 @@ namespace PlexRequests.Helpers.Analytics private static Logger Log = LogManager.GetCurrentClassLogger(); - public void TrackEvent(Category category, Action action, string label, string username, int? value = null) + public void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - Track(HitType.@event, username, cat, act, label, value); + Track(HitType.@event, username, cat, act, label, clientId, value); } - public async Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null) + public async Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - await TrackAsync(HitType.@event, username, cat, act, label, value); + await TrackAsync(HitType.@event, username, cat, act, clientId, label, value); } - public void TrackPageview(Category category, Action action, string label, string username, int? value = null) + public void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - Track(HitType.@pageview, username, cat, act, label, value); + Track(HitType.@pageview, username, cat, act, clientId, label, value); } - public async Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null) + public async Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - await TrackAsync(HitType.@pageview, username, cat, act, label, value); + await TrackAsync(HitType.@pageview, username, cat, act, clientId, label, value); } - public void TrackException(string message, string username, bool fatal) + public void TrackException(string message, string username, string clientId, bool fatal) { var fatalInt = fatal ? 1 : 0; - Track(HitType.exception, message, fatalInt, username); + Track(HitType.exception, message, fatalInt, username, clientId); } - public async Task TrackExceptionAsync(string message, string username, bool fatal) + public async Task TrackExceptionAsync(string message, string username, string clientId, bool fatal) { var fatalInt = fatal ? 1 : 0; - await TrackAsync(HitType.exception, message, fatalInt, username); + await TrackAsync(HitType.exception, message, fatalInt, username, clientId); } - private void Track(HitType type, string username, string category, string action, string label, int? value = null) + private void Track(HitType type, string username, string category, string action, string clientId, string label, int? value = null) { if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category)); if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action)); - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, category, action, label, value, null, null); + var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -101,13 +100,12 @@ namespace PlexRequests.Helpers.Analytics SendRequest(postDataString); } - private async Task TrackAsync(HitType type, string username, string category, string action, string label, int? value = null) + private async Task TrackAsync(HitType type, string username, string category, string action, string clientId, string label, int? value = null) { if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category)); if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action)); - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, category, action, label, value, null, null); + var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -115,12 +113,11 @@ namespace PlexRequests.Helpers.Analytics await SendRequestAsync(postDataString); } - private async Task TrackAsync(HitType type, string message, int fatal, string username) + private async Task TrackAsync(HitType type, string message, int fatal, string username, string clientId) { if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, null, null, null, null, message, fatal); + var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -129,12 +126,12 @@ namespace PlexRequests.Helpers.Analytics await SendRequestAsync(postDataString); } - private void Track(HitType type, string message, int fatal, string username) + private void Track(HitType type, string message, int fatal, string username, string clientId) { if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, null, null, null, null, message, fatal); + var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -196,20 +193,24 @@ namespace PlexRequests.Helpers.Analytics } } - private Dictionary BuildRequestData(HitType type, string username, string category, string action, string label, int? value, string exceptionDescription, int? fatal) + private Dictionary BuildRequestData(HitType type, string username, string category, string action, string clientId, string label, int? value, string exceptionDescription, int? fatal) { var postData = new Dictionary { { "v", "1" }, { "tid", TrackingId }, - { "t", type.ToString() }, - {"cid", Guid.NewGuid().ToString() } + { "t", type.ToString() } }; if (!string.IsNullOrEmpty(username)) { postData.Add("uid", username); } + + postData.Add("cid", !string.IsNullOrEmpty(clientId) + ? clientId + : Guid.NewGuid().ToString()); + if (!string.IsNullOrEmpty(label)) { postData.Add("el", label); diff --git a/PlexRequests.Helpers/Analytics/Category.cs b/PlexRequests.Helpers/Analytics/Category.cs index 7bedb16a5..252c6fdc8 100644 --- a/PlexRequests.Helpers/Analytics/Category.cs +++ b/PlexRequests.Helpers/Analytics/Category.cs @@ -37,5 +37,6 @@ namespace PlexRequests.Helpers.Analytics Issues, UserLogin, Services, + Navbar } } \ No newline at end of file diff --git a/PlexRequests.Helpers/Analytics/IAnalytics.cs b/PlexRequests.Helpers/Analytics/IAnalytics.cs index 4e228f545..d21dd18d1 100644 --- a/PlexRequests.Helpers/Analytics/IAnalytics.cs +++ b/PlexRequests.Helpers/Analytics/IAnalytics.cs @@ -37,8 +37,9 @@ namespace PlexRequests.Helpers.Analytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. - void TrackEvent(Category category, Action action, string label, string username, int? value = null); + void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the event asynchronous. @@ -47,9 +48,10 @@ namespace PlexRequests.Helpers.Analytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. /// - Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null); + Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the page view. @@ -58,8 +60,9 @@ namespace PlexRequests.Helpers.Analytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. - void TrackPageview(Category category, Action action, string label, string username, int? value = null); + void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the page view asynchronous. @@ -68,25 +71,28 @@ namespace PlexRequests.Helpers.Analytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. /// - Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null); + Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the exception. /// /// The message. /// The username. + /// The client identifier. /// if set to true [fatal]. - void TrackException(string message, string username, bool fatal); + void TrackException(string message, string username, string clientId, bool fatal); /// /// Tracks the exception asynchronous. /// /// The message. /// The username. + /// The client identifier. /// if set to true [fatal]. /// - Task TrackExceptionAsync(string message, string username, bool fatal); + Task TrackExceptionAsync(string message, string username, string clientId, bool fatal); } } \ No newline at end of file diff --git a/PlexRequests.Helpers/CookieHelper.cs b/PlexRequests.Helpers/CookieHelper.cs new file mode 100644 index 000000000..5d4b97a60 --- /dev/null +++ b/PlexRequests.Helpers/CookieHelper.cs @@ -0,0 +1,57 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CookieHelper.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Generic; + +namespace PlexRequests.Helpers +{ + public static class CookieHelper + { + private const string GaCookie = "_ga"; + + /// + /// Gets the analytic client identifier. + /// Example: Value = "GA1.1.306549087.1464005217" + /// + /// The cookies. + /// + public static string GetAnalyticClientId(IDictionary cookies) + { + var outString = string.Empty; + + if (cookies.TryGetValue(GaCookie, out outString)) + { + var split = outString.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + + return split.Length < 4 + ? string.Empty + : $"{split[2]}.{split[3]}"; + } + return string.Empty; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers/PlexRequests.Helpers.csproj b/PlexRequests.Helpers/PlexRequests.Helpers.csproj index 8bdfb5ed8..df0ffcf77 100644 --- a/PlexRequests.Helpers/PlexRequests.Helpers.csproj +++ b/PlexRequests.Helpers/PlexRequests.Helpers.csproj @@ -1,102 +1,103 @@ - - - - - Debug - AnyCPU - {1252336D-42A3-482A-804C-836E60173DFA} - Library - Properties - PlexRequests.Helpers - PlexRequests.Helpers - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Hangfire.Core.1.5.7\lib\net45\Hangfire.Core.dll - True - - - ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll - True - - - ..\packages\NLog.4.3.4\lib\net45\NLog.dll - True - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {1252336D-42A3-482A-804C-836E60173DFA} + Library + Properties + PlexRequests.Helpers + PlexRequests.Helpers + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hangfire.Core.1.5.7\lib\net45\Hangfire.Core.dll + True + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + True + + + ..\packages\NLog.4.3.4\lib\net45\NLog.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Resources/PlexRequests.Resources.csproj b/PlexRequests.Resources/PlexRequests.Resources.csproj new file mode 100644 index 000000000..2179f207a --- /dev/null +++ b/PlexRequests.Resources/PlexRequests.Resources.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + {9C266462-BE82-461A-87A2-9EDCFB95D732} + Library + Properties + PlexRequests.Resources + PlexRequests.Resources + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Resources/Properties/AssemblyInfo.cs b/PlexRequests.Resources/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1f1c2d1b7 --- /dev/null +++ b/PlexRequests.Resources/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Resources")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlexRequests.Resources")] +[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("9c266462-be82-461a-87a2-9edcfb95d732")] + +// 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")] diff --git a/PlexRequests.Services.Tests/app.config b/PlexRequests.Services.Tests/app.config index 44b249bff..afa1b4c43 100644 --- a/PlexRequests.Services.Tests/app.config +++ b/PlexRequests.Services.Tests/app.config @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs index 3fb831b12..be36d004f 100644 --- a/PlexRequests.Services/Jobs/CouchPotatoCacher.cs +++ b/PlexRequests.Services/Jobs/CouchPotatoCacher.cs @@ -24,6 +24,8 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using System; using System.Linq; using NLog; @@ -86,8 +88,18 @@ namespace PlexRequests.Services.Jobs // we do not want to set here... public int[] QueuedIds() { - var movies = Cache.Get(CacheKeys.CouchPotatoQueued); - return movies?.movies?.Select(x => x.info.tmdb_id).ToArray() ?? new int[] { }; + try + { + var movies = Cache.Get(CacheKeys.CouchPotatoQueued); + + var items = movies?.movies?.Select(x => x.info?.tmdb_id).Cast().ToArray(); + return items ?? new int[] { }; + } + catch (Exception e) + { + Log.Error(e); + return new int[] {}; + } } public void Execute(IJobExecutionContext context) diff --git a/PlexRequests.Services/Jobs/JobNames.cs b/PlexRequests.Services/Jobs/JobNames.cs index ff6791d92..8832d2c2b 100644 --- a/PlexRequests.Services/Jobs/JobNames.cs +++ b/PlexRequests.Services/Jobs/JobNames.cs @@ -34,5 +34,6 @@ namespace PlexRequests.Services.Jobs public const string SrCacher = "SickRage Cacher"; public const string PlexChecker = "Plex Availability Cacher"; public const string StoreCleanup = "Database Cleanup"; + public const string RequestLimitReset = "Request Limit Reset"; } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 227540bb5..dfadadd80 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -33,6 +33,7 @@ using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.Plex; using PlexRequests.Core; +using PlexRequests.Core.Models; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Helpers.Analytics; @@ -179,7 +180,8 @@ namespace PlexRequests.Services.Jobs { if (advanced) { - if (movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + if (!string.IsNullOrEmpty(movie.ProviderId) && + movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { return true; } @@ -225,7 +227,8 @@ namespace PlexRequests.Services.Jobs { if (advanced) { - if (show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + if (!string.IsNullOrEmpty(show.ProviderId) && + show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { return true; } diff --git a/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs b/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs new file mode 100644 index 000000000..13bd34463 --- /dev/null +++ b/PlexRequests.Services/Jobs/UserRequestLimitResetter.cs @@ -0,0 +1,119 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserRequestLimitResetter.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 System.Linq; + +using NLog; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +using Quartz; + +namespace PlexRequests.Services.Jobs +{ + public class UserRequestLimitResetter : IJob + { + public UserRequestLimitResetter(IJobRecord record, IRepository repo, ISettingsService settings) + { + Record = record; + Repo = repo; + Settings = settings; + } + private IJobRecord Record { get; } + private IRepository Repo { get; } + private ISettingsService Settings { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public void Execute(IJobExecutionContext context) + { + try + { + var settings = Settings.GetSettings(); + var users = Repo.GetAll(); + var requestLimits = users as RequestLimit[] ?? users.ToArray(); + + MovieLimit(settings, requestLimits); + TvLimit(settings, requestLimits); + AlbumLimit(settings, requestLimits); + } + catch (Exception e) + { + Log.Error(e); + } + finally + { + Record.Record(JobNames.RequestLimitReset); + } + } + + public void MovieLimit(PlexRequestSettings s, IEnumerable allUsers) + { + if (s.MovieWeeklyRequestLimit == 0) + { + return; // The limit has not been set + } + CheckAndDelete(allUsers, RequestType.Movie); + } + + public void TvLimit(PlexRequestSettings s, IEnumerable allUsers) + { + if (s.TvWeeklyRequestLimit == 0) + { + return; // The limit has not been set + } + CheckAndDelete(allUsers, RequestType.TvShow); + } + + public void AlbumLimit(PlexRequestSettings s, IEnumerable allUsers) + { + if (s.AlbumWeeklyRequestLimit == 0) + { + return; // The limit has not been set + } + CheckAndDelete(allUsers, RequestType.Album); + } + + private void CheckAndDelete(IEnumerable allUsers, RequestType type) + { + var users = allUsers.Where(x => x.RequestType == type); + foreach (var u in users) + { + if (u.FirstRequestDate > DateTime.UtcNow.AddDays(-7)) + { + Repo.Delete(u); + } + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 20ef3f612..e67e42e5f 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -1,201 +1,203 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: EmailMessageNotification.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.Threading.Tasks; - -using MailKit.Security; - -using MimeKit; -using NLog; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using SmtpClient = MailKit.Net.Smtp.SmtpClient; - -namespace PlexRequests.Services.Notification -{ - public class EmailMessageNotification : INotification - { - public EmailMessageNotification(ISettingsService settings) - { - EmailNotificationSettings = settings; - } - - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private ISettingsService EmailNotificationSettings { get; } - public string NotificationName => "EmailMessageNotification"; - - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetConfiguration(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var emailSettings = (EmailNotificationSettings)settings; - - if (!ValidateConfiguration(emailSettings)) - { - return; - } - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await EmailNewRequest(model, emailSettings); - break; - case NotificationType.Issue: - await EmailIssue(model, emailSettings); - break; - case NotificationType.RequestAvailable: - await EmailAvailableRequest(model, emailSettings); - break; - case NotificationType.RequestApproved: - throw new NotImplementedException(); - - case NotificationType.AdminNote: - throw new NotImplementedException(); - - case NotificationType.Test: - await EmailTest(model, emailSettings); - break; - } - - } - - private EmailNotificationSettings GetConfiguration() - { - var settings = EmailNotificationSettings.GetSettings(); - return settings; - } - - private bool ValidateConfiguration(EmailNotificationSettings settings) - { - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) - { - return false; - } - - if (!settings.EnableUserEmailNotifications) - { - if (!settings.Enabled) - { - return false; - } - } - - return true; - } - - private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, - Subject = $"Plex Requests: New request for {model.Title}!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - - await Send(message, settings); - } - - private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" }, - Subject = $"Plex Requests: New issue for {model.Title}!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - - await Send(message, settings); - } - - private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) - { - if (!settings.EnableUserEmailNotifications) - { - await Task.FromResult(false); - } - - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }, - Subject = $"Plex Requests: {model.Title} is now available!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); - - await Send(message, settings); - } - - private async Task Send(MimeMessage message, EmailNotificationSettings settings) - { - try - { - using (var client = new SmtpClient()) - { - client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions. - - // Note: since we don't have an OAuth2 token, disable - // the XOAUTH2 authentication mechanism. - client.AuthenticationMechanisms.Remove("XOAUTH2"); - - client.Authenticate(settings.EmailUsername, settings.EmailPassword); - - await client.SendAsync(message); - await client.DisconnectAsync(true); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - - private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = "This is just a test! Success!" }, - Subject = "Plex Requests: Test Message!", - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - await Send(message, settings); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: EmailMessageNotification.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.Threading.Tasks; + +using MailKit.Security; + +using MimeKit; +using NLog; + +using PlexRequests.Core; +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; +using PlexRequests.Store; + +namespace PlexRequests.Services.Notification +{ + public class EmailMessageNotification : INotification + { + public EmailMessageNotification(ISettingsService settings) + { + EmailNotificationSettings = settings; + } + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private ISettingsService EmailNotificationSettings { get; } + public string NotificationName => "EmailMessageNotification"; + + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetConfiguration(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var emailSettings = (EmailNotificationSettings)settings; + + if (!ValidateConfiguration(emailSettings)) + { + return; + } + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await EmailNewRequest(model, emailSettings); + break; + case NotificationType.Issue: + await EmailIssue(model, emailSettings); + break; + case NotificationType.RequestAvailable: + await EmailAvailableRequest(model, emailSettings); + break; + case NotificationType.RequestApproved: + throw new NotImplementedException(); + + case NotificationType.AdminNote: + throw new NotImplementedException(); + + case NotificationType.Test: + await EmailTest(model, emailSettings); + break; + } + + } + + private EmailNotificationSettings GetConfiguration() + { + var settings = EmailNotificationSettings.GetSettings(); + return settings; + } + + private bool ValidateConfiguration(EmailNotificationSettings settings) + { + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + { + return false; + } + + if (!settings.EnableUserEmailNotifications) + { + if (!settings.Enabled) + { + return false; + } + } + + return true; + } + + private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested the {model.RequestType.GetString()?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, + Subject = $"Plex Requests: New {model.RequestType.GetString()?.ToLower()} request for {model.Title}!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + + private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" }, + Subject = $"Plex Requests: New issue for {model.Title}!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + + private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) + { + if (!settings.EnableUserEmailNotifications) + { + await Task.FromResult(false); + } + + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }, + Subject = $"Plex Requests: {model.Title} is now available!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); + + await Send(message, settings); + } + + private async Task Send(MimeMessage message, EmailNotificationSettings settings) + { + try + { + using (var client = new SmtpClient()) + { + client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions. + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + client.Authenticate(settings.EmailUsername, settings.EmailPassword); + + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = "This is just a test! Success!" }, + Subject = "Plex Requests: Test Message!", + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + await Send(message, settings); + } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/NotificationModel.cs b/PlexRequests.Services/Notification/NotificationModel.cs index 2cc4e7fdb..11ebcbae3 100644 --- a/PlexRequests.Services/Notification/NotificationModel.cs +++ b/PlexRequests.Services/Notification/NotificationModel.cs @@ -1,40 +1,44 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: NotificationModel.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; - -namespace PlexRequests.Services.Notification -{ - public class NotificationModel - { - public string Title { get; set; } - public string Body { get; set; } - public DateTime DateTime { get; set; } - public NotificationType NotificationType { get; set; } - public string User { get; set; } - public string UserEmail { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NotificationModel.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 PlexRequests.Store; +using System; + +using PlexRequests.Core.Models; + +namespace PlexRequests.Services.Notification +{ + public class NotificationModel + { + public string Title { get; set; } + public string Body { get; set; } + public DateTime DateTime { get; set; } + public NotificationType NotificationType { get; set; } + public string User { get; set; } + public string UserEmail { get; set; } + public RequestType RequestType { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/PushbulletNotification.cs b/PlexRequests.Services/Notification/PushbulletNotification.cs index 70f318331..e2ae3c4c4 100644 --- a/PlexRequests.Services/Notification/PushbulletNotification.cs +++ b/PlexRequests.Services/Notification/PushbulletNotification.cs @@ -1,142 +1,144 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PushbulletNotification.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.Threading.Tasks; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; - -namespace PlexRequests.Services.Notification -{ - public class PushbulletNotification : INotification - { - public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService settings) - { - PushbulletApi = pushbulletApi; - SettingsService = settings; - } - private IPushbulletApi PushbulletApi { get; } - private ISettingsService SettingsService { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - public string NotificationName => "PushbulletNotification"; - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetSettings(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var pushSettings = (PushbulletNotificationSettings)settings; - - if (!ValidateConfiguration(pushSettings)) return; - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await PushNewRequestAsync(model, pushSettings); - break; - case NotificationType.Issue: - await PushIssueAsync(model, pushSettings); - break; - case NotificationType.RequestAvailable: - break; - case NotificationType.RequestApproved: - break; - case NotificationType.AdminNote: - break; - case NotificationType.Test: - await PushTestAsync(pushSettings); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - private bool ValidateConfiguration(PushbulletNotificationSettings settings) - { - if (!settings.Enabled) - { - return false; - } - if (string.IsNullOrEmpty(settings.AccessToken)) - { - return false; - } - return true; - } - - private PushbulletNotificationSettings GetSettings() - { - return SettingsService.GetSettings(); - } - - private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings) - { - var message = $"{model.Title} has been requested by user: {model.User}"; - var pushTitle = $"Plex Requests: {model.Title} has been requested!"; - await Push(settings, message, pushTitle); - } - - private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings) - { - var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; - var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}"; - await Push(settings, message, pushTitle); - } - - private async Task PushTestAsync(PushbulletNotificationSettings settings) - { - var message = "This is just a test! Success!"; - var pushTitle = "Plex Requests: Test Message!"; - await Push(settings, message, pushTitle); - } - - private async Task Push(PushbulletNotificationSettings settings, string message, string title) - { - try - { - var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier); - if (result != null) - { - Log.Error("Pushbullet api returned a null value, the notification did not get pushed"); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletNotification.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.Threading.Tasks; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; + +namespace PlexRequests.Services.Notification +{ + public class PushbulletNotification : INotification + { + public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService settings) + { + PushbulletApi = pushbulletApi; + SettingsService = settings; + } + private IPushbulletApi PushbulletApi { get; } + private ISettingsService SettingsService { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public string NotificationName => "PushbulletNotification"; + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetSettings(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var pushSettings = (PushbulletNotificationSettings)settings; + + if (!ValidateConfiguration(pushSettings)) return; + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await PushNewRequestAsync(model, pushSettings); + break; + case NotificationType.Issue: + await PushIssueAsync(model, pushSettings); + break; + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + case NotificationType.Test: + await PushTestAsync(pushSettings); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private bool ValidateConfiguration(PushbulletNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.AccessToken)) + { + return false; + } + return true; + } + + private PushbulletNotificationSettings GetSettings() + { + return SettingsService.GetSettings(); + } + + private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings) + { + var message = $"The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}"; + var pushTitle = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} {model.Title} has been requested!"; + await Push(settings, message, pushTitle); + } + + private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings) + { + var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}"; + await Push(settings, message, pushTitle); + } + + private async Task PushTestAsync(PushbulletNotificationSettings settings) + { + var message = "This is just a test! Success!"; + var pushTitle = "Plex Requests: Test Message!"; + await Push(settings, message, pushTitle); + } + + private async Task Push(PushbulletNotificationSettings settings, string message, string title) + { + try + { + var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier); + if (result != null) + { + Log.Error("Pushbullet api returned a null value, the notification did not get pushed"); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/PushoverNotification.cs b/PlexRequests.Services/Notification/PushoverNotification.cs index d39492563..2adafb11e 100644 --- a/PlexRequests.Services/Notification/PushoverNotification.cs +++ b/PlexRequests.Services/Notification/PushoverNotification.cs @@ -1,139 +1,141 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PushbulletNotification.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.Threading.Tasks; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; - -namespace PlexRequests.Services.Notification -{ - public class PushoverNotification : INotification - { - public PushoverNotification(IPushoverApi pushoverApi, ISettingsService settings) - { - PushoverApi = pushoverApi; - SettingsService = settings; - } - private IPushoverApi PushoverApi { get; } - private ISettingsService SettingsService { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - public string NotificationName => "PushoverNotification"; - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetSettings(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var pushSettings = (PushoverNotificationSettings)settings; - - if (!ValidateConfiguration(pushSettings)) return; - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await PushNewRequestAsync(model, pushSettings); - break; - case NotificationType.Issue: - await PushIssueAsync(model, pushSettings); - break; - case NotificationType.RequestAvailable: - break; - case NotificationType.RequestApproved: - break; - case NotificationType.AdminNote: - break; - case NotificationType.Test: - await PushTestAsync(model, pushSettings); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - private bool ValidateConfiguration(PushoverNotificationSettings settings) - { - if (!settings.Enabled) - { - return false; - } - if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken)) - { - return false; - } - return true; - } - - private PushoverNotificationSettings GetSettings() - { - return SettingsService.GetSettings(); - } - - private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings) - { - var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}"; - await Push(settings, message); - } - - private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings) - { - var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; - await Push(settings, message); - } - - private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings) - { - var message = $"Plex Requests: Test Message!"; - await Push(settings, message); - } - - private async Task Push(PushoverNotificationSettings settings, string message) - { - try - { - var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken); - if (result?.status != 1) - { - Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed"); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletNotification.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.Threading.Tasks; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; + +namespace PlexRequests.Services.Notification +{ + public class PushoverNotification : INotification + { + public PushoverNotification(IPushoverApi pushoverApi, ISettingsService settings) + { + PushoverApi = pushoverApi; + SettingsService = settings; + } + private IPushoverApi PushoverApi { get; } + private ISettingsService SettingsService { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public string NotificationName => "PushoverNotification"; + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetSettings(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var pushSettings = (PushoverNotificationSettings)settings; + + if (!ValidateConfiguration(pushSettings)) return; + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await PushNewRequestAsync(model, pushSettings); + break; + case NotificationType.Issue: + await PushIssueAsync(model, pushSettings); + break; + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + case NotificationType.Test: + await PushTestAsync(model, pushSettings); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private bool ValidateConfiguration(PushoverNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken)) + { + return false; + } + return true; + } + + private PushoverNotificationSettings GetSettings() + { + return SettingsService.GetSettings(); + } + + private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Plex Requests: The {model.RequestType.GetString()?.ToLower()} '{model.Title}' has been requested by user: {model.User}"; + await Push(settings, message); + } + + private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + await Push(settings, message); + } + + private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Plex Requests: Test Message!"; + await Push(settings, message); + } + + private async Task Push(PushoverNotificationSettings settings, string message) + { + try + { + var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken); + if (result?.status != 1) + { + Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed"); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/SlackNotification.cs b/PlexRequests.Services/Notification/SlackNotification.cs index 5da0c156a..f8c90d3ca 100644 --- a/PlexRequests.Services/Notification/SlackNotification.cs +++ b/PlexRequests.Services/Notification/SlackNotification.cs @@ -32,6 +32,7 @@ using NLog; using PlexRequests.Api.Interfaces; using PlexRequests.Api.Models.Notifications; using PlexRequests.Core; +using PlexRequests.Core.Models; using PlexRequests.Core.SettingModels; using PlexRequests.Services.Interfaces; diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj index fc61aab55..4f50fcac0 100644 --- a/PlexRequests.Services/PlexRequests.Services.csproj +++ b/PlexRequests.Services/PlexRequests.Services.csproj @@ -1,139 +1,139 @@ - - - - - Debug - AnyCPU - {566EFA49-68F8-4716-9693-A6B3F2624DEA} - Library - Properties - PlexRequests.Services - PlexRequests.Services - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - ..\packages\NLog.4.3.4\lib\net45\NLog.dll - True - - - - - - - - - - - - - - ..\packages\MimeKit.1.2.22\lib\net45\BouncyCastle.dll - - - ..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll - - - ..\packages\MailKit.1.2.21\lib\net451\MailKit.dll - - - ..\packages\MimeKit.1.2.22\lib\net45\MimeKit.dll - - - ..\Assemblies\Mono.Data.Sqlite.dll - - - ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {95834072-A675-415D-AA8F-877C91623810} - PlexRequests.Api.Interfaces - - - {CB37A5F8-6DFC-4554-99D3-A42B502E4591} - PlexRequests.Api.Models - - - {8CB8D235-2674-442D-9C6A-35FCAEEB160D} - PlexRequests.Api - - - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} - PlexRequests.Core - - - {1252336D-42A3-482A-804C-836E60173DFA} - PlexRequests.Helpers - - - {92433867-2B7B-477B-A566-96C382427525} - PlexRequests.Store - - - - + + + + + Debug + AnyCPU + {566EFA49-68F8-4716-9693-A6B3F2624DEA} + Library + Properties + PlexRequests.Services + PlexRequests.Services + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + ..\packages\NLog.4.3.4\lib\net45\NLog.dll + True + + + + + + + + + + + + + + ..\packages\MimeKit.1.2.22\lib\net45\BouncyCastle.dll + + + ..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll + + + ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll + + + ..\packages\MailKit.1.2.21\lib\net451\MailKit.dll + + + ..\packages\MimeKit.1.2.22\lib\net45\MimeKit.dll + + + ..\Assemblies\Mono.Data.Sqlite.dll + + + ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {95834072-A675-415D-AA8F-877C91623810} + PlexRequests.Api.Interfaces + + + {CB37A5F8-6DFC-4554-99D3-A42B502E4591} + PlexRequests.Api.Models + + + {8CB8D235-2674-442D-9C6A-35FCAEEB160D} + PlexRequests.Api + + + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} + PlexRequests.Core + + + {1252336D-42A3-482A-804C-836E60173DFA} + PlexRequests.Helpers + + + {92433867-2B7B-477B-A566-96C382427525} + PlexRequests.Store + + + + \ No newline at end of file diff --git a/PlexRequests.Services/app.config b/PlexRequests.Services/app.config index 44b249bff..afa1b4c43 100644 --- a/PlexRequests.Services/app.config +++ b/PlexRequests.Services/app.config @@ -1,11 +1,11 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/PlexRequests.Store/Models/PlexUsers.cs b/PlexRequests.Store/Models/PlexUsers.cs new file mode 100644 index 000000000..884fe1d4d --- /dev/null +++ b/PlexRequests.Store/Models/PlexUsers.cs @@ -0,0 +1,34 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexUsers.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.Store.Models +{ + public class PlexUsers : Entity + { + public int PlexUserId { get; set; } + public string UserAlias { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Store/Models/RequestLimit.cs b/PlexRequests.Store/Models/RequestLimit.cs new file mode 100644 index 000000000..1c86986c6 --- /dev/null +++ b/PlexRequests.Store/Models/RequestLimit.cs @@ -0,0 +1,41 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UsersToNotify.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 Dapper.Contrib.Extensions; + +namespace PlexRequests.Store.Models +{ + [Table("RequestLimit")] + public class RequestLimit : Entity + { + public string Username { get; set; } + public DateTime FirstRequestDate { get; set; } + public int RequestCount { get; set; } + public RequestType RequestType { get; set; } + } +} \ No newline at end of file diff --git a/PlexRequests.Store/PlexRequests.Store.csproj b/PlexRequests.Store/PlexRequests.Store.csproj index 335ea07db..dba4d4ef1 100644 --- a/PlexRequests.Store/PlexRequests.Store.csproj +++ b/PlexRequests.Store/PlexRequests.Store.csproj @@ -1,125 +1,127 @@ - - - - - Debug - AnyCPU - {92433867-2B7B-477B-A566-96C382427525} - Library - Properties - PlexRequests.Store - PlexRequests.Store - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll - True - - - ..\packages\Dapper.Contrib.1.50.0-beta8\lib\net45\Dapper.Contrib.dll - True - - - ..\Assemblies\Mono.Data.Sqlite.dll - - - ..\packages\NLog.4.3.4\lib\net45\NLog.dll - True - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Sql.resx - - - - - - - - Always - - - Always - - - - - ResXFileCodeGenerator - Sql.Designer.cs - - - - - - - - {1252336D-42A3-482A-804C-836E60173DFA} - PlexRequests.Helpers - - - - + + + + + Debug + AnyCPU + {92433867-2B7B-477B-A566-96C382427525} + Library + Properties + PlexRequests.Store + PlexRequests.Store + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Dapper.1.50.0-beta8\lib\net45\Dapper.dll + True + + + ..\packages\Dapper.Contrib.1.50.0-beta8\lib\net45\Dapper.Contrib.dll + True + + + ..\Assemblies\Mono.Data.Sqlite.dll + + + ..\packages\NLog.4.3.4\lib\net45\NLog.dll + True + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Sql.resx + + + + + + + + Always + + + Always + + + + + ResXFileCodeGenerator + Sql.Designer.cs + + + + + + + + {1252336D-42A3-482A-804C-836E60173DFA} + PlexRequests.Helpers + + + + \ No newline at end of file diff --git a/PlexRequests.Store/RequestedModel.cs b/PlexRequests.Store/RequestedModel.cs index c63ede547..b203db7d3 100644 --- a/PlexRequests.Store/RequestedModel.cs +++ b/PlexRequests.Store/RequestedModel.cs @@ -1,90 +1,108 @@ -using System; -using Dapper.Contrib.Extensions; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; - -namespace PlexRequests.Store -{ - [Table("Requested")] - public class RequestedModel : Entity - { - public RequestedModel() - { - RequestedUsers = new List(); - } - - // ReSharper disable once IdentifierTypo - public int ProviderId { get; set; } - public string ImdbId { get; set; } - public string TvDbId { get; set; } - public string Overview { get; set; } - public string Title { get; set; } - public string PosterPath { get; set; } - public DateTime ReleaseDate { get; set; } - public RequestType Type { get; set; } - public string Status { get; set; } - public bool Approved { get; set; } - - [Obsolete("Use RequestedUsers")] - public string RequestedBy { get; set; } - - public DateTime RequestedDate { get; set; } - public bool Available { get; set; } - public IssueState Issues { get; set; } - public string OtherMessage { get; set; } - public string AdminNote { get; set; } - public int[] SeasonList { get; set; } - public int SeasonCount { get; set; } - public string SeasonsRequested { get; set; } - public string MusicBrainzId { get; set; } - public List RequestedUsers { get; set; } - public string ArtistName { get; set; } - public string ArtistId { get; set; } - public int IssueId { get; set; } - - [JsonIgnore] - public List AllUsers - { - get - { - var u = new List(); - if (!string.IsNullOrEmpty(RequestedBy)) - { - u.Add(RequestedBy); - } - - if (RequestedUsers.Any()) - { - u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy)); - } - return u; - } - } - - [JsonIgnore] - public bool CanApprove => !Approved && !Available; - - public bool UserHasRequested(string username) - { - return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); - } - } - - public enum RequestType - { - Movie, - TvShow, - Album - } - - public enum IssueState - { - None = 99, - WrongAudio = 0, - NoSubtitles = 1, - WrongContent = 2, - PlaybackIssues = 3, - Other = 4, // Provide a message - } -} +using System; +using Dapper.Contrib.Extensions; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace PlexRequests.Store +{ + [Table("Requested")] + public class RequestedModel : Entity + { + public RequestedModel() + { + RequestedUsers = new List(); + } + + // ReSharper disable once IdentifierTypo + public int ProviderId { get; set; } + public string ImdbId { get; set; } + public string TvDbId { get; set; } + public string Overview { get; set; } + public string Title { get; set; } + public string PosterPath { get; set; } + public DateTime ReleaseDate { get; set; } + public RequestType Type { get; set; } + public string Status { get; set; } + public bool Approved { get; set; } + + [Obsolete("Use RequestedUsers")] + public string RequestedBy { get; set; } + + public DateTime RequestedDate { get; set; } + public bool Available { get; set; } + public IssueState Issues { get; set; } + public string OtherMessage { get; set; } + public string AdminNote { get; set; } + public int[] SeasonList { get; set; } + public int SeasonCount { get; set; } + public string SeasonsRequested { get; set; } + public string MusicBrainzId { get; set; } + public List RequestedUsers { get; set; } + public string ArtistName { get; set; } + public string ArtistId { get; set; } + public int IssueId { get; set; } + + [JsonIgnore] + public List AllUsers + { + get + { + var u = new List(); + if (!string.IsNullOrEmpty(RequestedBy)) + { + u.Add(RequestedBy); + } + + if (RequestedUsers.Any()) + { + u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy)); + } + return u; + } + } + + [JsonIgnore] + public bool CanApprove => !Approved && !Available; + + public bool UserHasRequested(string username) + { + return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); + } + } + + public enum RequestType + { + Movie, + TvShow, + Album + } + + public static class RequestTypeDisplay + { + public static string GetString(this RequestType type) + { + switch (type) + { + case RequestType.Movie: + return "Movie"; + case RequestType.TvShow: + return "TV Show"; + case RequestType.Album: + return "Album"; + default: + return string.Empty; + } + } + } + + public enum IssueState + { + None = 99, + WrongAudio = 0, + NoSubtitles = 1, + WrongContent = 2, + PlaybackIssues = 3, + Other = 4, // Provide a message + } +} diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 6e89f589c..5a5ba22a9 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -1,84 +1,102 @@ ---Any DB changes need to be made in this file. - -CREATE TABLE IF NOT EXISTS Users -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - UserGuid varchar(50) NOT NULL , - UserName varchar(50) NOT NULL, - Salt BLOB NOT NULL, - Hash BLOB NOT NULL, - Claims BLOB NOT NULL, - UserProperties BLOB -); - - -CREATE TABLE IF NOT EXISTS GlobalSettings -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - SettingsName varchar(50) NOT NULL, - Content varchar(100) NOT NULL -); -CREATE UNIQUE INDEX IF NOT EXISTS GlobalSettings_Id ON GlobalSettings (Id); - -CREATE TABLE IF NOT EXISTS RequestBlobs -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - ProviderId INTEGER NOT NULL, - Type INTEGER NOT NULL, - Content BLOB NOT NULL, - MusicId TEXT -); -CREATE UNIQUE INDEX IF NOT EXISTS RequestBlobs_Id ON RequestBlobs (Id); - -CREATE TABLE IF NOT EXISTS Logs -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - Date varchar(100) NOT NULL, - Level varchar(100) NOT NULL, - Logger varchar(100) NOT NULL, - Message varchar(100) NOT NULL, - CallSite varchar(100) NOT NULL, - Exception varchar(100) NOT NULL -); -CREATE UNIQUE INDEX IF NOT EXISTS Logs_Id ON Logs (Id); - -CREATE TABLE IF NOT EXISTS Audit -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - Date varchar(100) NOT NULL, - Username varchar(100) NOT NULL, - ChangeType varchar(100) NOT NULL, - OldValue varchar(100), - NewValue varchar(100) -); -CREATE UNIQUE INDEX IF NOT EXISTS Audit_Id ON Audit (Id); - - -CREATE TABLE IF NOT EXISTS DBInfo -( - SchemaVersion INTEGER -); - -CREATE TABLE IF NOT EXISTS ScheduledJobs -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - Name varchar(100) NOT NULL, - LastRun varchar(100) NOT NULL -); -CREATE UNIQUE INDEX IF NOT EXISTS ScheduledJobs_Id ON ScheduledJobs (Id); - -CREATE TABLE IF NOT EXISTS UsersToNotify -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - Username varchar(100) NOT NULL -); -CREATE UNIQUE INDEX IF NOT EXISTS UsersToNotify_Id ON UsersToNotify (Id); - -CREATE TABLE IF NOT EXISTS IssueBlobs -( - Id INTEGER PRIMARY KEY AUTOINCREMENT, - RequestId INTEGER, - Type INTEGER NOT NULL, - Content BLOB NOT NULL -); -CREATE UNIQUE INDEX IF NOT EXISTS IssueBlobs_Id ON IssueBlobs (Id); \ No newline at end of file +--Any DB changes need to be made in this file. + +CREATE TABLE IF NOT EXISTS Users +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + UserGuid varchar(50) NOT NULL , + UserName varchar(50) NOT NULL, + Salt BLOB NOT NULL, + Hash BLOB NOT NULL, + Claims BLOB NOT NULL, + UserProperties BLOB +); + + +CREATE TABLE IF NOT EXISTS GlobalSettings +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + SettingsName varchar(50) NOT NULL, + Content varchar(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS GlobalSettings_Id ON GlobalSettings (Id); + +CREATE TABLE IF NOT EXISTS RequestBlobs +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + ProviderId INTEGER NOT NULL, + Type INTEGER NOT NULL, + Content BLOB NOT NULL, + MusicId TEXT +); +CREATE UNIQUE INDEX IF NOT EXISTS RequestBlobs_Id ON RequestBlobs (Id); + +CREATE TABLE IF NOT EXISTS Logs +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Date varchar(100) NOT NULL, + Level varchar(100) NOT NULL, + Logger varchar(100) NOT NULL, + Message varchar(100) NOT NULL, + CallSite varchar(100) NOT NULL, + Exception varchar(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS Logs_Id ON Logs (Id); + +CREATE TABLE IF NOT EXISTS Audit +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Date varchar(100) NOT NULL, + Username varchar(100) NOT NULL, + ChangeType varchar(100) NOT NULL, + OldValue varchar(100), + NewValue varchar(100) +); +CREATE UNIQUE INDEX IF NOT EXISTS Audit_Id ON Audit (Id); + + +CREATE TABLE IF NOT EXISTS DBInfo +( + SchemaVersion INTEGER +); + +CREATE TABLE IF NOT EXISTS ScheduledJobs +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Name varchar(100) NOT NULL, + LastRun varchar(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS ScheduledJobs_Id ON ScheduledJobs (Id); + +CREATE TABLE IF NOT EXISTS UsersToNotify +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Username varchar(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS UsersToNotify_Id ON UsersToNotify (Id); + +CREATE TABLE IF NOT EXISTS IssueBlobs +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + RequestId INTEGER, + Type INTEGER NOT NULL, + Content BLOB NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS IssueBlobs_Id ON IssueBlobs (Id); + +CREATE TABLE IF NOT EXISTS RequestLimit +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + Username varchar(100) NOT NULL, + FirstRequestDate varchar(100) NOT NULL, + RequestCount INTEGER NOT NULL, + RequestType INTEGER NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS RequestLimit_Id ON RequestLimit (Id); + +CREATE TABLE IF NOT EXISTS PlexUsers +( + Id INTEGER PRIMARY KEY AUTOINCREMENT, + PlexUserId INTEGER NOT NULL, + UserAlias varchar(100) NOT NULL +); +CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON RequestLimit (Id); \ No newline at end of file diff --git a/PlexRequests.UI.Tests/AdminModuleTests.cs b/PlexRequests.UI.Tests/AdminModuleTests.cs index 66245d649..198be036f 100644 --- a/PlexRequests.UI.Tests/AdminModuleTests.cs +++ b/PlexRequests.UI.Tests/AdminModuleTests.cs @@ -1,361 +1,365 @@ -#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 System.Linq; - -using Moq; - -using Nancy; -using Nancy.Testing; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using NUnit.Framework; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; -using PlexRequests.UI.Models; -using PlexRequests.UI.Modules; -using PlexRequests.Helpers; -using PlexRequests.UI.Helpers; - -namespace PlexRequests.UI.Tests -{ - [TestFixture] - public class AdminModuleTests - { - private Mock> PlexRequestMock { get; set; } - private Mock> CpMock { get; set; } - private Mock> AuthMock { get; set; } - private Mock> PlexSettingsMock { get; set; } - private Mock> SonarrSettingsMock { get; set; } - private Mock> SickRageSettingsMock { get; set; } - private Mock> ScheduledJobsSettingsMock { get; set; } - private Mock> EmailMock { get; set; } - private Mock> PushbulletSettings { get; set; } - private Mock> PushoverSettings { get; set; } - private Mock> HeadphonesSettings { get; set; } - private Mock PlexMock { get; set; } - private Mock SonarrApiMock { get; set; } - private Mock PushbulletApi { get; set; } - private Mock PushoverApi { get; set; } - private Mock CpApi { get; set; } - private Mock RecorderMock { get; set; } - private Mock> LogRepo { get; set; } - private Mock NotificationService { get; set; } - private Mock Cache { get; set; } - private Mock> Log { get; set; } - private Mock> SlackSettings { get; set; } - private Mock> LandingPageSettings { get; set; } - private Mock SlackApi { get; set; } - - private ConfigurableBootstrapper Bootstrapper { get; set; } - - [SetUp] - public void Setup() - { - AuthMock = new Mock>(); - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - PlexMock = new Mock(); - PlexMock.Setup(x => x.SignIn("Username1", "Password1")) - .Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } }); - - PlexRequestMock = new Mock>(); - PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); - CpMock = new Mock>(); - PlexSettingsMock = new Mock>(); - SonarrApiMock = new Mock(); - SonarrSettingsMock = new Mock>(); - EmailMock = new Mock>(); - PushbulletApi = new Mock(); - PushbulletSettings = new Mock>(); - CpApi = new Mock(); - SickRageSettingsMock = new Mock>(); - LogRepo = new Mock>(); - PushoverSettings = new Mock>(); - PushoverApi = new Mock(); - NotificationService = new Mock(); - HeadphonesSettings = new Mock>(); - Cache = new Mock(); - Log = new Mock>(); - SlackApi = new Mock(); - SlackSettings = new Mock>(); - LandingPageSettings = new Mock>(); - ScheduledJobsSettingsMock = new Mock>(); - RecorderMock = new Mock(); - - - Bootstrapper = new ConfigurableBootstrapper(with => - { - with.Module(); - 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.Dependency(PushbulletApi.Object); - with.Dependency(PushbulletSettings.Object); - with.Dependency(CpApi.Object); - with.Dependency(SickRageSettingsMock.Object); - with.Dependency(LogRepo.Object); - with.Dependency(PushoverSettings.Object); - with.Dependency(PushoverApi.Object); - with.Dependency(NotificationService.Object); - with.Dependency(HeadphonesSettings.Object); - with.Dependency(Cache.Object); - with.Dependency(Log.Object); - with.Dependency(SlackApi.Object); - with.Dependency(LandingPageSettings.Object); - with.Dependency(SlackSettings.Object); - with.Dependency(ScheduledJobsSettingsMock.Object); - with.Dependency(RecorderMock.Object); - with.RootPathProvider(); - with.RequestStartup((container, pipelines, context) => - { - context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List {"Admin"} }; - }); - }); - - Bootstrapper.WithSession(new Dictionary()); - } - - [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(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()), 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(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()), 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(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()), 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(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()), Times.Once); - } - - [Test] - public void GetUsersSuccessfully() - { - var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } }; - PlexMock.Setup(x => x.GetUsers(It.IsAny())).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(result.Body.AsString()); - var user = body["users"]; - Assert.That(body, Is.Not.Null); - Assert.That(user.ToString().Contains("abc"), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - - [Test] - public void GetUsersReturnsNoUsers() - { - var users = new PlexFriends(); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).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(result.Body.AsString()); - Assert.That(body, Is.Not.Null); - Assert.That(string.IsNullOrWhiteSpace(body), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - - [Test] - public void GetUsersReturnsNull() - { - PlexMock.Setup(x => x.GetUsers(It.IsAny())).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(result.Body.AsString()); - Assert.That(body, Is.Not.Null); - Assert.That(string.IsNullOrWhiteSpace(body), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), 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(result.Body.AsString()); - var user = (string)body["users"]; - Assert.That(body, Is.Not.Null); - Assert.That(string.IsNullOrWhiteSpace(user), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - } +#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 System.Linq; + +using Moq; + +using Nancy; +using Nancy.Testing; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using NUnit.Framework; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Models; +using PlexRequests.UI.Modules; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; +using PlexRequests.UI.Helpers; + +namespace PlexRequests.UI.Tests +{ + [TestFixture] + public class AdminModuleTests + { + private Mock> PlexRequestMock { get; set; } + private Mock> CpMock { get; set; } + private Mock> AuthMock { get; set; } + private Mock> PlexSettingsMock { get; set; } + private Mock> SonarrSettingsMock { get; set; } + private Mock> SickRageSettingsMock { get; set; } + private Mock> ScheduledJobsSettingsMock { get; set; } + private Mock> EmailMock { get; set; } + private Mock> PushbulletSettings { get; set; } + private Mock> PushoverSettings { get; set; } + private Mock> HeadphonesSettings { get; set; } + private Mock PlexMock { get; set; } + private Mock SonarrApiMock { get; set; } + private Mock PushbulletApi { get; set; } + private Mock PushoverApi { get; set; } + private Mock CpApi { get; set; } + private Mock RecorderMock { get; set; } + private Mock> LogRepo { get; set; } + private Mock NotificationService { get; set; } + private Mock Cache { get; set; } + private Mock> Log { get; set; } + private Mock> SlackSettings { get; set; } + private Mock> LandingPageSettings { get; set; } + private Mock SlackApi { get; set; } + private Mock IAnalytics { get; set; } + + private ConfigurableBootstrapper Bootstrapper { get; set; } + + [SetUp] + public void Setup() + { + AuthMock = new Mock>(); + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + PlexMock = new Mock(); + PlexMock.Setup(x => x.SignIn("Username1", "Password1")) + .Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } }); + + PlexRequestMock = new Mock>(); + PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); + CpMock = new Mock>(); + PlexSettingsMock = new Mock>(); + SonarrApiMock = new Mock(); + SonarrSettingsMock = new Mock>(); + EmailMock = new Mock>(); + PushbulletApi = new Mock(); + PushbulletSettings = new Mock>(); + CpApi = new Mock(); + SickRageSettingsMock = new Mock>(); + LogRepo = new Mock>(); + PushoverSettings = new Mock>(); + PushoverApi = new Mock(); + NotificationService = new Mock(); + HeadphonesSettings = new Mock>(); + Cache = new Mock(); + Log = new Mock>(); + SlackApi = new Mock(); + SlackSettings = new Mock>(); + LandingPageSettings = new Mock>(); + ScheduledJobsSettingsMock = new Mock>(); + RecorderMock = new Mock(); + IAnalytics = new Mock(); + + + Bootstrapper = new ConfigurableBootstrapper(with => + { + with.Module(); + 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.Dependency(PushbulletApi.Object); + with.Dependency(PushbulletSettings.Object); + with.Dependency(CpApi.Object); + with.Dependency(SickRageSettingsMock.Object); + with.Dependency(LogRepo.Object); + with.Dependency(PushoverSettings.Object); + with.Dependency(PushoverApi.Object); + with.Dependency(NotificationService.Object); + with.Dependency(IAnalytics.Object); + with.Dependency(HeadphonesSettings.Object); + with.Dependency(Cache.Object); + with.Dependency(Log.Object); + with.Dependency(SlackApi.Object); + with.Dependency(LandingPageSettings.Object); + with.Dependency(SlackSettings.Object); + with.Dependency(ScheduledJobsSettingsMock.Object); + with.Dependency(RecorderMock.Object); + with.RootPathProvider(); + with.RequestStartup((container, pipelines, context) => + { + context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List {"Admin"} }; + }); + }); + + Bootstrapper.WithSession(new Dictionary()); + } + + [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(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()), 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(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()), 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(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()), 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(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()), Times.Once); + } + + [Test] + public void GetUsersSuccessfully() + { + var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } }; + PlexMock.Setup(x => x.GetUsers(It.IsAny())).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(result.Body.AsString()); + var user = body["users"]; + Assert.That(body, Is.Not.Null); + Assert.That(user.ToString().Contains("abc"), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + + [Test] + public void GetUsersReturnsNoUsers() + { + var users = new PlexFriends(); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).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(result.Body.AsString()); + Assert.That(body, Is.Not.Null); + Assert.That(string.IsNullOrWhiteSpace(body), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + + [Test] + public void GetUsersReturnsNull() + { + PlexMock.Setup(x => x.GetUsers(It.IsAny())).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(result.Body.AsString()); + Assert.That(body, Is.Not.Null); + Assert.That(string.IsNullOrWhiteSpace(body), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), 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(result.Body.AsString()); + var user = (string)body["users"]; + Assert.That(body, Is.Not.Null); + Assert.That(string.IsNullOrWhiteSpace(user), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI.Tests/IssuesModuleTests.cs b/PlexRequests.UI.Tests/IssuesModuleTests.cs deleted file mode 100644 index 411cebbf2..000000000 --- a/PlexRequests.UI.Tests/IssuesModuleTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: IssuesModuleTests.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 System.Linq; -using System.Threading.Tasks; - -using Moq; - -using Nancy; -using Nancy.Testing; - -using Newtonsoft.Json; - -using NUnit.Framework; - -using PlexRequests.Core; -using PlexRequests.Core.Models; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Services.Interfaces; -using PlexRequests.UI.Models; -using PlexRequests.UI.Modules; - -using Ploeh.AutoFixture; - -namespace PlexRequests.UI.Tests -{ - [TestFixture] - public class IssuesModuleTests - { - [SetUp] - public void Setup() - { - var f = new Fixture(); - ModelList = f.CreateMany(); - PlexRequestMock = new Mock>(); - PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); - PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings())); - IssueServiceMock = new Mock(); - RequestServiceMock = new Mock(); - NotificationServiceMock = new Mock(); - IssueServiceMock.Setup(x => x.GetAllAsync()).Returns(Task.FromResult(ModelList)); - - Bootstrapper = new ConfigurableBootstrapper( - with => - { - with.Module(); - with.Dependency(PlexRequestMock.Object); - with.Dependency(IssueServiceMock.Object); - with.Dependency(RequestServiceMock.Object); - with.Dependency(NotificationServiceMock.Object); - with.RootPathProvider(); - }); - - Bootstrapper.WithSession(new Dictionary { { SessionKeys.UsernameKey, "abc" } }); - } - - private Mock> PlexRequestMock { get; set; } - private Mock IssueServiceMock { get; set; } - private Mock RequestServiceMock { get; set; } - private Mock NotificationServiceMock { get; set; } - private ConfigurableBootstrapper Bootstrapper { get; set; } - private IEnumerable ModelList { get; set; } - - private IEnumerable NonResolvedModel => ModelList.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue); - - [Test] - public void GetIssuesNonAdminButAllCanSee() - { - var browser = new Browser(Bootstrapper); - var result = browser.Get( - "/issues/pending", - with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject>(result.Body.AsString()); - - Assert.That(body.Count, Is.EqualTo(NonResolvedModel.Count())); - Assert.That(body[0].Title, Is.Not.Empty); - } - - [Test] - public void GetIssuesForAdmin() - { - Bootstrapper = new ConfigurableBootstrapper( - with => - { - with.Module(); - with.Dependency(PlexRequestMock.Object); - with.Dependency(IssueServiceMock.Object); - with.Dependency(RequestServiceMock.Object); - with.Dependency(NotificationServiceMock.Object); - with.RootPathProvider(); - with.RequestStartup( - (container, pipelines, context) => - { - context.CurrentUser = new UserIdentity() { Claims = new[] { UserClaims.Admin } }; - }); - }); - - Bootstrapper.WithSession(new Dictionary { { SessionKeys.UsernameKey, "abc" } }); - var browser = new Browser(Bootstrapper); - var result = browser.Get( - "/issues/pending", - with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject>(result.Body.AsString()); - Assert.That(body.Count, Is.EqualTo(NonResolvedModel.Count())); - Assert.That(body[0].Title, Is.Not.Empty); - } - } -} \ No newline at end of file diff --git a/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj b/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj index 95ac4be3a..5ec37a74a 100644 --- a/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj +++ b/PlexRequests.UI.Tests/PlexRequests.UI.Tests.csproj @@ -105,7 +105,6 @@ - diff --git a/PlexRequests.UI.Tests/UserLoginModuleTests.cs b/PlexRequests.UI.Tests/UserLoginModuleTests.cs index 22d000575..c962fa335 100644 --- a/PlexRequests.UI.Tests/UserLoginModuleTests.cs +++ b/PlexRequests.UI.Tests/UserLoginModuleTests.cs @@ -1,437 +1,441 @@ -#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 System.Threading.Tasks; - -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 UserLoginModuleTests - { - private Mock> AuthMock { get; set; } - private Mock> PlexRequestMock { get; set; } - private Mock> LandingPageMock { get; set; } - private ConfigurableBootstrapper Bootstrapper { get; set; } - private Mock PlexMock { get; set; } - - [SetUp] - public void Setup() - { - AuthMock = new Mock>(); - PlexMock = new Mock(); - LandingPageMock = new Mock>(); - PlexRequestMock = new Mock>(); - PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); - PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings())); - LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings()); - Bootstrapper = new ConfigurableBootstrapper(with => - { - with.Module(); - with.Dependency(PlexRequestMock.Object); - with.Dependency(AuthMock.Object); - with.Dependency(PlexMock.Object); - with.Dependency(LandingPageMock.Object); - with.RootPathProvider(); - }); - } - - [Test] - public void LoginWithoutAuthentication() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void LoginWithoutAuthenticationWithEmptyUsername() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", string.Empty); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - AuthMock.Verify(x => x.GetSettings(), Times.Never); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void LoginWithUsernameSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Title = "abc", - }, - } - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithUsernameUnSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Username = "aaaa", - }, - } - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithUsernameAndPasswordSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Title = "abc", - } - } - }; - var plexAuth = new PlexAuthentication - { - user = new User - { - authentication_token = "abc" - } - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - with.FormValue("Password", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithUsernameAndPasswordUnSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Username = "abc", - }, - } - }; - var plexAuth = new PlexAuthentication - { - user = null - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); - - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - with.FormValue("Password", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void AttemptToLoginAsDeniedUser() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void Logout() - { - Bootstrapper.WithSession(new Dictionary { { SessionKeys.UsernameKey, "abc" } }); - - var browser = new Browser(Bootstrapper); - var result = browser.Get("/userlogin/logout", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - }); - - Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - } - - [Test] - public void LoginWithOwnerUsernameSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends() - } - }; - - var account = new PlexAccount { Username = "Jamie" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } }); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "Jamie"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithOwnerUsernameAndPasswordSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends() - } - }; - var plexAuth = new PlexAuthentication - { - user = new User - { - authentication_token = "abc", - username = "Jamie" - } - }; - - var account = new PlexAccount { Username = "Jamie" }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "jamie"); - with.FormValue("Password", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - } +#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 System.Threading.Tasks; + +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.Helpers.Analytics; +using PlexRequests.UI.Models; +using PlexRequests.UI.Modules; + +namespace PlexRequests.UI.Tests +{ + [TestFixture] + public class UserLoginModuleTests + { + private Mock> AuthMock { get; set; } + private Mock> PlexRequestMock { get; set; } + private Mock> LandingPageMock { get; set; } + private ConfigurableBootstrapper Bootstrapper { get; set; } + private Mock PlexMock { get; set; } + private Mock IAnalytics { get; set; } + + [SetUp] + public void Setup() + { + AuthMock = new Mock>(); + PlexMock = new Mock(); + LandingPageMock = new Mock>(); + PlexRequestMock = new Mock>(); + PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); + PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings())); + LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings()); + IAnalytics = new Mock(); + Bootstrapper = new ConfigurableBootstrapper(with => + { + with.Module(); + with.Dependency(PlexRequestMock.Object); + with.Dependency(AuthMock.Object); + with.Dependency(PlexMock.Object); + with.Dependency(LandingPageMock.Object); + with.Dependency(IAnalytics.Object); + with.RootPathProvider(); + }); + } + + [Test] + public void LoginWithoutAuthentication() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void LoginWithoutAuthenticationWithEmptyUsername() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", string.Empty); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + AuthMock.Verify(x => x.GetSettings(), Times.Never); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void LoginWithUsernameSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Title = "abc", + }, + } + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithUsernameUnSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Username = "aaaa", + }, + } + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithUsernameAndPasswordSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Title = "abc", + } + } + }; + var plexAuth = new PlexAuthentication + { + user = new User + { + authentication_token = "abc" + } + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + with.FormValue("Password", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithUsernameAndPasswordUnSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Username = "abc", + }, + } + }; + var plexAuth = new PlexAuthentication + { + user = null + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); + + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + with.FormValue("Password", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void AttemptToLoginAsDeniedUser() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void Logout() + { + Bootstrapper.WithSession(new Dictionary { { SessionKeys.UsernameKey, "abc" } }); + + var browser = new Browser(Bootstrapper); + var result = browser.Get("/userlogin/logout", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + }); + + Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + } + + [Test] + public void LoginWithOwnerUsernameSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends() + } + }; + + var account = new PlexAccount { Username = "Jamie" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } }); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "Jamie"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithOwnerUsernameAndPasswordSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends() + } + }; + var plexAuth = new PlexAuthentication + { + user = new User + { + authentication_token = "abc", + username = "Jamie" + } + }; + + var account = new PlexAccount { Username = "Jamie" }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "jamie"); + with.FormValue("Password", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI.Tests/app.config b/PlexRequests.UI.Tests/app.config index d1c064e34..3799f3a1a 100644 --- a/PlexRequests.UI.Tests/app.config +++ b/PlexRequests.UI.Tests/app.config @@ -1,28 +1,28 @@ - - - - -
- - - - - - - - - - - - - - - - - - - - - - + + + + +
+ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.UI/Bootstrapper.cs b/PlexRequests.UI/Bootstrapper.cs index a43f4c8f6..d29c9c324 100644 --- a/PlexRequests.UI/Bootstrapper.cs +++ b/PlexRequests.UI/Bootstrapper.cs @@ -1,229 +1,230 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Bootstrapper.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.Net; - -using Mono.Data.Sqlite; - -using Nancy; -using Nancy.Authentication.Forms; -using Nancy.Bootstrapper; -using Nancy.Conventions; -using Nancy.Cryptography; -using Nancy.Diagnostics; -using Nancy.Session; -using Nancy.TinyIoc; - -using PlexRequests.Api; -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; -using PlexRequests.UI.Helpers; -using Nancy.Json; - -using PlexRequests.Helpers.Analytics; -using PlexRequests.Services.Jobs; -using PlexRequests.UI.Jobs; - -using Quartz; -using Quartz.Impl; -using Quartz.Spi; - -namespace PlexRequests.UI -{ - public class Bootstrapper : DefaultNancyBootstrapper - { - // The bootstrapper enables you to reconfigure the composition of the framework, - // by overriding the various methods and properties. - // For more information https://github.com/NancyFx/Nancy/wiki/Bootstrapper - - protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) - { - ConfigureContainer(container); - - JsonSettings.MaxJsonLength = int.MaxValue; - - CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default); - StaticConfiguration.DisableErrorTraces = false; - - base.ApplicationStartup(container, pipelines); - - var settings = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); - var baseUrl = settings.GetSettings().BaseUrl; - var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login"; - - // Enable forms auth - var formsAuthConfiguration = new FormsAuthenticationConfiguration - { - RedirectUrl = redirect, - UserMapper = container.Resolve() - }; - - FormsAuthentication.Enable(pipelines, formsAuthConfiguration); - - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; - ServicePointManager.ServerCertificateValidationCallback += - (sender, certificate, chain, sslPolicyErrors) => true; - - SubscribeAllObservers(container); - - } - - protected override void ConfigureConventions(NancyConventions nancyConventions) - { - base.ConfigureConventions(nancyConventions); - - var settings = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); - var assetLocation = settings.GetSettings().BaseUrl; - nancyConventions.StaticContentsConventions.Add( - StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content") - ); - - nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui"); - } - - protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" }; - - private void SubscribeAllObservers(TinyIoCContainer container) - { - var notificationService = container.Resolve(); - - var emailSettingsService = container.Resolve>(); - var emailSettings = emailSettingsService.GetSettings(); - if (emailSettings.Enabled) - { - notificationService.Subscribe(new EmailMessageNotification(emailSettingsService)); - } - - var pushbulletService = container.Resolve>(); - var pushbulletSettings = pushbulletService.GetSettings(); - if (pushbulletSettings.Enabled) - { - notificationService.Subscribe(new PushbulletNotification(container.Resolve(), pushbulletService)); - } - - var pushoverService = container.Resolve>(); - var pushoverSettings = pushoverService.GetSettings(); - if (pushoverSettings.Enabled) - { - notificationService.Subscribe(new PushoverNotification(container.Resolve(), pushoverService)); - } - - var slackService = container.Resolve>(); - var slackSettings = slackService.GetSettings(); - if (slackSettings.Enabled) - { - notificationService.Subscribe(new SlackNotification(container.Resolve(), slackService)); - } - } - - protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) - { - //CORS Enable - pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) => - { - ctx.Response.WithHeader("Access-Control-Allow-Origin", "*") - .WithHeader("Access-Control-Allow-Methods", "POST,GET") - .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type"); - - }); - base.RequestStartup(container, pipelines, context); - } - - private void ConfigureContainer(TinyIoCContainer container) - { - container.Register().AsSingleton(); - container.Register(new DbConfiguration(new SqliteFactory())); - container.Register, UserRepository>(); - container.Register(); - container.Register(); - - // Settings - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - container.RegisterSetting(); - - // Repository - container.RegisterRepo(); - container.RegisterRepo(); - container.RegisterRepo(); - container.RegisterRepo(); - - - // Notification Service - container.Register().AsSingleton(); - - container.Register(); - container.Register(); - container.Register(); - container.Register(); - - // Services - container.Register(); - container.Register(); - container.Register(); - container.Register(); - container.Register(); - - container.Register(); - container.Register(); - container.Register(); - - - // Api - container.Register(); - container.Register(); - container.Register(); - container.Register(); - container.Register(); - container.Register(); - container.Register(); - container.Register(); - container.Register(); - - var loc = ServiceLocator.Instance; - loc.SetContainer(container); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Bootstrapper.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.Net; + +using Mono.Data.Sqlite; + +using Nancy; +using Nancy.Authentication.Forms; +using Nancy.Bootstrapper; +using Nancy.Conventions; +using Nancy.Cryptography; +using Nancy.Diagnostics; +using Nancy.Session; +using Nancy.TinyIoc; + +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Services; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Helpers; +using Nancy.Json; + +using PlexRequests.Helpers.Analytics; +using PlexRequests.Services.Jobs; +using PlexRequests.UI.Jobs; + +using Quartz; +using Quartz.Impl; +using Quartz.Spi; + +namespace PlexRequests.UI +{ + public class Bootstrapper : DefaultNancyBootstrapper + { + // The bootstrapper enables you to reconfigure the composition of the framework, + // by overriding the various methods and properties. + // For more information https://github.com/NancyFx/Nancy/wiki/Bootstrapper + + protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) + { + ConfigureContainer(container); + + JsonSettings.MaxJsonLength = int.MaxValue; + + CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default); + StaticConfiguration.DisableErrorTraces = false; + + base.ApplicationStartup(container, pipelines); + + var settings = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + var baseUrl = settings.GetSettings().BaseUrl; + var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login"; + + // Enable forms auth + var formsAuthConfiguration = new FormsAuthenticationConfiguration + { + RedirectUrl = redirect, + UserMapper = container.Resolve() + }; + + FormsAuthentication.Enable(pipelines, formsAuthConfiguration); + + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; + ServicePointManager.ServerCertificateValidationCallback += + (sender, certificate, chain, sslPolicyErrors) => true; + + SubscribeAllObservers(container); + + } + + protected override void ConfigureConventions(NancyConventions nancyConventions) + { + base.ConfigureConventions(nancyConventions); + + var settings = new SettingsServiceV2(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider())); + var assetLocation = settings.GetSettings().BaseUrl; + nancyConventions.StaticContentsConventions.Add( + StaticContentConventionBuilder.AddDirectory($"{assetLocation}/Content", "Content") + ); + + nancyConventions.StaticContentsConventions.AddDirectory($"{assetLocation}/docs", "swagger-ui"); + } + + protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" }; + + private void SubscribeAllObservers(TinyIoCContainer container) + { + var notificationService = container.Resolve(); + + var emailSettingsService = container.Resolve>(); + var emailSettings = emailSettingsService.GetSettings(); + if (emailSettings.Enabled) + { + notificationService.Subscribe(new EmailMessageNotification(emailSettingsService)); + } + + var pushbulletService = container.Resolve>(); + var pushbulletSettings = pushbulletService.GetSettings(); + if (pushbulletSettings.Enabled) + { + notificationService.Subscribe(new PushbulletNotification(container.Resolve(), pushbulletService)); + } + + var pushoverService = container.Resolve>(); + var pushoverSettings = pushoverService.GetSettings(); + if (pushoverSettings.Enabled) + { + notificationService.Subscribe(new PushoverNotification(container.Resolve(), pushoverService)); + } + + var slackService = container.Resolve>(); + var slackSettings = slackService.GetSettings(); + if (slackSettings.Enabled) + { + notificationService.Subscribe(new SlackNotification(container.Resolve(), slackService)); + } + } + + protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context) + { + //CORS Enable + pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) => + { + ctx.Response.WithHeader("Access-Control-Allow-Origin", "*") + .WithHeader("Access-Control-Allow-Methods", "POST,GET") + .WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type"); + + }); + base.RequestStartup(container, pipelines, context); + } + + private void ConfigureContainer(TinyIoCContainer container) + { + container.Register().AsSingleton(); + container.Register(new DbConfiguration(new SqliteFactory())); + container.Register, UserRepository>(); + container.Register(); + container.Register(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + + // Notification Service + container.Register().AsSingleton(); + // Settings + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + container.Register, SettingsServiceV2>(); + + // Repo's + container.Register, GenericRepository>(); + container.Register, GenericRepository>(); + container.Register, GenericRepository>(); + container.Register, GenericRepository>(); + container.Register, GenericRepository>(); + container.Register, GenericRepository>(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + + // Services + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + + container.Register(); + container.Register(); + container.Register(); + + + // Api + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + container.Register(); + + var loc = ServiceLocator.Instance; + loc.SetContainer(container); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Content/issue-details.js b/PlexRequests.UI/Content/issue-details.js index 28b10c997..92693ac94 100644 --- a/PlexRequests.UI/Content/issue-details.js +++ b/PlexRequests.UI/Content/issue-details.js @@ -27,19 +27,43 @@ $(".theNoteSaveButton").click(function (e) { }); }); // 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 +$('#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 issue = button.data('issue'); - 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 + 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 + var noteType = modal.find('.issue'); + noteType.val(issue); + }); - + + + $('.delete').click(function(e) { + e.preventDefault(); + var url = createBaseUrl(base, "/issues"); + var $form = $("#removeForm"); + + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: $form.serialize(), + dataType: "json", + success: function (response) { + if (checkJsonResponse(response)) { window.location.replace(url); } + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); + }); diff --git a/PlexRequests.UI/Content/issues.js b/PlexRequests.UI/Content/issues.js index f97ea0ba0..87410c45c 100644 --- a/PlexRequests.UI/Content/issues.js +++ b/PlexRequests.UI/Content/issues.js @@ -264,8 +264,9 @@ function buildIssueContext(result) { requestId: result.requestId, type: result.type, title: result.title, - issues: result.issues - }; + issues: result.issues, + admin: result.admin +}; return context; } diff --git a/PlexRequests.UI/Content/requests-1.7.js b/PlexRequests.UI/Content/requests-1.7.js index 041b67bcc..a65bc3425 100644 --- a/PlexRequests.UI/Content/requests-1.7.js +++ b/PlexRequests.UI/Content/requests-1.7.js @@ -159,7 +159,7 @@ $('#approveTVShows').click(function (e) { $('#deleteMovies').click(function (e) { e.preventDefault(); - if (!confirm("Are you sure you want to delete all TV show requests?")) return; + if (!confirm("Are you sure you want to delete all Movie requests?")) return; var buttonId = e.target.id; var origHtml = $(this).html(); diff --git a/PlexRequests.UI/Helpers/CultureHelper.cs b/PlexRequests.UI/Helpers/CultureHelper.cs new file mode 100644 index 000000000..d933d758f --- /dev/null +++ b/PlexRequests.UI/Helpers/CultureHelper.cs @@ -0,0 +1,117 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CultureHelper.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 System.Linq; +using System.Threading; + +namespace PlexRequests.UI.Helpers +{ + public class CultureHelper + { + private static readonly List ValidCultures = new List { "en-US", "de-DE", "fr-FR", "es-ES", "de", "en", "fr", "es","da","sv","it","nl" }; + + private static readonly List ImplimentedCultures = new List { + "en-US", + "en", + "de", + "fr", + "es", + "da", + "sv", + "it", + "nl" + }; + + /// + /// Returns true if the language is a right-to-left language. + /// + public static bool IsRighToLeft() + { + return Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft; + } + + /// + /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US" + /// + /// Culture's name (e.g. en-US) + public static string GetImplementedCulture(string name) + { + if (string.IsNullOrEmpty(name)) + { + return GetDefaultCulture(); + } + + // Validate the culture + if (!ValidCultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase))) + { + return GetDefaultCulture(); + } + + if (ImplimentedCultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase))) + { + return name; // We do have the culture, lets go with it + } + + // Find a close match. + var match = GetNeutralCulture(name); + + foreach (var c in ImplimentedCultures.Where(c => c.StartsWith(match, StringComparison.InvariantCultureIgnoreCase))) + { + return c; + } + + return GetDefaultCulture(); // return Default culture since we cannot find anything + } + + /// + /// Returns default culture name which is the first name declared (e.g. en-US) + /// + /// Culture string e.g. en-US + public static string GetDefaultCulture() + { + return ImplimentedCultures[0]; // return first culture + } + + public static string GetCurrentCulture() + { + return Thread.CurrentThread.CurrentCulture.Name; + } + + public static string GetCurrentNeutralCulture() + { + return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name); + } + + public static string GetNeutralCulture(string name) + { + if (!name.Contains("-")) return name; + + return name.Split('-')[0]; // Read first part only + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs index 5074cb9fc..da34eea2a 100644 --- a/PlexRequests.UI/Helpers/TvSender.cs +++ b/PlexRequests.UI/Helpers/TvSender.cs @@ -1,99 +1,100 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: TvSender.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 NLog; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.SickRage; -using PlexRequests.Api.Models.Sonarr; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Store; -using System.Linq; - -namespace PlexRequests.UI.Helpers -{ - public class TvSender - { - public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi) - { - SonarrApi = sonarrApi; - SickrageApi = srApi; - } - private ISonarrApi SonarrApi { get; } - private ISickRageApi SickrageApi { get; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) - { - return SendToSonarr(sonarrSettings, model, string.Empty); - } - - public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId) - { - - var qualityProfile = 0; - - if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality - { - if (!int.TryParse(qualityId, out qualityProfile)) - { - int.TryParse(sonarrSettings.QualityProfile, out qualityProfile); - } - } - - var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, - sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, - sonarrSettings.FullUri); - - Log.Trace("Sonarr Add Result: "); - Log.Trace(result.DumpJson()); - - return result; - } - - public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model) - { - return SendToSickRage(sickRageSettings, model, sickRageSettings.QualityProfile); - } - - public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model, string qualityId) - { - Log.Info("Sending to SickRage {0}", model.Title); - if (!sickRageSettings.Qualities.Any(x => x.Key == qualityId)) - { - qualityId = sickRageSettings.QualityProfile; - } - - var apiResult = SickrageApi.AddSeries(model.ProviderId, model.SeasonCount, model.SeasonList, qualityId, - sickRageSettings.ApiKey, sickRageSettings.FullUri); - - var result = apiResult.Result; - - - return result; - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: TvSender.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 NLog; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.SickRage; +using PlexRequests.Api.Models.Sonarr; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Store; +using System.Linq; + +namespace PlexRequests.UI.Helpers +{ + public class TvSender + { + public TvSender(ISonarrApi sonarrApi, ISickRageApi srApi) + { + SonarrApi = sonarrApi; + SickrageApi = srApi; + } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickrageApi { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model) + { + return SendToSonarr(sonarrSettings, model, string.Empty); + } + + public SonarrAddSeries SendToSonarr(SonarrSettings sonarrSettings, RequestedModel model, string qualityId) + { + var qualityProfile = 0; + + if (!string.IsNullOrEmpty(qualityId)) // try to parse the passed in quality, otherwise use the settings default quality + { + int.TryParse(qualityId, out qualityProfile); + } + + if (qualityProfile <= 0) + { + int.TryParse(sonarrSettings.QualityProfile, out qualityProfile); + } + + var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, + sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey, + sonarrSettings.FullUri); + + Log.Trace("Sonarr Add Result: "); + Log.Trace(result.DumpJson()); + + return result; + } + + public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model) + { + return SendToSickRage(sickRageSettings, model, sickRageSettings.QualityProfile); + } + + public SickRageTvAdd SendToSickRage(SickRageSettings sickRageSettings, RequestedModel model, string qualityId) + { + Log.Info("Sending to SickRage {0}", model.Title); + if (!sickRageSettings.Qualities.Any(x => x.Key == qualityId)) + { + qualityId = sickRageSettings.QualityProfile; + } + + var apiResult = SickrageApi.AddSeries(model.ProviderId, model.SeasonCount, model.SeasonList, qualityId, + sickRageSettings.ApiKey, sickRageSettings.FullUri); + + var result = apiResult.Result; + + + return result; + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Jobs/Scheduler.cs b/PlexRequests.UI/Jobs/Scheduler.cs index dc17a07d5..6e151f2bf 100644 --- a/PlexRequests.UI/Jobs/Scheduler.cs +++ b/PlexRequests.UI/Jobs/Scheduler.cs @@ -62,6 +62,7 @@ namespace PlexRequests.UI.Jobs var cp = JobBuilder.Create().WithIdentity("CouchPotatoCacher", "Cache").Build(); var store = JobBuilder.Create().WithIdentity("StoreBackup", "Database").Build(); var storeClean = JobBuilder.Create().WithIdentity("StoreCleanup", "Database").Build(); + var userRequestLimitReset = JobBuilder.Create().WithIdentity("UserRequestLimiter", "Request").Build(); jobs.Add(plex); jobs.Add(sickrage); @@ -69,6 +70,7 @@ namespace PlexRequests.UI.Jobs jobs.Add(cp); jobs.Add(store); jobs.Add(storeClean); + jobs.Add(userRequestLimitReset); return jobs; } @@ -150,6 +152,13 @@ namespace PlexRequests.UI.Jobs .WithSimpleSchedule(x => x.WithIntervalInHours(s.StoreCleanup).RepeatForever()) .Build(); + var userRequestLimiter = + TriggerBuilder.Create() + .WithIdentity("UserRequestLimiter", "Request") + .StartAt(DateTimeOffset.Now.AddMinutes(5)) // Everything has started on application start, lets wait 5 minutes + .WithSimpleSchedule(x => x.WithIntervalInHours(s.UserRequestLimitResetter).RepeatForever()) + .Build(); + triggers.Add(plexAvailabilityChecker); triggers.Add(srCacher); @@ -157,6 +166,7 @@ namespace PlexRequests.UI.Jobs triggers.Add(cpCacher); triggers.Add(storeBackup); triggers.Add(storeCleanup); + triggers.Add(userRequestLimiter); return triggers; } diff --git a/PlexRequests.UI/Models/IssuesViewMOdel.cs b/PlexRequests.UI/Models/IssuesViewMOdel.cs index d05b2a8e8..00f3c5082 100644 --- a/PlexRequests.UI/Models/IssuesViewMOdel.cs +++ b/PlexRequests.UI/Models/IssuesViewMOdel.cs @@ -36,6 +36,7 @@ namespace PlexRequests.UI.Models public string Title { get; set; } public string Issues { get; set; } public string Type { get; set; } + public bool Admin { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index daf5f0967..ab1b8cb4d 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -1,876 +1,915 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: AdminModule.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.Diagnostics; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Net; - -using Nancy; -using Nancy.Extensions; -using Nancy.ModelBinding; -using Nancy.Responses.Negotiation; -using Nancy.Validation; -using Nancy.Json; -using Nancy.Security; -using NLog; - -using MarkdownSharp; - -using PlexRequests.Api; -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; -using PlexRequests.UI.Helpers; -using PlexRequests.UI.Models; - - -namespace PlexRequests.UI.Modules -{ - public class AdminModule : BaseModule - { - private ISettingsService PrService { get; } - private ISettingsService CpService { get; } - private ISettingsService AuthService { get; } - private ISettingsService PlexService { get; } - private ISettingsService SonarrService { get; } - private ISettingsService SickRageService { get; } - private ISettingsService EmailService { get; } - private ISettingsService PushbulletService { get; } - private ISettingsService PushoverService { get; } - private ISettingsService HeadphonesService { get; } - private ISettingsService LogService { get; } - private IPlexApi PlexApi { get; } - private ISonarrApi SonarrApi { get; } - private IPushbulletApi PushbulletApi { get; } - private IPushoverApi PushoverApi { get; } - private ICouchPotatoApi CpApi { get; } - private IRepository LogsRepo { get; } - private INotificationService NotificationService { get; } - private ICacheProvider Cache { get; } - private ISettingsService SlackSettings { get; } - private ISettingsService LandingSettings { get; } - private ISettingsService ScheduledJobSettings { get; } - private ISlackApi SlackApi { get; } - private IJobRecord JobRecorder { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - public AdminModule(ISettingsService prService, - ISettingsService cpService, - ISettingsService auth, - ISettingsService plex, - ISettingsService sonarr, - ISettingsService sickrage, - ISonarrApi sonarrApi, - ISettingsService email, - IPlexApi plexApi, - ISettingsService pbSettings, - PushbulletApi pbApi, - ICouchPotatoApi cpApi, - ISettingsService pushoverSettings, - IPushoverApi pushoverApi, - IRepository logsRepo, - INotificationService notify, - ISettingsService headphones, - ISettingsService logs, - ICacheProvider cache, ISettingsService slackSettings, - ISlackApi slackApi, ISettingsService lp, - ISettingsService scheduler, IJobRecord rec) : base("admin", prService) - { - PrService = prService; - CpService = cpService; - AuthService = auth; - PlexService = plex; - SonarrService = sonarr; - SonarrApi = sonarrApi; - EmailService = email; - PlexApi = plexApi; - PushbulletService = pbSettings; - PushbulletApi = pbApi; - CpApi = cpApi; - SickRageService = sickrage; - LogsRepo = logsRepo; - PushoverService = pushoverSettings; - PushoverApi = pushoverApi; - NotificationService = notify; - HeadphonesService = headphones; - LogService = logs; - Cache = cache; - SlackSettings = slackSettings; - SlackApi = slackApi; - LandingSettings = lp; - ScheduledJobSettings = scheduler; - JobRecorder = rec; - - this.RequiresClaims(UserClaims.Admin); - - Get["/"] = _ => Admin(); - - Get["/authentication", true] = async (x, ct) => await Authentication(); - Post["/authentication", true] = async (x, ct) => await SaveAuthentication(); - - Post["/"] = _ => SaveAdmin(); - - Post["/requestauth"] = _ => RequestAuthToken(); - - Get["/getusers"] = _ => GetUsers(); - - Get["/couchpotato"] = _ => CouchPotato(); - Post["/couchpotato"] = _ => SaveCouchPotato(); - - Get["/plex"] = _ => Plex(); - Post["/plex"] = _ => SavePlex(); - - Get["/sonarr"] = _ => Sonarr(); - Post["/sonarr"] = _ => SaveSonarr(); - - Get["/sickrage"] = _ => Sickrage(); - Post["/sickrage"] = _ => SaveSickrage(); - - Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); - Post["/cpprofiles"] = _ => GetCpProfiles(); - - Get["/emailnotification"] = _ => EmailNotifications(); - Post["/emailnotification"] = _ => SaveEmailNotifications(); - Post["/testemailnotification"] = _ => TestEmailNotifications(); - Get["/status", true] = async (x,ct) => await Status(); - - Get["/pushbulletnotification"] = _ => PushbulletNotifications(); - Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); - Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications(); - - Get["/pushovernotification"] = _ => PushoverNotifications(); - Post["/pushovernotification"] = _ => SavePushoverNotifications(); - Post["/testpushovernotification"] = _ => TestPushoverNotifications(); - - Get["/logs"] = _ => Logs(); - Get["/loglevel"] = _ => GetLogLevels(); - Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level); - Get["/loadlogs"] = _ => LoadLogs(); - - Get["/headphones"] = _ => Headphones(); - Post["/headphones"] = _ => SaveHeadphones(); - - Post["/createapikey"] = x => CreateApiKey(); - - Post["/autoupdate"] = x => AutoUpdate(); - - Post["/testslacknotification"] = _ => TestSlackNotification(); - - Get["/slacknotification"] = _ => SlackNotifications(); - Post["/slacknotification"] = _ => SaveSlackNotifications(); - - Get["/landingpage", true] = async (x, ct) => await LandingPage(); - Post["/landingpage", true] = async (x, ct) => await SaveLandingPage(); - - Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs(); - Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs(); - } - - private async Task Authentication() - { - var settings = await AuthService.GetSettingsAsync(); - - return View["/Authentication", settings]; - } - - private async Task SaveAuthentication() - { - var model = this.Bind(); - - var result = await AuthService.SaveSettingsAsync(model); - if (result) - { - if (!string.IsNullOrEmpty(BaseUrl)) - { - return Context.GetRedirect($"~/{BaseUrl}/admin/authentication"); - } - return Context.GetRedirect("~/admin/authentication"); - } - if (!string.IsNullOrEmpty(BaseUrl)) - { - return Context.GetRedirect($"~/{BaseUrl}/error"); //TODO create error page - } - return Context.GetRedirect("~/error"); //TODO create error page - } - - private Negotiator Admin() - { - var settings = PrService.GetSettings(); - - return View["Settings", settings]; - } - - private Response SaveAdmin() - { - var model = this.Bind(); - var valid = this.Validate(model); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - if (!string.IsNullOrWhiteSpace(model.BaseUrl)) - { - if (model.BaseUrl.StartsWith("/", StringComparison.CurrentCultureIgnoreCase) || model.BaseUrl.StartsWith("\\", StringComparison.CurrentCultureIgnoreCase)) - { - model.BaseUrl = model.BaseUrl.Remove(0, 1); - } - } - var result = PrService.SaveSettings(model); - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); - } - - private Response RequestAuthToken() - { - var user = this.Bind(); - - if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password)) - { - return Response.AsJson(new { Result = false, Message = "Please provide a valid username and password" }); - } - - var model = PlexApi.SignIn(user.username, user.password); - - if (model?.user == null) - { - return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); - } - - var oldSettings = AuthService.GetSettings(); - if (oldSettings != null) - { - oldSettings.PlexAuthToken = model.user.authentication_token; - AuthService.SaveSettings(oldSettings); - } - else - { - var newModel = new AuthenticationSettings - { - PlexAuthToken = model.user.authentication_token - }; - AuthService.SaveSettings(newModel); - } - - return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token }); - } - - - private Response GetUsers() - { - var settings = AuthService.GetSettings(); - - var token = settings?.PlexAuthToken; - if (token == null) - { - return Response.AsJson(new { Result = true, Users = string.Empty }); - } - - try - { - var users = PlexApi.GetUsers(token); - if (users == null) - { - 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.Title); - return Response.AsJson(new { Result = true, Users = usernames }); - } - catch (Exception ex) - { - Log.Error(ex); - if (ex is WebException || ex is ApiRequestException) - { - return Response.AsJson(new { Result = false, Message = "Could not load the user list! We have connectivity problems connecting to Plex, Please ensure we can access Plex.Tv, The error has been logged." }); - } - - return Response.AsJson(new { Result = false, Message = ex.Message }); - } - } - - private Negotiator CouchPotato() - { - var settings = CpService.GetSettings(); - - return View["CouchPotato", settings]; - } - - private Response SaveCouchPotato() - { - var couchPotatoSettings = this.Bind(); - var valid = this.Validate(couchPotatoSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = CpService.SaveSettings(couchPotatoSettings); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Negotiator Plex() - { - var settings = PlexService.GetSettings(); - - return View["Plex", settings]; - } - - private Response SavePlex() - { - var plexSettings = this.Bind(); - var valid = this.Validate(plexSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - - var result = PlexService.SaveSettings(plexSettings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Negotiator Sonarr() - { - var settings = SonarrService.GetSettings(); - - return View["Sonarr", settings]; - } - - private Response SaveSonarr() - { - var sonarrSettings = this.Bind(); - - var valid = this.Validate(sonarrSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var sickRageEnabled = SickRageService.GetSettings().Enabled; - if (sickRageEnabled) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); - } - var result = SonarrService.SaveSettings(sonarrSettings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Sonarr!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Negotiator Sickrage() - { - var settings = SickRageService.GetSettings(); - - return View["Sickrage", settings]; - } - - private Response SaveSickrage() - { - var sickRageSettings = this.Bind(); - - var valid = this.Validate(sickRageSettings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var sonarrEnabled = SonarrService.GetSettings().Enabled; - if (sonarrEnabled) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sonarr is enabled, we cannot enable Sonarr and SickRage" }); - } - var result = SickRageService.SaveSettings(sickRageSettings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for SickRage!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response GetSonarrQualityProfiles() - { - var settings = this.Bind(); - var profiles = SonarrApi.GetProfiles(settings.ApiKey, settings.FullUri); - - // set the cache - if (profiles != null) - { - Cache.Set(CacheKeys.SonarrQualityProfiles, profiles); - } - - return Response.AsJson(profiles); - } - - - private Negotiator EmailNotifications() - { - var settings = EmailService.GetSettings(); - return View["EmailNotifications", settings]; - } - - private Response TestEmailNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new EmailMessageNotification(EmailService)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent email notification test"); - } - catch (Exception) - { - Log.Error("Failed to subscribe and publish test Email Notification"); - } - finally - { - NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); - } - - private Response SaveEmailNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = EmailService.SaveSettings(settings); - - if (settings.Enabled) - { - NotificationService.Subscribe(new EmailMessageNotification(EmailService)); - } - else - { - NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); - } - - Log.Info("Saved email settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Email Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private async Task Status() - { - var checker = new StatusChecker(); - var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30); - var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true}); - status.ReleaseNotes = md.Transform(status.ReleaseNotes); - return View["Status", status]; - } - - private Response AutoUpdate() - { - var url = Request.Form["url"]; - - var startInfo = Type.GetType("Mono.Runtime") != null - ? new ProcessStartInfo("mono PlexRequests.Updater.exe") { Arguments = url } - : new ProcessStartInfo("PlexRequests.Updater.exe") { Arguments = url }; - - Process.Start(startInfo); - - Environment.Exit(0); - return Nancy.Response.NoBody; - } - - private Negotiator PushbulletNotifications() - { - var settings = PushbulletService.GetSettings(); - return View["PushbulletNotifications", settings]; - } - - private Response SavePushbulletNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = PushbulletService.SaveSettings(settings); - if (settings.Enabled) - { - NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - } - else - { - NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - } - - Log.Info("Saved email settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response TestPushbulletNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent pushbullet notification test"); - } - catch (Exception) - { - Log.Error("Failed to subscribe and publish test Pushbullet Notification"); - } - finally - { - NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" }); - } - - private Negotiator PushoverNotifications() - { - var settings = PushoverService.GetSettings(); - return View["PushoverNotifications", settings]; - } - - private Response SavePushoverNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - - var result = PushoverService.SaveSettings(settings); - if (settings.Enabled) - { - NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); - } - else - { - NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); - } - - Log.Info("Saved email settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushover Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response TestPushoverNotifications() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent pushover notification test"); - } - catch (Exception) - { - Log.Error("Failed to subscribe and publish test Pushover Notification"); - } - finally - { - NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" }); - } - - private Response GetCpProfiles() - { - var settings = this.Bind(); - var valid = this.Validate(settings); - if (!valid.IsValid) - { - return Response.AsJson(valid.SendJsonError()); - } - var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey); - - // set the cache - if (profiles != null) - { - Cache.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); - } - - return Response.AsJson(profiles); - } - - private Negotiator Logs() - { - return View["Logs"]; - } - - private Response LoadLogs() - { - JsonSettings.MaxJsonLength = int.MaxValue; - var allLogs = LogsRepo.GetAll().OrderByDescending(x => x.Id).Take(200); - var model = new DatatablesModel { Data = new List() }; - foreach (var l in allLogs) - { - l.DateString = l.Date.ToString("G"); - model.Data.Add(l); - } - return Response.AsJson(model); - } - - private Response GetLogLevels() - { - var levels = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database")); - return Response.AsJson(levels.Levels); - } - - private Response UpdateLogLevels(int level) - { - var settings = LogService.GetSettings(); - - // apply the level - var newLevel = LogLevel.FromOrdinal(level); - LoggingHelper.ReconfigureLogLevel(newLevel); - - //save the log settings - settings.Level = level; - LogService.SaveSettings(settings); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}" }); - } - - private Negotiator Headphones() - { - var settings = HeadphonesService.GetSettings(); - return View["Headphones", settings]; - } - - private Response SaveHeadphones() - { - var settings = this.Bind(); - - var valid = this.Validate(settings); - if (!valid.IsValid) - { - var error = valid.SendJsonError(); - Log.Info("Error validating Headphones settings, message: {0}", error.Message); - return Response.AsJson(error); - } - - var result = HeadphonesService.SaveSettings(settings); - - Log.Info("Saved headphones settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Headphones!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private Response CreateApiKey() - { - this.RequiresClaims(UserClaims.Admin); - var apiKey = Guid.NewGuid().ToString("N"); - var settings = PrService.GetSettings(); - - settings.ApiKey = apiKey; - PrService.SaveSettings(settings); - - return Response.AsJson(apiKey); - } - - private Response TestSlackNotification() - { - var settings = this.BindAndValidate(); - if (!ModelValidationResult.IsValid) - { - return Response.AsJson(ModelValidationResult.SendJsonError()); - } - var notificationModel = new NotificationModel - { - NotificationType = NotificationType.Test, - DateTime = DateTime.Now - }; - try - { - NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); - settings.Enabled = true; - NotificationService.Publish(notificationModel, settings); - Log.Info("Sent slack notification test"); - } - catch (Exception e) - { - Log.Error(e, "Failed to subscribe and publish test Slack Notification"); - } - finally - { - NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." }); - } - - private Negotiator SlackNotifications() - { - var settings = SlackSettings.GetSettings(); - return View["SlackNotifications", settings]; - } - - private Response SaveSlackNotifications() - { - var settings = this.BindAndValidate(); - if (!ModelValidationResult.IsValid) - { - return Response.AsJson(ModelValidationResult.SendJsonError()); - } - - var result = SlackSettings.SaveSettings(settings); - if (settings.Enabled) - { - NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); - } - else - { - NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); - } - - Log.Info("Saved slack settings, result: {0}", result); - return Response.AsJson(result - ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Slack Notifications!" } - : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); - } - - private async Task LandingPage() - { - var settings = await LandingSettings.GetSettingsAsync(); - - return View["LandingPage", settings]; - } - - private async Task SaveLandingPage() - { - var settings = this.Bind(); - - var plexSettings = await PlexService.GetSettingsAsync(); - if (string.IsNullOrEmpty(plexSettings.Ip)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We cannot enable the landing page if Plex is not setup!" }); - } - - if (settings.Enabled && settings.EnabledNoticeTime && string.IsNullOrEmpty(settings.NoticeMessage)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "If you are going to enabled the notice, then we need a message!" }); - } - - var result = await LandingSettings.SaveSettingsAsync(settings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); - } - - private async Task GetScheduledJobs() - { - var s = await ScheduledJobSettings.GetSettingsAsync(); - var allJobs = await JobRecorder.GetJobsAsync(); - var jobsDict = allJobs.ToDictionary(k => k.Name, v => v.LastRun); - var model = new ScheduledJobsViewModel - { - CouchPotatoCacher = s.CouchPotatoCacher, - PlexAvailabilityChecker = s.PlexAvailabilityChecker, - SickRageCacher = s.SickRageCacher, - SonarrCacher = s.SonarrCacher, - StoreBackup = s.StoreBackup, - StoreCleanup = s.StoreCleanup, - JobRecorder = jobsDict - }; - return View["SchedulerSettings", model]; - } - - private async Task SaveScheduledJobs() - { - var settings = this.Bind(); - - var result = await ScheduledJobSettings.SaveSettingsAsync(settings); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: AdminModule.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.Diagnostics; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +using Nancy; +using Nancy.Extensions; +using Nancy.ModelBinding; +using Nancy.Responses.Negotiation; +using Nancy.Validation; +using Nancy.Json; +using Nancy.Security; +using NLog; + +using MarkdownSharp; + +using Nancy.Responses; + +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.Models; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; +using PlexRequests.Helpers.Exceptions; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; + +using Action = PlexRequests.Helpers.Analytics.Action; + +namespace PlexRequests.UI.Modules +{ + public class AdminModule : BaseModule + { + private ISettingsService PrService { get; } + private ISettingsService CpService { get; } + private ISettingsService AuthService { get; } + private ISettingsService PlexService { get; } + private ISettingsService SonarrService { get; } + private ISettingsService SickRageService { get; } + private ISettingsService EmailService { get; } + private ISettingsService PushbulletService { get; } + private ISettingsService PushoverService { get; } + private ISettingsService HeadphonesService { get; } + private ISettingsService LogService { get; } + private IPlexApi PlexApi { get; } + private ISonarrApi SonarrApi { get; } + private IPushbulletApi PushbulletApi { get; } + private IPushoverApi PushoverApi { get; } + private ICouchPotatoApi CpApi { get; } + private IRepository LogsRepo { get; } + private INotificationService NotificationService { get; } + private ICacheProvider Cache { get; } + private ISettingsService SlackSettings { get; } + private ISettingsService LandingSettings { get; } + private ISettingsService ScheduledJobSettings { get; } + private ISlackApi SlackApi { get; } + private IJobRecord JobRecorder { get; } + private IAnalytics Analytics { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public AdminModule(ISettingsService prService, + ISettingsService cpService, + ISettingsService auth, + ISettingsService plex, + ISettingsService sonarr, + ISettingsService sickrage, + ISonarrApi sonarrApi, + ISettingsService email, + IPlexApi plexApi, + ISettingsService pbSettings, + PushbulletApi pbApi, + ICouchPotatoApi cpApi, + ISettingsService pushoverSettings, + IPushoverApi pushoverApi, + IRepository logsRepo, + INotificationService notify, + ISettingsService headphones, + ISettingsService logs, + ICacheProvider cache, ISettingsService slackSettings, + ISlackApi slackApi, ISettingsService lp, + ISettingsService scheduler, IJobRecord rec, IAnalytics analytics) : base("admin", prService) + { + PrService = prService; + CpService = cpService; + AuthService = auth; + PlexService = plex; + SonarrService = sonarr; + SonarrApi = sonarrApi; + EmailService = email; + PlexApi = plexApi; + PushbulletService = pbSettings; + PushbulletApi = pbApi; + CpApi = cpApi; + SickRageService = sickrage; + LogsRepo = logsRepo; + PushoverService = pushoverSettings; + PushoverApi = pushoverApi; + NotificationService = notify; + HeadphonesService = headphones; + LogService = logs; + Cache = cache; + SlackSettings = slackSettings; + SlackApi = slackApi; + LandingSettings = lp; + ScheduledJobSettings = scheduler; + JobRecorder = rec; + Analytics = analytics; + + this.RequiresClaims(UserClaims.Admin); + + Get["/"] = _ => Admin(); + + Get["/authentication", true] = async (x, ct) => await Authentication(); + Post["/authentication", true] = async (x, ct) => await SaveAuthentication(); + + Post["/", true] = async (x, ct) => await SaveAdmin(); + + Post["/requestauth"] = _ => RequestAuthToken(); + + Get["/getusers"] = _ => GetUsers(); + + Get["/couchpotato"] = _ => CouchPotato(); + Post["/couchpotato"] = _ => SaveCouchPotato(); + + Get["/plex"] = _ => Plex(); + Post["/plex"] = _ => SavePlex(); + + Get["/sonarr"] = _ => Sonarr(); + Post["/sonarr"] = _ => SaveSonarr(); + + Get["/sickrage"] = _ => Sickrage(); + Post["/sickrage"] = _ => SaveSickrage(); + + Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles(); + Post["/cpprofiles"] = _ => GetCpProfiles(); + + Get["/emailnotification"] = _ => EmailNotifications(); + Post["/emailnotification"] = _ => SaveEmailNotifications(); + Post["/testemailnotification"] = _ => TestEmailNotifications(); + Get["/status", true] = async (x,ct) => await Status(); + + Get["/pushbulletnotification"] = _ => PushbulletNotifications(); + Post["/pushbulletnotification"] = _ => SavePushbulletNotifications(); + Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications(); + + Get["/pushovernotification"] = _ => PushoverNotifications(); + Post["/pushovernotification"] = _ => SavePushoverNotifications(); + Post["/testpushovernotification"] = _ => TestPushoverNotifications(); + + Get["/logs"] = _ => Logs(); + Get["/loglevel"] = _ => GetLogLevels(); + Post["/loglevel"] = _ => UpdateLogLevels(Request.Form.level); + Get["/loadlogs"] = _ => LoadLogs(); + + Get["/headphones"] = _ => Headphones(); + Post["/headphones"] = _ => SaveHeadphones(); + + Post["/createapikey"] = x => CreateApiKey(); + + Post["/autoupdate"] = x => AutoUpdate(); + + Post["/testslacknotification"] = _ => TestSlackNotification(); + + Get["/slacknotification"] = _ => SlackNotifications(); + Post["/slacknotification"] = _ => SaveSlackNotifications(); + + Get["/landingpage", true] = async (x, ct) => await LandingPage(); + Post["/landingpage", true] = async (x, ct) => await SaveLandingPage(); + + Get["/scheduledjobs", true] = async (x, ct) => await GetScheduledJobs(); + Post["/scheduledjobs", true] = async (x, ct) => await SaveScheduledJobs(); + + Post["/clearlogs", true] = async (x, ct) => await ClearLogs(); + } + + private async Task Authentication() + { + var settings = await AuthService.GetSettingsAsync(); + + return View["/Authentication", settings]; + } + + private async Task SaveAuthentication() + { + var model = this.Bind(); + + var result = await AuthService.SaveSettingsAsync(model); + if (result) + { + if (!string.IsNullOrEmpty(BaseUrl)) + { + return Context.GetRedirect($"~/{BaseUrl}/admin/authentication"); + } + return Context.GetRedirect("~/admin/authentication"); + } + if (!string.IsNullOrEmpty(BaseUrl)) + { + return Context.GetRedirect($"~/{BaseUrl}/error"); //TODO create error page + } + return Context.GetRedirect("~/error"); //TODO create error page + } + + private Negotiator Admin() + { + var settings = PrService.GetSettings(); + + return View["Settings", settings]; + } + + private async Task SaveAdmin() + { + var model = this.Bind(); + var valid = this.Validate(model); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + if (!string.IsNullOrWhiteSpace(model.BaseUrl)) + { + if (model.BaseUrl.StartsWith("/", StringComparison.CurrentCultureIgnoreCase) || model.BaseUrl.StartsWith("\\", StringComparison.CurrentCultureIgnoreCase)) + { + model.BaseUrl = model.BaseUrl.Remove(0, 1); + } + } + if (!model.CollectAnalyticData) + { + Analytics.TrackEventAsync(Category.Admin, Action.Save, "CollectAnalyticData turned off", Username, CookieHelper.GetAnalyticClientId(Cookies)); + } + var result = PrService.SaveSettings(model); + + Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies)); + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); + } + + private Response RequestAuthToken() + { + var user = this.Bind(); + + if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password)) + { + return Response.AsJson(new { Result = false, Message = "Please provide a valid username and password" }); + } + + var model = PlexApi.SignIn(user.username, user.password); + + if (model?.user == null) + { + return Response.AsJson(new { Result = false, Message = "Incorrect username or password!" }); + } + + var oldSettings = AuthService.GetSettings(); + if (oldSettings != null) + { + oldSettings.PlexAuthToken = model.user.authentication_token; + AuthService.SaveSettings(oldSettings); + } + else + { + var newModel = new AuthenticationSettings + { + PlexAuthToken = model.user.authentication_token + }; + AuthService.SaveSettings(newModel); + } + + return Response.AsJson(new { Result = true, AuthToken = model.user.authentication_token }); + } + + + private Response GetUsers() + { + var settings = AuthService.GetSettings(); + + var token = settings?.PlexAuthToken; + if (token == null) + { + return Response.AsJson(new { Result = true, Users = string.Empty }); + } + + try + { + var users = PlexApi.GetUsers(token); + if (users == null) + { + 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.Title); + return Response.AsJson(new { Result = true, Users = usernames }); + } + catch (Exception ex) + { + Log.Error(ex); + if (ex is WebException || ex is ApiRequestException) + { + return Response.AsJson(new { Result = false, Message = "Could not load the user list! We have connectivity problems connecting to Plex, Please ensure we can access Plex.Tv, The error has been logged." }); + } + + return Response.AsJson(new { Result = false, Message = ex.Message }); + } + } + + private Negotiator CouchPotato() + { + var settings = CpService.GetSettings(); + + return View["CouchPotato", settings]; + } + + private Response SaveCouchPotato() + { + var couchPotatoSettings = this.Bind(); + var valid = this.Validate(couchPotatoSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = CpService.SaveSettings(couchPotatoSettings); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for CouchPotato!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Negotiator Plex() + { + var settings = PlexService.GetSettings(); + + return View["Plex", settings]; + } + + private Response SavePlex() + { + var plexSettings = this.Bind(); + var valid = this.Validate(plexSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + + var result = PlexService.SaveSettings(plexSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Negotiator Sonarr() + { + var settings = SonarrService.GetSettings(); + + return View["Sonarr", settings]; + } + + private Response SaveSonarr() + { + var sonarrSettings = this.Bind(); + + var valid = this.Validate(sonarrSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var sickRageEnabled = SickRageService.GetSettings().Enabled; + if (sickRageEnabled) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "SickRage is enabled, we cannot enable Sonarr and SickRage" }); + } + var result = SonarrService.SaveSettings(sonarrSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Sonarr!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Negotiator Sickrage() + { + var settings = SickRageService.GetSettings(); + + return View["Sickrage", settings]; + } + + private Response SaveSickrage() + { + var sickRageSettings = this.Bind(); + + var valid = this.Validate(sickRageSettings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var sonarrEnabled = SonarrService.GetSettings().Enabled; + if (sonarrEnabled) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sonarr is enabled, we cannot enable Sonarr and SickRage" }); + } + var result = SickRageService.SaveSettings(sickRageSettings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for SickRage!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response GetSonarrQualityProfiles() + { + var settings = this.Bind(); + var profiles = SonarrApi.GetProfiles(settings.ApiKey, settings.FullUri); + + // set the cache + if (profiles != null) + { + Cache.Set(CacheKeys.SonarrQualityProfiles, profiles); + } + + return Response.AsJson(profiles); + } + + + private Negotiator EmailNotifications() + { + var settings = EmailService.GetSettings(); + return View["EmailNotifications", settings]; + } + + private Response TestEmailNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new EmailMessageNotification(EmailService)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent email notification test"); + } + catch (Exception) + { + Log.Error("Failed to subscribe and publish test Email Notification"); + } + finally + { + NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" }); + } + + private Response SaveEmailNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = EmailService.SaveSettings(settings); + + if (settings.Enabled) + { + NotificationService.Subscribe(new EmailMessageNotification(EmailService)); + } + else + { + NotificationService.UnSubscribe(new EmailMessageNotification(EmailService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Email Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private async Task Status() + { + var checker = new StatusChecker(); + var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30); + var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true}); + status.ReleaseNotes = md.Transform(status.ReleaseNotes); + return View["Status", status]; + } + + private Response AutoUpdate() + { + var url = Request.Form["url"]; + + var startInfo = Type.GetType("Mono.Runtime") != null + ? new ProcessStartInfo("mono PlexRequests.Updater.exe") { Arguments = url } + : new ProcessStartInfo("PlexRequests.Updater.exe") { Arguments = url }; + + Process.Start(startInfo); + + Environment.Exit(0); + return Nancy.Response.NoBody; + } + + private Negotiator PushbulletNotifications() + { + var settings = PushbulletService.GetSettings(); + return View["PushbulletNotifications", settings]; + } + + private Response SavePushbulletNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = PushbulletService.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + else + { + NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response TestPushbulletNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent pushbullet notification test"); + } + catch (Exception) + { + Log.Error("Failed to subscribe and publish test Pushbullet Notification"); + } + finally + { + NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" }); + } + + private Negotiator PushoverNotifications() + { + var settings = PushoverService.GetSettings(); + return View["PushoverNotifications", settings]; + } + + private Response SavePushoverNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + + var result = PushoverService.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + else + { + NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + + Log.Info("Saved email settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushover Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response TestPushoverNotifications() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent pushover notification test"); + } + catch (Exception) + { + Log.Error("Failed to subscribe and publish test Pushover Notification"); + } + finally + { + NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" }); + } + + private Response GetCpProfiles() + { + var settings = this.Bind(); + var valid = this.Validate(settings); + if (!valid.IsValid) + { + return Response.AsJson(valid.SendJsonError()); + } + var profiles = CpApi.GetProfiles(settings.FullUri, settings.ApiKey); + + // set the cache + if (profiles != null) + { + Cache.Set(CacheKeys.CouchPotatoQualityProfiles, profiles); + } + + return Response.AsJson(profiles); + } + + private Negotiator Logs() + { + return View["Logs"]; + } + + private Response LoadLogs() + { + JsonSettings.MaxJsonLength = int.MaxValue; + var allLogs = LogsRepo.GetAll().OrderByDescending(x => x.Id).Take(200); + var model = new DatatablesModel { Data = new List() }; + foreach (var l in allLogs) + { + l.DateString = l.Date.ToString("G"); + model.Data.Add(l); + } + return Response.AsJson(model); + } + + private Response GetLogLevels() + { + var levels = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database")); + return Response.AsJson(levels.Levels); + } + + private Response UpdateLogLevels(int level) + { + var settings = LogService.GetSettings(); + + // apply the level + var newLevel = LogLevel.FromOrdinal(level); + LoggingHelper.ReconfigureLogLevel(newLevel); + + //save the log settings + settings.Level = level; + LogService.SaveSettings(settings); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"The new log level is now {newLevel}" }); + } + + private Negotiator Headphones() + { + var settings = HeadphonesService.GetSettings(); + return View["Headphones", settings]; + } + + private Response SaveHeadphones() + { + var settings = this.Bind(); + + var valid = this.Validate(settings); + if (!valid.IsValid) + { + var error = valid.SendJsonError(); + Log.Info("Error validating Headphones settings, message: {0}", error.Message); + return Response.AsJson(error); + } + + var result = HeadphonesService.SaveSettings(settings); + + Log.Info("Saved headphones settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Headphones!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private Response CreateApiKey() + { + this.RequiresClaims(UserClaims.Admin); + var apiKey = Guid.NewGuid().ToString("N"); + var settings = PrService.GetSettings(); + + settings.ApiKey = apiKey; + PrService.SaveSettings(settings); + + return Response.AsJson(apiKey); + } + + private Response TestSlackNotification() + { + var settings = this.BindAndValidate(); + if (!ModelValidationResult.IsValid) + { + return Response.AsJson(ModelValidationResult.SendJsonError()); + } + var notificationModel = new NotificationModel + { + NotificationType = NotificationType.Test, + DateTime = DateTime.Now + }; + try + { + NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); + settings.Enabled = true; + NotificationService.Publish(notificationModel, settings); + Log.Info("Sent slack notification test"); + } + catch (Exception e) + { + Log.Error(e, "Failed to subscribe and publish test Slack Notification"); + } + finally + { + NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Slack Notification! If you do not receive it please check the logs." }); + } + + private Negotiator SlackNotifications() + { + var settings = SlackSettings.GetSettings(); + return View["SlackNotifications", settings]; + } + + private Response SaveSlackNotifications() + { + var settings = this.BindAndValidate(); + if (!ModelValidationResult.IsValid) + { + return Response.AsJson(ModelValidationResult.SendJsonError()); + } + + var result = SlackSettings.SaveSettings(settings); + if (settings.Enabled) + { + NotificationService.Subscribe(new SlackNotification(SlackApi, SlackSettings)); + } + else + { + NotificationService.UnSubscribe(new SlackNotification(SlackApi, SlackSettings)); + } + + Log.Info("Saved slack settings, result: {0}", result); + return Response.AsJson(result + ? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Slack Notifications!" } + : new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." }); + } + + private async Task LandingPage() + { + var settings = await LandingSettings.GetSettingsAsync(); + if (settings.NoticeEnd == DateTime.MinValue) + { + settings.NoticeEnd = DateTime.Now; + } + if (settings.NoticeStart == DateTime.MinValue) + { + settings.NoticeStart = DateTime.Now; + } + return View["LandingPage", settings]; + } + + private async Task SaveLandingPage() + { + var settings = this.Bind(); + + var plexSettings = await PlexService.GetSettingsAsync(); + if (string.IsNullOrEmpty(plexSettings.Ip)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We cannot enable the landing page if Plex is not setup!" }); + } + + if (settings.Enabled && settings.EnabledNoticeTime && string.IsNullOrEmpty(settings.NoticeMessage)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "If you are going to enabled the notice, then we need a message!" }); + } + + var result = await LandingSettings.SaveSettingsAsync(settings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); + } + + private async Task GetScheduledJobs() + { + var s = await ScheduledJobSettings.GetSettingsAsync(); + var allJobs = await JobRecorder.GetJobsAsync(); + var jobsDict = allJobs.ToDictionary(k => k.Name, v => v.LastRun); + var model = new ScheduledJobsViewModel + { + CouchPotatoCacher = s.CouchPotatoCacher, + PlexAvailabilityChecker = s.PlexAvailabilityChecker, + SickRageCacher = s.SickRageCacher, + SonarrCacher = s.SonarrCacher, + StoreBackup = s.StoreBackup, + StoreCleanup = s.StoreCleanup, + JobRecorder = jobsDict + }; + return View["SchedulerSettings", model]; + } + + private async Task SaveScheduledJobs() + { + var settings = this.Bind(); + + var result = await ScheduledJobSettings.SaveSettingsAsync(settings); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not save to Db Please check the logs" }); + } + + private async Task ClearLogs() + { + try + { + var allLogs = await LogsRepo.GetAllAsync(); + foreach (var logEntity in allLogs) + { + await LogsRepo.DeleteAsync(logEntity); + } + return Response.AsJson(new JsonResponseModel { Result = true }); + } + catch (Exception e) + { + Log.Error(e); + return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message }); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs index 2ebce8fa7..7bfa7eeb6 100644 --- a/PlexRequests.UI/Modules/BaseModule.cs +++ b/PlexRequests.UI/Modules/BaseModule.cs @@ -1,68 +1,78 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: BaseModule.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Linq; - -using Nancy; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.UI.Models; - -namespace PlexRequests.UI.Modules -{ - public abstract class BaseModule : NancyModule - { - protected string BaseUrl { get; set; } - - protected BaseModule(ISettingsService settingsService) - { - var settings = settingsService.GetSettings(); - var baseUrl = settings.BaseUrl; - BaseUrl = baseUrl; - - var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl; - - ModulePath = modulePath; - } - - protected BaseModule(string modulePath, ISettingsService settingsService) - { - var settings = settingsService.GetSettings(); - var baseUrl = settings.BaseUrl; - BaseUrl = baseUrl; - - var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}"; - - ModulePath = settingModulePath; - } - - private int _dateTimeOffset = -1; +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: BaseModule.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 System.Linq; +using System.Threading; + +using Nancy; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public abstract class BaseModule : NancyModule + { + protected string BaseUrl { get; set; } + + + protected BaseModule(ISettingsService settingsService) + { + + var settings = settingsService.GetSettings(); + var baseUrl = settings.BaseUrl; + BaseUrl = baseUrl; + + var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl; + + ModulePath = modulePath; + + Before += (ctx) => SetCookie(); + } + + protected BaseModule(string modulePath, ISettingsService settingsService) + { + + var settings = settingsService.GetSettings(); + var baseUrl = settings.BaseUrl; + BaseUrl = baseUrl; + + var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}"; + + ModulePath = settingModulePath; + + Before += (ctx) => SetCookie(); + } + + private int _dateTimeOffset = -1; protected int DateTimeOffset { get @@ -73,7 +83,7 @@ namespace PlexRequests.UI.Modules } return _dateTimeOffset; } - } + } private string _username; protected string Username @@ -82,12 +92,21 @@ namespace PlexRequests.UI.Modules { if (string.IsNullOrEmpty(_username)) { - _username = Session[SessionKeys.UsernameKey].ToString(); + try + { + _username = Session[SessionKeys.UsernameKey].ToString(); + } + catch (Exception) + { + return string.Empty; + } } return _username; } } + protected IDictionary Cookies => Request?.Cookies; + protected bool IsAdmin { get @@ -100,6 +119,41 @@ namespace PlexRequests.UI.Modules return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser); } } - - } + protected string Culture { get; set; } + protected const string CultureCookieName = "_culture"; + protected Response SetCookie() + { + try + { + string cultureName; + + // Attempt to read the culture cookie from Request + var outCookie = string.Empty; + if (Cookies.TryGetValue(CultureCookieName, out outCookie)) + { + cultureName = outCookie; + } + else + { + cultureName = Request.Headers?.AcceptLanguage?.FirstOrDefault()?.Item1; + } + + // Validate culture name + cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe + + + // Modify current thread's cultures + Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName); + Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; + + Culture = Thread.CurrentThread.CurrentCulture.Name; + } + catch (Exception) + { + // Couldn't Set the culture + } + return null; + } + + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/CultureModule.cs b/PlexRequests.UI/Modules/CultureModule.cs new file mode 100644 index 000000000..8d093d32a --- /dev/null +++ b/PlexRequests.UI/Modules/CultureModule.cs @@ -0,0 +1,79 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CultureModule.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.Threading.Tasks; + +using Nancy; +using Nancy.Extensions; +using Nancy.Responses; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; +using PlexRequests.UI.Helpers; + +namespace PlexRequests.UI.Modules +{ + public class CultureModule : BaseModule + { + public CultureModule(ISettingsService pr, IAnalytics a) : base("culture",pr) + { + Analytics = a; + + Get["/", true] = async(x,c) => await SetCulture(); + } + + private IAnalytics Analytics { get; } + + public async Task SetCulture() + { + var culture = (string)Request.Query["l"]; + var returnUrl = (string)Request.Query["u"]; + + // Validate + culture = CultureHelper.GetImplementedCulture(culture); + + var outCookie = string.Empty; + if (Cookies.TryGetValue(CultureCookieName, out outCookie)) + { + Cookies[CultureCookieName] = culture; + } + else + { + Cookies.Add(CultureCookieName, culture); + } + var cookie = Cookies[CultureCookieName]; + var response = Context.GetRedirect(returnUrl); + + response.WithCookie(CultureCookieName, cookie ?? culture, DateTime.Now.AddYears(1)); + Analytics.TrackEventAsync(Category.Navbar, PlexRequests.Helpers.Analytics.Action.Language, culture, Username, CookieHelper.GetAnalyticClientId(Cookies)); + + return response; + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/IssuesModule.cs b/PlexRequests.UI/Modules/IssuesModule.cs index 9e10ac156..e2ed05aae 100644 --- a/PlexRequests.UI/Modules/IssuesModule.cs +++ b/PlexRequests.UI/Modules/IssuesModule.cs @@ -48,7 +48,7 @@ namespace PlexRequests.UI.Modules Get["/issuecount", true] = async (x, ct) => await IssueCount(); Get["/tabCount", true] = async (x, ct) => await TabCount(); - Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.provierId, IssueState.Other, (string)Request.Form.commentArea); + Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea); Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null); @@ -78,7 +78,7 @@ namespace PlexRequests.UI.Modules foreach (var i in issuesModels) { - var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), }; + var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), Admin = IsAdmin }; // Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues var state = i.Issues.Select(x => x.Issue).ToArray(); @@ -317,8 +317,6 @@ namespace PlexRequests.UI.Modules /// Filters the issues. Checks to see if we have set UsersCanViewOnlyOwnIssues in the database and filters upon the user logged in and that setting. ///
/// The issues. - /// if set to true [show resolved]. - /// private async Task> FilterIssuesAsync(IEnumerable issues, bool showResolved = false) { var settings = await PlexRequestSettings.GetSettingsAsync(); @@ -364,29 +362,35 @@ namespace PlexRequests.UI.Modules return myIssues; } - private async Task RemoveIssue(int issueId) + private async Task RemoveIssue(int issueId) { try { this.RequiresClaims(UserClaims.Admin); var issue = await IssuesService.GetAsync(issueId); var request = await RequestService.GetAsync(issue.RequestId); + if (request.Id > 0) + { + request.IssueId = 0; // No issue; - request.IssueId = 0; // No issue; - - var result = await RequestService.UpdateRequestAsync(request); - if (result) + var result = await RequestService.UpdateRequestAsync(request); + if (result) + { + await IssuesService.DeleteIssueAsync(issueId); + } + } + else { await IssuesService.DeleteIssueAsync(issueId); } - return View["Index"]; + return Response.AsJson(new JsonResponseModel { Result = true }); } catch (Exception e) { Log.Error(e); - return View["Index"]; + return Response.AsJson(new JsonResponseModel() { Result = false, Message = "Could not delete issue! Check the logs."}); } } diff --git a/PlexRequests.UI/Modules/LandingPageModule.cs b/PlexRequests.UI/Modules/LandingPageModule.cs index 4564e32ef..9468662e6 100644 --- a/PlexRequests.UI/Modules/LandingPageModule.cs +++ b/PlexRequests.UI/Modules/LandingPageModule.cs @@ -58,6 +58,7 @@ namespace PlexRequests.UI.Modules private async Task Index() { + var s = await LandingSettings.GetSettingsAsync(); var model = new LandingPageViewModel { diff --git a/PlexRequests.UI/Modules/PlexUsersModule.cs b/PlexRequests.UI/Modules/PlexUsersModule.cs new file mode 100644 index 000000000..901199505 --- /dev/null +++ b/PlexRequests.UI/Modules/PlexUsersModule.cs @@ -0,0 +1,98 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexUsersModule.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.Linq; +using System.Threading.Tasks; + +using Nancy; +using Nancy.Responses.Negotiation; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Music; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public class PlexUsersModule : BaseAuthModule + { + public PlexUsersModule(ISettingsService pr, IPlexApi plexApi, ISettingsService auth, + IRepository repo) : base("plexusers", pr) + { + PlexApi = plexApi; + AuthSettings = auth; + Repo = repo; + + Get["/"] = x => Index(); + Get["/users", true] = async (x, ct) => await GetPlexUsers(); + + Post["/alias", true] = async (x, ct) => await Alias(); + } + + private IPlexApi PlexApi { get; } + private ISettingsService AuthSettings { get; } + private IRepository Repo { get; } + + + private Negotiator Index() + { + return View["Index"]; + } + + private async Task GetPlexUsers() + { + var authSettings = await AuthSettings.GetSettingsAsync(); + var users = PlexApi.GetUsers(authSettings.PlexAuthToken); + + return Response.AsJson(users.User); + } + + private async Task Alias() + { + var plexUserId = (int)Request.Form["plexid"]; + var alias = (string)Request.Form["alias"]; + var allUsers = await Repo.GetAllAsync(); + var existingUser = allUsers.FirstOrDefault(x => x.PlexUserId == plexUserId); + if (existingUser == null) + { + // Create a new mapping + existingUser = new PlexUsers { PlexUserId = plexUserId, UserAlias = alias }; + } + else + { + // Modify existing alias + existingUser.UserAlias = alias; + } + + await Repo.InsertAsync(existingUser); + + return Response.AsJson(new JsonResponseModel { Result = true }); + } + } +} \ No newline at end of file diff --git a/PlexRequests.UI/Modules/RequestsModule.cs b/PlexRequests.UI/Modules/RequestsModule.cs index 8d73c0bb0..13f557186 100644 --- a/PlexRequests.UI/Modules/RequestsModule.cs +++ b/PlexRequests.UI/Modules/RequestsModule.cs @@ -1,377 +1,379 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: RequestsModule.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System; -using System.Linq; - -using Nancy; -using Nancy.Responses.Negotiation; -using Nancy.Security; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store; -using PlexRequests.UI.Models; -using PlexRequests.Helpers; -using PlexRequests.UI.Helpers; -using System.Collections.Generic; -using PlexRequests.Api.Interfaces; -using System.Threading.Tasks; - -using NLog; - -namespace PlexRequests.UI.Modules -{ - public class RequestsModule : BaseAuthModule - { - public RequestsModule( - IRequestService service, - ISettingsService prSettings, - ISettingsService plex, - INotificationService notify, - ISettingsService sonarrSettings, - ISettingsService sickRageSettings, - ISettingsService cpSettings, - ICouchPotatoApi cpApi, - ISonarrApi sonarrApi, - ISickRageApi sickRageApi, - ICacheProvider cache) : base("requests", prSettings) - { - Service = service; - PrSettings = prSettings; - PlexSettings = plex; - NotificationService = notify; - SonarrSettings = sonarrSettings; - SickRageSettings = sickRageSettings; - CpSettings = cpSettings; - SonarrApi = sonarrApi; - SickRageApi = sickRageApi; - CpApi = cpApi; - Cache = cache; - - Get["/", true] = async (x, ct) => await LoadRequests(); - Get["/movies", true] = async (x, ct) => await GetMovies(); - Get["/tvshows", true] = async (c, ct) => await GetTvShows(); - Get["/albums", true] = async (x, ct) => await GetAlbumRequests(); - Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id); - Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null); - Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea); - - Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id); - - Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available); - } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - private IRequestService Service { get; } - private INotificationService NotificationService { get; } - private ISettingsService PrSettings { get; } - private ISettingsService PlexSettings { get; } - private ISettingsService SonarrSettings { get; } - private ISettingsService SickRageSettings { get; } - private ISettingsService CpSettings { get; } - private ISonarrApi SonarrApi { get; } - private ISickRageApi SickRageApi { get; } - private ICouchPotatoApi CpApi { get; } - private ICacheProvider Cache { get; } - - private async Task LoadRequests() - { - var settings = await PrSettings.GetSettingsAsync(); - return View["Index", settings]; - } - - private async Task GetMovies() - { - var settings = PrSettings.GetSettings(); - - var allRequests = await Service.GetAllAsync(); - allRequests = allRequests.Where(x => x.Type == RequestType.Movie); - - var dbMovies = allRequests.ToList(); - - if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) - { - dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList(); - } - - List qualities = new List(); - - if (IsAdmin) - { - var cpSettings = CpSettings.GetSettings(); - if (cpSettings.Enabled) - { - try - { - var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () => - { - return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false); - }); - - qualities = result.list.Select(x => new QualityModel() { Id = x._id, Name = x.label }).ToList(); - - } - catch (Exception e) - { - Log.Info(e); - } - } - } - - var viewModel = dbMovies.Select(movie => new RequestViewModel - { - ProviderId = movie.ProviderId, - Type = movie.Type, - Status = movie.Status, - ImdbId = movie.ImdbId, - Id = movie.Id, - PosterPath = movie.PosterPath, - ReleaseDate = movie.ReleaseDate, - ReleaseDateTicks = movie.ReleaseDate.Ticks, - RequestedDate = movie.RequestedDate, - Released = DateTime.Now > movie.ReleaseDate, - RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks, - Approved = movie.Available || movie.Approved, - Title = movie.Title, - Overview = movie.Overview, - RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { }, - ReleaseYear = movie.ReleaseDate.Year.ToString(), - Available = movie.Available, - Admin = IsAdmin, - IssueId = movie.IssueId, - Qualities = qualities.ToArray() - }).ToList(); - - return Response.AsJson(viewModel); - } - - private async Task GetTvShows() - { - var settings = PrSettings.GetSettings(); - - var requests = await Service.GetAllAsync(); - requests = requests.Where(x => x.Type == RequestType.TvShow); - - var dbTv = requests; - - if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) - { - dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList(); - } - - IEnumerable qualities = new List(); - if (IsAdmin) - { - try - { - var sonarrSettings = SonarrSettings.GetSettings(); - if (sonarrSettings.Enabled) - { - var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () => - { - return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri)); - }); - qualities = result.Result.Select(x => new QualityModel() { Id = x.id.ToString(), Name = x.name }).ToList(); - } - else - { - var sickRageSettings = SickRageSettings.GetSettings(); - if (sickRageSettings.Enabled) - { - qualities = sickRageSettings.Qualities.Select(x => new QualityModel() { Id = x.Key, Name = x.Value }).ToList(); - } - } - } - catch (Exception e) - { - Log.Info(e); - } - - } - - var viewModel = dbTv.Select(tv => - { - return new RequestViewModel - { - ProviderId = tv.ProviderId, - Type = tv.Type, - Status = tv.Status, - ImdbId = tv.ImdbId, - Id = tv.Id, - PosterPath = tv.PosterPath, - ReleaseDate = tv.ReleaseDate, - ReleaseDateTicks = tv.ReleaseDate.Ticks, - RequestedDate = tv.RequestedDate, - RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks, - Released = DateTime.Now > tv.ReleaseDate, - Approved = tv.Available || tv.Approved, - Title = tv.Title, - Overview = tv.Overview, - RequestedUsers = IsAdmin ? tv.AllUsers.ToArray() : new string[] { }, - ReleaseYear = tv.ReleaseDate.Year.ToString(), - Available = tv.Available, - Admin = IsAdmin, - IssueId = tv.IssueId, - TvSeriesRequestType = tv.SeasonsRequested, - Qualities = qualities.ToArray() - }; - }).ToList(); - - return Response.AsJson(viewModel); - } - - private async Task GetAlbumRequests() - { - var settings = PrSettings.GetSettings(); - var dbAlbum = await Service.GetAllAsync(); - dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album); - if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) - { - dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username)); - } - - var viewModel = dbAlbum.Select(album => - { - return new RequestViewModel - { - ProviderId = album.ProviderId, - Type = album.Type, - Status = album.Status, - ImdbId = album.ImdbId, - Id = album.Id, - PosterPath = album.PosterPath, - ReleaseDate = album.ReleaseDate, - ReleaseDateTicks = album.ReleaseDate.Ticks, - RequestedDate = album.RequestedDate, - RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks, - Released = DateTime.Now > album.ReleaseDate, - Approved = album.Available || album.Approved, - Title = album.Title, - Overview = album.Overview, - RequestedUsers = IsAdmin ? album.AllUsers.ToArray() : new string[] { }, - ReleaseYear = album.ReleaseDate.Year.ToString(), - Available = album.Available, - Admin = IsAdmin, - IssueId = album.IssueId, - TvSeriesRequestType = album.SeasonsRequested, - MusicBrainzId = album.MusicBrainzId, - ArtistName = album.ArtistName - - }; - }).ToList(); - - return Response.AsJson(viewModel); - } - - private async Task DeleteRequest(int requestid) - { - this.RequiresClaims(UserClaims.Admin); - - var currentEntity = await Service.GetAsync(requestid); - await Service.DeleteRequestAsync(currentEntity); - return Response.AsJson(new JsonResponseModel { Result = true }); - } - - /// - /// Reports the issue. - /// Comment can be null if the IssueState != Other - /// - /// The request identifier. - /// The issue. - /// The comment. - /// - private async Task ReportIssue(int requestId, IssueState issue, string comment) - { - var originalRequest = await Service.GetAsync(requestId); - if (originalRequest == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); - } - originalRequest.Issues = issue; - originalRequest.OtherMessage = !string.IsNullOrEmpty(comment) - ? $"{Username} - {comment}" - : string.Empty; - - - var result = await Service.UpdateRequestAsync(originalRequest); - - var model = new NotificationModel - { - User = Username, - NotificationType = NotificationType.Issue, - Title = originalRequest.Title, - DateTime = DateTime.Now, - Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords() - }; - await NotificationService.Publish(model); - - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); - } - - private async Task ClearIssue(int requestId) - { - this.RequiresClaims(UserClaims.Admin); - - var originalRequest = await Service.GetAsync(requestId); - if (originalRequest == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" }); - } - originalRequest.Issues = IssueState.None; - originalRequest.OtherMessage = string.Empty; - - var result = await Service.UpdateRequestAsync(originalRequest); - return Response.AsJson(result - ? new JsonResponseModel { Result = true } - : new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" }); - } - - private async Task ChangeRequestAvailability(int requestId, bool available) - { - this.RequiresClaims(UserClaims.Admin); - var originalRequest = await Service.GetAsync(requestId); - if (originalRequest == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" }); - } - - originalRequest.Available = available; - - var result = await Service.UpdateRequestAsync(originalRequest); - return Response.AsJson(result - ? new { Result = true, Available = available, Message = string.Empty } - : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); - } - - - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: RequestsModule.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Linq; + +using Nancy; +using Nancy.Responses.Negotiation; +using Nancy.Security; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store; +using PlexRequests.UI.Models; +using PlexRequests.Helpers; +using PlexRequests.UI.Helpers; +using System.Collections.Generic; +using PlexRequests.Api.Interfaces; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Core.Models; + +namespace PlexRequests.UI.Modules +{ + public class RequestsModule : BaseAuthModule + { + public RequestsModule( + IRequestService service, + ISettingsService prSettings, + ISettingsService plex, + INotificationService notify, + ISettingsService sonarrSettings, + ISettingsService sickRageSettings, + ISettingsService cpSettings, + ICouchPotatoApi cpApi, + ISonarrApi sonarrApi, + ISickRageApi sickRageApi, + ICacheProvider cache) : base("requests", prSettings) + { + Service = service; + PrSettings = prSettings; + PlexSettings = plex; + NotificationService = notify; + SonarrSettings = sonarrSettings; + SickRageSettings = sickRageSettings; + CpSettings = cpSettings; + SonarrApi = sonarrApi; + SickRageApi = sickRageApi; + CpApi = cpApi; + Cache = cache; + + Get["/", true] = async (x, ct) => await LoadRequests(); + Get["/movies", true] = async (x, ct) => await GetMovies(); + Get["/tvshows", true] = async (c, ct) => await GetTvShows(); + Get["/albums", true] = async (x, ct) => await GetAlbumRequests(); + Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id); + Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null); + Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea); + + Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id); + + Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available); + } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + private IRequestService Service { get; } + private INotificationService NotificationService { get; } + private ISettingsService PrSettings { get; } + private ISettingsService PlexSettings { get; } + private ISettingsService SonarrSettings { get; } + private ISettingsService SickRageSettings { get; } + private ISettingsService CpSettings { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickRageApi { get; } + private ICouchPotatoApi CpApi { get; } + private ICacheProvider Cache { get; } + + private async Task LoadRequests() + { + var settings = await PrSettings.GetSettingsAsync(); + return View["Index", settings]; + } + + private async Task GetMovies() + { + var settings = PrSettings.GetSettings(); + + var allRequests = await Service.GetAllAsync(); + allRequests = allRequests.Where(x => x.Type == RequestType.Movie); + + var dbMovies = allRequests.ToList(); + + if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) + { + dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList(); + } + + List qualities = new List(); + + if (IsAdmin) + { + var cpSettings = CpSettings.GetSettings(); + if (cpSettings.Enabled) + { + try + { + var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () => + { + return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false); + }); + + qualities = result.list.Select(x => new QualityModel() { Id = x._id, Name = x.label }).ToList(); + + } + catch (Exception e) + { + Log.Info(e); + } + } + } + + var viewModel = dbMovies.Select(movie => new RequestViewModel + { + ProviderId = movie.ProviderId, + Type = movie.Type, + Status = movie.Status, + ImdbId = movie.ImdbId, + Id = movie.Id, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + ReleaseDateTicks = movie.ReleaseDate.Ticks, + RequestedDate = movie.RequestedDate, + Released = DateTime.Now > movie.ReleaseDate, + RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks, + Approved = movie.Available || movie.Approved, + Title = movie.Title, + Overview = movie.Overview, + RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { }, + ReleaseYear = movie.ReleaseDate.Year.ToString(), + Available = movie.Available, + Admin = IsAdmin, + IssueId = movie.IssueId, + Qualities = qualities.ToArray() + }).ToList(); + + return Response.AsJson(viewModel); + } + + private async Task GetTvShows() + { + var settings = PrSettings.GetSettings(); + + var requests = await Service.GetAllAsync(); + requests = requests.Where(x => x.Type == RequestType.TvShow); + + var dbTv = requests; + + if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) + { + dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList(); + } + + IEnumerable qualities = new List(); + if (IsAdmin) + { + try + { + var sonarrSettings = SonarrSettings.GetSettings(); + if (sonarrSettings.Enabled) + { + var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () => + { + return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri)); + }); + qualities = result.Result.Select(x => new QualityModel() { Id = x.id.ToString(), Name = x.name }).ToList(); + } + else + { + var sickRageSettings = SickRageSettings.GetSettings(); + if (sickRageSettings.Enabled) + { + qualities = sickRageSettings.Qualities.Select(x => new QualityModel() { Id = x.Key, Name = x.Value }).ToList(); + } + } + } + catch (Exception e) + { + Log.Info(e); + } + + } + + var viewModel = dbTv.Select(tv => + { + return new RequestViewModel + { + ProviderId = tv.ProviderId, + Type = tv.Type, + Status = tv.Status, + ImdbId = tv.ImdbId, + Id = tv.Id, + PosterPath = tv.PosterPath, + ReleaseDate = tv.ReleaseDate, + ReleaseDateTicks = tv.ReleaseDate.Ticks, + RequestedDate = tv.RequestedDate, + RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks, + Released = DateTime.Now > tv.ReleaseDate, + Approved = tv.Available || tv.Approved, + Title = tv.Title, + Overview = tv.Overview, + RequestedUsers = IsAdmin ? tv.AllUsers.ToArray() : new string[] { }, + ReleaseYear = tv.ReleaseDate.Year.ToString(), + Available = tv.Available, + Admin = IsAdmin, + IssueId = tv.IssueId, + TvSeriesRequestType = tv.SeasonsRequested, + Qualities = qualities.ToArray() + }; + }).ToList(); + + return Response.AsJson(viewModel); + } + + private async Task GetAlbumRequests() + { + var settings = PrSettings.GetSettings(); + var dbAlbum = await Service.GetAllAsync(); + dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album); + if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin) + { + dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username)); + } + + var viewModel = dbAlbum.Select(album => + { + return new RequestViewModel + { + ProviderId = album.ProviderId, + Type = album.Type, + Status = album.Status, + ImdbId = album.ImdbId, + Id = album.Id, + PosterPath = album.PosterPath, + ReleaseDate = album.ReleaseDate, + ReleaseDateTicks = album.ReleaseDate.Ticks, + RequestedDate = album.RequestedDate, + RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks, + Released = DateTime.Now > album.ReleaseDate, + Approved = album.Available || album.Approved, + Title = album.Title, + Overview = album.Overview, + RequestedUsers = IsAdmin ? album.AllUsers.ToArray() : new string[] { }, + ReleaseYear = album.ReleaseDate.Year.ToString(), + Available = album.Available, + Admin = IsAdmin, + IssueId = album.IssueId, + TvSeriesRequestType = album.SeasonsRequested, + MusicBrainzId = album.MusicBrainzId, + ArtistName = album.ArtistName + + }; + }).ToList(); + + return Response.AsJson(viewModel); + } + + private async Task DeleteRequest(int requestid) + { + this.RequiresClaims(UserClaims.Admin); + + var currentEntity = await Service.GetAsync(requestid); + await Service.DeleteRequestAsync(currentEntity); + return Response.AsJson(new JsonResponseModel { Result = true }); + } + + /// + /// Reports the issue. + /// Comment can be null if the IssueState != Other + /// + /// The request identifier. + /// The issue. + /// The comment. + /// + private async Task ReportIssue(int requestId, IssueState issue, string comment) + { + var originalRequest = await Service.GetAsync(requestId); + if (originalRequest == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); + } + originalRequest.Issues = issue; + originalRequest.OtherMessage = !string.IsNullOrEmpty(comment) + ? $"{Username} - {comment}" + : string.Empty; + + + var result = await Service.UpdateRequestAsync(originalRequest); + + var model = new NotificationModel + { + User = Username, + NotificationType = NotificationType.Issue, + Title = originalRequest.Title, + DateTime = DateTime.Now, + Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords() + }; + await NotificationService.Publish(model); + + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" }); + } + + private async Task ClearIssue(int requestId) + { + this.RequiresClaims(UserClaims.Admin); + + var originalRequest = await Service.GetAsync(requestId); + if (originalRequest == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" }); + } + originalRequest.Issues = IssueState.None; + originalRequest.OtherMessage = string.Empty; + + var result = await Service.UpdateRequestAsync(originalRequest); + return Response.AsJson(result + ? new JsonResponseModel { Result = true } + : new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" }); + } + + private async Task ChangeRequestAvailability(int requestId, bool available) + { + this.RequiresClaims(UserClaims.Admin); + var originalRequest = await Service.GetAsync(requestId); + if (originalRequest == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" }); + } + + originalRequest.Available = available; + + var result = await Service.UpdateRequestAsync(originalRequest); + return Response.AsJson(result + ? new { Result = true, Available = available, Message = string.Empty } + : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); + } + + + } +} diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 380df096f..fb15377d4 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -1,983 +1,963 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SearchModule.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 System.Globalization; -using System.Linq; - -using Nancy; -using Nancy.Responses.Negotiation; - -using NLog; - -using PlexRequests.Api; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Music; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store; -using PlexRequests.UI.Helpers; -using PlexRequests.UI.Models; -using System.Threading.Tasks; - -using Nancy.Extensions; -using Nancy.Responses; - -using PlexRequests.Api.Models.Tv; -using PlexRequests.Core.Models; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; - -using TMDbLib.Objects.General; - -namespace PlexRequests.UI.Modules -{ - public class SearchModule : BaseAuthModule - { - public SearchModule(ICacheProvider cache, ISettingsService cpSettings, - ISettingsService prSettings, IAvailabilityChecker checker, - IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, - ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, - INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, - ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, - ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, - IIssueService issue) : base("search", prSettings) - { - Auth = auth; - PlexService = plexService; - PlexApi = plexApi; - CpService = cpSettings; - PrService = prSettings; - MovieApi = new TheMovieDbApi(); - Cache = cache; - Checker = checker; - CpCacher = cpCacher; - SonarrCacher = sonarrCacher; - SickRageCacher = sickRageCacher; - RequestService = request; - SonarrApi = sonarrApi; - SonarrService = sonarrSettings; - CouchPotatoApi = cpApi; - SickRageService = sickRageService; - SickrageApi = srApi; - NotificationService = notify; - MusicBrainzApi = mbApi; - HeadphonesApi = hpApi; - HeadphonesService = hpService; - UsersToNotifyRepo = u; - EmailNotificationSettings = email; - IssueService = issue; - - - Get["/", true] = async (x, ct) => await RequestLoad(); - - Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); - Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); - Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); - Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); - - Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); - Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); - - Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); - Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); - Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); - - Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); - Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings(); - - Get["/seasons"] = x => GetSeasons(); - } - private IPlexApi PlexApi { get; } - private TheMovieDbApi MovieApi { get; } - private INotificationService NotificationService { get; } - private ICouchPotatoApi CouchPotatoApi { get; } - private ISonarrApi SonarrApi { get; } - private ISickRageApi SickrageApi { get; } - private IRequestService RequestService { get; } - private ICacheProvider Cache { get; } - private ISettingsService Auth { get; } - private ISettingsService PlexService { get; } - private ISettingsService CpService { get; } - private ISettingsService PrService { get; } - private ISettingsService SonarrService { get; } - private ISettingsService SickRageService { get; } - private ISettingsService HeadphonesService { get; } - private ISettingsService EmailNotificationSettings { get; } - private IAvailabilityChecker Checker { get; } - private ICouchPotatoCacher CpCacher { get; } - private ISonarrCacher SonarrCacher { get; } - private ISickRageCacher SickRageCacher { get; } - private IMusicBrainzApi MusicBrainzApi { get; } - private IHeadphonesApi HeadphonesApi { get; } - private IRepository UsersToNotifyRepo { get; } - private IIssueService IssueService { get; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - private async Task RequestLoad() - { - var settings = await PrService.GetSettingsAsync(); - - Log.Trace("Loading Index"); - return View["Search/Index", settings]; - } - - private async Task UpcomingMovies() - { - Log.Trace("Loading upcoming movies"); - return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); - } - - private async Task CurrentlyPlayingMovies() - { - Log.Trace("Loading currently playing movies"); - return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); - } - - private async Task SearchMovie(string searchTerm) - { - Log.Trace("Searching for Movie {0}", searchTerm); - return await ProcessMovies(MovieSearchType.Search, searchTerm); - } - - private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) - { - var apiMovies = new List(); - await Task.Factory.StartNew( - () => - { - switch (searchType) - { - case MovieSearchType.Search: - return - MovieApi.SearchMovie(searchTerm) - .Result.Select( - x => - new MovieResult() - { - Adult = x.Adult, - BackdropPath = x.BackdropPath, - GenreIds = x.GenreIds, - Id = x.Id, - OriginalLanguage = x.OriginalLanguage, - OriginalTitle = x.OriginalTitle, - Overview = x.Overview, - Popularity = x.Popularity, - PosterPath = x.PosterPath, - ReleaseDate = x.ReleaseDate, - Title = x.Title, - Video = x.Video, - VoteAverage = x.VoteAverage, - VoteCount = x.VoteCount - }) - .ToList(); - case MovieSearchType.CurrentlyPlaying: - return MovieApi.GetCurrentPlayingMovies().Result.ToList(); - case MovieSearchType.Upcoming: - return MovieApi.GetUpcomingMovies().Result.ToList(); - default: - return new List(); - } - }).ContinueWith( - (t) => - { - apiMovies = t.Result; - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Movie); - - var distinctResults = allResults.DistinctBy(x => x.ProviderId); - var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); - - - var cpCached = CpCacher.QueuedIds(); - var plexMovies = Checker.GetPlexMovies(); - var settings = await PrService.GetSettingsAsync(); - var viewMovies = new List(); - foreach (MovieResult movie in apiMovies) - { - var viewMovie = new SearchMovieViewModel - { - Adult = movie.Adult, - BackdropPath = movie.BackdropPath, - GenreIds = movie.GenreIds, - Id = movie.Id, - OriginalLanguage = movie.OriginalLanguage, - OriginalTitle = movie.OriginalTitle, - Overview = movie.Overview, - Popularity = movie.Popularity, - PosterPath = movie.PosterPath, - ReleaseDate = movie.ReleaseDate, - Title = movie.Title, - Video = movie.Video, - VoteAverage = movie.VoteAverage, - VoteCount = movie.VoteCount - }; - var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) - { - viewMovie.Available = true; - } - else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db - { - var dbm = dbMovies[movie.Id]; - - viewMovie.Requested = true; - viewMovie.Approved = dbm.Approved; - viewMovie.Available = dbm.Available; - } - else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db - { - viewMovie.Requested = true; - } - - viewMovies.Add(viewMovie); - } - - return Response.AsJson(viewMovies); - } - - private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) - { - if (usersCanViewOnlyOwnRequests) - { - var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); - return result.Value == null || result.Value.UserHasRequested(Username); - } - - return true; - } - - private async Task SearchTvShow(string searchTerm) - { - var plexSettings = await PlexService.GetSettingsAsync(); - Log.Trace("Searching for TV Show {0}", searchTerm); - - var apiTv = new List(); - await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => - { - apiTv = t.Result; - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.TvShow); - - var dbTv = allResults.ToDictionary(x => x.ProviderId); - - if (!apiTv.Any()) - { - Log.Trace("TV Show data is null"); - return Response.AsJson(""); - } - - var sonarrCached = SonarrCacher.QueuedIds(); - var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays - var plexTvShows = Checker.GetPlexTvShows(); - - var viewTv = new List(); - foreach (var t in apiTv) - { - var banner = t.show.image?.medium; - if (!string.IsNullOrEmpty(banner)) - { - banner = banner.Replace("http", "https"); - } - - var viewT = new SearchTvShowViewModel - { - Banner = banner, - FirstAired = t.show.premiered, - Id = t.show.externals?.thetvdb ?? 0, - ImdbId = t.show.externals?.imdb, - Network = t.show.network?.name, - NetworkId = t.show.network?.id.ToString(), - Overview = t.show.summary.RemoveHtml(), - Rating = t.score.ToString(CultureInfo.CurrentUICulture), - Runtime = t.show.runtime.ToString(), - SeriesId = t.show.id, - SeriesName = t.show.name, - Status = t.show.status - }; - - - var providerId = string.Empty; - - if (plexSettings.AdvancedSearch) - { - providerId = viewT.Id.ToString(); - } - - if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) - { - viewT.Available = true; - } - else if (t.show?.externals?.thetvdb != null) - { - int tvdbid = (int)t.show.externals.thetvdb; - - if (dbTv.ContainsKey(tvdbid)) - { - var dbt = dbTv[tvdbid]; - - viewT.Requested = true; - viewT.Approved = dbt.Approved; - viewT.Available = dbt.Available; - } - else if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db - { - viewT.Requested = true; - } - } - - viewTv.Add(viewT); - } - - return Response.AsJson(viewTv); - } - - private async Task SearchMusic(string searchTerm) - { - var apiAlbums = new List(); - await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => - { - apiAlbums = t.Result.releases ?? new List(); - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Album); - - var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); - - var plexAlbums = Checker.GetPlexAlbums(); - - var viewAlbum = new List(); - foreach (var a in apiAlbums) - { - var viewA = new SearchMusicViewModel - { - Title = a.title, - Id = a.id, - Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), - Overview = a.disambiguation, - ReleaseDate = a.date, - TrackCount = a.TrackCount, - ReleaseType = a.status, - Country = a.country - }; - - DateTime release; - DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); - var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) - { - viewA.Available = true; - } - if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) - { - var dba = dbAlbum[a.id]; - - viewA.Requested = true; - viewA.Approved = dba.Approved; - viewA.Available = dba.Available; - } - - viewAlbum.Add(viewA); - } - return Response.AsJson(viewAlbum); - } - - private async Task RequestMovie(int movieId) - { - var movieInfo = MovieApi.GetMovieInformation(movieId).Result; - var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; - Log.Trace("Getting movie info from TheMovieDb"); - - var settings = await PrService.GetSettingsAsync(); - - // check if the movie has already been requested - Log.Info("Requesting movie with id {0}", movieId); - var existingRequest = await RequestService.CheckRequestAsync(movieId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this movie, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" }); - } - - Log.Debug("movie with id {0} doesnt exists", movieId); - - try - { - var movies = Checker.GetPlexMovies(); - if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); - } - } - catch (Exception e) - { - Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" }); - } - //#endif - - var model = new RequestedModel - { - ProviderId = movieInfo.Id, - Type = RequestType.Movie, - Overview = movieInfo.Overview, - ImdbId = movieInfo.ImdbId, - PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath, - Title = movieInfo.Title, - ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, - Status = movieInfo.Status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - - }; - - if (ShouldAutoApprove(RequestType.Movie, settings)) - { - var cpSettings = await CpService.GetSettingsAsync(); - - if (cpSettings.Enabled) - { - Log.Info("Adding movie to CP (No approval required)"); - var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, - cpSettings.FullUri, cpSettings.ProfileId); - Log.Debug("Adding movie to CP result {0}", result); - if (result) - { - model.Approved = true; - Log.Info("Adding movie to database (No approval required)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notificationModel); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = - "Something went wrong adding the movie to CouchPotato! Please check your settings." - }); - } - else - { - model.Approved = true; - Log.Info("Adding movie to database (No approval required)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notificationModel); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - } - - try - { - Log.Info("Adding movie to database"); - var id = await RequestService.AddRequestAsync(model); - - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; - await NotificationService.Publish(notificationModel); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - catch (Exception e) - { - Log.Fatal(e); - - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); - } - } - - /// - /// Requests the tv show. - /// - /// The show identifier. - /// The seasons. - /// - private async Task RequestTvShow(int showId, string seasons) - { - var tvApi = new TvMazeApi(); - - var showInfo = tvApi.ShowLookupByTheTvDbId(showId); - DateTime firstAir; - DateTime.TryParse(showInfo.premiered, out firstAir); - string fullShowName = $"{showInfo.name} ({firstAir.Year})"; - //#if !DEBUG - - var settings = await PrService.GetSettingsAsync(); - - // check if the show has already been requested - Log.Info("Requesting tv show with id {0}", showId); - var existingRequest = await RequestService.CheckRequestAsync(showId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this show, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" }); - } - - try - { - var shows = Checker.GetPlexTvShows(); - var providerId = string.Empty; - var plexSettings = await PlexService.GetSettingsAsync(); - if (plexSettings.AdvancedSearch) - { - providerId = showId.ToString(); - } - if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); - } - } - catch (Exception) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" }); - } - //#endif - - - var model = new RequestedModel - { - ProviderId = showInfo.externals?.thetvdb ?? 0, - Type = RequestType.TvShow, - Overview = showInfo.summary.RemoveHtml(), - PosterPath = showInfo.image?.medium, - Title = showInfo.name, - ReleaseDate = firstAir, - Status = showInfo.status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - ImdbId = showInfo.externals?.imdb ?? string.Empty, - SeasonCount = showInfo.seasonCount, - TvDbId = showId.ToString() - }; - - var seasonsList = new List(); - switch (seasons) - { - case "first": - seasonsList.Add(1); - model.SeasonsRequested = "First"; - break; - case "latest": - seasonsList.Add(model.SeasonCount); - model.SeasonsRequested = "Latest"; - break; - case "all": - model.SeasonsRequested = "All"; - break; - default: - model.SeasonsRequested = seasons; - var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var seasonsCount = new int[split.Length]; - for (var i = 0; i < split.Length; i++) - { - int tryInt; - int.TryParse(split[i], out tryInt); - seasonsCount[i] = tryInt; - } - seasonsList.AddRange(seasonsCount); - break; - } - - model.SeasonList = seasonsList.ToArray(); - - if (ShouldAutoApprove(RequestType.TvShow, settings)) - { - var sonarrSettings = await SonarrService.GetSettingsAsync(); - var sender = new TvSender(SonarrApi, SickrageApi); - if (sonarrSettings.Enabled) - { - var result = sender.SendToSonarr(sonarrSettings, model); - if (!string.IsNullOrEmpty(result?.title)) - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notify1 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify1); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - - return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); - - } - - var srSettings = SickRageService.GetSettings(); - if (srSettings.Enabled) - { - var result = sender.SendToSickRage(srSettings, model); - if (result?.result == "success") - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required & SickRage)"); - await RequestService.AddRequestAsync(model); - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "Message From SickRage: " + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." }); - } - - if (!srSettings.Enabled && !sonarrSettings.Enabled) - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup"); - await RequestService.AddRequestAsync(model); - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); - - } - - await RequestService.AddRequestAsync(model); - - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; - await NotificationService.Publish(notificationModel); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - private bool ShouldSendNotification() - { - var sendNotification = true; - var claims = Context.CurrentUser?.Claims; - if (claims != null) - { - var enumerable = claims as string[] ?? claims.ToArray(); - if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser)) - { - sendNotification = false; // Don't bother sending a notification if the user is an admin - } - } - return sendNotification; - } - - - private async Task RequestAlbum(string releaseId) - { - var settings = await PrService.GetSettingsAsync(); - var existingRequest = await RequestService.CheckRequestAsync(releaseId); - Log.Debug("Checking for an existing request"); - - if (existingRequest != null) - { - Log.Debug("We do have an existing album request"); - if (!existingRequest.UserHasRequested(Username)) - { - - Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username); - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" }); - } - - Log.Debug("This is a new request"); - - var albumInfo = MusicBrainzApi.GetAlbum(releaseId); - DateTime release; - DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); - - var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; - if (artist == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" }); - } - - var albums = Checker.GetPlexAlbums(); - var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); - - if (alreadyInPlex) - { - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{albumInfo.title} is already in Plex!" - }); - } - - var img = GetMusicBrainzCoverArt(albumInfo.id); - - var model = new RequestedModel - { - Title = albumInfo.title, - MusicBrainzId = albumInfo.id, - Overview = albumInfo.disambiguation, - PosterPath = img, - Type = RequestType.Album, - ProviderId = 0, - RequestedUsers = new List { Username }, - Status = albumInfo.status, - Issues = IssueState.None, - RequestedDate = DateTime.UtcNow, - ReleaseDate = release, - ArtistName = artist.name, - ArtistId = artist.id - }; - - if (ShouldAutoApprove(RequestType.Album, settings)) - { - Log.Debug("We don't require approval OR the user is in the whitelist"); - var hpSettings = HeadphonesService.GetSettings(); - - if (!hpSettings.Enabled) - { - await RequestService.AddRequestAsync(model); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - await sender.AddAlbum(model); - model.Approved = true; - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - await RequestService.AddRequestAsync(model); - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - private string GetMusicBrainzCoverArt(string id) - { - var coverArt = MusicBrainzApi.GetCoverArt(id); - var firstImage = coverArt?.images?.FirstOrDefault(); - var img = string.Empty; - - if (firstImage != null) - { - img = firstImage.thumbnails?.small ?? firstImage.image; - } - - return img; - } - - private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) - { - // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval - if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; - - // check by request type if the category requires approval or not - switch (requestType) - { - case RequestType.Movie: - return !prSettings.RequireMovieApproval; - case RequestType.TvShow: - return !prSettings.RequireTvShowApproval; - case RequestType.Album: - return !prSettings.RequireMusicApproval; - default: - return false; - } - } - - private async Task NotifyUser(bool notify) - { - var authSettings = await Auth.GetSettingsAsync(); - var auth = authSettings.UserAuthentication; - var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); - var email = emailSettings.EnableUserEmailNotifications; - if (!auth) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" }); - } - if (!email) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." }); - } - var username = Username; - var originalList = await UsersToNotifyRepo.GetAllAsync(); - if (!notify) - { - if (originalList == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" }); - } - var userToRemove = originalList.FirstOrDefault(x => x.Username == username); - if (userToRemove != null) - { - await UsersToNotifyRepo.DeleteAsync(userToRemove); - } - return Response.AsJson(new JsonResponseModel { Result = true }); - } - - - if (originalList == null) - { - var userModel = new UsersToNotify { Username = username }; - var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); - return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); - } - - var existingUser = originalList.FirstOrDefault(x => x.Username == username); - if (existingUser != null) - { - return Response.AsJson(new JsonResponseModel { Result = true }); // It's already enabled - } - else - { - var userModel = new UsersToNotify { Username = username }; - var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); - return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); - } - - } - private async Task GetUserNotificationSettings() - { - var all = await UsersToNotifyRepo.GetAllAsync(); - var retVal = all.FirstOrDefault(x => x.Username == Username); - return Response.AsJson(retVal != null); - } - - private Response GetSeasons() - { - var tv = new TvMazeApi(); - var seriesId = (int)Request.Query.tvId; - var show = tv.ShowLookupByTheTvDbId(seriesId); - var seasons = tv.GetSeasons(show.id); - var model = seasons.Select(x => x.number); - return Response.AsJson(model); - } - - - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SearchModule.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 System.Globalization; +using System.Linq; + +using Nancy; +using Nancy.Responses.Negotiation; + +using NLog; + +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Music; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; +using System.Threading.Tasks; + +using Nancy.Extensions; +using Nancy.Responses; + +using PlexRequests.Api.Models.Tv; +using PlexRequests.Core.Models; +using PlexRequests.Helpers.Analytics; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +using TMDbLib.Objects.General; + +using Action = PlexRequests.Helpers.Analytics.Action; + +namespace PlexRequests.UI.Modules +{ + public class SearchModule : BaseAuthModule + { + public SearchModule(ICacheProvider cache, ISettingsService cpSettings, + ISettingsService prSettings, IAvailabilityChecker checker, + IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, + ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, + INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, + ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, + ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, + IIssueService issue, IAnalytics a, IRepository rl) : base("search", prSettings) + { + Auth = auth; + PlexService = plexService; + PlexApi = plexApi; + CpService = cpSettings; + PrService = prSettings; + MovieApi = new TheMovieDbApi(); + Cache = cache; + Checker = checker; + CpCacher = cpCacher; + SonarrCacher = sonarrCacher; + SickRageCacher = sickRageCacher; + RequestService = request; + SonarrApi = sonarrApi; + SonarrService = sonarrSettings; + CouchPotatoApi = cpApi; + SickRageService = sickRageService; + SickrageApi = srApi; + NotificationService = notify; + MusicBrainzApi = mbApi; + HeadphonesApi = hpApi; + HeadphonesService = hpService; + UsersToNotifyRepo = u; + EmailNotificationSettings = email; + IssueService = issue; + Analytics = a; + RequestLimitRepo = rl; + + + Get["/", true] = async (x, ct) => await RequestLoad(); + + Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); + Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); + Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); + Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); + + Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); + Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); + + Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); + Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); + Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); + + Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); + Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings(); + + Get["/seasons"] = x => GetSeasons(); + } + private IPlexApi PlexApi { get; } + private TheMovieDbApi MovieApi { get; } + private INotificationService NotificationService { get; } + private ICouchPotatoApi CouchPotatoApi { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickrageApi { get; } + private IRequestService RequestService { get; } + private ICacheProvider Cache { get; } + private ISettingsService Auth { get; } + private ISettingsService PlexService { get; } + private ISettingsService CpService { get; } + private ISettingsService PrService { get; } + private ISettingsService SonarrService { get; } + private ISettingsService SickRageService { get; } + private ISettingsService HeadphonesService { get; } + private ISettingsService EmailNotificationSettings { get; } + private IAvailabilityChecker Checker { get; } + private ICouchPotatoCacher CpCacher { get; } + private ISonarrCacher SonarrCacher { get; } + private ISickRageCacher SickRageCacher { get; } + private IMusicBrainzApi MusicBrainzApi { get; } + private IHeadphonesApi HeadphonesApi { get; } + private IRepository UsersToNotifyRepo { get; } + private IIssueService IssueService { get; } + private IAnalytics Analytics { get; } + private IRepository RequestLimitRepo { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private async Task RequestLoad() + { + var settings = await PrService.GetSettingsAsync(); + + return View["Search/Index", settings]; + } + + private async Task UpcomingMovies() + { + Analytics.TrackEventAsync(Category.Search, Action.Movie, "Upcoming", Username, CookieHelper.GetAnalyticClientId(Cookies)); + return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); + } + + private async Task CurrentlyPlayingMovies() + { + Analytics.TrackEventAsync(Category.Search, Action.Movie, "CurrentlyPlaying", Username, CookieHelper.GetAnalyticClientId(Cookies)); + return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); + } + + private async Task SearchMovie(string searchTerm) + { + Analytics.TrackEventAsync(Category.Search, Action.Movie, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + return await ProcessMovies(MovieSearchType.Search, searchTerm); + } + + private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) + { + var apiMovies = new List(); + await Task.Factory.StartNew( + () => + { + switch (searchType) + { + case MovieSearchType.Search: + return + MovieApi.SearchMovie(searchTerm) + .Result.Select( + x => + new MovieResult() + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.GenreIds, + Id = x.Id, + OriginalLanguage = x.OriginalLanguage, + OriginalTitle = x.OriginalTitle, + Overview = x.Overview, + Popularity = x.Popularity, + PosterPath = x.PosterPath, + ReleaseDate = x.ReleaseDate, + Title = x.Title, + Video = x.Video, + VoteAverage = x.VoteAverage, + VoteCount = x.VoteCount + }) + .ToList(); + case MovieSearchType.CurrentlyPlaying: + return MovieApi.GetCurrentPlayingMovies().Result.ToList(); + case MovieSearchType.Upcoming: + return MovieApi.GetUpcomingMovies().Result.ToList(); + default: + return new List(); + } + }).ContinueWith( + (t) => + { + apiMovies = t.Result; + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Movie); + + var distinctResults = allResults.DistinctBy(x => x.ProviderId); + var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); + + + var cpCached = CpCacher.QueuedIds(); + var plexMovies = Checker.GetPlexMovies(); + var settings = await PrService.GetSettingsAsync(); + var viewMovies = new List(); + foreach (MovieResult movie in apiMovies) + { + var viewMovie = new SearchMovieViewModel + { + Adult = movie.Adult, + BackdropPath = movie.BackdropPath, + GenreIds = movie.GenreIds, + Id = movie.Id, + OriginalLanguage = movie.OriginalLanguage, + OriginalTitle = movie.OriginalTitle, + Overview = movie.Overview, + Popularity = movie.Popularity, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + Title = movie.Title, + Video = movie.Video, + VoteAverage = movie.VoteAverage, + VoteCount = movie.VoteCount + }; + var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); + if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) + { + viewMovie.Available = true; + } + else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db + { + var dbm = dbMovies[movie.Id]; + + viewMovie.Requested = true; + viewMovie.Approved = dbm.Approved; + viewMovie.Available = dbm.Available; + } + else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db + { + viewMovie.Requested = true; + } + + viewMovies.Add(viewMovie); + } + + return Response.AsJson(viewMovies); + } + + private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) + { + if (usersCanViewOnlyOwnRequests) + { + var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); + return result.Value == null || result.Value.UserHasRequested(Username); + } + + return true; + } + + private async Task SearchTvShow(string searchTerm) + { + Analytics.TrackEventAsync(Category.Search, Action.TvShow, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + var plexSettings = await PlexService.GetSettingsAsync(); + Log.Trace("Searching for TV Show {0}", searchTerm); + + var apiTv = new List(); + await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => + { + apiTv = t.Result; + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.TvShow); + + var dbTv = allResults.ToDictionary(x => x.ProviderId); + + if (!apiTv.Any()) + { + Log.Trace("TV Show data is null"); + return Response.AsJson(""); + } + + var sonarrCached = SonarrCacher.QueuedIds(); + var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays + var plexTvShows = Checker.GetPlexTvShows(); + + var viewTv = new List(); + foreach (var t in apiTv) + { + var banner = t.show.image?.medium; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); + } + + var viewT = new SearchTvShowViewModel + { + Banner = banner, + FirstAired = t.show.premiered, + Id = t.show.externals?.thetvdb ?? 0, + ImdbId = t.show.externals?.imdb, + Network = t.show.network?.name, + NetworkId = t.show.network?.id.ToString(), + Overview = t.show.summary.RemoveHtml(), + Rating = t.score.ToString(CultureInfo.CurrentUICulture), + Runtime = t.show.runtime.ToString(), + SeriesId = t.show.id, + SeriesName = t.show.name, + Status = t.show.status + }; + + + var providerId = string.Empty; + + if (plexSettings.AdvancedSearch) + { + providerId = viewT.Id.ToString(); + } + + if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) + { + viewT.Available = true; + } + else if (t.show?.externals?.thetvdb != null) + { + int tvdbid = (int)t.show.externals.thetvdb; + + if (dbTv.ContainsKey(tvdbid)) + { + var dbt = dbTv[tvdbid]; + + viewT.Requested = true; + viewT.Approved = dbt.Approved; + viewT.Available = dbt.Available; + } + else if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db + { + viewT.Requested = true; + } + } + + viewTv.Add(viewT); + } + + return Response.AsJson(viewTv); + } + + private async Task SearchMusic(string searchTerm) + { + Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies)); + var apiAlbums = new List(); + await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => + { + apiAlbums = t.Result.releases ?? new List(); + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Album); + + var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); + + var plexAlbums = Checker.GetPlexAlbums(); + + var viewAlbum = new List(); + foreach (var a in apiAlbums) + { + var viewA = new SearchMusicViewModel + { + Title = a.title, + Id = a.id, + Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), + Overview = a.disambiguation, + ReleaseDate = a.date, + TrackCount = a.TrackCount, + ReleaseType = a.status, + Country = a.country + }; + + DateTime release; + DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); + var artist = a.ArtistCredit?.FirstOrDefault()?.artist; + if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) + { + viewA.Available = true; + } + if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) + { + var dba = dbAlbum[a.id]; + + viewA.Requested = true; + viewA.Approved = dba.Approved; + viewA.Available = dba.Available; + } + + viewAlbum.Add(viewA); + } + return Response.AsJson(viewAlbum); + } + + private async Task RequestMovie(int movieId) + { + var settings = await PrService.GetSettingsAsync(); + if (!await CheckRequestLimit(settings, RequestType.Movie)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Movies! Please contact your admin." }); + } + + Analytics.TrackEventAsync(Category.Search, Action.Request, "Movie", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var movieInfo = MovieApi.GetMovieInformation(movieId).Result; + var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; + Log.Trace("Getting movie info from TheMovieDb"); + + // check if the movie has already been requested + Log.Info("Requesting movie with id {0}", movieId); + var existingRequest = await RequestService.CheckRequestAsync(movieId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this movie, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" }); + } + + Log.Debug("movie with id {0} doesnt exists", movieId); + + try + { + var movies = Checker.GetPlexMovies(); + if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); + } + } + catch (Exception e) + { + Log.Error(e); + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" }); + } + //#endif + + var model = new RequestedModel + { + ProviderId = movieInfo.Id, + Type = RequestType.Movie, + Overview = movieInfo.Overview, + ImdbId = movieInfo.ImdbId, + PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath, + Title = movieInfo.Title, + ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, + Status = movieInfo.Status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + + }; + + if (ShouldAutoApprove(RequestType.Movie, settings)) + { + var cpSettings = await CpService.GetSettingsAsync(); + + if (cpSettings.Enabled) + { + Log.Info("Adding movie to CP (No approval required)"); + var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, + cpSettings.FullUri, cpSettings.ProfileId); + Log.Debug("Adding movie to CP result {0}", result); + if (result) + { + return await AddRequest(model, settings, $"{fullMovieName} was successfully added!"); + } + + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + "Something went wrong adding the movie to CouchPotato! Please check your settings." + }); + } + + return await AddRequest(model, settings, $"{fullMovieName} was successfully added!"); + } + + try + { + return await AddRequest(model, settings, $"{fullMovieName} was successfully added!"); + } + catch (Exception e) + { + Log.Fatal(e); + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); + } + } + + /// + /// Requests the tv show. + /// + /// The show identifier. + /// The seasons. + /// + private async Task RequestTvShow(int showId, string seasons) + { + var settings = await PrService.GetSettingsAsync(); + if (!await CheckRequestLimit(settings, RequestType.TvShow)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for TV Shows! Please contact your admin." }); + } + Analytics.TrackEventAsync(Category.Search, Action.Request, "TvShow", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var tvApi = new TvMazeApi(); + + var showInfo = tvApi.ShowLookupByTheTvDbId(showId); + DateTime firstAir; + DateTime.TryParse(showInfo.premiered, out firstAir); + string fullShowName = $"{showInfo.name} ({firstAir.Year})"; + //#if !DEBUG + + + // check if the show has already been requested + Log.Info("Requesting tv show with id {0}", showId); + var existingRequest = await RequestService.CheckRequestAsync(showId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this show, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" }); + } + + try + { + var shows = Checker.GetPlexTvShows(); + var providerId = string.Empty; + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.AdvancedSearch) + { + providerId = showId.ToString(); + } + if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); + } + } + catch (Exception) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" }); + } + //#endif + + + var model = new RequestedModel + { + ProviderId = showInfo.externals?.thetvdb ?? 0, + Type = RequestType.TvShow, + Overview = showInfo.summary.RemoveHtml(), + PosterPath = showInfo.image?.medium, + Title = showInfo.name, + ReleaseDate = firstAir, + Status = showInfo.status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + ImdbId = showInfo.externals?.imdb ?? string.Empty, + SeasonCount = showInfo.seasonCount, + TvDbId = showId.ToString() + }; + + var seasonsList = new List(); + switch (seasons) + { + case "first": + seasonsList.Add(1); + model.SeasonsRequested = "First"; + break; + case "latest": + seasonsList.Add(model.SeasonCount); + model.SeasonsRequested = "Latest"; + break; + case "all": + model.SeasonsRequested = "All"; + break; + default: + model.SeasonsRequested = seasons; + var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var seasonsCount = new int[split.Length]; + for (var i = 0; i < split.Length; i++) + { + int tryInt; + int.TryParse(split[i], out tryInt); + seasonsCount[i] = tryInt; + } + seasonsList.AddRange(seasonsCount); + break; + } + + model.SeasonList = seasonsList.ToArray(); + + if (ShouldAutoApprove(RequestType.TvShow, settings)) + { + var sonarrSettings = await SonarrService.GetSettingsAsync(); + var sender = new TvSender(SonarrApi, SickrageApi); + if (sonarrSettings.Enabled) + { + var result = sender.SendToSonarr(sonarrSettings, model); + if (!string.IsNullOrEmpty(result?.title)) + { + return await AddRequest(model, settings, $"{fullShowName} was successfully added!"); + } + + return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); + } + + var srSettings = SickRageService.GetSettings(); + if (srSettings.Enabled) + { + var result = sender.SendToSickRage(srSettings, model); + if (result?.result == "success") + { + return await AddRequest(model, settings, $"{fullShowName} was successfully added!"); + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "Message From SickRage: " + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." }); + } + + if (!srSettings.Enabled && !sonarrSettings.Enabled) + { + return await AddRequest(model, settings, $"{fullShowName} was successfully added!"); + } + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); + + } + return await AddRequest(model, settings, $"{fullShowName} was successfully added!"); + } + + private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) + { + var sendNotification = ShouldAutoApprove(type, prSettings) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true; + var claims = Context.CurrentUser?.Claims; + if (claims != null) + { + var enumerable = claims as string[] ?? claims.ToArray(); + if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser)) + { + sendNotification = false; // Don't bother sending a notification if the user is an admin + } + } + return sendNotification; + } + + + private async Task RequestAlbum(string releaseId) + { + var settings = await PrService.GetSettingsAsync(); + if (!await CheckRequestLimit(settings, RequestType.Album)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "You have reached your weekly request limit for Albums! Please contact your admin." }); + } + Analytics.TrackEventAsync(Category.Search, Action.Request, "Album", Username, CookieHelper.GetAnalyticClientId(Cookies)); + var existingRequest = await RequestService.CheckRequestAsync(releaseId); + Log.Debug("Checking for an existing request"); + + if (existingRequest != null) + { + Log.Debug("We do have an existing album request"); + if (!existingRequest.UserHasRequested(Username)) + { + + Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username); + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" }); + } + + Log.Debug("This is a new request"); + + var albumInfo = MusicBrainzApi.GetAlbum(releaseId); + DateTime release; + DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); + + var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; + if (artist == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" }); + } + + var albums = Checker.GetPlexAlbums(); + var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); + + if (alreadyInPlex) + { + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{albumInfo.title} is already in Plex!" + }); + } + + var img = GetMusicBrainzCoverArt(albumInfo.id); + + var model = new RequestedModel + { + Title = albumInfo.title, + MusicBrainzId = albumInfo.id, + Overview = albumInfo.disambiguation, + PosterPath = img, + Type = RequestType.Album, + ProviderId = 0, + RequestedUsers = new List { Username }, + Status = albumInfo.status, + Issues = IssueState.None, + RequestedDate = DateTime.UtcNow, + ReleaseDate = release, + ArtistName = artist.name, + ArtistId = artist.id + }; + + if (ShouldAutoApprove(RequestType.Album, settings)) + { + Log.Debug("We don't require approval OR the user is in the whitelist"); + var hpSettings = HeadphonesService.GetSettings(); + + if (!hpSettings.Enabled) + { + await RequestService.AddRequestAsync(model); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); + await sender.AddAlbum(model); + return await AddRequest(model, settings, $"{model.Title} was successfully added!"); + } + + return await AddRequest(model, settings, $"{model.Title} was successfully added!"); + } + + private string GetMusicBrainzCoverArt(string id) + { + var coverArt = MusicBrainzApi.GetCoverArt(id); + var firstImage = coverArt?.images?.FirstOrDefault(); + var img = string.Empty; + + if (firstImage != null) + { + img = firstImage.thumbnails?.small ?? firstImage.image; + } + + return img; + } + + private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) + { + // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval + if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; + + // check by request type if the category requires approval or not + switch (requestType) + { + case RequestType.Movie: + return !prSettings.RequireMovieApproval; + case RequestType.TvShow: + return !prSettings.RequireTvShowApproval; + case RequestType.Album: + return !prSettings.RequireMusicApproval; + default: + return false; + } + } + + private async Task NotifyUser(bool notify) + { + Analytics.TrackEventAsync(Category.Search, Action.Save, "NotifyUser", Username, CookieHelper.GetAnalyticClientId(Cookies), notify ? 1 : 0); + var authSettings = await Auth.GetSettingsAsync(); + var auth = authSettings.UserAuthentication; + var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); + var email = emailSettings.EnableUserEmailNotifications; + if (!auth) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" }); + } + if (!email) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." }); + } + var username = Username; + var originalList = await UsersToNotifyRepo.GetAllAsync(); + if (!notify) + { + if (originalList == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" }); + } + var userToRemove = originalList.FirstOrDefault(x => x.Username == username); + if (userToRemove != null) + { + await UsersToNotifyRepo.DeleteAsync(userToRemove); + } + return Response.AsJson(new JsonResponseModel { Result = true }); + } + + + if (originalList == null) + { + var userModel = new UsersToNotify { Username = username }; + var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); + return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); + } + + var existingUser = originalList.FirstOrDefault(x => x.Username == username); + if (existingUser != null) + { + return Response.AsJson(new JsonResponseModel { Result = true }); // It's already enabled + } + else + { + var userModel = new UsersToNotify { Username = username }; + var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); + return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); + } + + } + private async Task GetUserNotificationSettings() + { + var all = await UsersToNotifyRepo.GetAllAsync(); + var retVal = all.FirstOrDefault(x => x.Username == Username); + return Response.AsJson(retVal != null); + } + + private Response GetSeasons() + { + var tv = new TvMazeApi(); + var seriesId = (int)Request.Query.tvId; + var show = tv.ShowLookupByTheTvDbId(seriesId); + var seasons = tv.GetSeasons(show.id); + var model = seasons.Select(x => x.number); + return Response.AsJson(model); + } + + private async Task CheckRequestLimit(PlexRequestSettings s, RequestType type) + { + if (IsAdmin) + return true; + + if (s.ApprovalWhiteList.Contains(Username)) + return true; + + var requestLimit = GetRequestLimitForType(type, s); + if (requestLimit == 0) + { + return true; + } + + var limit = await RequestLimitRepo.GetAllAsync(); + var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == type); + if (usersLimit == null) + { + // Have not set a requestLimit yet + return true; + } + + return usersLimit.RequestCount >= requestLimit; + } + + private int GetRequestLimitForType(RequestType type, PlexRequestSettings s) + { + int requestLimit; + switch (type) + { + case RequestType.Movie: + requestLimit = s.MovieWeeklyRequestLimit; + break; + case RequestType.TvShow: + requestLimit = s.TvWeeklyRequestLimit; + break; + case RequestType.Album: + requestLimit = s.AlbumWeeklyRequestLimit; + break; + default: + throw new ArgumentOutOfRangeException(nameof(type), type, null); + } + return requestLimit; + } + + private async Task AddRequest(RequestedModel model, PlexRequestSettings settings, string message) + { + + model.Approved = true; + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(RequestType.Movie, settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.Movie + }; + await NotificationService.Publish(notificationModel); + } + + var limit = await RequestLimitRepo.GetAllAsync(); + var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type); + if (usersLimit == null) + { + await RequestLimitRepo.InsertAsync(new RequestLimit + { + Username = Username, + RequestType = model.Type, + FirstRequestDate = DateTime.UtcNow, + RequestCount = 1 + }); + } + else + { + usersLimit.RequestCount++; + await RequestLimitRepo.UpdateAsync(usersLimit); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = message }); + } + } +} diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index cfa0c03a9..fb0d5400d 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -1,220 +1,236 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UserLoginModule.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.Threading.Tasks; - -using Nancy; -using Nancy.Extensions; -using Nancy.Responses.Negotiation; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.UI.Models; - -namespace PlexRequests.UI.Modules -{ - public class UserLoginModule : BaseModule - { - public UserLoginModule(ISettingsService auth, IPlexApi api, ISettingsService pr, ISettingsService lp) : base("userlogin", pr) - { - AuthService = auth; - LandingPageSettings = lp; - Api = api; - Get["/", true] = async (x, ct) => await Index(); - Post["/"] = x => LoginUser(); - Get["/logout"] = x => Logout(); - } - - private ISettingsService AuthService { get; } - private ISettingsService LandingPageSettings { get; } - private IPlexApi Api { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - - public async Task Index() - { - var query = Request.Query["landing"]; - var landingCheck = (bool?)query ?? true; - if (landingCheck) - { - var landingSettings = await LandingPageSettings.GetSettingsAsync(); - - if (landingSettings.Enabled) - { - if (landingSettings.BeforeLogin) - { - var model = new LandingPageViewModel - { - Enabled = landingSettings.Enabled, - Id = landingSettings.Id, - EnabledNoticeTime = landingSettings.EnabledNoticeTime, - NoticeEnable = landingSettings.NoticeEnable, - NoticeEnd = landingSettings.NoticeEnd, - NoticeMessage = landingSettings.NoticeMessage, - NoticeStart = landingSettings.NoticeStart, - ContinueUrl = landingSettings.BeforeLogin ? $"userlogin" : $"search" - }; - - return View["Landing/Index", model]; - } - } - } - var settings = await AuthService.GetSettingsAsync(); - return View["Index", settings]; - } - - private Response LoginUser() - { - var dateTimeOffset = Request.Form.DateTimeOffset; - var username = Request.Form.username.Value; - Log.Debug("Username \"{0}\" attempting to login", username); - if (string.IsNullOrWhiteSpace(username)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); - } - - var authenticated = false; - - var settings = AuthService.GetSettings(); - - 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" }); - } - - var password = string.Empty; - if (settings.UsePassword) - { - Log.Debug("Using password"); - password = Request.Form.password.Value; - } - - - if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex - { - Log.Debug("Need to auth and also provide pass"); - var signedIn = (PlexAuthentication)Api.SignIn(username, password); - if (signedIn.user?.authentication_token != null) - { - Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); - if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username)) - { - Log.Debug("User is the account owner"); - authenticated = true; - } - else - { - authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); - Log.Debug("Friends list result = {0}", authenticated); - } - } - } - else if (settings.UserAuthentication) // Check against the users in Plex - { - Log.Debug("Need to auth"); - authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); - if (CheckIfUserIsOwner(settings.PlexAuthToken, username)) - { - Log.Debug("User is the account owner"); - authenticated = true; - } - Log.Debug("Friends list result = {0}", authenticated); - } - else if (!settings.UserAuthentication) // No auth, let them pass! - { - Log.Debug("No need to auth"); - authenticated = true; - } - - if (authenticated) - { - Log.Debug("We are authenticated! Setting session."); - // Add to the session (Used in the BaseModules) - Session[SessionKeys.UsernameKey] = (string)username; - } - - Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset; - - if (!authenticated) - { - return Response.AsJson(new JsonResponseModel {Result = false, Message = "Incorrect User or Password"}); - } - - var landingSettings = LandingPageSettings.GetSettings(); - - if (landingSettings.Enabled) - { - if (!landingSettings.BeforeLogin) - return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" }); - } - return Response.AsJson(new JsonResponseModel {Result = true, Message = "search" }); - } - - - - private Response Logout() - { - Log.Debug("Logging Out"); - if (Session[SessionKeys.UsernameKey] != null) - { - Session.Delete(SessionKeys.UsernameKey); - } - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) - ? $"~/{BaseUrl}/userlogin" - : "~/userlogin"); - } - - private bool CheckIfUserIsOwner(string authToken, string userName) - { - var userAccount = Api.GetAccount(authToken); - if (userAccount == null) - { - return false; - } - return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase); - } - - private bool CheckIfUserIsInPlexFriends(string username, string authToken) - { - var users = Api.GetUsers(authToken); - var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title)); - return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)); - } - - private bool IsUserInDeniedList(string username, AuthenticationSettings settings) - { - return settings.DeniedUserList.Any(x => x.Equals(username)); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserLoginModule.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.Threading.Tasks; + +using Nancy; +using Nancy.Extensions; +using Nancy.Responses.Negotiation; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; +using PlexRequests.UI.Models; + +using Action = PlexRequests.Helpers.Analytics.Action; + +namespace PlexRequests.UI.Modules +{ + public class UserLoginModule : BaseModule + { + public UserLoginModule(ISettingsService auth, IPlexApi api, ISettingsService pr, ISettingsService lp, IAnalytics a) : base("userlogin", pr) + { + AuthService = auth; + LandingPageSettings = lp; + Analytics = a; + Api = api; + Get["/", true] = async (x, ct) => await Index(); + Post["/"] = x => LoginUser(); + Get["/logout"] = x => Logout(); + } + + private ISettingsService AuthService { get; } + private ISettingsService LandingPageSettings { get; } + private IPlexApi Api { get; } + private IAnalytics Analytics { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public async Task Index() + { + var query = Request.Query["landing"]; + var landingCheck = (bool?)query ?? true; + if (landingCheck) + { + var landingSettings = await LandingPageSettings.GetSettingsAsync(); + + if (landingSettings.Enabled) + { + + if (landingSettings.BeforeLogin) + { +#pragma warning disable 4014 + Analytics.TrackEventAsync( +#pragma warning restore 4014 + Category.LandingPage, + Action.View, + "Going To LandingPage before login", + Username, + CookieHelper.GetAnalyticClientId(Cookies)); + + var model = new LandingPageViewModel + { + Enabled = landingSettings.Enabled, + Id = landingSettings.Id, + EnabledNoticeTime = landingSettings.EnabledNoticeTime, + NoticeEnable = landingSettings.NoticeEnable, + NoticeEnd = landingSettings.NoticeEnd, + NoticeMessage = landingSettings.NoticeMessage, + NoticeStart = landingSettings.NoticeStart, + ContinueUrl = landingSettings.BeforeLogin ? $"userlogin" : $"search" + }; + + return View["Landing/Index", model]; + } + } + } + var settings = await AuthService.GetSettingsAsync(); + return View["Index", settings]; + } + + private Response LoginUser() + { + var dateTimeOffset = Request.Form.DateTimeOffset; + var username = Request.Form.username.Value; + Log.Debug("Username \"{0}\" attempting to login", username); + if (string.IsNullOrWhiteSpace(username)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); + } + + var authenticated = false; + + var settings = AuthService.GetSettings(); + + 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" }); + } + + var password = string.Empty; + if (settings.UsePassword) + { + Log.Debug("Using password"); + password = Request.Form.password.Value; + } + + + if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex + { + Log.Debug("Need to auth and also provide pass"); + var signedIn = (PlexAuthentication)Api.SignIn(username, password); + if (signedIn.user?.authentication_token != null) + { + Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); + if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username)) + { + Log.Debug("User is the account owner"); + authenticated = true; + } + else + { + authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); + Log.Debug("Friends list result = {0}", authenticated); + } + } + } + else if (settings.UserAuthentication) // Check against the users in Plex + { + Log.Debug("Need to auth"); + authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); + if (CheckIfUserIsOwner(settings.PlexAuthToken, username)) + { + Log.Debug("User is the account owner"); + authenticated = true; + } + Log.Debug("Friends list result = {0}", authenticated); + } + else if (!settings.UserAuthentication) // No auth, let them pass! + { + Log.Debug("No need to auth"); + authenticated = true; + } + + if (authenticated) + { + Log.Debug("We are authenticated! Setting session."); + // Add to the session (Used in the BaseModules) + Session[SessionKeys.UsernameKey] = (string)username; + } + + Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset; + + if (!authenticated) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); + } + + var landingSettings = LandingPageSettings.GetSettings(); + + if (landingSettings.Enabled) + { + if (!landingSettings.BeforeLogin) + return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" }); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "search" }); + } + + + + private Response Logout() + { + Log.Debug("Logging Out"); + if (Session[SessionKeys.UsernameKey] != null) + { + Session.Delete(SessionKeys.UsernameKey); + } + return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) + ? $"~/{BaseUrl}/userlogin" + : "~/userlogin"); + } + + private bool CheckIfUserIsOwner(string authToken, string userName) + { + var userAccount = Api.GetAccount(authToken); + if (userAccount == null) + { + return false; + } + return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase); + } + + private bool CheckIfUserIsInPlexFriends(string username, string authToken) + { + var users = Api.GetUsers(authToken); + var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title)); + return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)); + } + + private bool IsUserInDeniedList(string username, AuthenticationSettings settings) + { + return settings.DeniedUserList.Any(x => x.Equals(username)); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj index 75290e3d1..0495bb18b 100644 --- a/PlexRequests.UI/PlexRequests.UI.csproj +++ b/PlexRequests.UI/PlexRequests.UI.csproj @@ -1,657 +1,677 @@ - - - - - Debug - AnyCPU - {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6} - Exe - Properties - PlexRequests.UI - PlexRequests - v4.5 - 512 - ..\..\ - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll - True - - - ..\packages\Nancy.Metadata.Modules.1.4.1\lib\net40\Nancy.Metadata.Modules.dll - True - - - ..\packages\Nancy.Swagger.0.1.0-alpha3\lib\net40\Nancy.Swagger.dll - True - - - ..\packages\NLog.4.3.4\lib\net45\NLog.dll - True - - - ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll - True - - - ..\packages\Swagger.ObjectModel.0.1.0-alpha3\lib\net40\Swagger.ObjectModel.dll - True - - - - - - - - - - - - ..\packages\CommandLineParser.2.0.275-beta\lib\net45\CommandLine.dll - - - ..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll - - - ..\packages\Dapper.1.42\lib\net45\Dapper.dll - - - ..\packages\FluentValidation.6.2.1.0\lib\Net45\FluentValidation.dll - - - ..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll - - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll - - - ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - - - ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll - - - ..\Assemblies\Mono.Data.Sqlite.dll - - - ..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll - - - ..\packages\Nancy.Authentication.Basic.1.4.1\lib\net40\Nancy.Authentication.Basic.dll - - - ..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll - - - ..\packages\Nancy.Hosting.Self.1.4.1\lib\net40\Nancy.Hosting.Self.dll - - - ..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll - - - ..\packages\Nancy.Validation.FluentValidation.1.4.1\lib\net40\Nancy.Validation.FluentValidation.dll - - - ..\packages\Nancy.Viewengines.Razor.1.4.1\lib\net40\Nancy.ViewEngines.Razor.dll - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll - - - ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll - - - ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - base.scss - PreserveNewest - - - base.css - PreserveNewest - - - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - datepicker.scss - - - datepicker.css - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - original.scss - PreserveNewest - - - original.css - PreserveNewest - - - plex.scss - PreserveNewest - - - plex.css - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - PreserveNewest - - - PreserveNewest - - - Always - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - moment.min.js - - - moment.min.es5.js - - - Always - - - pace.scss - - - pace.css - Always - - - PreserveNewest - - - PreserveNewest - - - - - Always - - - - compilerconfig.json - - - PreserveNewest - - - - - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - PreserveNewest - - - Designer - - - Designer - - - Always - - - Designer - - - Always - - - Always - - - Always - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - web.config - - - web.config - - - - - - {95834072-A675-415D-AA8F-877C91623810} - PlexRequests.Api.Interfaces - - - {CB37A5F8-6DFC-4554-99D3-A42B502E4591} - PlexRequests.Api.Models - - - {8CB8D235-2674-442D-9C6A-35FCAEEB160D} - PlexRequests.Api - - - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} - PlexRequests.Core - - - {1252336D-42A3-482A-804C-836E60173DFA} - PlexRequests.Helpers - - - {566EFA49-68F8-4716-9693-A6B3F2624DEA} - PlexRequests.Services - - - {92433867-2B7B-477B-A566-96C382427525} - PlexRequests.Store - - - {ebe6fc1c-7b4b-47e9-af54-0ee0604a2be5} - PlexRequests.Updater - - - - - PreserveNewest - - - - - False - Microsoft .NET Framework 4.5.2 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6} + Exe + Properties + PlexRequests.UI + PlexRequests + v4.5 + 512 + ..\..\ + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + True + + + ..\packages\Nancy.Metadata.Modules.1.4.1\lib\net40\Nancy.Metadata.Modules.dll + True + + + ..\packages\Nancy.Swagger.0.1.0-alpha3\lib\net40\Nancy.Swagger.dll + True + + + ..\packages\NLog.4.3.4\lib\net45\NLog.dll + True + + + ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll + True + + + ..\packages\Swagger.ObjectModel.0.1.0-alpha3\lib\net40\Swagger.ObjectModel.dll + True + + + + + + + + + + + + ..\packages\CommandLineParser.2.0.275-beta\lib\net45\CommandLine.dll + + + ..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll + + + ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll + + + ..\packages\Dapper.1.42\lib\net45\Dapper.dll + + + ..\packages\FluentValidation.6.2.1.0\lib\Net45\FluentValidation.dll + + + ..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + + ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll + + + ..\Assemblies\Mono.Data.Sqlite.dll + + + ..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll + + + ..\packages\Nancy.Authentication.Basic.1.4.1\lib\net40\Nancy.Authentication.Basic.dll + + + ..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll + + + ..\packages\Nancy.Hosting.Self.1.4.1\lib\net40\Nancy.Hosting.Self.dll + + + ..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll + + + ..\packages\Nancy.Validation.FluentValidation.1.4.1\lib\net40\Nancy.Validation.FluentValidation.dll + + + ..\packages\Nancy.Viewengines.Razor.1.4.1\lib\net40\Nancy.ViewEngines.Razor.dll + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + ..\packages\Quartz.2.3.3\lib\net40\Quartz.dll + + + ..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll + + + ..\packages\TMDbLib.0.9.0.0-alpha\lib\net45\TMDbLib.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + UI.resx + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + base.scss + PreserveNewest + + + base.css + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + datepicker.scss + + + datepicker.css + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + original.scss + PreserveNewest + + + original.css + PreserveNewest + + + plex.scss + PreserveNewest + + + plex.css + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + PreserveNewest + + + PreserveNewest + + + Always + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + moment.min.js + + + moment.min.es5.js + + + Always + + + pace.scss + + + pace.css + Always + + + PreserveNewest + + + PreserveNewest + + + + + Always + + + + compilerconfig.json + + + PreserveNewest + + + + + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + PreserveNewest + + + Designer + + + Designer + + + Always + + + Designer + + + Always + + + Always + + + Always + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + web.config + + + web.config + + + + + + + + + + + + PublicResXFileCodeGenerator + UI.Designer.cs + + + + + + {95834072-A675-415D-AA8F-877C91623810} + PlexRequests.Api.Interfaces + + + {CB37A5F8-6DFC-4554-99D3-A42B502E4591} + PlexRequests.Api.Models + + + {8CB8D235-2674-442D-9C6A-35FCAEEB160D} + PlexRequests.Api + + + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581} + PlexRequests.Core + + + {1252336D-42A3-482A-804C-836E60173DFA} + PlexRequests.Helpers + + + {566EFA49-68F8-4716-9693-A6B3F2624DEA} + PlexRequests.Services + + + {92433867-2B7B-477B-A566-96C382427525} + PlexRequests.Store + + + {ebe6fc1c-7b4b-47e9-af54-0ee0604a2be5} + PlexRequests.Updater + + + + + PreserveNewest + + + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.Designer.cs b/PlexRequests.UI/Resources/UI.Designer.cs new file mode 100644 index 000000000..8fa67c228 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.Designer.cs @@ -0,0 +1,919 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PlexRequests.UI.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class UI { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal UI() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PlexRequests.UI.Resources.UI", typeof(UI).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Approve. + /// + public static string Common_Approve { + get { + return ResourceManager.GetString("Common_Approve", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Approved. + /// + public static string Common_Approved { + get { + return ResourceManager.GetString("Common_Approved", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close. + /// + public static string Common_Close { + get { + return ResourceManager.GetString("Common_Close", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove. + /// + public static string Common_Remove { + get { + return ResourceManager.GetString("Common_Remove", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + public static string Common_Save { + get { + return ResourceManager.GetString("Common_Save", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Issue. + /// + public static string Issues_Issue { + get { + return ResourceManager.GetString("Issues_Issue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save Changes. + /// + public static string Issues_Modal_Save { + get { + return ResourceManager.GetString("Issues_Modal_Save", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add an issue. + /// + public static string Issues_Modal_Title { + get { + return ResourceManager.GetString("Issues_Modal_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Subtitles. + /// + public static string Issues_NoSubs { + get { + return ResourceManager.GetString("Issues_NoSubs", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Other. + /// + public static string Issues_Other { + get { + return ResourceManager.GetString("Issues_Other", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Playback Issues. + /// + public static string Issues_Playback { + get { + return ResourceManager.GetString("Issues_Playback", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wrong Audio. + /// + public static string Issues_WrongAudio { + get { + return ResourceManager.GetString("Issues_WrongAudio", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wrong Content. + /// + public static string Issues_WrongContent { + get { + return ResourceManager.GetString("Issues_WrongContent", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Something went wrong!. + /// + public static string Javascript_SomethingWentWrong { + get { + return ResourceManager.GetString("Javascript_SomethingWentWrong", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Success!. + /// + public static string Javascript_Success { + get { + return ResourceManager.GetString("Javascript_Success", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Admin. + /// + public static string Layout_Admin { + get { + return ResourceManager.GetString("Layout_Admin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Change Password. + /// + public static string Layout_ChangePassword { + get { + return ResourceManager.GetString("Layout_ChangePassword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Danish. + /// + public static string Layout_Danish { + get { + return ResourceManager.GetString("Layout_Danish", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Donate. + /// + public static string Layout_Donate { + get { + return ResourceManager.GetString("Layout_Donate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dutch. + /// + public static string Layout_Dutch { + get { + return ResourceManager.GetString("Layout_Dutch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to English. + /// + public static string Layout_English { + get { + return ResourceManager.GetString("Layout_English", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to German. + /// + public static string Layout_German { + get { + return ResourceManager.GetString("Layout_German", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Issues. + /// + public static string Layout_Issues { + get { + return ResourceManager.GetString("Layout_Issues", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Italian. + /// + public static string Layout_Italian { + get { + return ResourceManager.GetString("Layout_Italian", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logout. + /// + public static string Layout_Logout { + get { + return ResourceManager.GetString("Layout_Logout", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Portuguese. + /// + public static string Layout_Portuguese { + get { + return ResourceManager.GetString("Layout_Portuguese", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Requests. + /// + public static string Layout_Requests { + get { + return ResourceManager.GetString("Layout_Requests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search. + /// + public static string Layout_Search { + get { + return ResourceManager.GetString("Layout_Search", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings. + /// + public static string Layout_Settings { + get { + return ResourceManager.GetString("Layout_Settings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Spanish. + /// + public static string Layout_Spanish { + get { + return ResourceManager.GetString("Layout_Spanish", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Swedish. + /// + public static string Layout_Swedish { + get { + return ResourceManager.GetString("Layout_Swedish", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Plex Requests. + /// + public static string Layout_Title { + get { + return ResourceManager.GetString("Layout_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to There is a new update available! Click. + /// + public static string Layout_UpdateAvailablePart1 { + get { + return ResourceManager.GetString("Layout_UpdateAvailablePart1", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Here!. + /// + public static string Layout_UpdateAvailablePart2 { + get { + return ResourceManager.GetString("Layout_UpdateAvailablePart2", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Welcome. + /// + public static string Layout_Welcome { + get { + return ResourceManager.GetString("Layout_Welcome", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Albums. + /// + public static string Requests_AlbumsTabTitle { + get { + return ResourceManager.GetString("Requests_AlbumsTabTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Approve Movies. + /// + public static string Requests_ApproveMovies { + get { + return ResourceManager.GetString("Requests_ApproveMovies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Approve Music. + /// + public static string Requests_ApproveMusic { + get { + return ResourceManager.GetString("Requests_ApproveMusic", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Approve TV Shows. + /// + public static string Requests_ApproveTvShows { + get { + return ResourceManager.GetString("Requests_ApproveTvShows", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Available. + /// + public static string Requests_Available { + get { + return ResourceManager.GetString("Requests_Available", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete Movies. + /// + public static string Requests_DeleteMovies { + get { + return ResourceManager.GetString("Requests_DeleteMovies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete Music. + /// + public static string Requests_DeleteMusic { + get { + return ResourceManager.GetString("Requests_DeleteMusic", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete TV Shows. + /// + public static string Requests_DeleteTVShows { + get { + return ResourceManager.GetString("Requests_DeleteTVShows", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Filter. + /// + public static string Requests_Filter { + get { + return ResourceManager.GetString("Requests_Filter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All. + /// + public static string Requests_Filter_All { + get { + return ResourceManager.GetString("Requests_Filter_All", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Approved. + /// + public static string Requests_Filter_Approved { + get { + return ResourceManager.GetString("Requests_Filter_Approved", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Available. + /// + public static string Requests_Filter_Available { + get { + return ResourceManager.GetString("Requests_Filter_Available", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Approved. + /// + public static string Requests_Filter_NotApproved { + get { + return ResourceManager.GetString("Requests_Filter_NotApproved", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Available. + /// + public static string Requests_Filter_NotAvailable { + get { + return ResourceManager.GetString("Requests_Filter_NotAvailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Not Released. + /// + public static string Requests_Filter_NotReleased { + get { + return ResourceManager.GetString("Requests_Filter_NotReleased", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Released. + /// + public static string Requests_Filter_Released { + get { + return ResourceManager.GetString("Requests_Filter_Released", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mark Available. + /// + public static string Requests_MarkAvailable { + get { + return ResourceManager.GetString("Requests_MarkAvailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mark Unavailable. + /// + public static string Requests_MarkUnavailable { + get { + return ResourceManager.GetString("Requests_MarkUnavailable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Movies. + /// + public static string Requests_MoviesTabTitle { + get { + return ResourceManager.GetString("Requests_MoviesTabTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Order. + /// + public static string Requests_Order { + get { + return ResourceManager.GetString("Requests_Order", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Latest Releases. + /// + public static string Requests_Order_LatestReleases { + get { + return ResourceManager.GetString("Requests_Order_LatestReleases", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Latest Requests. + /// + public static string Requests_Order_LatestRequests { + get { + return ResourceManager.GetString("Requests_Order_LatestRequests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Oldest Releases. + /// + public static string Requests_Order_OldestReleases { + get { + return ResourceManager.GetString("Requests_Order_OldestReleases", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Oldest Requests. + /// + public static string Requests_Order_OldestRequests { + get { + return ResourceManager.GetString("Requests_Order_OldestRequests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Below you can see yours and all other requests, as well as their download and approval status.. + /// + public static string Requests_Paragraph { + get { + return ResourceManager.GetString("Requests_Paragraph", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Release Date. + /// + public static string Requests_ReleaseDate { + get { + return ResourceManager.GetString("Requests_ReleaseDate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Requested By. + /// + public static string Requests_RequestedBy { + get { + return ResourceManager.GetString("Requests_RequestedBy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Requested Date. + /// + public static string Requests_RequestedDate { + get { + return ResourceManager.GetString("Requests_RequestedDate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Seasons Requested. + /// + public static string Requests_SeasonsRequested { + get { + return ResourceManager.GetString("Requests_SeasonsRequested", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Requests. + /// + public static string Requests_Title { + get { + return ResourceManager.GetString("Requests_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggle Dropdown. + /// + public static string Requests_ToggleDropdown { + get { + return ResourceManager.GetString("Requests_ToggleDropdown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TV Shows. + /// + public static string Requests_TvShowTabTitle { + get { + return ResourceManager.GetString("Requests_TvShowTabTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Albums. + /// + public static string Search_Albums { + get { + return ResourceManager.GetString("Search_Albums", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to All Seasons. + /// + public static string Search_AllSeasons { + get { + return ResourceManager.GetString("Search_AllSeasons", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Available. + /// + public static string Search_Available { + get { + return ResourceManager.GetString("Search_Available", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Coming Soon. + /// + public static string Search_ComingSoon { + get { + return ResourceManager.GetString("Search_ComingSoon", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Country. + /// + public static string Search_Country { + get { + return ResourceManager.GetString("Search_Country", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to First Season. + /// + public static string Search_FirstSeason { + get { + return ResourceManager.GetString("Search_FirstSeason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In Theaters. + /// + public static string Search_InTheaters { + get { + return ResourceManager.GetString("Search_InTheaters", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Latest Season. + /// + public static string Search_LatestSeason { + get { + return ResourceManager.GetString("Search_LatestSeason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Seasons. + /// + public static string Search_Modal_SeasonsTitle { + get { + return ResourceManager.GetString("Search_Modal_SeasonsTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Movies. + /// + public static string Search_Movies { + get { + return ResourceManager.GetString("Search_Movies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!. + /// + public static string Search_Paragraph { + get { + return ResourceManager.GetString("Search_Paragraph", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Report Issue. + /// + public static string Search_ReportIssue { + get { + return ResourceManager.GetString("Search_ReportIssue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request. + /// + public static string Search_Request { + get { + return ResourceManager.GetString("Search_Request", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Requested. + /// + public static string Search_Requested { + get { + return ResourceManager.GetString("Search_Requested", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Season. + /// + public static string Search_Season { + get { + return ResourceManager.GetString("Search_Season", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Select . + /// + public static string Search_SelectSeason { + get { + return ResourceManager.GetString("Search_SelectSeason", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Send me a notification when items I have requested have been added. + /// + public static string Search_SendNotificationText { + get { + return ResourceManager.GetString("Search_SendNotificationText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Suggestions. + /// + public static string Search_Suggestions { + get { + return ResourceManager.GetString("Search_Suggestions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search. + /// + public static string Search_Title { + get { + return ResourceManager.GetString("Search_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Track Count. + /// + public static string Search_TrackCount { + get { + return ResourceManager.GetString("Search_TrackCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TV Shows. + /// + public static string Search_TvShows { + get { + return ResourceManager.GetString("Search_TvShows", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Want to watch a movie or tv show but it's not currently on Plex? + /// Login below with your Plex.tv username and password!. + /// + public static string UserLogin_Paragraph { + get { + return ResourceManager.GetString("UserLogin_Paragraph", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your login details are only used to authenticate your Plex account.. + /// + public static string UserLogin_Paragraph_SpanHover { + get { + return ResourceManager.GetString("UserLogin_Paragraph_SpanHover", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Password. + /// + public static string UserLogin_Password { + get { + return ResourceManager.GetString("UserLogin_Password", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sign In. + /// + public static string UserLogin_SignIn { + get { + return ResourceManager.GetString("UserLogin_SignIn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Login. + /// + public static string UserLogin_Title { + get { + return ResourceManager.GetString("UserLogin_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Plex.tv Username . + /// + public static string UserLogin_Username { + get { + return ResourceManager.GetString("UserLogin_Username", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Username. + /// + public static string UserLogin_Username_Placeholder { + get { + return ResourceManager.GetString("UserLogin_Username_Placeholder", resourceCulture); + } + } + } +} diff --git a/PlexRequests.UI/Resources/UI.da.resx b/PlexRequests.UI/Resources/UI.da.resx new file mode 100644 index 000000000..c8acb402e --- /dev/null +++ b/PlexRequests.UI/Resources/UI.da.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Log ind + + + Ønsker du at se en film eller tv-show, men det er i øjeblikket ikke på Plex? Log nedenfor med dit Plex.tv brugernavn og password !! + + + Dine login-oplysninger bruges kun til at godkende din Plex konto. + + + Plex.tv Brugernavn + + + Brugernavn + + + Adgangskode + + + log på + + + Noget gik galt + + + Fuldført + + + Plex Requests + + + Søg + + + Anmodninger + + + Issues + + + STØT + + + Administrator + + + Indstillinger + + + Skift adgangskode + + + Log ud + + + Der er en ny opdatering tilgængelig! Klik + + + Dansk + + + Spansk + + + Tysk + + + Dansk + + + Portugisisk + + + Swedish + + + Italiensk + + + her + + + Hollandsk + + + Film + + + TV-shows + + + Album + + + Ønsker at se noget, der ikke i øjeblikket på Plex ?! Intet problem! Bare søge efter det nedenfor og anmode den ! + + + Søg + + + Forslag + + + Kommer snart + + + Teatre + + + Send mig en meddelelse, når emner, jeg har anmodet er blevet tilføjet + + + Gem + + + Tilgængelig + + + Forespørgsel sendt + + + Forespørgsel + + + Alle sæsoner + + + Første sæson + + + Sidste sæson + + + Vælg + + + Rapport Issue + + + Forkert lyd + + + Undertekster + + + Forkert indhold + + + Playback Issues + + + Andet + + + Track Count + + + Land + + + Årstid + + + Luk + + + Tilføj et problem! + + + Gem ændringer + + + Sæson + + + Velkommen + + + Anmodninger + + + Nedenfor kan du se dine og alle andre anmodninger, samt deres download og godkendelse status. + + + Film + + + TV-shows + + + Album + + + Slet Film + + + Godkend film + + + Slet tv-udsendelser + + + Godkend tv-udsendelser + + + Slet Music + + + Godkend Music + + + Alle + + + Godkendt + + + Godkendt + + + Tilgængelig + + + Ikke tilgængelig + + + Udgivet + + + Udgivet: + + + Ordrer + + + Filter + + + Seneste anmodninger + + + Ældste anmodninger + + + Nyeste udgivelser + + + Ældste udgivelser + + + Frigivelsesdato + + + Seasons Anmodet + + + Anmodet Af + + + Ønskede dato + + + Toggle Dropdown + + + Godkend + + + Fjern + + + ikke tilgængelig + + + Mark tilgængelig + + + Godkendt + + + Tilgængelig + + + Aktieemission + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.de.resx b/PlexRequests.UI/Resources/UI.de.resx new file mode 100644 index 000000000..6d5bd0920 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.de.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Anmelden + + + Möchten Sie einen Film oder eine TV-Show zu sehen, aber es ist derzeit nicht auf Plex? Loggen Sie sich unten mit Ihrem Plex.tv Benutzernamen und Passwort ! + + + Ihre Login-Daten werden verwendet, nur Ihr Plex Konto zu authentifizieren. + + + Plex.tv Benutzername + + + Benutzername + + + Passwort + + + Anmelden + + + Irgendetwas ist falsch gelaufen + + + Erfolg + + + Plex Requests + + + Suche + + + Anfragen + + + Probleme + + + Spenden + + + Verwaltung + + + Einstellungen + + + Passwort ändern + + + Ausloggen + + + Es gibt ein neues Update verfügbar! Klicken + + + Englisch + + + Spanisch + + + Deutsch + + + Dänisch + + + Portugiesisch + + + Schwedisch + + + Italienisch + + + hier + + + Niederländisch + + + Filme + + + SERIEN + + + Alben + + + Möchten Sie etwas zu sehen, die derzeit nicht auf Plex ist ?! Kein Problem! Suchen Sie einfach nach unten und es es wünschen ! + + + Suche + + + Vorschläge + + + Demnächst + + + Theatern + + + Senden Sie mir eine Benachrichtigung, wenn Gegenstände, die ich angefordert wurden hinzugefügt + + + Speichern + + + V ERFÜGBAR + + + angefragt + + + Angefordert + + + alle Saisonen + + + Erste Saison + + + Neueste Saison + + + Auswählen + + + Report Ausgabe + + + Falsche Audio + + + Keine Untertitel + + + Falscher Inhaltstyp. + + + Wiedergabe-Probleme + + + Sonstige + + + Track-Count + + + Land + + + Jahreszeiten + + + Schließen + + + Fügen Sie ein Problem + + + Änderungen speichern + + + Staffel + + + Herzlich willkommen + + + Anfragen + + + Im Folgenden finden Sie Ihre und alle anderen Anfragen, sowie deren Download und Genehmigungsstatus zu sehen. + + + Filme + + + SERIEN + + + Alben + + + Löschen von Filmen + + + Genehmigen-Filme + + + Löschen TV Shows + + + Genehmigen TV Shows + + + Löschen Music + + + Genehmigen Music + + + Alle + + + Genehmigt + + + Nicht bestätigt + + + V ERFÜGBAR + + + Nicht verfügbar + + + Frei + + + Frei + + + Bestellung + + + Filter! + + + Aktuelle Anfragen + + + Älteste Anfragen + + + Neueste Veröffentlichungen + + + Die ältesten Releases + + + Veröffentlichung + + + Jahreszeiten heraus + + + Beantragt von + + + angefragt + + + Toggle Dropdown + + + Genehmigen + + + Entfernen + + + Nicht verfügbar + + + V ERFÜGBAR + + + Genehmigt + + + V ERFÜGBAR + + + Problemstellung + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.es.resx b/PlexRequests.UI/Resources/UI.es.resx new file mode 100644 index 000000000..b1f7dd630 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.es.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + INICIAR SESIÓN + + + ¿Quieres ver una película o programa de televisión, pero no es actualmente en Plex? Ingresa abajo con su nombre de usuario y contraseña Plex.tv ! + + + Sus datos de acceso sólo se utilizan para autenticar su cuenta Plex. + + + Plex.tv nombre de usuario + + + Username + + + Contraseña + + + Iniciar sesión + + + Algo salió mal + + + ¡Éxito! + + + Plex Requests + + + Buscar + + + Peticiones + + + Problemas + + + Dona + + + Administración + + + Ajustes + + + Cambiar contraseña + + + Desconectarse + + + Hay una nueva actualización disponible! Hacer clic + + + Inglés + + + Spanish + + + German + + + Danés + + + Portugués + + + Sueco + + + Italiano + + + aquí + + + Holandés + + + Películas + + + Serie de TV + + + Álbumes + + + ¿Quieres ver algo que no se encuentra actualmente en Plex ?! ¡No hay problema! Sólo la búsqueda de abajo y que solicitarlo ! + + + Buscar + + + Sugerencias + + + Muy Pronto + + + En los cines + + + Envíame una notificación cuando se han añadido elementos que he solicitado + + + Ahorra + + + disponible + + + Pedido + + + Solicitud + + + Todas las temporadas + + + Primera Temporada + + + Última estación + + + Seleccionar + + + Informe del problema + + + Audio mal + + + Subtitles + + + Contenido incorrecto + + + Problemas de reproducción + + + Otro + + + El número de pistas + + + País + + + Temporadas + + + Cerrar + + + Añadir un problema + + + Guardar cambios + + + Temporada + + + Bienvenido + + + Peticiones + + + A continuación se puede ver la suya y todas las demás solicitudes, así como su descarga y aprobación de estado. + + + Películas + + + Serie de TV + + + Álbumes + + + Eliminar películas + + + Aprobar películas + + + Eliminar programas de televisión + + + Aprobar programas de televisión + + + Eliminar la música + + + Aprobar la música + + + Todo + + + Aprobado + + + No Aprovado + + + disponible + + + No disponible + + + Publicado + + + Publicado + + + Pedido + + + Filtra + + + Las últimas peticiones + + + Las solicitudes más antiguas + + + Últimos Lanzamientos + + + Estrenos más antiguas + + + Fecha de lanzamiento + + + Estaciones solicitado + + + SOLICITADO POR + + + ¡Fecha solicitada + + + Toggle Desplegable + + + Trabajo aprobado + + + Eliminar + + + Marcar como no disponible + + + disponible + + + Aprobado + + + disponible + + + emitir + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.fr.resx b/PlexRequests.UI/Resources/UI.fr.resx new file mode 100644 index 000000000..52168e37b --- /dev/null +++ b/PlexRequests.UI/Resources/UI.fr.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Se connecter + + + Vous voulez regarder un film ou la télévision, mais il est pas actuellement sur Plex? Connectez-vous ci-dessous avec votre nom d'utilisateur et mot de passe Plex.tv! + + + Vos informations de connexion sont uniquement utilisées pour authentifier votre compte Plex. + + + Plex.tv Nom d'utilisateur + + + Nom d’utilisateur + + + Mot de passe + + + Se connecter + + + Quelque-chose s'est mal passé + + + Succès + + + Plex Requests + + + Chercher + + + Requêtes + + + Sortie + + + Faire Un Don + + + d'Administration + + + Paramètres + + + Modifier le mot de passe + + + Déconnexion + + + Il y a une nouvelle mise à jour disponible! Cliquez + + + Anglais + + + Espagnol + + + Allemand + + + Danois + + + Portugais + + + Suédois (homonymie) + + + Italien + + + ici + + + Néerlandais + + + Films + + + Émissions de télévision + + + Albums + + + Vous voulez regarder quelque chose qui est pas actuellement sur Plex ?! Pas de problème! Il suffit de chercher ci-dessous et demander ! + + + Chercher + + + Suggestions + + + Bientôt disponible + + + Théâtre + + + Envoyez-moi une notification lorsque des éléments que j'ai demandés ont été ajoutés + + + Enregistrer + + + Disponible + + + Demandé + + + Requête + + + Saisons + + + Première saison + + + Dernière saison + + + Sélectionner + + + Formulaire de rapport d'incident + + + Mauvais Audio + + + Pas de sous-titres + + + Contenu erroné + + + Problèmes de lecture + + + Autre + + + Nombre de pistes + + + Pays + + + Saisons + + + Fermer + + + Ajouter une question + + + Sauvegarder les modifications + + + SAISON + + + Bienvenue + + + Requêtes + + + Ci-dessous vous pouvez voir la vôtre et toutes les autres demandes, ainsi que le téléchargement et l'état d'approbation + + + Films + + + Émissions de télévision + + + Albums + + + Supprimer Films + + + Approuver Films + + + Supprimer Séries TV + + + Approuver Séries TV + + + Supprimer la musique + + + Approuver la musique + + + Tous + + + Approuvé + + + Par voie orale pas approuvée + + + Disponible + + + Non disponible + + + Publié + + + Publié + + + Commandez + + + Filtrer + + + Dernières demandes + + + Le plus ancien demandes + + + Dernières informations + + + Le plus ancien de presse + + + Date de commercialisation + + + Seasons Demandés + + + Demandé par + + + Date demandée + + + Basculer Dropdown + + + Approuver + + + Supprimer + + + Marquer comme indisponible + + + Marquer comme disponible + + + Approuvé + + + Disponible + + + Question en litige + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.it.resx b/PlexRequests.UI/Resources/UI.it.resx new file mode 100644 index 000000000..5cb7fa1eb --- /dev/null +++ b/PlexRequests.UI/Resources/UI.it.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Accesso + + + Vuoi guardare un film o tv ma non è attualmente in Plex? Effettua il login con il tuo username e la password Plex.tv ! + + + I dati di accesso vengono utilizzati solo per autenticare l&#39;account Plex.! + + + Plex.tv Nome utente + + + Nome utente + + + Parola d'ordine + + + Accedi + + + Errore + + + Successo + + + Plex Requests + + + C'è un nuovo aggiornamento disponibile! Clic + + + Cerca! + + + Requests + + + problemi quantificati + + + Donazione + + + admin + + + Impostazioni + + + Modifica password + + + Disconnettersi + + + Inglese + + + Spagnolo + + + Tedesco + + + Danese + + + Portoghese + + + Svedese + + + Italiano + + + Qui! + + + Olandese + + + Cerca + + + Film + + + Spettacoli TV + + + Album + + + Voglia di guardare qualcosa che non è attualmente il Plex?! Non c'è problema! Basta cercare per esso qui sotto e richiederla! + + + Suggerimenti + + + Novita + + + Nei Cinema + + + Inviami una notifica quando sono stati aggiunti elementi che ho chiesto! + + + Salva + + + disponibili + + + Richiesto + + + Richiedi + + + Tutte le Stagioni + + + Prima stagione + + + Ultima stagione + + + seleziona + + + Segnala il problema + + + Audio sbagliato + + + Nessun sottotitolo + + + Contenuto sbagliato + + + Problemi di riproduzione + + + Altre + + + Conta di pista + + + Nazione + + + Stagioni + + + Chiudi + + + Aggiungere un problema + + + Salva Cambia + + + Stagione + + + Benvenuta + + + Requests + + + Qui sotto potete vedere il vostro e tutte le altre richieste, così come il loro download e l'approvazione dello stato + + + Film + + + Spettacoli TV + + + Album + + + Cancellare i filmati + + + Approva Film + + + Cancellare programmi TV + + + Approvare programmi TV + + + Eliminare la musica + + + Approva Musica + + + Tutto ciò + + + Approvato + + + Approvato + + + disponibili + + + Non disponibile + + + Rilasciato + + + Non rilasciato + + + Ordina + + + Filtro + + + Ultimi Richieste + + + I più vecchi richieste + + + Ultime uscite + + + I più vecchi uscite + + + Data di disponibilità + + + Stagioni obbligatorio + + + Richiesto + + + Richiesto + + + Imposta/rimuovi a discesa + + + Approva + + + Togliere + + + Segna non disponibile + + + Segna disponibile + + + Approvato + + + disponibili + + + Problema + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.nl.resx b/PlexRequests.UI/Resources/UI.nl.resx new file mode 100644 index 000000000..91932a6b1 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.nl.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Inloggen + + + Wilt u een film of tv-show te kijken, maar het is op het moment niet op Plex? Log hieronder in met uw gebruikersnaam en wachtwoord Plex.tv !! + + + Uw login gegevens worden alleen gebruikt om uw account te verifiëren Plex. + + + Plex.tv Gebruikersnaam + + + Gebruikersnaam + + + Wachtwoord + + + Aanmelden! + + + Er is iets fout gegaan + + + Succes + + + Plex Requests + + + Zoeken + + + Verzoeken + + + UItgaves + + + Doneren + + + Admin + + + Instellingen + + + Wachtwoord wijzigen + + + Uitloggen + + + Er is een nieuwe update beschikbaar is! Klik + + + English + + + Spanish + + + German + + + Koffiebroodje + + + Portuguese + + + Zweeds + + + Italian + + + Hier! + + + Dutch + + + Zoeken + + + Aangevraagd + + + films + + + TV programma's + + + albums + + + Wilt u kijken naar iets dat is momenteel niet op de Plex?! Geen probleem! Onderzoek enkel naar het hieronder en vraag het! + + + Suggesties + + + Binnenkort verwacht + + + In de Theaters + + + Stuur me een bericht wanneer objecten die ik heb gevraagd zijn toegevoegd + + + Opslaan + + + Beschikbaar + + + Verzoek + + + Alle seizoenen + + + Eerste seizoen + + + Laatste seizoen + + + Selecteer + + + Probleem melden + + + Verkeerde Audio + + + Geen ondertiteling + + + Verkeerde inhoud + + + Problemen met het afspelen + + + Overig + + + Spoor de graaf + + + Land + + + Seasons + + + Dichtbij + + + Een actie-item toevoegen + + + Wijzigingen opslaan + + + Seizoen + + + Welkom + + + Verzoeken + + + Hieronder vindt u de jouwe en alle andere verzoeken kan zien, evenals hun download en goedkeuring-status. + + + films + + + TV programma's + + + albums + + + Verwijder Movies + + + Goedkeuren Movies + + + Verwijder TV Shows + + + Goedkeuren TV Shows + + + Verwijderen Muziek + + + Goedkeuren Music + + + Alle + + + Goedgekeurd + + + Nog niet gestart + + + Beschikbaar + + + Niet beschikbaar + + + Released + + + Niet vrijgegeven + + + Bestel + + + Filter! + + + Laatste aanvragen + + + Oudste aanvragen + + + Nieuwste releases + + + Oudste Releases + + + Uitgiftedatum + + + Seasons gevraagd + + + Verzocht door + + + Aanvraag datum + + + Toggle Dropdown + + + Goedkeuren + + + verwijder + + + Niet beschikbaar + + + Mark beschikbaar + + + Goedgekeurd + + + Beschikbaar + + + Uitgave + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.pt.resx b/PlexRequests.UI/Resources/UI.pt.resx new file mode 100644 index 000000000..345322658 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.pt.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Entrar + + + Quer assistir a um filme ou programa de TV, mas não está atualmente em Plex? Entre abaixo com seu nome de usuário e senha Plex.tv !! + + + Seus dados de login são apenas usados ​​para autenticar sua conta Plex.! + + + Plex.tv usuário + + + Nome de usuário + + + Senha + + + Assinar em! + + + Alguma coisa saiu errada. + + + Sucesso + + + Plex Requests + + + Buscar + + + Pedidos + + + Issues + + + Doar + + + Administrativo + + + Configurações + + + Alterar Senha + + + Sair + + + Há uma nova atualização disponível! Clique + + + Inglês + + + Espanhol + + + Alemão + + + Dinamarquês + + + Português + + + Sueco + + + Italiano + + + aqui + + + Holandês + + + Filmes + + + Todas as Series de TV + + + Álbuns + + + Quer assistir algo que não está atualmente em Plex ?! Sem problemas! Basta procurá-lo abaixo e solicitá-lo !! + + + Buscar + + + Sugestões + + + Em breve! + + + somente nos cinemas + + + Envie-me uma notificação quando os itens I solicitados foram adicionados! + + + Salvar + + + Disponível + + + Requeridos + + + Pedido + + + Todas as temporadas + + + Primeira Temporada + + + Últimas estação + + + Selecione + + + relatório do problema + + + Áudio errado + + + Sem legendas + + + Conteúdo errado + + + Problemas de reprodução + + + Outra + + + Contagem pista + + + País + + + Temporadas + + + Fechar + + + Adicionar um problema + + + Salvar alterações + + + Temporada + + + Bem vinda + + + Pedidos + + + Abaixo você pode ver o seu e todos os outros pedidos, bem como o seu estado de download e aprovação. + + + Filmes + + + Todas as Series de TV + + + Álbuns + + + Apagar filmes + + + Aprovar filmes + + + Excluir programas televisivo + + + Aprovar programas televisivo + + + Excluir música + + + Aprovar a música + + + tudo + + + Aprovado + + + Aprovado + + + Disponível + + + Não disponível + + + Liberada + + + Liberada + + + de Fabricação + + + Filtro! + + + Últimos pedidos + + + Mais antigos pedidos + + + Últimos Lançamentos + + + Mais antigos Lançamentos + + + Data de liberação + + + Estações pedida + + + solicitado por + + + data solicitada + + + Alternar Menu Suspenso + + + Aprovar + + + REMOVER + + + Marcar como indisponível + + + Marcar como disponível + + + Aprovado + + + Disponível + + + Questão + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx new file mode 100644 index 000000000..ed2293355 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.resx @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Login + + + Want to watch a movie or tv show but it's not currently on Plex? + Login below with your Plex.tv username and password! + + + Your login details are only used to authenticate your Plex account. + + + Plex.tv Username + + + Username + + + Password + + + Sign In + + + Something went wrong! + + + Success! + + + Plex Requests + + + Search + + + Requests + + + Issues + + + Donate + + + Admin + + + Settings + + + Change Password + + + Logout + + + There is a new update available! Click + + + English + + + Spanish + + + German + + + Danish + + + Portuguese + + + Swedish + + + Italian + + + Here! + + + Dutch + + + Movies + + + TV Shows + + + Albums + + + Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it! + + + Search + + + Suggestions + + + Coming Soon + + + In Theaters + + + Send me a notification when items I have requested have been added + + + Save + + + Available + + + Requested + + + Request + + + All Seasons + + + First Season + + + Latest Season + + + Select + + + Report Issue + + + Wrong Audio + + + No Subtitles + + + Wrong Content + + + Playback Issues + + + Other + + + Track Count + + + Country + + + Seasons + + + Close + + + Add an issue + + + Save Changes + + + Season + + + Welcome + + + Requests + + + Below you can see yours and all other requests, as well as their download and approval status. + + + Movies + + + TV Shows + + + Albums + + + Delete Movies + + + Approve Movies + + + Delete TV Shows + + + Approve TV Shows + + + Delete Music + + + Approve Music + + + All + + + Approved + + + Not Approved + + + Available + + + Not Available + + + Released + + + Not Released + + + Order + + + Filter + + + Latest Requests + + + Oldest Requests + + + Latest Releases + + + Oldest Releases + + + Release Date + + + Seasons Requested + + + Requested By + + + Requested Date + + + Toggle Dropdown + + + Approve + + + Remove + + + Mark Unavailable + + + Mark Available + + + Approved + + + Available + + + Issue + + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI.sv.resx b/PlexRequests.UI/Resources/UI.sv.resx new file mode 100644 index 000000000..6f5040485 --- /dev/null +++ b/PlexRequests.UI/Resources/UI.sv.resx @@ -0,0 +1,405 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Logga in + + + Vill du titta på en film eller TV-show, men det är inte närvarande på Plex? Logga in nedan med Plex.tv användarnamn och lösenord !! + + + Dina inloggningsuppgifter används endast för att autentisera ditt Plex-konto. + + + Plex.tv användarnamn + + + Användarnamn + + + Lösenord + + + Logga in + + + Något gick fel + + + Lyckades + + + Plex Requests + + + Sök + + + Begäran + + + Frågor + + + Donera + + + admin + + + Inställningar + + + Byt lösenord + + + Logga ut + + + Det finns en ny uppdatering tillgänglig! Klick + + + Svenska + + + Spanska + + + Tyska + + + Danska + + + Portugisiska + + + Svenska + + + Italienska + + + Här + + + dutch + + + Fråga + + + Filmer + + + Tv program + + + Album + + + Vill titta på något som inte är närvarande på Plex ?! Inga problem! Bara söka efter den nedan och begär det ! + + + Sök + + + Förslag + + + Kommer snart + + + Teater + + + Skicka mig ett meddelande när objekt jag har begärt har lagts till! + + + Spara + + + Tillgänglig + + + Begärd + + + Alla säsonger + + + Första säsongen + + + Senaste säsongen + + + Välj + + + Rapporten fråga + + + Fel ljud + + + Inga undertexter + + + Fel innehåll + + + Uppspelningsproblem + + + Annat + + + Spår räknas + + + Land + + + Säsonger + + + Stäng + + + Lägg till en fråga + + + Spara Ändringar + + + Årstid + + + Välkommen + + + Begäran + + + Nedan kan du se din och alla andra förfrågningar, liksom deras nedladdning och godkännandestatus. + + + Filmer + + + Tv program + + + Album + + + Radera filmer + + + Godkänn filmer + + + Radera TV-program + + + Godkänna TV-program + + + Radera musik + + + Godkänn musik + + + Alla + + + Godkänd + + + Ej Godkänd + + + Tillgänglig + + + Inte tillgängligt + + + Släppte ut + + + Inte släppt + + + Beställning + + + Filter + + + Senaste förfrågningar + + + Äldsta önskemål + + + Senaste versionerna + + + Äldsta meddelanden + + + Släpptes + + + Säsonger Requested + + + Begärd av + + + Önskat datum + + + Växla rullgardinslista + + + Godkänn + + + ta bort + + + Ej tillgänglig + + + Tillgänglig + + + Godkänd + + + Tillgänglig + + + Problem + + \ No newline at end of file diff --git a/PlexRequests.UI/Startup.cs b/PlexRequests.UI/Startup.cs index 14aa38657..6f7f5fd8c 100644 --- a/PlexRequests.UI/Startup.cs +++ b/PlexRequests.UI/Startup.cs @@ -1,59 +1,59 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: Startup.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.TinyIoc; - -using NLog; - -using Owin; - -using PlexRequests.UI.Helpers; -using PlexRequests.UI.Jobs; - -namespace PlexRequests.UI -{ - public class Startup - { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - - public void Configuration(IAppBuilder app) - { - try - { - app.UseNancy(); - var scheduler = new Scheduler(); - scheduler.StartScheduler(); - } - catch (Exception exception) - { - Log.Fatal(exception); - throw; - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: Startup.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.TinyIoc; + +using NLog; + +using Owin; + +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Jobs; + +namespace PlexRequests.UI +{ + public class Startup + { + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + + public void Configuration(IAppBuilder app) + { + try + { + app.UseNancy(); + var scheduler = new Scheduler(); + scheduler.StartScheduler(); + } + catch (Exception exception) + { + Log.Fatal(exception); + throw; + } + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/PlexRequestsValidator.cs b/PlexRequests.UI/Validators/PlexRequestsValidator.cs index 6f8ba4d3e..c82817185 100644 --- a/PlexRequests.UI/Validators/PlexRequestsValidator.cs +++ b/PlexRequests.UI/Validators/PlexRequestsValidator.cs @@ -45,6 +45,7 @@ namespace PlexRequests.UI.Validators RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("landing").WithMessage("You cannot use 'landing' as this is reserved by the application."); } } } \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/Logs.cshtml b/PlexRequests.UI/Views/Admin/Logs.cshtml index 9fc58d202..8e7b2a096 100644 --- a/PlexRequests.UI/Views/Admin/Logs.cshtml +++ b/PlexRequests.UI/Views/Admin/Logs.cshtml @@ -1,123 +1,151 @@ -@using PlexRequests.UI.Helpers -@Html.Partial("_Sidebar") -@Html.LoadTableAssets() - -@{ - var baseUrl = Html.GetBaseUrl(); - var formAction = "/admin/loglevel"; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - formAction = "/" + baseUrl.ToHtmlString() + formAction; - } -} - -
-
- Logs -
-
- -
- -
-
-
-
- -
-
-
- - - - - - - - - - - -
MessageAreaLog LevelDate
-
-
- - - - \ No newline at end of file diff --git a/PlexRequests.UI/Views/Admin/SchedulerSettings.cshtml b/PlexRequests.UI/Views/Admin/SchedulerSettings.cshtml index 37e85e772..db4ed7c36 100644 --- a/PlexRequests.UI/Views/Admin/SchedulerSettings.cshtml +++ b/PlexRequests.UI/Views/Admin/SchedulerSettings.cshtml @@ -61,6 +61,15 @@ + + Please note, this will not reset the users request limit, it will just check every X hours to see if it needs to be reset. +
+ +
+ +
+
+
diff --git a/PlexRequests.UI/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index b6ad5d09d..79aa9eb47 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -1,294 +1,329 @@ -@using PlexRequests.UI.Helpers -@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase -@Html.Partial("_Sidebar") -@{ - int port; - if (Model.Port == 0) - { - port = 3579; - } - else - { - port = Model.Port; - } - - - var baseUrl = Html.GetBaseUrl(); - var formAction = "/admin"; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - formAction = "/" + baseUrl.ToHtmlString() + formAction; - } - var plexTheme = string.Empty; - var originalTheme = string.Empty; - - if (!string.IsNullOrEmpty(Model.ThemeName)) - { - plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty; - originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty; - } - else - { - plexTheme = "selected=\"selected\""; - } -} -
-
-
- Plex Request Settings -
- - -
- -
-
- You will have to restart after changing the port. - -
- - -
- -
-
- You will have to restart after changing the url base. - -
- -
- - -
-
-
-
-
- -
- -
- -
-
- -
-
- - @if (Model.SearchForMovies) - { - - } - else - { - - } - -
-
- -
-
- - @if (Model.SearchForTvShows) - { - - } - else - { - - } - -
-
-
-
- - @if (Model.SearchForMusic) - { - - } - else - { - - } - -
-
-
-
- - @if (Model.RequireMovieApproval) - { - - } - else - { - - } -
-
-
-
- - @if (Model.RequireTvShowApproval) - { - - } - else - { - - } - - -
-
-
-
- - @if (Model.RequireMusicApproval) - { - - } - else - { - - } - - -
-
- -
-
- - @if (Model.UsersCanViewOnlyOwnRequests) - { - - - } - else - { - - } - - -
-
- -
-
- - @if (Model.UsersCanViewOnlyOwnIssues) - { - - - } - else - { - - } -
-
- -
-
- - @if (Model.CollectAnalyticData) - { - - - } - else - { - - } -
-
- - - -

A comma separated list of users whose requests do not require approval.

-
- -
- -
-
- - @*
- -
- -
-
//TODO: Need to implement this*@ - -
-
-
-
- -
-
-
-
-
- - \ No newline at end of file diff --git a/PlexRequests.UI/Views/Issues/Details.cshtml b/PlexRequests.UI/Views/Issues/Details.cshtml index 53a6c8994..4c4f06178 100644 --- a/PlexRequests.UI/Views/Issues/Details.cshtml +++ b/PlexRequests.UI/Views/Issues/Details.cshtml @@ -42,7 +42,7 @@ } @if (Model.IssueStatus == IssueStatus.ResolvedIssue) { -
+
diff --git a/PlexRequests.UI/Views/Issues/Index.cshtml b/PlexRequests.UI/Views/Issues/Index.cshtml index 2c18ccf41..9f8b15bd1 100644 --- a/PlexRequests.UI/Views/Issues/Index.cshtml +++ b/PlexRequests.UI/Views/Issues/Index.cshtml @@ -26,7 +26,7 @@

Type

-

Issue's

+

Issues

@@ -61,7 +61,7 @@
- - - - - - - -@Html.LoadRequestAssets() - - +@using Nancy.Security +@using PlexRequests.UI.Helpers +@using PlexRequests.UI.Resources +@{ + var baseUrl = Html.GetBaseUrl(); + var formAction = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + formAction = "/" + baseUrl.ToHtmlString(); + } +} +
+

@UI.Requests_Title

+

@UI.Requests_Paragraph

+
+ + + +
+ + +
+
+
+
+
+ @if (Context.CurrentUser.IsAuthenticated()) //TODO replace with IsAdmin + { + @if (Model.SearchForMovies) + { + + + } + @if (Model.SearchForTvShows) + { + + + } + @if (Model.SearchForMusic) + { + + + } + } +
+ + +
+
+
+ @if (Model.SearchForMovies) + { + + +
+ +
+
+ +
+
+
+ } + + @if (Model.SearchForTvShows) + { + +
+ +
+
+ +
+
+
+ } + + @if (Model.SearchForMusic) + { + +
+ +
+
+ +
+
+
+ } +
+ +
+ + + + + + + + +@Html.LoadRequestAssets() + + diff --git a/PlexRequests.UI/Views/Search/Index.cshtml b/PlexRequests.UI/Views/Search/Index.cshtml index 6a5be0006..a37b099ae 100644 --- a/PlexRequests.UI/Views/Search/Index.cshtml +++ b/PlexRequests.UI/Views/Search/Index.cshtml @@ -1,326 +1,327 @@ -@using PlexRequests.UI.Helpers -@{ - var baseUrl = Html.GetBaseUrl(); - var url = string.Empty; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - url = "/" + baseUrl.ToHtmlString(); - } -} -
-

Search

-

Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!

-
- - - - - - -
- @if (Model.SearchForMovies) - { - -
- -
-
- -
-
-
- } - - - @if (Model.SearchForTvShows) - { - -
-
- -
- -
-
-
-
- -
-
-
- } - - @if (Model.SearchForMusic) - { - -
-
- -
- -
-
-
-
- -
-
-
- } - - -
-
-
-
-
- - -
-
-
-
- - -
-
-
-
- -
-
-
-
-
-
-
- - - - - - - - - - - - - - - -@Html.LoadSearchAssets() +@using PlexRequests.UI.Helpers +@using PlexRequests.UI.Resources +@{ + var baseUrl = Html.GetBaseUrl(); + var url = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + url = "/" + baseUrl.ToHtmlString(); + } +} +
+

@UI.Search_Title

+

@UI.Search_Paragraph

+
+ + + + + + +
+ @if (Model.SearchForMovies) + { + + + } + + + @if (Model.SearchForTvShows) + { + +
+
+ +
+ +
+
+
+
+ +
+
+
+ } + + @if (Model.SearchForMusic) + { + +
+
+ +
+ +
+
+
+
+ +
+
+
+ } + + +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +@Html.LoadSearchAssets() diff --git a/PlexRequests.UI/Views/Shared/Blank.cshtml b/PlexRequests.UI/Views/Shared/Blank.cshtml index d5b1f73eb..cafdf0d63 100644 --- a/PlexRequests.UI/Views/Shared/Blank.cshtml +++ b/PlexRequests.UI/Views/Shared/Blank.cshtml @@ -17,7 +17,7 @@ Plex Requests - + @Html.LoadAnalytics() @Html.LoadAssets() diff --git a/PlexRequests.UI/Views/Shared/_Layout.cshtml b/PlexRequests.UI/Views/Shared/_Layout.cshtml index fee4537f8..3cc50a93b 100644 --- a/PlexRequests.UI/Views/Shared/_Layout.cshtml +++ b/PlexRequests.UI/Views/Shared/_Layout.cshtml @@ -1,157 +1,191 @@ -@using Nancy.Security -@using Nancy.Session -@using PlexRequests.UI.Helpers -@using PlexRequests.UI.Models -@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase -@{ - var baseUrl = Html.GetBaseUrl(); - var url = string.Empty; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - url = "/" + baseUrl.ToHtmlString(); - } -} - - - - Plex Requests - - - @Html.LoadAnalytics() - @Html.LoadAssets() - - - - - - -
- @RenderBody() -
-
- - - -
- - - \ No newline at end of file diff --git a/PlexRequests.UI/Views/UserLogin/Index.cshtml b/PlexRequests.UI/Views/UserLogin/Index.cshtml index ff2d574ff..0c04b8f7b 100644 --- a/PlexRequests.UI/Views/UserLogin/Index.cshtml +++ b/PlexRequests.UI/Views/UserLogin/Index.cshtml @@ -1,70 +1,70 @@ -@using PlexRequests.UI.Helpers -
-

Login

-
-

- Want to watch a movie or tv show but it's not currently on Plex? - Login below with your Plex.tv username and password! -

-
-
-
-
- -
-
- -
-
-
- @if (Model.UsePassword) - { -
-
- -
-
- -
-
-
- } - - -
-
- - \ No newline at end of file diff --git a/PlexRequests.UI/app.config b/PlexRequests.UI/app.config index 747d7cb7a..1de7c9330 100644 --- a/PlexRequests.UI/app.config +++ b/PlexRequests.UI/app.config @@ -1,50 +1,50 @@ - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/PlexRequests.sln b/PlexRequests.sln index 93ac3b1f4..3aa64d8b8 100644 --- a/PlexRequests.sln +++ b/PlexRequests.sln @@ -1,102 +1,105 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI", "PlexRequests.UI\PlexRequests.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api", "PlexRequests.Api\PlexRequests.Api.csproj", "{8CB8D235-2674-442D-9C6A-35FCAEEB160D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Interfaces", "PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj", "{95834072-A675-415D-AA8F-877C91623810}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core", "PlexRequests.Core\PlexRequests.Core.csproj", "{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Store", "PlexRequests.Store\PlexRequests.Store.csproj", "{92433867-2B7B-477B-A566-96C382427525}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4BC839C-B8FF-48BE-B22E-536A0A0A81A5}" - ProjectSection(SolutionItems) = preProject - .travis.yml = .travis.yml - appveyor.yml = appveyor.yml - LICENSE = LICENSE - README.md = README.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers", "PlexRequests.Helpers\PlexRequests.Helpers.csproj", "{1252336D-42A3-482A-804C-836E60173DFA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI.Tests", "PlexRequests.UI.Tests\PlexRequests.UI.Tests.csproj", "{A930E2CF-79E2-45F9-B06A-9A719A254CE4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core.Tests", "PlexRequests.Core.Tests\PlexRequests.Core.Tests.csproj", "{FCFECD5D-47F6-454D-8692-E27A921BE655}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services", "PlexRequests.Services\PlexRequests.Services.csproj", "{566EFA49-68F8-4716-9693-A6B3F2624DEA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Models", "PlexRequests.Api.Models\PlexRequests.Api.Models.csproj", "{CB37A5F8-6DFC-4554-99D3-A42B502E4591}" -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.Updater", "PlexRequests.Updater\PlexRequests.Updater.csproj", "{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.Build.0 = Release|Any CPU - {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.Build.0 = Release|Any CPU - {95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.Build.0 = Debug|Any CPU - {95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.ActiveCfg = Release|Any CPU - {95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.Build.0 = Release|Any CPU - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.Build.0 = Release|Any CPU - {92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.Build.0 = Release|Any CPU - {1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.Build.0 = Release|Any CPU - {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.Build.0 = Release|Any CPU - {FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.Build.0 = Release|Any CPU - {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.Build.0 = Release|Any CPU - {CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.ActiveCfg = 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.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 - {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.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 - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25123.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI", "PlexRequests.UI\PlexRequests.UI.csproj", "{68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api", "PlexRequests.Api\PlexRequests.Api.csproj", "{8CB8D235-2674-442D-9C6A-35FCAEEB160D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Interfaces", "PlexRequests.Api.Interfaces\PlexRequests.Api.Interfaces.csproj", "{95834072-A675-415D-AA8F-877C91623810}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core", "PlexRequests.Core\PlexRequests.Core.csproj", "{DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Store", "PlexRequests.Store\PlexRequests.Store.csproj", "{92433867-2B7B-477B-A566-96C382427525}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F4BC839C-B8FF-48BE-B22E-536A0A0A81A5}" + ProjectSection(SolutionItems) = preProject + .travis.yml = .travis.yml + appveyor.yml = appveyor.yml + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers", "PlexRequests.Helpers\PlexRequests.Helpers.csproj", "{1252336D-42A3-482A-804C-836E60173DFA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.UI.Tests", "PlexRequests.UI.Tests\PlexRequests.UI.Tests.csproj", "{A930E2CF-79E2-45F9-B06A-9A719A254CE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Core.Tests", "PlexRequests.Core.Tests\PlexRequests.Core.Tests.csproj", "{FCFECD5D-47F6-454D-8692-E27A921BE655}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Services", "PlexRequests.Services\PlexRequests.Services.csproj", "{566EFA49-68F8-4716-9693-A6B3F2624DEA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Api.Models", "PlexRequests.Api.Models\PlexRequests.Api.Models.csproj", "{CB37A5F8-6DFC-4554-99D3-A42B502E4591}" +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.Updater", "PlexRequests.Updater\PlexRequests.Updater.csproj", "{EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexRequests.Helpers.Tests", "PlexRequests.Helpers.Tests\PlexRequests.Helpers.Tests.csproj", "{0E6395D3-B074-49E8-898D-0EB99E507E0E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68F5F5F3-B8BB-4911-875F-6F00AAE04EA6}.Release|Any CPU.Build.0 = Release|Any CPU + {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CB8D235-2674-442D-9C6A-35FCAEEB160D}.Release|Any CPU.Build.0 = Release|Any CPU + {95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95834072-A675-415D-AA8F-877C91623810}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95834072-A675-415D-AA8F-877C91623810}.Release|Any CPU.Build.0 = Release|Any CPU + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD7DC444-D3BF-4027-8AB9-EFC71F5EC581}.Release|Any CPU.Build.0 = Release|Any CPU + {92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92433867-2B7B-477B-A566-96C382427525}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92433867-2B7B-477B-A566-96C382427525}.Release|Any CPU.Build.0 = Release|Any CPU + {1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1252336D-42A3-482A-804C-836E60173DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1252336D-42A3-482A-804C-836E60173DFA}.Release|Any CPU.Build.0 = Release|Any CPU + {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A930E2CF-79E2-45F9-B06A-9A719A254CE4}.Release|Any CPU.Build.0 = Release|Any CPU + {FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCFECD5D-47F6-454D-8692-E27A921BE655}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCFECD5D-47F6-454D-8692-E27A921BE655}.Release|Any CPU.Build.0 = Release|Any CPU + {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {566EFA49-68F8-4716-9693-A6B3F2624DEA}.Release|Any CPU.Build.0 = Release|Any CPU + {CB37A5F8-6DFC-4554-99D3-A42B502E4591}.Debug|Any CPU.ActiveCfg = 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.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 + {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBE6FC1C-7B4B-47E9-AF54-0EE0604A2BE5}.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 + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + RESX_PrefixTranslations = False + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md index 521df81d6..d5b424b76 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,58 @@ -# Plex Requests .NET! - -[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex) -[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net) -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open") -[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net) -[![Stories in Progress](https://badge.waffle.io/tidusjar/PlexRequests.Net.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/PlexRequests.Net) - -This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy! -I wanted to write a similar application in .Net! - -# Features - -* Movie and TV Show searching, can't find something on Plex? Just request it! -* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports! -* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)! -* Secure authentication so you don't have to worry about those script kiddies -* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?! -* We have allowed the ability for a user to add a custom note on a request -* It automatically update the status of requests when they are available on Plex -* Slick, responsive and mobile friendly UI -* Headphones integration! -* Ability to run with a reverse proxy! - -# Preview () - -![Preview](http://i.imgur.com/DgwkIsW.gif) - -#Installation -Download the latest [Release](https://github.com/tidusjar/PlexRequests.Net/releases). -Extract the .zip file (Unblock if on Windows! Right Click > Properties > Unblock). -Just run `PlexRequests.exe`! (Mono compatible `mono PlexRequests.exe`) - -# FAQ -Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ) - -# Docker - -Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile: - -# Contributors - -We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it! - -Please feed free to submit a pull request! - -# Donation -If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet) - -## A massive thanks to everyone below for all their help! - -[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat, [EddiYo](https://github.com/EddiYo), [SaskiFX](https://github.com/SaskiFX), [zenjabba](https://github.com/zenjabba) - -## Stats -[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput) + +![](http://i.imgur.com/s4nswSA.png?1) +____ +[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex) +[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net) +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open") +[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net) +[![Stories in Progress](https://badge.waffle.io/tidusjar/PlexRequests.Net.svg?label=in progress&title=In Progress)](http://waffle.io/tidusjar/PlexRequests.Net) + +This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy! +I wanted to write a similar application in .Net! + +# Features + +* Movie and TV Show searching, can't find something on Plex? Just request it! +* Notifications! Get notified via Email, Pushbullet and Pushover for new requests and issue reports! +* Send your TV Shows to either [Sonarr](https://sonarr.tv/) or [SickRage](http://www.sickrage.ca/)! +* Secure authentication so you don't have to worry about those script kiddies +* We check to see if the request is already in Plex, if it's already in Plex then why you requesting it?! +* We have allowed the ability for a user to add a custom note on a request +* It automatically update the status of requests when they are available on Plex +* Slick, responsive and mobile friendly UI +* Headphones integration! +* Ability to run with a reverse proxy! + +# Preview + +![Preview](http://i.imgur.com/DgwkIsW.gif) + +#Installation + +[Windows Guide!](http://www.htpcguides.com/install-plex-requests-net-windows-system-service/) +[Ubuntu Guide!](http://www.htpcguides.com/install-plex-requests-net-ubuntu-14-x/) + +# FAQ +Do you have an issue or a question? if so check out our [FAQ!](https://github.com/tidusjar/PlexRequests.Net/wiki/FAQ) + +# Docker + +Looking for a Docker Image? Well [rogueosb](https://github.com/rogueosb/) has created a docker image for us, You can find it [here](https://github.com/rogueosb/docker-plexrequestsnet) :smile: + +# Contributors + +We are looking for any contributions to the project! Just pick up a task, if you have any questions ask and i'll get straight on it! + +Please feed free to submit a pull request! + +# Donation +If you feel like donating you can [here!](https://paypal.me/PlexRequestsNet) + +## A massive thanks to everyone below for all their help! + +[heartisall](https://github.com/heartisall), [Stuke00](https://github.com/Stuke00), [shiitake](https://github.com/shiitake), [Drewster727](https://github.com/Drewster727), Majawat, [EddiYo](https://github.com/EddiYo), [SaskiFX](https://github.com/SaskiFX), [zenjabba](https://github.com/zenjabba) + +## Stats +[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput) diff --git a/appveyor.yml b/appveyor.yml index 7cd7c24cd..a23086f4c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,26 +1,26 @@ -version: 1.8.{build} -configuration: Release -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '1.8.0' - assembly_file_version: '{version}' - assembly_informational_version: '1.8.0' -before_build: -- cmd: appveyor-retry nuget restore -build: - verbosity: minimal -after_build: -- cmd: >- - 7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\ - - appveyor PushArtifact PlexRequests.zip - -deploy: -- provider: GitHub - release: PlexRequests v$(appveyor_build_version) - auth_token: - secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp - draft: true - on: - branch: master +version: 1.8.{build} +configuration: Release +assembly_info: + patch: true + file: '**\AssemblyInfo.*' + assembly_version: '1.8.3' + assembly_file_version: '{version}' + assembly_informational_version: '1.8.3' +before_build: +- cmd: appveyor-retry nuget restore +build: + verbosity: minimal +after_build: +- cmd: >- + 7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\ + + appveyor PushArtifact PlexRequests.zip + +deploy: +- provider: GitHub + release: PlexRequests v$(appveyor_build_version) + auth_token: + secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp + draft: true + on: + branch: master