This commit is contained in:
tidusjar 2016-08-28 17:10:24 +01:00
commit 0f98f12756
13 changed files with 297 additions and 101 deletions

View file

@ -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<RequestedModel> modelChanged, string apiKey);
Task NotifyUsers(RequestedModel modelChanged, string apiKey);
}
}

View file

@ -53,7 +53,7 @@ namespace PlexRequests.Services.Jobs
public class PlexAvailabilityChecker : IJob, IAvailabilityChecker public class PlexAvailabilityChecker : IJob, IAvailabilityChecker
{ {
public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache, public PlexAvailabilityChecker(ISettingsService<PlexSettings> plexSettings, IRequestService request, IPlexApi plex, ICacheProvider cache,
INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo) INotificationService notify, IJobRecord rec, IRepository<UsersToNotify> users, IRepository<PlexEpisodes> repo, INotificationEngine e)
{ {
Plex = plexSettings; Plex = plexSettings;
RequestService = request; RequestService = request;
@ -63,6 +63,7 @@ namespace PlexRequests.Services.Jobs
Job = rec; Job = rec;
UserNotifyRepo = users; UserNotifyRepo = users;
EpisodeRepo = repo; EpisodeRepo = repo;
NotificationEngine = e;
} }
private ISettingsService<PlexSettings> Plex { get; } private ISettingsService<PlexSettings> Plex { get; }
@ -74,6 +75,9 @@ namespace PlexRequests.Services.Jobs
private INotificationService Notification { get; } private INotificationService Notification { get; }
private IJobRecord Job { get; } private IJobRecord Job { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; } private IRepository<UsersToNotify> UserNotifyRepo { get; }
private INotificationEngine NotificationEngine { get; }
public void CheckAndUpdateAll() public void CheckAndUpdateAll()
{ {
var plexSettings = Plex.GetSettings(); var plexSettings = Plex.GetSettings();
@ -148,7 +152,7 @@ namespace PlexRequests.Services.Jobs
if (modifiedModel.Any()) if (modifiedModel.Any())
{ {
NotifyUsers(modifiedModel, plexSettings.PlexAuthToken); NotificationEngine.NotifyUsers(modifiedModel, plexSettings.PlexAuthToken);
RequestService.BatchUpdate(modifiedModel); RequestService.BatchUpdate(modifiedModel);
} }
@ -489,63 +493,6 @@ namespace PlexRequests.Services.Jobs
return true; return true;
} }
private void NotifyUsers(IEnumerable<RequestedModel> 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) public void Execute(IJobExecutionContext context)
{ {
try try

View file

@ -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<UsersToNotify> repo, INotificationService service)
{
PlexApi = p;
UserNotifyRepo = repo;
Notification = service;
}
private IPlexApi PlexApi { get; }
private IRepository<UsersToNotify> UserNotifyRepo { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private INotificationService Notification { get; }
public async Task NotifyUsers(IEnumerable<RequestedModel> 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);
}
}
}

View file

@ -75,6 +75,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Interfaces\IJobRecord.cs" /> <Compile Include="Interfaces\IJobRecord.cs" />
<Compile Include="Interfaces\INotificationEngine.cs" />
<Compile Include="Jobs\JobRecord.cs" /> <Compile Include="Jobs\JobRecord.cs" />
<Compile Include="Jobs\JobNames.cs" /> <Compile Include="Jobs\JobNames.cs" />
<Compile Include="Jobs\PlexEpisodeCacher.cs" /> <Compile Include="Jobs\PlexEpisodeCacher.cs" />
@ -97,6 +98,7 @@
<Compile Include="Interfaces\INotification.cs" /> <Compile Include="Interfaces\INotification.cs" />
<Compile Include="Interfaces\INotificationService.cs" /> <Compile Include="Interfaces\INotificationService.cs" />
<Compile Include="Notification\EmailMessageNotification.cs" /> <Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationEngine.cs" />
<Compile Include="Notification\NotificationModel.cs" /> <Compile Include="Notification\NotificationModel.cs" />
<Compile Include="Notification\NotificationService.cs" /> <Compile Include="Notification\NotificationService.cs" />
<Compile Include="Notification\PushoverNotification.cs" /> <Compile Include="Notification\PushoverNotification.cs" />

View file

@ -9,6 +9,7 @@
$scope.selectedUser = {}; // User on the right side $scope.selectedUser = {}; // User on the right side
$scope.selectedClaims = {}; $scope.selectedClaims = {};
$scope.sortType = "username"; $scope.sortType = "username";
$scope.sortReverse = false; $scope.sortReverse = false;
$scope.searchTerm = ""; $scope.searchTerm = "";
@ -27,17 +28,17 @@
// Get all users in the system // Get all users in the system
$scope.getUsers = function () { $scope.getUsers = function () {
$scope.users = userManagementService.getUsers() $scope.users = userManagementService.getUsers()
.then(function (data) { .then(function (data) {
$scope.users = data.data; $scope.users = data.data;
}); });
}; };
// Get the claims and populate the create dropdown // Get the claims and populate the create dropdown
$scope.getClaims = function () { $scope.getClaims = function () {
userManagementService.getClaims() userManagementService.getClaims()
.then(function (data) { .then(function (data) {
$scope.claims = data.data; $scope.claims = data.data;
}); });
} }
// Create a user, do some validation too // Create a user, do some validation too
@ -50,23 +51,35 @@
return; return;
} }
userManagementService.addUser($scope.user, $scope.selectedClaims).then(function (data) { userManagementService.addUser($scope.user, $scope.selectedClaims)
if (data.message) { .then(function (data) {
$scope.error.error = true; if (data.message) {
$scope.error.errorMessage = data.message; $scope.error.error = true;
} else { $scope.error.errorMessage = data.message;
$scope.users.push(data); // Push the new user into the array to update the DOM } else {
$scope.user = {}; $scope.users.push(data); // Push the new user into the array to update the DOM
$scope.selectedClaims = {}; $scope.user = {};
} $scope.selectedClaims = {};
}); }
});
}; };
$scope.$watch('claims|filter:{selected:true}', function (nv) { $scope.$watch('claims|filter:{selected:true}',
$scope.selectedClaims = nv.map(function (claim) { function (nv) {
return claim.name; $scope.selectedClaims = nv.map(function (claim) {
}); return claim.name;
}, true); });
},
true);
$scope.updateUser = function () {
}
function getBaseUrl() {
return $('#baseUrl').val();
}
// On page load // On page load

View file

@ -5,6 +5,21 @@
return opts.inverse(this); 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 () { $(function () {

View file

@ -8,21 +8,6 @@
return s; return s;
} }
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() { $(function() {
$('[data-toggle="tooltip"]').tooltip(); $('[data-toggle="tooltip"]').tooltip();
}); });

View file

@ -207,6 +207,20 @@ namespace PlexRequests.UI.Helpers
return helper.Raw(asset); return helper.Raw(asset);
} }
public static IHtmlString LoadUserManagementAssets(this HtmlHelpers helper)
{
var assetLocation = GetBaseUrl();
var content = GetContentUrl(assetLocation);
var controller = $"<script src=\"{content}/Content/app/userManagement/userManagementController.js?v={Assembly}\" type=\"text/javascript\"></script>";
controller += $"<script src=\"{content}/Content/app/userManagement/userManagementService.js?v={Assembly}\" type=\"text/javascript\"></script>";
return helper.Raw(controller);
}
public static IHtmlString LoadTableAssets(this HtmlHelpers helper) public static IHtmlString LoadTableAssets(this HtmlHelpers helper)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();

View file

@ -16,6 +16,7 @@ namespace PlexRequests.UI.Models
public UserType Type { get; set; } public UserType Type { get; set; }
public string EmailAddress { get; set; } public string EmailAddress { get; set; }
public UserManagementPlexInformation PlexInfo { get; set; } public UserManagementPlexInformation PlexInfo { get; set; }
public string[] ClaimsArray { get; set; }
} }
public class UserManagementPlexInformation public class UserManagementPlexInformation

View file

@ -67,7 +67,8 @@ namespace PlexRequests.UI.Modules
ISonarrApi sonarrApi, ISonarrApi sonarrApi,
ISickRageApi sickRageApi, ISickRageApi sickRageApi,
ICacheProvider cache, ICacheProvider cache,
IAnalytics an) : base("requests", prSettings) IAnalytics an,
INotificationEngine engine) : base("requests", prSettings)
{ {
Service = service; Service = service;
PrSettings = prSettings; PrSettings = prSettings;
@ -81,6 +82,7 @@ namespace PlexRequests.UI.Modules
CpApi = cpApi; CpApi = cpApi;
Cache = cache; Cache = cache;
Analytics = an; Analytics = an;
NotificationEngine = engine;
Get["/", true] = async (x, ct) => await LoadRequests(); Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies(); Get["/movies", true] = async (x, ct) => await GetMovies();
@ -108,6 +110,7 @@ namespace PlexRequests.UI.Modules
private ISickRageApi SickRageApi { get; } private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; } private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; } private ICacheProvider Cache { get; }
private INotificationEngine NotificationEngine { get; }
private async Task<Negotiator> LoadRequests() private async Task<Negotiator> LoadRequests()
{ {
@ -376,6 +379,8 @@ namespace PlexRequests.UI.Modules
originalRequest.Available = available; originalRequest.Available = available;
var result = await Service.UpdateRequestAsync(originalRequest); var result = await Service.UpdateRequestAsync(originalRequest);
var plexService = await PlexSettings.GetSettingsAsync();
await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken);
return Response.AsJson(result return Response.AsJson(result
? new { Result = true, Available = available, Message = string.Empty } ? new { Result = true, Available = available, Message = string.Empty }
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" }); : new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });

View file

@ -63,7 +63,8 @@ namespace PlexRequests.UI.Modules
Claims = claimsString, Claims = claimsString,
Username = user.UserName, Username = user.UserName,
Type = UserType.LocalUser, Type = UserType.LocalUser,
EmailAddress = userProps.EmailAddress EmailAddress = userProps.EmailAddress,
ClaimsArray = claims
}); });
} }

View file

@ -50,6 +50,7 @@ namespace PlexRequests.UI.NinjectModules
Bind<ICustomUserMapper>().To<UserMapper>(); Bind<ICustomUserMapper>().To<UserMapper>();
Bind<INotificationService>().To<NotificationService>().InSingletonScope(); Bind<INotificationService>().To<NotificationService>().InSingletonScope();
Bind<INotificationEngine>().To<NotificationEngine>();
} }
} }
} }

View file

@ -1,8 +1,7 @@
@using PlexRequests.UI.Helpers @using PlexRequests.UI.Helpers
@inherits PlexRequests.UI.Helpers.AngularViewBase @inherits PlexRequests.UI.Helpers.AngularViewBase
<script src="~/Content/app/userManagement/userManagementController.js"></script> @Html.LoadUserManagementAssets()
<script src="~/Content/app/userManagement/userManagementService.js"></script>
<div ng-controller="userManagementController" ng-init="init()"> <div ng-controller="userManagementController" ng-init="init()">
<br /> <br />
@ -110,9 +109,27 @@
<div> <div>
<strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span> <strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span>
</div> </div>
</div> <br/>
<br/>
<div ng-show="selectedUser.type === 1">
<!--Edit-->
<strong>Modify Roles:</strong>
<!--Load all claims-->
<div class="checkbox" ng-repeat="claim in claims">
<input id="claimCheckboxEdit_{{$id}}" class="checkbox-custom" name="selectedClaims[]" ng-checked="@*//TODO: Need to figure our how to preselect them*@" ng-model="claim.selected" type="checkbox" value="claim" />
<label for="claimCheckboxEdit_{{$id}}">{{claim.name}}</label>
</div>
<button ng-click="updateUser()" class="btn btn-primary-outline">Update</button>
</div>
</div> <!-- End of user side menu -->
</div> </div>