diff --git a/PlexRequests.Api/ApiRequest.cs b/PlexRequests.Api/ApiRequest.cs
index 704880f9b..8cb023ea6 100644
--- a/PlexRequests.Api/ApiRequest.cs
+++ b/PlexRequests.Api/ApiRequest.cs
@@ -63,6 +63,7 @@ namespace PlexRequests.Api
Log.Trace("Api Content Response:");
Log.Trace(response.Content);
+
if (response.ErrorException != null)
{
var message = "Error retrieving response. Check inner details for more info.";
diff --git a/PlexRequests.Core/PlexRequests.Core.csproj b/PlexRequests.Core/PlexRequests.Core.csproj
index e4448d873..2e2d1b3a1 100644
--- a/PlexRequests.Core/PlexRequests.Core.csproj
+++ b/PlexRequests.Core/PlexRequests.Core.csproj
@@ -86,6 +86,7 @@
+
diff --git a/PlexRequests.Core/SettingModels/PlexSettings.cs b/PlexRequests.Core/SettingModels/PlexSettings.cs
index e83e61f4d..09be5fb15 100644
--- a/PlexRequests.Core/SettingModels/PlexSettings.cs
+++ b/PlexRequests.Core/SettingModels/PlexSettings.cs
@@ -24,6 +24,9 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
+
+using Newtonsoft.Json;
+
namespace PlexRequests.Core.SettingModels
{
public sealed class PlexSettings : ExternalSettings
@@ -36,5 +39,6 @@ namespace PlexRequests.Core.SettingModels
public bool EnableTvEpisodeSearching { get; set; }
public string PlexAuthToken { get; set; }
+ public string MachineIdentifier { get; set; }
}
}
\ No newline at end of file
diff --git a/PlexRequests.Core/SettingModels/RequestSettings.cs b/PlexRequests.Core/SettingModels/RequestSettings.cs
new file mode 100644
index 000000000..5a9140947
--- /dev/null
+++ b/PlexRequests.Core/SettingModels/RequestSettings.cs
@@ -0,0 +1,57 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2016 Jamie Rees
+// File: RequestSettings.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace PlexRequests.Core.SettingModels
+{
+ public sealed class RequestSettings : Settings
+ {
+ public OrderType Order { get; set; }
+ public List Filters { get; set; }
+ }
+
+ public enum OrderType
+ {
+ NewRequests,
+ OldRequests,
+ NewReleases,
+ OldReleases
+ }
+
+ public enum FilterType
+ {
+ // ALL is not here, it's managed in the angular controller
+ Approved,
+ NotApproved,
+ Available,
+ NotAvailable,
+ Released,
+ NotReleased
+ }
+}
\ No newline at end of file
diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs
index 50b27dd2d..dac15a731 100644
--- a/PlexRequests.Core/Setup.cs
+++ b/PlexRequests.Core/Setup.cs
@@ -26,6 +26,7 @@
#endregion
using System;
+using System.Linq;
using System.Text.RegularExpressions;
using Mono.Data.Sqlite;
@@ -66,6 +67,11 @@ namespace PlexRequests.Core
{
MigrateToVersion1900();
}
+
+ if(version > 1899 && version <= 1910)
+ {
+ MigrateToVersion1910();
+ }
}
return Db.DbConnection().ConnectionString;
@@ -244,5 +250,30 @@ namespace PlexRequests.Core
Log.Error(e);
}
}
+
+ ///
+ /// Migrates to version1910.
+ ///
+ public void MigrateToVersion1910()
+ {
+ try
+ {
+ // Get the new machine Identifier
+ var settings = new SettingsServiceV2(new SettingsJsonRepository(Db, new MemoryCacheProvider()));
+ var plex = settings.GetSettings();
+ if (!string.IsNullOrEmpty(plex.PlexAuthToken))
+ {
+ var api = new PlexApi(new ApiRequest());
+ var server = api.GetServer(plex.PlexAuthToken); // Get the server info
+ plex.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plex.PlexAuthToken)?.MachineIdentifier;
+
+ settings.SaveSettings(plex); // Save the new settings
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
}
}
diff --git a/PlexRequests.Core/UserMapper.cs b/PlexRequests.Core/UserMapper.cs
index 5be4a868a..3c15d548e 100644
--- a/PlexRequests.Core/UserMapper.cs
+++ b/PlexRequests.Core/UserMapper.cs
@@ -99,7 +99,7 @@ namespace PlexRequests.Core
return users.Any();
}
- private Guid? CreateUser(string username, string password, string[] claims = default(string[]), UserProperties properties = null)
+ public Guid? CreateUser(string username, string password, string[] claims = default(string[]), UserProperties properties = null)
{
var salt = PasswordHasher.GenerateSalt();
@@ -134,6 +134,13 @@ namespace PlexRequests.Core
return CreateUser(username, password, new[] { UserClaims.User }, properties);
}
+
+ public IEnumerable GetAllClaims()
+ {
+ var properties = typeof(UserClaims).GetConstantsValues();
+ return properties;
+ }
+
public bool UpdatePassword(string username, string oldPassword, string newPassword)
{
var users = Repo.GetAll();
@@ -175,6 +182,8 @@ namespace PlexRequests.Core
public interface ICustomUserMapper
{
+ Guid? CreateUser(string username, string password, string[] claims, UserProperties props);
+ IEnumerable GetAllClaims();
IEnumerable GetUsers();
Task> GetUsersAsync();
UsersModel GetUser(Guid userId);
diff --git a/PlexRequests.Helpers.Tests/PlexHelperTests.cs b/PlexRequests.Helpers.Tests/PlexHelperTests.cs
index 6f09d6c39..33a1bf1a3 100644
--- a/PlexRequests.Helpers.Tests/PlexHelperTests.cs
+++ b/PlexRequests.Helpers.Tests/PlexHelperTests.cs
@@ -61,6 +61,12 @@ namespace PlexRequests.Helpers.Tests
return PlexHelper.GetSeasonNumberFromTitle(title);
}
+ [TestCaseSource(nameof(MediaUrls))]
+ public string GetPlexMediaUrlTest(string machineId, string mediaId)
+ {
+ return PlexHelper.GetPlexMediaUrl(machineId, mediaId);
+ }
+
private static IEnumerable PlexGuids
{
get
@@ -75,6 +81,15 @@ namespace PlexRequests.Helpers.Tests
}
}
+ private static IEnumerable MediaUrls
+ {
+ get
+ {
+ yield return new TestCaseData("abcd","99").Returns("https://app.plex.tv/web/app#!/server/abcd/details/%2Flibrary%2Fmetadata%2F99").SetName("Test 1");
+ yield return new TestCaseData("a54d1db669799308cd704b791f331eca6648b952", "51").Returns("https://app.plex.tv/web/app#!/server/a54d1db669799308cd704b791f331eca6648b952/details/%2Flibrary%2Fmetadata%2F51").SetName("Test 2");
+ }
+ }
+
private static IEnumerable SeasonNumbers
{
get
diff --git a/PlexRequests.Helpers.Tests/TypeHelperTests.cs b/PlexRequests.Helpers.Tests/TypeHelperTests.cs
index 295b955ad..1d93c375f 100644
--- a/PlexRequests.Helpers.Tests/TypeHelperTests.cs
+++ b/PlexRequests.Helpers.Tests/TypeHelperTests.cs
@@ -26,7 +26,7 @@
#endregion
using System;
using System.Collections.Generic;
-
+using System.Linq;
using NUnit.Framework;
using PlexRequests.Store;
@@ -42,6 +42,14 @@ namespace PlexRequests.Helpers.Tests
return input.GetPropertyNames();
}
+ [Test]
+ public void GetConstantsTest()
+ {
+ var consts = typeof(UserClaims).GetConstantsValues();
+ Assert.That(consts.Contains("Admin"),Is.True);
+ Assert.That(consts.Contains("PowerUser"),Is.True);
+ Assert.That(consts.Contains("User"),Is.True);
+ }
private static IEnumerable TypeData
{
diff --git a/PlexRequests.Helpers/PlexHelper.cs b/PlexRequests.Helpers/PlexHelper.cs
index baafcd451..1e186ddba 100644
--- a/PlexRequests.Helpers/PlexHelper.cs
+++ b/PlexRequests.Helpers/PlexHelper.cs
@@ -95,6 +95,13 @@ namespace PlexRequests.Helpers
return 0;
}
+
+ public static string GetPlexMediaUrl(string machineId, string mediaId)
+ {
+ var url =
+ $"https://app.plex.tv/web/app#!/server/{machineId}/details/%2Flibrary%2Fmetadata%2F{mediaId}";
+ return url;
+ }
}
public class EpisodeModelHelper
diff --git a/PlexRequests.Helpers/TypeHelper.cs b/PlexRequests.Helpers/TypeHelper.cs
index 27a108bef..77eae6360 100644
--- a/PlexRequests.Helpers/TypeHelper.cs
+++ b/PlexRequests.Helpers/TypeHelper.cs
@@ -25,7 +25,9 @@
// ************************************************************************/
#endregion
using System;
+using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
namespace PlexRequests.Helpers
{
@@ -35,5 +37,19 @@ namespace PlexRequests.Helpers
{
return t.GetProperties().Select(x => x.Name).ToArray();
}
+
+ public static IEnumerable GetConstants(this Type type)
+ {
+ var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
+
+ return fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly);
+ }
+
+ public static IEnumerable GetConstantsValues(this Type type) where T : class
+ {
+ var fieldInfos = GetConstants(type);
+
+ return fieldInfos.Select(fi => fi.GetRawConstantValue() as T);
+ }
}
}
\ No newline at end of file
diff --git a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs
index 5b8ba361f..801af7509 100644
--- a/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs
+++ b/PlexRequests.Services.Tests/PlexAvailabilityCheckerTests.cs
@@ -42,6 +42,7 @@ using PlexRequests.Services.Interfaces;
using PlexRequests.Helpers;
using PlexRequests.Services.Jobs;
using PlexRequests.Services.Models;
+using PlexRequests.Services.Notification;
using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
@@ -63,6 +64,11 @@ namespace PlexRequests.Services.Tests
private Mock JobRec { get; set; }
private Mock> NotifyUsers { get; set; }
private Mock> PlexEpisodes { get; set; }
+ private Mock Engine
+ {
+ get;
+ set;
+ }
[SetUp]
public void Setup()
@@ -76,7 +82,8 @@ namespace PlexRequests.Services.Tests
NotifyUsers = new Mock>();
PlexEpisodes = new Mock>();
JobRec = new Mock();
- Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object);
+ Engine = new Mock();
+ Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object, Engine.Object);
}
@@ -212,8 +219,7 @@ namespace PlexRequests.Services.Tests
new PlexEpisodes {EpisodeNumber = 1, ShowTitle = "The Flash",ProviderId = 23.ToString(), SeasonNumber = 1, EpisodeTitle = "Pilot"}
};
PlexEpisodes.Setup(x => x.Custom(It.IsAny>>())).Returns(expected);
- Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object);
-
+
var result = Checker.IsEpisodeAvailable(providerId, season, episode);
return result;
@@ -242,6 +248,7 @@ namespace PlexRequests.Services.Tests
}
});
CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedMovies);
+ SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create());
var movies = Checker.GetPlexMovies();
Assert.That(movies.Any(x => x.ProviderId == "1212"));
@@ -258,6 +265,7 @@ namespace PlexRequests.Services.Tests
new Directory1 {Type = "show", Title = "title1", Year = "2016", ProviderId = "1212", Seasons = new List()}
}
});
+ SettingsMock.Setup(x => x.GetSettings()).Returns(F.Create());
CacheMock.Setup(x => x.Get>(CacheKeys.PlexLibaries)).Returns(cachedTv);
var movies = Checker.GetPlexTvShows();
@@ -268,8 +276,6 @@ namespace PlexRequests.Services.Tests
public async Task GetAllPlexEpisodes()
{
PlexEpisodes.Setup(x => x.GetAllAsync()).ReturnsAsync(F.CreateMany().ToList());
- Checker = new PlexAvailabilityChecker(SettingsMock.Object, RequestMock.Object, PlexMock.Object, CacheMock.Object, NotificationMock.Object, JobRec.Object, NotifyUsers.Object, PlexEpisodes.Object);
-
var episodes = await Checker.GetEpisodes();
Assert.That(episodes.Count(), Is.GreaterThan(0));
diff --git a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs
index 8992e6545..d966e2b5f 100644
--- a/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs
+++ b/PlexRequests.Services/Interfaces/IAvailabilityChecker.cs
@@ -42,6 +42,9 @@ namespace PlexRequests.Services.Interfaces
List GetPlexAlbums();
bool IsAlbumAvailable(PlexAlbum[] plexAlbums, string title, string year, string artist);
bool IsEpisodeAvailable(string theTvDbId, int season, int episode);
+ PlexAlbum GetAlbum(PlexAlbum[] plexAlbums, string title, string year, string artist);
+ PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null);
+ PlexTvShow GetTvShow(PlexTvShow[] plexShows, string title, string year, string providerId = null, int[] seasons = null);
///
/// Gets the episode's stored in the cache.
///
diff --git a/PlexRequests.Services/Interfaces/INotificationEngine.cs b/PlexRequests.Services/Interfaces/INotificationEngine.cs
new file mode 100644
index 000000000..ba53eb9bf
--- /dev/null
+++ b/PlexRequests.Services/Interfaces/INotificationEngine.cs
@@ -0,0 +1,39 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2016 Jamie Rees
+// File: INotificationEngine.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 PlexRequests.Store;
+
+namespace PlexRequests.Services.Interfaces
+{
+ public interface INotificationEngine
+ {
+ Task NotifyUsers(IEnumerable modelChanged, string apiKey);
+ Task NotifyUsers(RequestedModel modelChanged, string apiKey);
+ }
+}
\ No newline at end of file
diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs
index e58a18c17..c8b46b32f 100644
--- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs
+++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs
@@ -53,7 +53,7 @@ namespace PlexRequests.Services.Jobs
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
{
public PlexAvailabilityChecker(ISettingsService plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
- INotificationService notify, IJobRecord rec, IRepository users, IRepository repo)
+ INotificationService notify, IJobRecord rec, IRepository users, IRepository repo, INotificationEngine e)
{
Plex = plexSettings;
RequestService = request;
@@ -63,6 +63,7 @@ namespace PlexRequests.Services.Jobs
Job = rec;
UserNotifyRepo = users;
EpisodeRepo = repo;
+ NotificationEngine = e;
}
private ISettingsService Plex { get; }
@@ -74,6 +75,9 @@ namespace PlexRequests.Services.Jobs
private INotificationService Notification { get; }
private IJobRecord Job { get; }
private IRepository UserNotifyRepo { get; }
+ private INotificationEngine NotificationEngine { get; }
+
+
public void CheckAndUpdateAll()
{
var plexSettings = Plex.GetSettings();
@@ -148,7 +152,7 @@ namespace PlexRequests.Services.Jobs
if (modifiedModel.Any())
{
- NotifyUsers(modifiedModel, plexSettings.PlexAuthToken);
+ NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken);
RequestService.BatchUpdate(modifiedModel);
}
@@ -158,6 +162,7 @@ namespace PlexRequests.Services.Jobs
public List GetPlexMovies()
{
+ var settings = Plex.GetSettings();
var movies = new List();
var libs = Cache.Get>(CacheKeys.PlexLibaries);
if (libs != null)
@@ -175,6 +180,7 @@ namespace PlexRequests.Services.Jobs
ReleaseYear = video.Year,
Title = video.Title,
ProviderId = video.ProviderId,
+ Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, video.RatingKey)
}));
}
}
@@ -182,6 +188,12 @@ namespace PlexRequests.Services.Jobs
}
public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, string providerId = null)
+ {
+ var movie = GetMovie(plexMovies, title, year, providerId);
+ return movie != null;
+ }
+
+ public PlexMovie GetMovie(PlexMovie[] plexMovies, string title, string year, string providerId = null)
{
var advanced = !string.IsNullOrEmpty(providerId);
foreach (var movie in plexMovies)
@@ -191,20 +203,21 @@ namespace PlexRequests.Services.Jobs
if (!string.IsNullOrEmpty(movie.ProviderId) &&
movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
- return true;
+ return movie;
}
}
if (movie.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
movie.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
- return true;
+ return movie;
}
}
- return false;
+ return null;
}
public List GetPlexTvShows()
{
+ var settings = Plex.GetSettings();
var shows = new List();
var libs = Cache.Get>(CacheKeys.PlexLibaries);
if (libs != null)
@@ -224,7 +237,9 @@ namespace PlexRequests.Services.Jobs
Title = x.Title,
ReleaseYear = x.Year,
ProviderId = x.ProviderId,
- Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray()
+ Seasons = x.Seasons?.Select(d => PlexHelper.GetSeasonNumberFromTitle(d.Title)).ToArray(),
+ Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey)
+
}));
}
}
@@ -232,6 +247,14 @@ namespace PlexRequests.Services.Jobs
}
public bool IsTvShowAvailable(PlexTvShow[] plexShows, string title, string year, string providerId = null, int[] seasons = null)
+ {
+ var show = GetTvShow(plexShows, title, year, providerId, seasons);
+ return show != null;
+ }
+
+
+ public PlexTvShow GetTvShow(PlexTvShow[] plexShows, string title, string year, string providerId = null,
+ int[] seasons = null)
{
var advanced = !string.IsNullOrEmpty(providerId);
foreach (var show in plexShows)
@@ -242,23 +265,23 @@ namespace PlexRequests.Services.Jobs
{
if (seasons.Any(season => show.Seasons.Contains(season)))
{
- return true;
+ return show;
}
- return false;
+ return null;
}
if (!string.IsNullOrEmpty(show.ProviderId) &&
show.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase))
{
- return true;
+ return show;
}
}
if (show.Title.Equals(title, StringComparison.CurrentCultureIgnoreCase) &&
show.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase))
{
- return true;
+ return show;
}
}
- return false;
+ return null;
}
public bool IsEpisodeAvailable(string theTvDbId, int season, int episode)
@@ -328,6 +351,7 @@ namespace PlexRequests.Services.Jobs
public List GetPlexAlbums()
{
+ var settings = Plex.GetSettings();
var albums = new List();
var libs = Cache.Get>(CacheKeys.PlexLibaries);
if (libs != null)
@@ -344,7 +368,8 @@ namespace PlexRequests.Services.Jobs
{
Title = x.Title,
ReleaseYear = x.Year,
- Artist = x.ParentTitle
+ Artist = x.ParentTitle,
+ Url = PlexHelper.GetPlexMediaUrl(settings.MachineIdentifier, x.RatingKey)
}));
}
}
@@ -355,7 +380,13 @@ namespace PlexRequests.Services.Jobs
{
return plexAlbums.Any(x =>
x.Title.Contains(title) &&
- //x.ReleaseYear.Equals(year, StringComparison.CurrentCultureIgnoreCase) &&
+ x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase));
+ }
+
+ public PlexAlbum GetAlbum(PlexAlbum[] plexAlbums, string title, string year, string artist)
+ {
+ return plexAlbums.FirstOrDefault(x =>
+ x.Title.Contains(title) &&
x.Artist.Equals(artist, StringComparison.CurrentCultureIgnoreCase));
}
@@ -462,63 +493,6 @@ namespace PlexRequests.Services.Jobs
return true;
}
- private void NotifyUsers(IEnumerable modelChanged, string apiKey)
- {
- try
- {
- var plexUser = PlexApi.GetUsers(apiKey);
- var userAccount = PlexApi.GetAccount(apiKey);
-
- var adminUsername = userAccount.Username ?? string.Empty;
-
- var users = UserNotifyRepo.GetAll().ToList();
- Log.Debug("Notifying Users Count {0}", users.Count);
- foreach (var model in modelChanged)
- {
- var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers);
- foreach (var user in selectedUsers)
- {
- Log.Info("Notifying user {0}", user);
- if (user == adminUsername)
- {
- Log.Info("This user is the Plex server owner");
- PublishUserNotification(userAccount.Username, userAccount.Email, model.Title);
- return;
- }
-
- var email = plexUser.User.FirstOrDefault(x => x.Username == user);
- if (email == null)
- {
- Log.Info("There is no email address for this Plex user, cannot send notification");
- // We do not have a plex user that requested this!
- continue;
- }
-
- Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);
- PublishUserNotification(email.Username, email.Email, model.Title);
- }
- }
- }
- catch (Exception e)
- {
- Log.Error(e);
- }
- }
-
- private void PublishUserNotification(string username, string email, string title)
- {
- var notificationModel = new NotificationModel
- {
- User = username,
- UserEmail = email,
- NotificationType = NotificationType.RequestAvailable,
- Title = title
- };
-
- // Send the notification to the user.
- Notification.Publish(notificationModel);
- }
-
public void Execute(IJobExecutionContext context)
{
try
diff --git a/PlexRequests.Services/Models/PlexAlbum.cs b/PlexRequests.Services/Models/PlexAlbum.cs
index 5d2bd7254..09d4b2638 100644
--- a/PlexRequests.Services/Models/PlexAlbum.cs
+++ b/PlexRequests.Services/Models/PlexAlbum.cs
@@ -1,9 +1,10 @@
-namespace PlexRequests.Services.Models
-{
- public class PlexAlbum
- {
- public string Title { get; set; }
- public string Artist { get; set; }
- public string ReleaseYear { get; set; }
- }
-}
+namespace PlexRequests.Services.Models
+{
+ public class PlexAlbum
+ {
+ public string Title { get; set; }
+ public string Artist { get; set; }
+ public string ReleaseYear { get; set; }
+ public string Url { get; set; }
+ }
+}
diff --git a/PlexRequests.Services/Models/PlexMovie.cs b/PlexRequests.Services/Models/PlexMovie.cs
index 0149698ba..27eca9948 100644
--- a/PlexRequests.Services/Models/PlexMovie.cs
+++ b/PlexRequests.Services/Models/PlexMovie.cs
@@ -1,9 +1,10 @@
-namespace PlexRequests.Services.Models
-{
- public class PlexMovie
- {
- public string Title { get; set; }
- public string ReleaseYear { get; set; }
- public string ProviderId { get; set; }
- }
-}
+namespace PlexRequests.Services.Models
+{
+ public class PlexMovie
+ {
+ public string Title { get; set; }
+ public string ReleaseYear { get; set; }
+ public string ProviderId { get; set; }
+ public string Url { get; set; }
+ }
+}
diff --git a/PlexRequests.Services/Models/PlexTvShow.cs b/PlexRequests.Services/Models/PlexTvShow.cs
index 5ac629132..aecf6f088 100644
--- a/PlexRequests.Services/Models/PlexTvShow.cs
+++ b/PlexRequests.Services/Models/PlexTvShow.cs
@@ -6,5 +6,6 @@
public string ReleaseYear { get; set; }
public string ProviderId { get; set; }
public int[] Seasons { get; set; }
+ public string Url { get; set; }
}
}
diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs
index 3bbbc43cb..a81a15290 100644
--- a/PlexRequests.Services/Notification/EmailMessageNotification.cs
+++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs
@@ -104,7 +104,14 @@ namespace PlexRequests.Services.Notification
private bool ValidateConfiguration(EmailNotificationSettings settings)
{
- if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
+ if (settings.Authentication)
+ {
+ if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword))
+ {
+ return false;
+ }
+ }
+ if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString()))
{
return false;
}
@@ -186,7 +193,7 @@ namespace PlexRequests.Services.Notification
{
client.Authenticate(settings.EmailUsername, settings.EmailPassword);
}
-
+ Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated);
await client.SendAsync(message);
await client.DisconnectAsync(true);
}
diff --git a/PlexRequests.Services/Notification/NotificationEngine.cs b/PlexRequests.Services/Notification/NotificationEngine.cs
new file mode 100644
index 000000000..ccbe07fec
--- /dev/null
+++ b/PlexRequests.Services/Notification/NotificationEngine.cs
@@ -0,0 +1,156 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2016 Jamie Rees
+// File: NotificationEngine.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.Tasks;
+using NLog;
+using NLog.Fluent;
+using PlexRequests.Api;
+using PlexRequests.Api.Interfaces;
+using PlexRequests.Core.Models;
+using PlexRequests.Services.Interfaces;
+using PlexRequests.Store;
+using PlexRequests.Store.Models;
+using PlexRequests.Store.Repository;
+
+namespace PlexRequests.Services.Notification
+{
+ public class NotificationEngine : INotificationEngine
+ {
+ public NotificationEngine(IPlexApi p, IRepository repo, INotificationService service)
+ {
+ PlexApi = p;
+ UserNotifyRepo = repo;
+ Notification = service;
+ }
+
+ private IPlexApi PlexApi { get; }
+ private IRepository UserNotifyRepo { get; }
+ private static Logger Log = LogManager.GetCurrentClassLogger();
+ private INotificationService Notification { get; }
+
+ public async Task NotifyUsers(IEnumerable modelChanged, string apiKey)
+ {
+ try
+ {
+ var plexUser = PlexApi.GetUsers(apiKey);
+ var userAccount = PlexApi.GetAccount(apiKey);
+
+ var adminUsername = userAccount.Username ?? string.Empty;
+
+ var users = UserNotifyRepo.GetAll().ToList();
+ Log.Debug("Notifying Users Count {0}", users.Count);
+ foreach (var model in modelChanged)
+ {
+ var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers, StringComparer.CurrentCultureIgnoreCase);
+ foreach (var user in selectedUsers)
+ {
+ Log.Info("Notifying user {0}", user);
+ if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase))
+ {
+ Log.Info("This user is the Plex server owner");
+ await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title);
+ return;
+ }
+
+ var email = plexUser.User.FirstOrDefault(x => x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase));
+ if (email == null)
+ {
+ Log.Info("There is no email address for this Plex user, cannot send notification");
+ // We do not have a plex user that requested this!
+ continue;
+ }
+
+ Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);
+ await PublishUserNotification(email.Username, email.Email, model.Title);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+
+ public async Task NotifyUsers(RequestedModel model, string apiKey)
+ {
+ try
+ {
+ var plexUser = PlexApi.GetUsers(apiKey);
+ var userAccount = PlexApi.GetAccount(apiKey);
+
+ var adminUsername = userAccount.Username ?? string.Empty;
+
+ var users = UserNotifyRepo.GetAll().ToList();
+ Log.Debug("Notifying Users Count {0}", users.Count);
+
+ var selectedUsers = users.Select(x => x.Username).Intersect(model.RequestedUsers, StringComparer.CurrentCultureIgnoreCase);
+ foreach (var user in selectedUsers)
+ {
+ Log.Info("Notifying user {0}", user);
+ if (user.Equals(adminUsername, StringComparison.CurrentCultureIgnoreCase))
+ {
+ Log.Info("This user is the Plex server owner");
+ await PublishUserNotification(userAccount.Username, userAccount.Email, model.Title);
+ return;
+ }
+
+ var email = plexUser.User.FirstOrDefault(x => x.Username.Equals(user, StringComparison.CurrentCultureIgnoreCase));
+ if (email == null)
+ {
+ Log.Info("There is no email address for this Plex user, cannot send notification");
+ // We do not have a plex user that requested this!
+ continue;
+ }
+
+ Log.Info("Sending notification to: {0} at: {1}, for title: {2}", email.Username, email.Email, model.Title);
+ await PublishUserNotification(email.Username, email.Email, model.Title);
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error(e);
+ }
+ }
+
+ private async Task PublishUserNotification(string username, string email, string title)
+ {
+ var notificationModel = new NotificationModel
+ {
+ User = username,
+ UserEmail = email,
+ NotificationType = NotificationType.RequestAvailable,
+ Title = title
+ };
+
+ // Send the notification to the user.
+ await Notification.Publish(notificationModel);
+ }
+ }
+}
\ No newline at end of file
diff --git a/PlexRequests.Services/PlexRequests.Services.csproj b/PlexRequests.Services/PlexRequests.Services.csproj
index df4d694c9..6aed947bc 100644
--- a/PlexRequests.Services/PlexRequests.Services.csproj
+++ b/PlexRequests.Services/PlexRequests.Services.csproj
@@ -75,6 +75,7 @@
+
@@ -97,6 +98,7 @@
+
diff --git a/PlexRequests.UI.Tests/UserLoginModuleTests.cs b/PlexRequests.UI.Tests/UserLoginModuleTests.cs
index 16b138eea..22f57750e 100644
--- a/PlexRequests.UI.Tests/UserLoginModuleTests.cs
+++ b/PlexRequests.UI.Tests/UserLoginModuleTests.cs
@@ -72,9 +72,9 @@ namespace PlexRequests.UI.Tests
LandingPageMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new LandingPageSettings());
IAnalytics = new Mock();
Linker = new Mock();
- Linker.Setup(x => x.BuildAbsoluteUri(It.IsAny(), "SearchIndex", null)).Returns(new Uri("http://www.searchindex.com"));
- Linker.Setup(x => x.BuildAbsoluteUri(It.IsAny(), "LandingPageIndex", null)).Returns(new Uri("http://www.landingpage.com"));
- Linker.Setup(x => x.BuildAbsoluteUri(It.IsAny(), "UserLoginIndex", null)).Returns(new Uri("http://www.userloginindex.com"));
+ Linker.Setup(x => x.BuildRelativeUri(It.IsAny(), "SearchIndex", null)).Returns(new Uri("http://www.searchindex.com"));
+ Linker.Setup(x => x.BuildRelativeUri(It.IsAny(), "LandingPageIndex", null)).Returns(new Uri("http://www.landingpage.com"));
+ Linker.Setup(x => x.BuildRelativeUri(It.IsAny(), "UserLoginIndex", null)).Returns(new Uri("http://www.userloginindex.com"));
PlexSettingsMock = new Mock>();
PlexSettingsMock.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings() {PlexAuthToken = "abc"});
Bootstrapper = new ConfigurableBootstrapper(with =>
diff --git a/PlexRequests.UI/Content/app/requests/requestsController.js b/PlexRequests.UI/Content/app/requests/requestsController.js
new file mode 100644
index 000000000..204dcf9a2
--- /dev/null
+++ b/PlexRequests.UI/Content/app/requests/requestsController.js
@@ -0,0 +1,51 @@
+(function () {
+ var controller = function($scope, requestsService) {
+
+ $scope.requests = [];
+ $scope.selectedTab = {};
+ $scope.currentPage = 1;
+ $scope.tabs = [];
+
+ $scope.plexSettings = {};
+ $scope.requestSettings = {};
+
+ // Search
+ $scope.searchTerm = "";
+
+
+ // Called on page load
+ $scope.init = function() {
+ // Get the settings
+ $scope.plexSettings = requestsService.getPlexRequestSettings(getBaseUrl());
+ $scope.requestSettings = requestsService.getRequestSettings(getBaseUrl());
+
+ if ($scope.plexSettings.SearchForMovies) {
+ $scope.selectedTab = "movies";
+
+ // Load the movie Requests
+ $scope.requests = requestsService.getRequests("movie", getBaseUrl());
+ }
+ };
+
+
+ $scope.changeTab = function(tab) {
+ // load the data from the tab
+ switch (tab) {
+ // Set the selected tab and load the appropriate data
+ }
+
+ };
+
+ $scope.search = function() {
+ $scope.requests = requestsService.getRequests
+ };
+
+ function getBaseUrl() {
+ return $('#baseUrl').val();
+ }
+
+
+ }
+
+ angular.module('PlexRequests').controller('requestsController', ["$scope", "requestsService", controller]);
+}());
\ No newline at end of file
diff --git a/PlexRequests.UI/Content/app/requests/requestsService.js b/PlexRequests.UI/Content/app/requests/requestsService.js
new file mode 100644
index 000000000..b702c0727
--- /dev/null
+++ b/PlexRequests.UI/Content/app/requests/requestsService.js
@@ -0,0 +1,49 @@
+(function () {
+
+ var requestsService = function ($http) {
+
+ $http.defaults.headers.common['Content-Type'] = 'application/json'; // Set default headers
+
+ var getRequests = function (type, baseUrl) {
+ switch (type) {
+ case "movie":
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/movies"));
+ case "tv":
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/tvshows"));
+ case "album":
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/albums"));
+ }
+ return null;
+ };
+
+ var getPlexRequestSettings = function (baseUrl) {
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/plexrequestsettings"));
+ }
+
+ var getRequestsSettings = function (baseUrl) {
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/requestsettings"));
+ }
+
+ var getRequestsSearch = function (type, baseUrl, searchTerm) {
+ switch (type) {
+ case "movie":
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/movies/"+ searchTerm));
+ case "tv":
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/tvshows/" + searchTerm));
+ case "album":
+ return $http.get(createBaseUrl(baseUrl, "/requestsbeta/albums/" + searchTerm));
+ }
+ return null;
+ };
+
+ return {
+ getRequests: getRequests,
+ getRequestsSearch: getRequestsSearch,
+ getPlexRequestSettings: getPlexRequestSettings,
+ getRequestSettings: getRequestsSettings
+ };
+ }
+
+ angular.module('PlexRequests').factory('requestsService', ["$http", requestsService]);
+
+}());
\ No newline at end of file
diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementController.js b/PlexRequests.UI/Content/app/userManagement/userManagementController.js
index e9678e539..88a85e7aa 100644
--- a/PlexRequests.UI/Content/app/userManagement/userManagementController.js
+++ b/PlexRequests.UI/Content/app/userManagement/userManagementController.js
@@ -4,49 +4,91 @@
$scope.user = {}; // The local user
$scope.users = []; // list of users
+ $scope.claims = []; // List of claims
- $scope.selectedUser = {};
+ $scope.selectedUser = {}; // User on the right side
+ $scope.selectedClaims = {};
- $scope.sortType = 'username';
+
+ $scope.sortType = "username";
$scope.sortReverse = false;
- $scope.searchTerm = '';
+ $scope.searchTerm = "";
+
$scope.error = {
error: false,
errorMessage: ""
};
+ // Select a user to populate on the right side
$scope.selectUser = function (id) {
- $scope.selectedUser = {};
$scope.selectedUser = $scope.users.find(x => x.id === id);
}
+ // Get all users in the system
$scope.getUsers = function () {
$scope.users = userManagementService.getUsers()
- .then(function (data) {
- $scope.users = data.data;
- });
+ .then(function (data) {
+ $scope.users = data.data;
+ });
};
+ // Get the claims and populate the create dropdown
+ $scope.getClaims = function () {
+ userManagementService.getClaims()
+ .then(function (data) {
+ $scope.claims = data.data;
+ });
+ }
+
+ // Create a user, do some validation too
$scope.addUser = function () {
+
if (!$scope.user.username || !$scope.user.password) {
$scope.error.error = true;
$scope.error.errorMessage = "Please provide a correct username and password";
generateNotify($scope.error.errorMessage, 'warning');
return;
}
- userManagementService.addUser($scope.user).then(function (data) {
- if (data.message) {
- $scope.error.error = true;
- $scope.error.errorMessage = data.message;
- } else {
- $scope.users.push(data);
- $scope.user = {};
- }
- });
+
+ userManagementService.addUser($scope.user, $scope.selectedClaims)
+ .then(function (data) {
+ if (data.message) {
+ $scope.error.error = true;
+ $scope.error.errorMessage = data.message;
+ } else {
+ $scope.users.push(data); // Push the new user into the array to update the DOM
+ $scope.user = {};
+ $scope.selectedClaims = {};
+ }
+ });
};
+
+ $scope.$watch('claims|filter:{selected:true}',
+ function (nv) {
+ $scope.selectedClaims = nv.map(function (claim) {
+ return claim.name;
+ });
+ },
+ true);
+
+
+ $scope.updateUser = function () {
+
+ }
+
+ function getBaseUrl() {
+ return $('#baseUrl').val();
+ }
+
+
+ // On page load
+ $scope.init = function () {
+ $scope.getUsers();
+ $scope.getClaims();
+ return;
+ }
}
angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService", controller]);
-
}());
\ No newline at end of file
diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementService.js b/PlexRequests.UI/Content/app/userManagement/userManagementService.js
index fe7b7ef07..e1640e3ea 100644
--- a/PlexRequests.UI/Content/app/userManagement/userManagementService.js
+++ b/PlexRequests.UI/Content/app/userManagement/userManagementService.js
@@ -2,27 +2,32 @@
var userManagementService = function ($http) {
- $http.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded'; // Set default headers
+ $http.defaults.headers.common['Content-Type'] = 'application/json'; // Set default headers
var getUsers = function () {
return $http.get('/usermanagement/users');
};
- var addUser = function (user) {
- if (!user) {
+ var addUser = function (user, claims) {
+ if (!user || claims.length === 0) {
return null;
}
return $http({
url: '/usermanagement/createuser',
method: "POST",
- data: $.param(user)
+ data: { username: user.username, password: user.password, claims: claims, email: user.email }
});
}
+ var getClaims = function () {
+ return $http.get('/usermanagement/claims');
+ }
+
return {
getUsers: getUsers,
- addUser: addUser
+ addUser: addUser,
+ getClaims: getClaims
};
}
diff --git a/PlexRequests.UI/Content/requests.js b/PlexRequests.UI/Content/requests.js
index 765db5ecb..678fa60fd 100644
--- a/PlexRequests.UI/Content/requests.js
+++ b/PlexRequests.UI/Content/requests.js
@@ -578,7 +578,7 @@ function tvLoad() {
results.forEach(function (result) {
var ep = result.episodes;
ep.forEach(function (episode) {
- var foundItem = tvObject.find(x => x.seasonNumber === episode.seasonNumber);
+ var foundItem = tvObject.find(function(x) { return x.seasonNumber === episode.seasonNumber });
if (!foundItem) {
var obj = { seasonNumber: episode.seasonNumber, episodes: [] }
tvObject.push(obj);
diff --git a/PlexRequests.UI/Content/search.js b/PlexRequests.UI/Content/search.js
index 37b41a95e..310ba27fe 100644
--- a/PlexRequests.UI/Content/search.js
+++ b/PlexRequests.UI/Content/search.js
@@ -5,6 +5,21 @@
return opts.inverse(this);
});
+Function.prototype.bind = function (parent) {
+ var f = this;
+ var args = [];
+
+ for (var a = 1; a < arguments.length; a++) {
+ args[args.length] = arguments[a];
+ }
+
+ var temp = function () {
+ return f.apply(parent, args);
+ }
+
+ return (temp);
+}
+
$(function () {
@@ -56,7 +71,9 @@ $(function () {
if (searchTimer) {
clearTimeout(searchTimer);
}
- searchTimer = setTimeout(movieSearch, 800);
+ searchTimer = setTimeout(function () {
+ movieSearch();
+ }.bind(this), 800);
});
@@ -75,7 +92,9 @@ $(function () {
if (searchTimer) {
clearTimeout(searchTimer);
}
- searchTimer = setTimeout(tvSearch, 400);
+ searchTimer = setTimeout(function () {
+ tvSearch();
+ }.bind(this), 800);
});
// Click TV dropdown option
@@ -116,7 +135,9 @@ $(function () {
if (searchTimer) {
clearTimeout(searchTimer);
}
- searchTimer = setTimeout(musicSearch, 400);
+ searchTimer = setTimeout(function () {
+ musicSearch();
+ }.bind(this), 800);
});
@@ -423,7 +444,8 @@ $(function () {
imdb: result.imdbId,
requested: result.requested,
approved: result.approved,
- available: result.available
+ available: result.available,
+ url: result.plexUrl
};
return context;
@@ -444,7 +466,8 @@ $(function () {
approved: result.approved,
available: result.available,
episodes: result.episodes,
- tvFullyAvailable: result.tvFullyAvailable
+ tvFullyAvailable: result.tvFullyAvailable,
+ url: result.plexUrl
};
return context;
}
@@ -464,7 +487,8 @@ $(function () {
country: result.country,
requested: result.requested,
approved: result.approved,
- available: result.available
+ available: result.available,
+ url: result.plexUrl
};
return context;
@@ -484,7 +508,7 @@ $(function () {
var $content = $("#seasonsBody");
$content.html("");
$('#selectedSeasonsId').val(id);
- results.forEach(function(result) {
+ results.forEach(function (result) {
var context = buildSeasonsContext(result);
$content.append(seasonsTemplate(context));
});
@@ -503,7 +527,7 @@ $(function () {
};
});
- $('#seasonsRequest').click(function(e) {
+ $('#seasonsRequest').click(function (e) {
e.preventDefault();
var tvId = $('#selectedSeasonsId').val();
var url = createBaseUrl(base, '/search/seasons/');
@@ -522,7 +546,7 @@ $(function () {
var $checkedSeasons = $('.selectedSeasons:checkbox:checked');
$checkedSeasons.each(function (index, element) {
- if (index < $checkedSeasons.length -1) {
+ if (index < $checkedSeasons.length - 1) {
seasonsParam = seasonsParam + element.id + ",";
} else {
seasonsParam = seasonsParam + element.id;
@@ -536,7 +560,7 @@ $(function () {
var url = $form.prop('action');
sendRequestAjax(data, type, url, tvId);
-
+
});
$('#episodesModal').on('show.bs.modal', function (event) {
@@ -560,7 +584,9 @@ $(function () {
results.forEach(function (result) {
var episodes = buildEpisodesView(result);
- if (!seenSeasons.find(x => x === episodes.season)) {
+ if (!seenSeasons.find(function(x) {
+ return x === episodes.season
+ })) {
// Create the seasons heading
seenSeasons.push(episodes.season);
var context = buildSeasonsCount(result);
@@ -586,7 +612,7 @@ $(function () {
loadingButton("episodesRequest", "primary");
var tvId = $('#selectedEpisodeId').val();
-
+
var $form = $('#form' + tvId);
var model = [];
@@ -623,7 +649,7 @@ $(function () {
}
},
- error: function(e) {
+ error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
diff --git a/PlexRequests.UI/Helpers/BaseUrlHelper.cs b/PlexRequests.UI/Helpers/BaseUrlHelper.cs
index 625d71380..2ce349ec0 100644
--- a/PlexRequests.UI/Helpers/BaseUrlHelper.cs
+++ b/PlexRequests.UI/Helpers/BaseUrlHelper.cs
@@ -82,9 +82,8 @@ namespace PlexRequests.UI.Helpers
$"",
$"",
$"",
- $"",
- $"",
- $"",
+ $"",
+ $"",
$"",
};
@@ -95,11 +94,10 @@ namespace PlexRequests.UI.Helpers
$"",
$"",
$"",
- $"",
+ $"",
$"",
$"",
$"",
- $"",
$""
};
@@ -118,6 +116,17 @@ namespace PlexRequests.UI.Helpers
return helper.Raw(sb.ToString());
}
+ public static IHtmlString LoadDateTimePickerAsset(this HtmlHelpers helper)
+ {
+ var content = GetBaseUrl();
+
+ var sb = new StringBuilder();
+ var startUrl = $"{content}/Content";
+ sb.AppendLine($"");
+ sb.AppendLine($"");
+
+ return helper.Raw(sb.ToString());
+ }
public static IHtmlString LoadAngularAssets(this HtmlHelpers helper)
{
var sb = new StringBuilder();
@@ -135,7 +144,7 @@ namespace PlexRequests.UI.Helpers
var startUrl = $"{content}/Content";
sb.AppendLine($""); // Load angular first
- sb.AppendLine($"");
+ sb.AppendLine($"");
return helper.Raw(sb.ToString());
}
@@ -147,7 +156,7 @@ namespace PlexRequests.UI.Helpers
var content = GetContentUrl(assetLocation);
- sb.AppendLine($"");
+ sb.AppendLine($"");
return helper.Raw(sb.ToString());
}
@@ -159,7 +168,7 @@ namespace PlexRequests.UI.Helpers
var content = GetContentUrl(assetLocation);
- sb.AppendLine($"");
+ sb.AppendLine($"");
return helper.Raw(sb.ToString());
}
@@ -171,7 +180,7 @@ namespace PlexRequests.UI.Helpers
var content = GetContentUrl(assetLocation);
- sb.AppendLine($"");
+ sb.AppendLine($"");
return helper.Raw(sb.ToString());
}
@@ -183,7 +192,7 @@ namespace PlexRequests.UI.Helpers
var content = GetContentUrl(assetLocation);
- sb.AppendLine($"");
+ sb.AppendLine($"");
return helper.Raw(sb.ToString());
}
@@ -193,11 +202,25 @@ namespace PlexRequests.UI.Helpers
var assetLocation = GetBaseUrl();
var content = GetContentUrl(assetLocation);
- var asset = $"";
+ var asset = $"";
return helper.Raw(asset);
}
+
+ public static IHtmlString LoadUserManagementAssets(this HtmlHelpers helper)
+ {
+ var assetLocation = GetBaseUrl();
+ var content = GetContentUrl(assetLocation);
+
+ var controller = $"";
+ controller += $"";
+
+
+ return helper.Raw(controller);
+ }
+
+
public static IHtmlString LoadTableAssets(this HtmlHelpers helper)
{
var sb = new StringBuilder();
@@ -222,7 +245,7 @@ namespace PlexRequests.UI.Helpers
var assetLocation = GetBaseUrl();
var content = GetContentUrl(assetLocation);
- var asset = $"";
+ var asset = $"";
return helper.Raw(asset);
}
diff --git a/PlexRequests.UI/Helpers/TvSender.cs b/PlexRequests.UI/Helpers/TvSender.cs
index 13a941512..05b2799ab 100644
--- a/PlexRequests.UI/Helpers/TvSender.cs
+++ b/PlexRequests.UI/Helpers/TvSender.cs
@@ -131,7 +131,7 @@ namespace PlexRequests.UI.Helpers
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.SeasonCount, model.SeasonList, sonarrSettings.ApiKey,
- sonarrSettings.FullUri);
+ sonarrSettings.FullUri, true, true);
return result;
}
diff --git a/PlexRequests.UI/Models/SearchViewModel.cs b/PlexRequests.UI/Models/SearchViewModel.cs
index 776b9d2b1..9c11d32ef 100644
--- a/PlexRequests.UI/Models/SearchViewModel.cs
+++ b/PlexRequests.UI/Models/SearchViewModel.cs
@@ -1,37 +1,38 @@
-#region Copyright
-// /************************************************************************
-// Copyright (c) 2016 Jamie Rees
-// File: SearchTvShowViewModel.cs
-// Created By: Jamie Rees
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-// ************************************************************************/
-#endregion
-
-
-namespace PlexRequests.UI.Models
-{
- public class SearchViewModel
- {
- public bool Approved { get; set; }
- public bool Requested { get; set; }
- public bool Available { get; set; }
- }
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2016 Jamie Rees
+// File: SearchTvShowViewModel.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+
+
+namespace PlexRequests.UI.Models
+{
+ public class SearchViewModel
+ {
+ public bool Approved { get; set; }
+ public bool Requested { get; set; }
+ public bool Available { get; set; }
+ public string PlexUrl { get; set; }
+ }
}
\ No newline at end of file
diff --git a/PlexRequests.UI/Models/SessionKeys.cs b/PlexRequests.UI/Models/SessionKeys.cs
index fefc7d8bd..766a84bf6 100644
--- a/PlexRequests.UI/Models/SessionKeys.cs
+++ b/PlexRequests.UI/Models/SessionKeys.cs
@@ -31,5 +31,6 @@ namespace PlexRequests.UI.Models
public const string UsernameKey = "Username";
public const string ClientDateTimeOffsetKey = "ClientDateTimeOffset";
public const string UserWizardPlexAuth = nameof(UserWizardPlexAuth);
+ public const string UserWizardMachineId = nameof(UserWizardMachineId);
}
}
diff --git a/PlexRequests.UI/Models/UserManagementUsersViewModel.cs b/PlexRequests.UI/Models/UserManagementUsersViewModel.cs
index 57fcd1f5a..915f90075 100644
--- a/PlexRequests.UI/Models/UserManagementUsersViewModel.cs
+++ b/PlexRequests.UI/Models/UserManagementUsersViewModel.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using Newtonsoft.Json;
namespace PlexRequests.UI.Models
{
@@ -15,6 +16,7 @@ namespace PlexRequests.UI.Models
public UserType Type { get; set; }
public string EmailAddress { get; set; }
public UserManagementPlexInformation PlexInfo { get; set; }
+ public string[] ClaimsArray { get; set; }
}
public class UserManagementPlexInformation
@@ -42,5 +44,18 @@ namespace PlexRequests.UI.Models
PlexUser,
LocalUser
}
+
+ public class UserManagementCreateModel
+ {
+ [JsonProperty("username")]
+ public string Username { get; set; }
+ [JsonProperty("password")]
+ public string Password { get; set; }
+ [JsonProperty("claims")]
+ public string[] Claims { get; set; }
+
+ [JsonProperty("email")]
+ public string EmailAddress { get; set; }
+ }
}
diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs
index 4d6ca7e43..a04edca3c 100644
--- a/PlexRequests.UI/Modules/AdminModule.cs
+++ b/PlexRequests.UI/Modules/AdminModule.cs
@@ -161,7 +161,7 @@ namespace PlexRequests.UI.Modules
Post["/couchpotato"] = _ => SaveCouchPotato();
Get["/plex"] = _ => Plex();
- Post["/plex"] = _ => SavePlex();
+ Post["/plex", true] = async (x, ct) => await SavePlex();
Get["/sonarr"] = _ => Sonarr();
Post["/sonarr"] = _ => SaveSonarr();
@@ -170,13 +170,13 @@ namespace PlexRequests.UI.Modules
Post["/sickrage"] = _ => SaveSickrage();
Post["/sonarrprofiles"] = _ => GetSonarrQualityProfiles();
- Post["/cpprofiles", true] = async (x,ct) => await GetCpProfiles();
+ Post["/cpprofiles", true] = async (x, ct) => await GetCpProfiles();
Post["/cpapikey"] = x => GetCpApiKey();
Get["/emailnotification"] = _ => EmailNotifications();
Post["/emailnotification"] = _ => SaveEmailNotifications();
Post["/testemailnotification"] = _ => TestEmailNotifications();
- Get["/status", true] = async (x,ct) => await Status();
+ Get["/status", true] = async (x, ct) => await Status();
Get["/pushbulletnotification"] = _ => PushbulletNotifications();
Post["/pushbulletnotification"] = _ => SavePushbulletNotifications();
@@ -268,7 +268,7 @@ namespace PlexRequests.UI.Modules
Analytics.TrackEventAsync(Category.Admin, Action.Save, "CollectAnalyticData turned off", Username, CookieHelper.GetAnalyticClientId(Cookies));
}
var result = PrService.SaveSettings(model);
-
+
Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies));
return Response.AsJson(result
? new JsonResponseModel { Result = true }
@@ -377,7 +377,7 @@ namespace PlexRequests.UI.Modules
return View["Plex", settings];
}
- private Response SavePlex()
+ private async Task SavePlex()
{
var plexSettings = this.Bind();
var valid = this.Validate(plexSettings);
@@ -386,8 +386,11 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(valid.SendJsonError());
}
+ //Lookup identifier
+ var server = PlexApi.GetServer(plexSettings.PlexAuthToken);
+ plexSettings.MachineIdentifier = server.Server.FirstOrDefault(x => x.AccessToken == plexSettings.PlexAuthToken)?.MachineIdentifier;
- var result = PlexService.SaveSettings(plexSettings);
+ var result = await PlexService.SaveSettingsAsync(plexSettings);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Plex!" }
@@ -517,7 +520,7 @@ namespace PlexRequests.UI.Modules
{
if (string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword))
{
- return Response.AsJson(new JsonResponseModel {Result = false, Message = "SMTP Authentication is enabled, please specify a username and password"});
+ return Response.AsJson(new JsonResponseModel { Result = false, Message = "SMTP Authentication is enabled, please specify a username and password" });
}
}
@@ -542,7 +545,7 @@ namespace PlexRequests.UI.Modules
{
var checker = new StatusChecker();
var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30);
- var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true});
+ var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true });
status.ReleaseNotes = md.Transform(status.ReleaseNotes);
return View["Status", status];
}
@@ -711,7 +714,7 @@ namespace PlexRequests.UI.Modules
private Response GetCpApiKey()
{
var settings = this.Bind();
-
+
if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password))
{
return Response.AsJson(new { Message = "Please enter a username and password to request the Api Key", Result = false });
@@ -938,12 +941,12 @@ namespace PlexRequests.UI.Modules
{
await LogsRepo.DeleteAsync(logEntity);
}
- return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully."});
+ return Response.AsJson(new JsonResponseModel { Result = true, Message = "Logs cleared successfully." });
}
catch (Exception e)
{
Log.Error(e);
- return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
+ return Response.AsJson(new JsonResponseModel { Result = false, Message = e.Message });
}
}
}
diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs
index 1a205202b..a01253786 100644
--- a/PlexRequests.UI/Modules/BaseModule.cs
+++ b/PlexRequests.UI/Modules/BaseModule.cs
@@ -122,7 +122,7 @@ namespace PlexRequests.UI.Modules
{
get
{
- if (Context?.CurrentUser == null)
+ if (!LoggedIn)
{
return false;
}
@@ -130,6 +130,9 @@ namespace PlexRequests.UI.Modules
return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser);
}
}
+
+ protected bool LoggedIn => Context?.CurrentUser != null;
+
protected string Culture { get; set; }
protected const string CultureCookieName = "_culture";
protected Response SetCookie()
diff --git a/PlexRequests.UI/Modules/IndexModule.cs b/PlexRequests.UI/Modules/IndexModule.cs
index 3d7023d39..fd13acb35 100644
--- a/PlexRequests.UI/Modules/IndexModule.cs
+++ b/PlexRequests.UI/Modules/IndexModule.cs
@@ -59,23 +59,23 @@ namespace PlexRequests.UI.Modules
if (!string.IsNullOrEmpty(Username))
{
// They are not logged in
- return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "LandingPageIndex").ToString());
+ return Context.GetRedirect(Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString());
}
- return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "SearchIndex").ToString());
+ return Context.GetRedirect(Linker.BuildRelativeUri(Context, "SearchIndex").ToString());
}
// After login
if (string.IsNullOrEmpty(Username))
{
// Not logged in yet
- return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "UserLoginIndex").ToString());
+ return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString());
}
// Send them to landing
- var landingUrl = Linker.BuildAbsoluteUri(Context, "LandingPageIndex").ToString();
+ var landingUrl = Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString();
return Context.GetRedirect(landingUrl);
}
- return Context.GetRedirect(Linker.BuildAbsoluteUri(Context, "UserLoginIndex").ToString());
+ return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString());
}
}
}
\ No newline at end of file
diff --git a/PlexRequests.UI/Modules/LandingPageModule.cs b/PlexRequests.UI/Modules/LandingPageModule.cs
index 92517f2f7..dccf69430 100644
--- a/PlexRequests.UI/Modules/LandingPageModule.cs
+++ b/PlexRequests.UI/Modules/LandingPageModule.cs
@@ -52,7 +52,7 @@ namespace PlexRequests.UI.Modules
var s = await LandingSettings.GetSettingsAsync();
if (!s.BeforeLogin && string.IsNullOrEmpty(Username)) //We are signed in
{
- var url = Linker.BuildAbsoluteUri(Context, "SearchIndex").ToString();
+ var url = Linker.BuildRelativeUri(Context, "SearchIndex").ToString();
return Response.AsRedirect(url);
}
diff --git a/PlexRequests.UI/Modules/LoginModule.cs b/PlexRequests.UI/Modules/LoginModule.cs
index 8e6a9d39c..8c3bd550d 100644
--- a/PlexRequests.UI/Modules/LoginModule.cs
+++ b/PlexRequests.UI/Modules/LoginModule.cs
@@ -1,150 +1,154 @@
-#region Copyright
-// /************************************************************************
-// Copyright (c) 2016 Jamie Rees
-// File: LoginModule.cs
-// Created By: Jamie Rees
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-// ************************************************************************/
-#endregion
-using System;
-using System.Dynamic;
-
-using Nancy;
-using Nancy.Authentication.Forms;
-using Nancy.Extensions;
-using Nancy.Responses.Negotiation;
-using Nancy.Security;
-
-using PlexRequests.Core;
-using PlexRequests.Core.SettingModels;
-using PlexRequests.Helpers;
-using PlexRequests.UI.Models;
-
-namespace PlexRequests.UI.Modules
-{
- public class LoginModule : BaseModule
- {
- public LoginModule(ISettingsService pr, ICustomUserMapper m) : base(pr)
- {
- UserMapper = m;
- Get["/login"] = _ =>
- {
- {
- dynamic model = new ExpandoObject();
- model.Redirect = Request.Query.redirect.Value ?? string.Empty;
- model.Errored = Request.Query.error.HasValue;
- var adminCreated = UserMapper.DoUsersExist();
- model.AdminExists = adminCreated;
- return View["Index", model];
- }
-
- };
-
- Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/");
-
- Post["/login"] = x =>
- {
- var username = (string)Request.Form.Username;
- var password = (string)Request.Form.Password;
- var dtOffset = (int)Request.Form.DateTimeOffset;
- var redirect = (string)Request.Form.Redirect;
-
- var userId = UserMapper.ValidateUser(username, password);
-
- if (userId == null)
- {
- return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username);
- }
- DateTime? expiry = null;
- if (Request.Form.RememberMe.HasValue)
- {
- expiry = DateTime.Now.AddDays(7);
- }
- Session[SessionKeys.UsernameKey] = username;
- Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset;
- if(redirect.Contains("userlogin")){
- redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search";
- }
- return this.LoginAndRedirect(userId.Value, expiry, redirect);
- };
-
- Get["/register"] = x =>
- {
- {
- dynamic model = new ExpandoObject();
- model.Errored = Request.Query.error.HasValue;
-
- return View["Register", model];
- }
- };
-
- Post["/register"] = x =>
- {
- var username = (string)Request.Form.Username;
- var exists = UserMapper.DoUsersExist();
- if (exists)
- {
- return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true");
- }
- var userId = UserMapper.CreateAdmin(username, Request.Form.Password);
- Session[SessionKeys.UsernameKey] = username;
- return this.LoginAndRedirect((Guid)userId);
- };
-
- Get["/changepassword"] = _ => ChangePassword();
- Post["/changepassword"] = _ => ChangePasswordPost();
- }
- private ICustomUserMapper UserMapper { get; }
-
- private Negotiator ChangePassword()
- {
- this.RequiresAuthentication();
- return View["ChangePassword"];
- }
-
- private Response ChangePasswordPost()
- {
- var username = Context.CurrentUser.UserName;
- var oldPass = Request.Form.OldPassword;
- var newPassword = Request.Form.NewPassword;
- var newPasswordAgain = Request.Form.NewPasswordAgain;
-
- if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) ||
- string.IsNullOrEmpty(newPasswordAgain))
- {
- return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false });
- }
-
- if (!newPassword.Equals(newPasswordAgain))
- {
- return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false });
- }
-
- var result = UserMapper.UpdatePassword(username, oldPass, newPassword);
- if (result)
- {
- return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true });
- }
-
- return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false });
- }
- }
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2016 Jamie Rees
+// File: LoginModule.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+using System;
+using System.Dynamic;
+
+using Nancy;
+using Nancy.Authentication.Forms;
+using Nancy.Extensions;
+using Nancy.Linker;
+using Nancy.Responses.Negotiation;
+using Nancy.Security;
+
+using PlexRequests.Core;
+using PlexRequests.Core.SettingModels;
+using PlexRequests.Helpers;
+using PlexRequests.UI.Models;
+
+namespace PlexRequests.UI.Modules
+{
+ public class LoginModule : BaseModule
+ {
+ public LoginModule(ISettingsService pr, ICustomUserMapper m, IResourceLinker linker) : base(pr)
+ {
+ UserMapper = m;
+ Get["/login"] = _ =>
+ {
+ if (LoggedIn)
+ {
+ var url = linker.BuildRelativeUri(Context, "SearchIndex");
+ return Response.AsRedirect(url.ToString());
+ }
+ dynamic model = new ExpandoObject();
+ model.Redirect = Request.Query.redirect.Value ?? string.Empty;
+ model.Errored = Request.Query.error.HasValue;
+ var adminCreated = UserMapper.DoUsersExist();
+ model.AdminExists = adminCreated;
+ return View["Index", model];
+ };
+
+ Get["/logout"] = x => this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/");
+
+ Post["/login"] = x =>
+ {
+ var username = (string)Request.Form.Username;
+ var password = (string)Request.Form.Password;
+ var dtOffset = (int)Request.Form.DateTimeOffset;
+ var redirect = (string)Request.Form.Redirect;
+
+ var userId = UserMapper.ValidateUser(username, password);
+
+ if (userId == null)
+ {
+ return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/login?error=true&username=" + username : "~/login?error=true&username=" + username);
+ }
+ DateTime? expiry = null;
+ if (Request.Form.RememberMe.HasValue)
+ {
+ expiry = DateTime.Now.AddDays(7);
+ }
+ Session[SessionKeys.UsernameKey] = username;
+ Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset;
+ if (redirect.Contains("userlogin"))
+ {
+ redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search";
+ }
+ return this.LoginAndRedirect(userId.Value, expiry, redirect);
+ };
+
+ Get["/register"] = x =>
+ {
+ {
+ dynamic model = new ExpandoObject();
+ model.Errored = Request.Query.error.HasValue;
+
+ return View["Register", model];
+ }
+ };
+
+ Post["/register"] = x =>
+ {
+ var username = (string)Request.Form.Username;
+ var exists = UserMapper.DoUsersExist();
+ if (exists)
+ {
+ return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/register?error=true" : "~/register?error=true");
+ }
+ var userId = UserMapper.CreateAdmin(username, Request.Form.Password);
+ Session[SessionKeys.UsernameKey] = username;
+ return this.LoginAndRedirect((Guid)userId);
+ };
+
+ Get["/changepassword"] = _ => ChangePassword();
+ Post["/changepassword"] = _ => ChangePasswordPost();
+ }
+ private ICustomUserMapper UserMapper { get; }
+
+ private Negotiator ChangePassword()
+ {
+ this.RequiresAuthentication();
+ return View["ChangePassword"];
+ }
+
+ private Response ChangePasswordPost()
+ {
+ var username = Context.CurrentUser.UserName;
+ var oldPass = Request.Form.OldPassword;
+ var newPassword = Request.Form.NewPassword;
+ var newPasswordAgain = Request.Form.NewPasswordAgain;
+
+ if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) ||
+ string.IsNullOrEmpty(newPasswordAgain))
+ {
+ return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false });
+ }
+
+ if (!newPassword.Equals(newPasswordAgain))
+ {
+ return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false });
+ }
+
+ var result = UserMapper.UpdatePassword(username, oldPass, newPassword);
+ if (result)
+ {
+ return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true });
+ }
+
+ return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false });
+ }
+ }
}
\ No newline at end of file
diff --git a/PlexRequests.UI/Modules/RequestsBetaModule.cs b/PlexRequests.UI/Modules/RequestsBetaModule.cs
new file mode 100644
index 000000000..a8a1a6e3f
--- /dev/null
+++ b/PlexRequests.UI/Modules/RequestsBetaModule.cs
@@ -0,0 +1,453 @@
+#region Copyright
+// /************************************************************************
+// Copyright (c) 2016 Jamie Rees
+// File: RequestsModule.cs
+// Created By: Jamie Rees
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+// ************************************************************************/
+#endregion
+
+using System;
+using System.Linq;
+
+using Nancy;
+using Nancy.Responses.Negotiation;
+using Nancy.Security;
+
+using PlexRequests.Core;
+using PlexRequests.Core.SettingModels;
+using PlexRequests.Services.Interfaces;
+using PlexRequests.Services.Notification;
+using PlexRequests.Store;
+using PlexRequests.UI.Models;
+using PlexRequests.Helpers;
+using PlexRequests.UI.Helpers;
+using System.Collections.Generic;
+using PlexRequests.Api.Interfaces;
+using System.Threading.Tasks;
+
+using NLog;
+
+using PlexRequests.Core.Models;
+using PlexRequests.Helpers.Analytics;
+
+using Action = PlexRequests.Helpers.Analytics.Action;
+
+namespace PlexRequests.UI.Modules
+{
+ public class RequestsBetaModule : BaseAuthModule
+ {
+ public RequestsBetaModule(
+ IRequestService service,
+ ISettingsService prSettings,
+ ISettingsService requestSettings,
+ ISettingsService plex,
+ INotificationService notify,
+ ISettingsService sonarrSettings,
+ ISettingsService sickRageSettings,
+ ISettingsService cpSettings,
+ ICouchPotatoApi cpApi,
+ ISonarrApi sonarrApi,
+ ISickRageApi sickRageApi,
+ ICacheProvider cache,
+ IAnalytics an) : base("requestsbeta", prSettings)
+ {
+ Service = service;
+ PrSettings = prSettings;
+ PlexSettings = plex;
+ NotificationService = notify;
+ SonarrSettings = sonarrSettings;
+ SickRageSettings = sickRageSettings;
+ CpSettings = cpSettings;
+ SonarrApi = sonarrApi;
+ SickRageApi = sickRageApi;
+ CpApi = cpApi;
+ Cache = cache;
+ Analytics = an;
+
+ Get["/"] = x => LoadRequests();
+ Get["/plexrequestsettings", true] = async (x, ct) => await GetPlexRequestSettings();
+ Get["/requestsettings", true] = async (x, ct) => await GetRequestSettings();
+ Get["/movies", true] = async (x, ct) => await GetMovies();
+ Get["/movies/{searchTerm}", true] = async (x, ct) => await GetMovies((string)x.searchTerm);
+
+
+ // Everything below is not being used in the beta page
+ Get["/tvshows", true] = async (c, ct) => await GetTvShows();
+ Get["/albums", true] = async (x, ct) => await GetAlbumRequests();
+ Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id);
+ Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
+ Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
+
+ Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id);
+
+ Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
+ }
+
+ private static Logger Log = LogManager.GetCurrentClassLogger();
+ private IRequestService Service { get; }
+ private IAnalytics Analytics { get; }
+ private INotificationService NotificationService { get; }
+ private ISettingsService PrSettings { get; }
+ private ISettingsService PlexSettings { get; }
+ private ISettingsService RequestSettings { get; }
+ private ISettingsService SonarrSettings { get; }
+ private ISettingsService SickRageSettings { get; }
+ private ISettingsService CpSettings { get; }
+ private ISonarrApi SonarrApi { get; }
+ private ISickRageApi SickRageApi { get; }
+ private ICouchPotatoApi CpApi { get; }
+ private ICacheProvider Cache { get; }
+
+ private Negotiator LoadRequests()
+ {
+ return View["Index"];
+ }
+
+ private async Task GetPlexRequestSettings()
+ {
+ return Response.AsJson(await PrSettings.GetSettingsAsync());
+ }
+
+ private async Task GetRequestSettings()
+ {
+ return Response.AsJson(await RequestSettings.GetSettingsAsync());
+ }
+
+ private async Task GetMovies(string searchTerm = null, bool approved = false, bool notApproved = false,
+ bool available = false, bool notAvailable = false, bool released = false, bool notReleased = false)
+ {
+ var dbMovies = await FilterMovies(searchTerm, approved, notApproved, available, notAvailable, released, notReleased);
+ var qualities = await GetQualityProfiles();
+ var model = MapMoviesToView(dbMovies.ToList(), qualities);
+
+ return Response.AsJson(model);
+ }
+
+ private async Task GetTvShows()
+ {
+ var settingsTask = PrSettings.GetSettingsAsync();
+
+ var requests = await Service.GetAllAsync();
+ requests = requests.Where(x => x.Type == RequestType.TvShow);
+
+ var dbTv = requests;
+ var settings = await settingsTask;
+ if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
+ {
+ dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
+ }
+
+ IEnumerable qualities = new List();
+ if (IsAdmin)
+ {
+ try
+ {
+ var sonarrSettings = await SonarrSettings.GetSettingsAsync();
+ if (sonarrSettings.Enabled)
+ {
+ var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () =>
+ {
+ return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri));
+ });
+ qualities = result.Result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList();
+ }
+ else
+ {
+ var sickRageSettings = await SickRageSettings.GetSettingsAsync();
+ if (sickRageSettings.Enabled)
+ {
+ qualities = sickRageSettings.Qualities.Select(x => new QualityModel { Id = x.Key, Name = x.Value }).ToList();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Info(e);
+ }
+
+ }
+
+ var viewModel = dbTv.Select(tv => new RequestViewModel
+ {
+ ProviderId = tv.ProviderId,
+ Type = tv.Type,
+ Status = tv.Status,
+ ImdbId = tv.ImdbId,
+ Id = tv.Id,
+ PosterPath = tv.PosterPath,
+ ReleaseDate = tv.ReleaseDate,
+ ReleaseDateTicks = tv.ReleaseDate.Ticks,
+ RequestedDate = tv.RequestedDate,
+ RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks,
+ Released = DateTime.Now > tv.ReleaseDate,
+ Approved = tv.Available || tv.Approved,
+ Title = tv.Title,
+ Overview = tv.Overview,
+ RequestedUsers = IsAdmin ? tv.AllUsers.ToArray() : new string[] { },
+ ReleaseYear = tv.ReleaseDate.Year.ToString(),
+ Available = tv.Available,
+ Admin = IsAdmin,
+ IssueId = tv.IssueId,
+ TvSeriesRequestType = tv.SeasonsRequested,
+ Qualities = qualities.ToArray(),
+ Episodes = tv.Episodes.ToArray(),
+ }).ToList();
+
+ return Response.AsJson(viewModel);
+ }
+
+ private async Task GetAlbumRequests()
+ {
+ var settings = PrSettings.GetSettings();
+ var dbAlbum = await Service.GetAllAsync();
+ dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album);
+ if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
+ {
+ dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
+ }
+
+ var viewModel = dbAlbum.Select(album =>
+ {
+ return new RequestViewModel
+ {
+ ProviderId = album.ProviderId,
+ Type = album.Type,
+ Status = album.Status,
+ ImdbId = album.ImdbId,
+ Id = album.Id,
+ PosterPath = album.PosterPath,
+ ReleaseDate = album.ReleaseDate,
+ ReleaseDateTicks = album.ReleaseDate.Ticks,
+ RequestedDate = album.RequestedDate,
+ RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks,
+ Released = DateTime.Now > album.ReleaseDate,
+ Approved = album.Available || album.Approved,
+ Title = album.Title,
+ Overview = album.Overview,
+ RequestedUsers = IsAdmin ? album.AllUsers.ToArray() : new string[] { },
+ ReleaseYear = album.ReleaseDate.Year.ToString(),
+ Available = album.Available,
+ Admin = IsAdmin,
+ IssueId = album.IssueId,
+ TvSeriesRequestType = album.SeasonsRequested,
+ MusicBrainzId = album.MusicBrainzId,
+ ArtistName = album.ArtistName
+
+ };
+ }).ToList();
+
+ return Response.AsJson(viewModel);
+ }
+
+ private async Task DeleteRequest(int requestid)
+ {
+ this.RequiresClaims(UserClaims.Admin);
+ Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
+
+ var currentEntity = await Service.GetAsync(requestid);
+ await Service.DeleteRequestAsync(currentEntity);
+ return Response.AsJson(new JsonResponseModel { Result = true });
+ }
+
+ ///
+ /// Reports the issue.
+ /// Comment can be null if the IssueState != Other
+ ///
+ /// The request identifier.
+ /// The issue.
+ /// The comment.
+ ///
+ private async Task ReportIssue(int requestId, IssueState issue, string comment)
+ {
+ var originalRequest = await Service.GetAsync(requestId);
+ if (originalRequest == null)
+ {
+ return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
+ }
+ originalRequest.Issues = issue;
+ originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
+ ? $"{Username} - {comment}"
+ : string.Empty;
+
+
+ var result = await Service.UpdateRequestAsync(originalRequest);
+
+ var model = new NotificationModel
+ {
+ User = Username,
+ NotificationType = NotificationType.Issue,
+ Title = originalRequest.Title,
+ DateTime = DateTime.Now,
+ Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
+ };
+ await NotificationService.Publish(model);
+
+ return Response.AsJson(result
+ ? new JsonResponseModel { Result = true }
+ : new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
+ }
+
+ private async Task ClearIssue(int requestId)
+ {
+ this.RequiresClaims(UserClaims.Admin);
+
+ var originalRequest = await Service.GetAsync(requestId);
+ if (originalRequest == null)
+ {
+ return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" });
+ }
+ originalRequest.Issues = IssueState.None;
+ originalRequest.OtherMessage = string.Empty;
+
+ var result = await Service.UpdateRequestAsync(originalRequest);
+ return Response.AsJson(result
+ ? new JsonResponseModel { Result = true }
+ : new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
+ }
+
+ private async Task ChangeRequestAvailability(int requestId, bool available)
+ {
+ this.RequiresClaims(UserClaims.Admin);
+ Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
+ var originalRequest = await Service.GetAsync(requestId);
+ if (originalRequest == null)
+ {
+ return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" });
+ }
+
+ originalRequest.Available = available;
+
+ var result = await Service.UpdateRequestAsync(originalRequest);
+ return Response.AsJson(result
+ ? new { Result = true, Available = available, Message = string.Empty }
+ : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
+ }
+
+ private List MapMoviesToView(List dbMovies, List qualities)
+ {
+ return dbMovies.Select(movie => new RequestViewModel
+ {
+ ProviderId = movie.ProviderId,
+ Type = movie.Type,
+ Status = movie.Status,
+ ImdbId = movie.ImdbId,
+ Id = movie.Id,
+ PosterPath = movie.PosterPath,
+ ReleaseDate = movie.ReleaseDate,
+ ReleaseDateTicks = movie.ReleaseDate.Ticks,
+ RequestedDate = movie.RequestedDate,
+ Released = DateTime.Now > movie.ReleaseDate,
+ RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
+ Approved = movie.Available || movie.Approved,
+ Title = movie.Title,
+ Overview = movie.Overview,
+ RequestedUsers = IsAdmin ? movie.AllUsers.ToArray() : new string[] { },
+ ReleaseYear = movie.ReleaseDate.Year.ToString(),
+ Available = movie.Available,
+ Admin = IsAdmin,
+ IssueId = movie.IssueId,
+ Qualities = qualities.ToArray()
+ }).ToList();
+ }
+
+ private async Task> GetQualityProfiles()
+ {
+ var qualities = new List();
+ if (IsAdmin)
+ {
+ var cpSettings = CpSettings.GetSettings();
+ if (cpSettings.Enabled)
+ {
+ try
+ {
+ var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
+ {
+ return await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey)).ConfigureAwait(false);
+ });
+ if (result != null)
+ {
+ qualities = result.list.Select(x => new QualityModel { Id = x._id, Name = x.label }).ToList();
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Info(e);
+ }
+ }
+ }
+ return qualities;
+ }
+
+ private async Task> FilterMovies(string searchTerm = null, bool approved = false, bool notApproved = false,
+ bool available = false, bool notAvailable = false, bool released = false, bool notReleased = false)
+ {
+ var settings = PrSettings.GetSettings();
+ var allRequests = await Service.GetAllAsync();
+ allRequests = allRequests.Where(x => x.Type == RequestType.Movie);
+
+ var dbMovies = allRequests;
+
+ if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
+ {
+ dbMovies = dbMovies.Where(x => x.UserHasRequested(Username));
+ }
+
+ // Filter the movies on the search term
+ if (!string.IsNullOrEmpty(searchTerm))
+ {
+ dbMovies = dbMovies.Where(x => x.Title.Contains(searchTerm));
+ }
+
+ if (approved)
+ {
+ dbMovies = dbMovies.Where(x => x.Approved);
+ }
+
+ if (notApproved)
+ {
+ dbMovies = dbMovies.Where(x => !x.Approved);
+ }
+
+ if (available)
+ {
+ dbMovies = dbMovies.Where(x => x.Available);
+ }
+
+ if (notAvailable)
+ {
+ dbMovies = dbMovies.Where(x => !x.Available);
+ }
+
+ if (released)
+ {
+ dbMovies = dbMovies.Where(x => DateTime.Now > x.ReleaseDate);
+ }
+
+ if (notReleased)
+ {
+ dbMovies = dbMovies.Where(x => DateTime.Now < x.ReleaseDate);
+ }
+
+ return dbMovies;
+ }
+ }
+}
diff --git a/PlexRequests.UI/Modules/RequestsModule.cs b/PlexRequests.UI/Modules/RequestsModule.cs
index cb8ed6882..8a2a9821a 100644
--- a/PlexRequests.UI/Modules/RequestsModule.cs
+++ b/PlexRequests.UI/Modules/RequestsModule.cs
@@ -67,7 +67,8 @@ namespace PlexRequests.UI.Modules
ISonarrApi sonarrApi,
ISickRageApi sickRageApi,
ICacheProvider cache,
- IAnalytics an) : base("requests", prSettings)
+ IAnalytics an,
+ INotificationEngine engine) : base("requests", prSettings)
{
Service = service;
PrSettings = prSettings;
@@ -81,6 +82,7 @@ namespace PlexRequests.UI.Modules
CpApi = cpApi;
Cache = cache;
Analytics = an;
+ NotificationEngine = engine;
Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies();
@@ -108,6 +110,7 @@ namespace PlexRequests.UI.Modules
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; }
+ private INotificationEngine NotificationEngine { get; }
private async Task LoadRequests()
{
@@ -376,6 +379,8 @@ namespace PlexRequests.UI.Modules
originalRequest.Available = available;
var result = await Service.UpdateRequestAsync(originalRequest);
+ var plexService = await PlexSettings.GetSettingsAsync();
+ await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken);
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 5f0fa4ed1..73f016f24 100644
--- a/PlexRequests.UI/Modules/SearchModule.cs
+++ b/PlexRequests.UI/Modules/SearchModule.cs
@@ -110,7 +110,7 @@ namespace PlexRequests.UI.Modules
Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm);
Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm);
- Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm);
+ Get["music/{searchTerm}", true] = async (x, ct) => await SearchAlbum((string)x.searchTerm);
Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id);
Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies();
@@ -252,9 +252,11 @@ namespace PlexRequests.UI.Modules
VoteCount = movie.VoteCount
};
var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies);
- if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString()))
+ var plexMovie = Checker.GetMovie(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString());
+ if (plexMovie != null)
{
viewMovie.Available = true;
+ viewMovie.PlexUrl = plexMovie.Url;
}
else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db
{
@@ -343,9 +345,12 @@ namespace PlexRequests.UI.Modules
providerId = viewT.Id.ToString();
}
- if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId))
+ var plexShow = Checker.GetTvShow(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4),
+ providerId);
+ if (plexShow != null)
{
viewT.Available = true;
+ viewT.PlexUrl = plexShow.Url;
}
else if (t.show?.externals?.thetvdb != null)
{
@@ -371,7 +376,7 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(viewTv);
}
- private async Task SearchMusic(string searchTerm)
+ private async Task SearchAlbum(string searchTerm)
{
Analytics.TrackEventAsync(Category.Search, Action.Album, searchTerm, Username, CookieHelper.GetAnalyticClientId(Cookies));
var apiAlbums = new List();
@@ -405,9 +410,11 @@ namespace PlexRequests.UI.Modules
DateTime release;
DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release);
var artist = a.ArtistCredit?.FirstOrDefault()?.artist;
- if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name))
+ var plexAlbum = Checker.GetAlbum(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name);
+ if (plexAlbum != null)
{
viewA.Available = true;
+ viewA.PlexUrl = plexAlbum.Url;
}
if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id))
{
diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs
index 61481faef..e30f4adc2 100644
--- a/PlexRequests.UI/Modules/UserLoginModule.cs
+++ b/PlexRequests.UI/Modules/UserLoginModule.cs
@@ -61,7 +61,17 @@ namespace PlexRequests.UI.Modules
PlexSettings = plexSettings;
Linker = linker;
- Get["UserLoginIndex", "/", true] = async (x, ct) => await Index();
+ Get["UserLoginIndex", "/", true] = async (x, ct) =>
+ {
+ if (!string.IsNullOrEmpty(Username) || IsAdmin)
+ {
+ var uri = Linker.BuildRelativeUri(Context, "SearchIndex");
+ return Response.AsRedirect(uri.ToString());
+ }
+ var settings = await AuthService.GetSettingsAsync();
+ return View["Index", settings];
+ };
+
Post["/", true] = async (x, ct) => await LoginUser();
Get["/logout"] = x => Logout();
}
@@ -75,12 +85,6 @@ namespace PlexRequests.UI.Modules
private static Logger Log = LogManager.GetCurrentClassLogger();
- public async Task Index()
- {
- var settings = await AuthService.GetSettingsAsync();
- return View["Index", settings];
- }
-
private async Task LoginUser()
{
var dateTimeOffset = Request.Form.DateTimeOffset;
@@ -89,7 +93,7 @@ namespace PlexRequests.UI.Modules
if (string.IsNullOrWhiteSpace(username))
{
Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass;
- var uri = Linker.BuildAbsoluteUri(Context, "UserLoginIndex");
+ var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
return Response.AsRedirect(uri.ToString()); // TODO Check this
}
@@ -102,7 +106,7 @@ namespace PlexRequests.UI.Modules
{
Log.Debug("User is in denied list, not allowing them to authenticate");
Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass;
- var uri = Linker.BuildAbsoluteUri(Context, "UserLoginIndex");
+ var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
return Response.AsRedirect(uri.ToString()); // TODO Check this
}
@@ -161,7 +165,7 @@ namespace PlexRequests.UI.Modules
if (!authenticated)
{
- var uri = Linker.BuildAbsoluteUri(Context, "UserLoginIndex");
+ var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass;
return Response.AsRedirect(uri.ToString()); // TODO Check this
}
@@ -172,11 +176,11 @@ namespace PlexRequests.UI.Modules
{
if (!landingSettings.BeforeLogin)
{
- var uri = Linker.BuildAbsoluteUri(Context, "LandingPageIndex");
+ var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex");
return Response.AsRedirect(uri.ToString());
}
}
- var retVal = Linker.BuildAbsoluteUri(Context, "SearchIndex");
+ var retVal = Linker.BuildRelativeUri(Context, "SearchIndex");
return Response.AsRedirect(retVal.ToString()); // TODO Check this
}
diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs
index 9667dac6f..1e9909565 100644
--- a/PlexRequests.UI/Modules/UserManagementModule.cs
+++ b/PlexRequests.UI/Modules/UserManagementModule.cs
@@ -4,9 +4,10 @@ using System.Linq;
using System.Threading.Tasks;
using Nancy;
+using Nancy.Extensions;
using Nancy.Responses.Negotiation;
using Nancy.Security;
-
+using Newtonsoft.Json;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.Models;
@@ -30,9 +31,10 @@ namespace PlexRequests.UI.Modules
Get["/"] = x => Load();
Get["/users", true] = async (x, ct) => await LoadUsers();
- Post["/createuser"] = x => CreateUser(Request.Form["username"].ToString(), Request.Form["password"].ToString());
+ Post["/createuser"] = x => CreateUser();
Get["/local/{id}"] = x => LocalDetails((Guid)x.id);
- Get["/plex/{id}", true] = async (x,ct) => await PlexDetails(x.id);
+ Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id);
+ Get["/claims"] = x => GetClaims();
}
private ICustomUserMapper UserMapper { get; }
@@ -57,11 +59,12 @@ namespace PlexRequests.UI.Modules
model.Add(new UserManagementUsersViewModel
{
- Id= user.UserGuid,
+ Id = user.UserGuid,
Claims = claimsString,
Username = user.UserName,
Type = UserType.LocalUser,
- EmailAddress = userProps.EmailAddress
+ EmailAddress = userProps.EmailAddress,
+ ClaimsArray = claims
});
}
@@ -91,9 +94,17 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(model);
}
- private Response CreateUser(string username, string password)
+ private Response CreateUser()
{
- if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
+ var body = Request.Body.AsString();
+ if (string.IsNullOrEmpty(body))
+ {
+ return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" });
+ }
+
+ var model = JsonConvert.DeserializeObject(body);
+
+ if (string.IsNullOrWhiteSpace(model.Username) || string.IsNullOrWhiteSpace(model.Password))
{
return Response.AsJson(new JsonResponseModel
{
@@ -101,7 +112,7 @@ namespace PlexRequests.UI.Modules
Message = "Please enter in a valid Username and Password"
});
}
- var user = UserMapper.CreateRegularUser(username, password);
+ var user = UserMapper.CreateUser(model.Username, model.Password, model.Claims, new UserProperties { EmailAddress = model.EmailAddress });
if (user.HasValue)
{
return Response.AsJson(user);
@@ -139,6 +150,21 @@ namespace PlexRequests.UI.Modules
return Nancy.Response.NoBody;
}
+
+ ///
+ /// Returns all claims for the users.
+ ///
+ ///
+ private Response GetClaims()
+ {
+ var retVal = new List();
+ var claims = UserMapper.GetAllClaims();
+ foreach (var c in claims)
+ {
+ retVal.Add(new { Name = c, Selected = false });
+ }
+ return Response.AsJson(retVal);
+ }
}
}
diff --git a/PlexRequests.UI/Modules/UserWizardModule.cs b/PlexRequests.UI/Modules/UserWizardModule.cs
index 253288929..1bfbbd679 100644
--- a/PlexRequests.UI/Modules/UserWizardModule.cs
+++ b/PlexRequests.UI/Modules/UserWizardModule.cs
@@ -34,7 +34,7 @@ using Nancy.Extensions;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Validation;
-
+using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
@@ -84,7 +84,9 @@ namespace PlexRequests.UI.Modules
private ICustomUserMapper Mapper { get; }
private IAnalytics Analytics { get; }
-
+ private static Logger Log = LogManager.GetCurrentClassLogger();
+
+
private Response PlexAuth()
{
var user = this.Bind();
@@ -103,9 +105,10 @@ namespace PlexRequests.UI.Modules
// Set the auth token in the session so we can use it in the next form
Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token;
-
+
var servers = PlexApi.GetServer(model.user.authentication_token);
var firstServer = servers.Server.FirstOrDefault();
+
return Response.AsJson(new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme });
}
@@ -119,6 +122,20 @@ namespace PlexRequests.UI.Modules
}
form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString(); // Set the auth token from the previous form
+ // Get the machine ID from the settings (This could have changed)
+ try
+ {
+ var servers = PlexApi.GetServer(form.PlexAuthToken);
+ var firstServer = servers.Server.FirstOrDefault(x => x.AccessToken == form.PlexAuthToken);
+
+ Session[SessionKeys.UserWizardMachineId] = firstServer?.MachineIdentifier;
+ }
+ catch (Exception e)
+ {
+ // Probably bad settings, just continue
+ Log.Error(e);
+ }
+
var result = await PlexSettings.SaveSettingsAsync(form);
if (result)
{
diff --git a/PlexRequests.UI/NinjectModules/ConfigurationModule.cs b/PlexRequests.UI/NinjectModules/ConfigurationModule.cs
index 5bb10dca3..ae55786e1 100644
--- a/PlexRequests.UI/NinjectModules/ConfigurationModule.cs
+++ b/PlexRequests.UI/NinjectModules/ConfigurationModule.cs
@@ -50,6 +50,7 @@ namespace PlexRequests.UI.NinjectModules
Bind().To();
Bind().To().InSingletonScope();
+ Bind().To();
}
}
}
\ No newline at end of file
diff --git a/PlexRequests.UI/PlexRequests.UI.csproj b/PlexRequests.UI/PlexRequests.UI.csproj
index d178bbc92..1779f378b 100644
--- a/PlexRequests.UI/PlexRequests.UI.csproj
+++ b/PlexRequests.UI/PlexRequests.UI.csproj
@@ -250,6 +250,7 @@
+
@@ -282,6 +283,12 @@
Always
+
+ Always
+
+
+ Always
+
PreserveNewest
diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx
index 58e8f71c8..4f23fce97 100644
--- a/PlexRequests.UI/Resources/UI.resx
+++ b/PlexRequests.UI/Resources/UI.resx
@@ -440,4 +440,7 @@
There is no information available for the release date
+
+ View In Plex
+
\ No newline at end of file
diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs
index 20ab447d7..6cd282c32 100644
--- a/PlexRequests.UI/Resources/UI1.Designer.cs
+++ b/PlexRequests.UI/Resources/UI1.Designer.cs
@@ -987,6 +987,15 @@ namespace PlexRequests.UI.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to View In Plex.
+ ///
+ public static string Search_ViewInPlex {
+ get {
+ return ResourceManager.GetString("Search_ViewInPlex", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to You have reached your weekly request limit for Albums! Please contact your admin..
///
diff --git a/PlexRequests.UI/Validators/PlexRequestsValidator.cs b/PlexRequests.UI/Validators/PlexRequestsValidator.cs
index c82817185..7d61eba2c 100644
--- a/PlexRequests.UI/Validators/PlexRequestsValidator.cs
+++ b/PlexRequests.UI/Validators/PlexRequestsValidator.cs
@@ -24,6 +24,8 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
+
+using System;
using FluentValidation;
using PlexRequests.Core.SettingModels;
@@ -34,18 +36,18 @@ namespace PlexRequests.UI.Validators
{
public PlexRequestsValidator()
{
- RuleFor(x => x.BaseUrl).NotEqual("requests").WithMessage("You cannot use 'requests' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("admin").WithMessage("You cannot use 'admin' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("search").WithMessage("You cannot use 'search' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("issues").WithMessage("You cannot use 'issues' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("userlogin").WithMessage("You cannot use 'userlogin' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("login").WithMessage("You cannot use 'login' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("test").WithMessage("You cannot use 'test' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("approval").WithMessage("You cannot use 'approval' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application.");
- RuleFor(x => x.BaseUrl).NotEqual("landing").WithMessage("You cannot use 'landing' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("requests",StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'requests' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("admin", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'admin' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("search", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'search' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("issues", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'issues' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("userlogin", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'userlogin' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("login", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'login' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("test", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'test' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("approval", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'approval' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("updatechecker", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'updatechecker' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("usermanagement", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'usermanagement' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("api", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'api' as this is reserved by the application.");
+ RuleFor(x => x.BaseUrl).NotEqual("landing", StringComparer.CurrentCultureIgnoreCase).WithMessage("You cannot use 'landing' as this is reserved by the application.");
}
}
}
\ No newline at end of file
diff --git a/PlexRequests.UI/Views/Admin/LandingPage.cshtml b/PlexRequests.UI/Views/Admin/LandingPage.cshtml
index 9f6dcec6f..38d3cc6fa 100644
--- a/PlexRequests.UI/Views/Admin/LandingPage.cshtml
+++ b/PlexRequests.UI/Views/Admin/LandingPage.cshtml
@@ -1,7 +1,7 @@
@using PlexRequests.UI.Helpers
@Html.Partial("_Sidebar")
@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase
-
+@Html.LoadDateTimePickerAsset()