Merge pull request #110 from Drewster727/dev

misc changes (sorting, ui, requesting, testing, other)
pull/130/head
Jamie 9 years ago
commit 0b1a589a70

@ -33,7 +33,7 @@ namespace PlexRequests.Core
public interface IRequestService
{
long AddRequest(RequestedModel model);
bool CheckRequest(int providerId);
RequestedModel CheckRequest(int providerId);
void DeleteRequest(RequestedModel request);
bool UpdateRequest(RequestedModel model);
RequestedModel Get(int id);

@ -58,10 +58,11 @@ namespace PlexRequests.Core
return result ? id : -1;
}
public bool CheckRequest(int providerId)
public RequestedModel CheckRequest(int providerId)
{
var blobs = Repo.GetAll();
return blobs.Any(x => x.ProviderId == providerId);
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId);
return blob != null ? ByteConverterHelper.ReturnObject<RequestedModel>(blob.Content) : null;
}
public void DeleteRequest(RequestedModel request)

@ -24,6 +24,10 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace PlexRequests.Core.SettingModels
{
public class PlexRequestSettings : Settings
@ -36,6 +40,29 @@ namespace PlexRequests.Core.SettingModels
public bool RequireMovieApproval { get; set; }
public bool RequireTvShowApproval { get; set; }
public bool RequireMusicApproval { get; set; }
public bool UsersCanViewOnlyOwnRequests { get; set; }
public int WeeklyRequestLimit { get; set; }
public string NoApprovalUsers { get; set; }
[JsonIgnore]
public List<string> NoApprovalUserList
{
get
{
var users = new List<string>();
if (string.IsNullOrEmpty(NoApprovalUsers))
{
return users;
}
var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var user in splitUsers)
{
if (!string.IsNullOrWhiteSpace(user))
users.Add(user.Trim());
}
return users;
}
}
}
}

@ -27,6 +27,7 @@
using System.Threading.Tasks;
using PlexRequests.Services.Notification;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Services.Interfaces
{
@ -35,5 +36,7 @@ namespace PlexRequests.Services.Interfaces
string NotificationName { get; }
Task NotifyAsync(NotificationModel model);
Task NotifyAsync(NotificationModel model, Settings settings);
}
}

@ -27,12 +27,14 @@
using System.Threading.Tasks;
using PlexRequests.Services.Notification;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Services.Interfaces
{
public interface INotificationService
{
Task Publish(NotificationModel model);
Task Publish(NotificationModel model, Settings settings);
void Subscribe(INotification notification);
void UnSubscribe(INotification notification);

@ -46,24 +46,29 @@ namespace PlexRequests.Services.Notification
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
private EmailNotificationSettings Settings => GetConfiguration();
public string NotificationName => "EmailMessageNotification";
public async Task NotifyAsync(NotificationModel model)
{
var configuration = GetConfiguration();
if (!ValidateConfiguration(configuration))
{
return;
}
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var emailSettings = (EmailNotificationSettings)settings;
if (!ValidateConfiguration(emailSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await EmailNewRequest(model);
await EmailNewRequest(model, emailSettings);
break;
case NotificationType.Issue:
await EmailIssue(model);
await EmailIssue(model, emailSettings);
break;
case NotificationType.RequestAvailable:
throw new NotImplementedException();
@ -74,6 +79,10 @@ namespace PlexRequests.Services.Notification
case NotificationType.AdminNote:
throw new NotImplementedException();
case NotificationType.Test:
await EmailTest(model, emailSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
@ -100,23 +109,23 @@ namespace PlexRequests.Services.Notification
return true;
}
private async Task EmailNewRequest(NotificationModel model)
private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MailMessage
{
IsBodyHtml = true,
To = { new MailAddress(Settings.RecipientEmail) },
To = { new MailAddress(settings.RecipientEmail) },
Body = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}",
From = new MailAddress(Settings.EmailSender),
From = new MailAddress(settings.EmailSender),
Subject = $"Plex Requests: New request for {model.Title}!"
};
try
{
using (var smtp = new SmtpClient(Settings.EmailHost, Settings.EmailPort))
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
{
smtp.Credentials = new NetworkCredential(Settings.EmailUsername, Settings.EmailPassword);
smtp.EnableSsl = Settings.Ssl;
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
smtp.EnableSsl = settings.Ssl;
await smtp.SendMailAsync(message).ConfigureAwait(false);
}
}
@ -130,23 +139,53 @@ namespace PlexRequests.Services.Notification
}
}
private async Task EmailIssue(NotificationModel model)
private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MailMessage
{
IsBodyHtml = true,
To = { new MailAddress(Settings.RecipientEmail) },
To = { new MailAddress(settings.RecipientEmail) },
Body = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!",
From = new MailAddress(Settings.RecipientEmail),
From = new MailAddress(settings.RecipientEmail),
Subject = $"Plex Requests: New issue for {model.Title}!"
};
try
{
using (var smtp = new SmtpClient(Settings.EmailHost, Settings.EmailPort))
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
{
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
smtp.EnableSsl = settings.Ssl;
await smtp.SendMailAsync(message).ConfigureAwait(false);
}
}
catch (SmtpException smtp)
{
Log.Error(smtp);
}
catch (Exception e)
{
Log.Error(e);
}
}
private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings)
{
var message = new MailMessage
{
IsBodyHtml = true,
To = { new MailAddress(settings.RecipientEmail) },
Body = "This is just a test! Success!",
From = new MailAddress(settings.RecipientEmail),
Subject = "Plex Requests: Test Message!"
};
try
{
using (var smtp = new SmtpClient(settings.EmailHost, settings.EmailPort))
{
smtp.Credentials = new NetworkCredential(Settings.EmailUsername, Settings.EmailPassword);
smtp.EnableSsl = Settings.Ssl;
smtp.Credentials = new NetworkCredential(settings.EmailUsername, settings.EmailPassword);
smtp.EnableSsl = settings.Ssl;
await smtp.SendMailAsync(message).ConfigureAwait(false);
}
}

@ -32,6 +32,7 @@ using System.Threading.Tasks;
using NLog;
using PlexRequests.Services.Interfaces;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Services.Notification
{
@ -47,6 +48,13 @@ namespace PlexRequests.Services.Notification
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
}
public async Task Publish(NotificationModel model, Settings settings)
{
var notificationTasks = Observers.Values.Select(notification => NotifyAsync(notification, model, settings));
await Task.WhenAll(notificationTasks).ConfigureAwait(false);
}
public void Subscribe(INotification notification)
{
Observers.TryAdd(notification.NotificationName, notification);
@ -67,6 +75,19 @@ namespace PlexRequests.Services.Notification
{
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
}
}
private static async Task NotifyAsync(INotification notification, NotificationModel model, Settings settings)
{
try
{
await notification.NotifyAsync(model, settings).ConfigureAwait(false);
}
catch (Exception ex)
{
Log.Error(ex, $"Notification '{notification.NotificationName}' failed with exception");
}
}
}
}

@ -33,5 +33,6 @@ namespace PlexRequests.Services.Notification
RequestAvailable,
RequestApproved,
AdminNote,
Test
}
}

@ -51,18 +51,25 @@ namespace PlexRequests.Services.Notification
public string NotificationName => "PushbulletNotification";
public async Task NotifyAsync(NotificationModel model)
{
if (!ValidateConfiguration())
{
return;
}
var configuration = GetSettings();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var pushSettings = (PushbulletNotificationSettings)settings;
if (!ValidateConfiguration(pushSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await PushNewRequestAsync(model);
await PushNewRequestAsync(model, pushSettings);
break;
case NotificationType.Issue:
await PushIssueAsync(model);
await PushIssueAsync(model, pushSettings);
break;
case NotificationType.RequestAvailable:
break;
@ -70,18 +77,21 @@ namespace PlexRequests.Services.Notification
break;
case NotificationType.AdminNote:
break;
case NotificationType.Test:
await PushTestAsync(model, pushSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ValidateConfiguration()
private bool ValidateConfiguration(PushbulletNotificationSettings settings)
{
if (!Settings.Enabled)
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(Settings.AccessToken))
if (string.IsNullOrEmpty(settings.AccessToken))
{
return false;
}
@ -93,13 +103,13 @@ namespace PlexRequests.Services.Notification
return SettingsService.GetSettings();
}
private async Task PushNewRequestAsync(NotificationModel model)
private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = $"{model.Title} has been requested by user: {model.User}";
var pushTitle = $"Plex Requests: {model.Title} has been requested!";
try
{
var result = await PushbulletApi.PushAsync(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier);
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
if (result == null)
{
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
@ -111,13 +121,31 @@ namespace PlexRequests.Services.Notification
}
}
private async Task PushIssueAsync(NotificationModel model)
private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}";
try
{
var result = await PushbulletApi.PushAsync(Settings.AccessToken, pushTitle, message, Settings.DeviceIdentifier);
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
if (result != null)
{
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
private async Task PushTestAsync(NotificationModel model, PushbulletNotificationSettings settings)
{
var message = "This is just a test! Success!";
var pushTitle = "Plex Requests: Test Message!";
try
{
var result = await PushbulletApi.PushAsync(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
if (result != null)
{
Log.Error("Pushbullet api returned a null value, the notification did not get pushed");

@ -51,18 +51,25 @@ namespace PlexRequests.Services.Notification
public string NotificationName => "PushoverNotification";
public async Task NotifyAsync(NotificationModel model)
{
if (!ValidateConfiguration())
{
return;
}
var configuration = GetSettings();
await NotifyAsync(model, configuration);
}
public async Task NotifyAsync(NotificationModel model, Settings settings)
{
if (settings == null) await NotifyAsync(model);
var pushSettings = (PushoverNotificationSettings)settings;
if (!ValidateConfiguration(pushSettings)) return;
switch (model.NotificationType)
{
case NotificationType.NewRequest:
await PushNewRequestAsync(model);
await PushNewRequestAsync(model, pushSettings);
break;
case NotificationType.Issue:
await PushIssueAsync(model);
await PushIssueAsync(model, pushSettings);
break;
case NotificationType.RequestAvailable:
break;
@ -70,18 +77,21 @@ namespace PlexRequests.Services.Notification
break;
case NotificationType.AdminNote:
break;
case NotificationType.Test:
await PushTestAsync(model, pushSettings);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private bool ValidateConfiguration()
private bool ValidateConfiguration(PushoverNotificationSettings settings)
{
if (!Settings.Enabled)
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(Settings.AccessToken) || string.IsNullOrEmpty(Settings.UserToken))
if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken))
{
return false;
}
@ -93,12 +103,12 @@ namespace PlexRequests.Services.Notification
return SettingsService.GetSettings();
}
private async Task PushNewRequestAsync(NotificationModel model)
private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}";
try
{
var result = await PushoverApi.PushAsync(Settings.AccessToken, message, Settings.UserToken);
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
if (result?.status != 1)
{
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
@ -110,12 +120,29 @@ namespace PlexRequests.Services.Notification
}
}
private async Task PushIssueAsync(NotificationModel model)
private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}";
try
{
var result = await PushoverApi.PushAsync(Settings.AccessToken, message, Settings.UserToken);
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
if (result?.status != 1)
{
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings)
{
var message = $"Plex Requests: Test Message!";
try
{
var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken);
if (result?.status != 1)
{
Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed");

@ -2,12 +2,20 @@
using System.Security.Cryptography;
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace PlexRequests.Store
{
[Table("Requested")]
public class RequestedModel : Entity
{
public RequestedModel()
{
RequestedUsers = new List<string>();
}
// ReSharper disable once IdentifierTypo
public int ProviderId { get; set; }
public string ImdbId { get; set; }
@ -18,7 +26,10 @@ namespace PlexRequests.Store
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
[Obsolete("Use RequestedUsers")]
public string RequestedBy { get; set; }
public DateTime RequestedDate { get; set; }
public bool Available { get; set; }
public IssueState Issues { get; set; }
@ -27,6 +38,40 @@ namespace PlexRequests.Store
public int[] SeasonList { get; set; }
public int SeasonCount { get; set; }
public string SeasonsRequested { get; set; }
public List<string> RequestedUsers { get; set; }
[JsonIgnore]
public List<string> AllUsers
{
get
{
var u = new List<string>();
if (!string.IsNullOrEmpty(RequestedBy))
{
u.Add(RequestedBy);
}
if (RequestedUsers.Any())
{
u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy));
}
return u;
}
}
[JsonIgnore]
public bool CanApprove
{
get
{
return !Approved && !Available;
}
}
public bool UserHasRequested(string username)
{
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));
}
}
public enum RequestType

@ -22,7 +22,9 @@
.form-control-custom {
background-color: #4e5d6c !important;
color: white !important; }
color: white !important;
border-radius: 0;
box-shadow: 0 0 0 !important; }
h1 {
font-size: 3.5rem !important;
@ -40,6 +42,18 @@ label {
margin-bottom: 0.5rem !important;
font-size: 16px !important; }
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: #4e5d6c; }
.navbar .nav a .fa {
font-size: 130%;
top: 1px;
position: relative;
display: inline-block;
margin-right: 5px; }
.btn-danger-outline {
color: #d9534f !important;
background-color: transparent;
@ -162,3 +176,14 @@ label {
.scroll-top-wrapper i.fa {
line-height: inherit; }
.no-search-results {
text-align: center; }
.no-search-results .no-search-results-icon {
font-size: 10em;
color: #4e5d6c; }
.no-search-results .no-search-results-text {
margin: 20px 0;
color: #ccc; }

@ -1 +1 @@
@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}
@media(min-width:768px){.row{position:relative;}.bottom-align-text{position:absolute;bottom:0;right:0;}}@media(max-width:48em){.home{padding-top:1rem;}}@media(min-width:48em){.home{padding-top:4rem;}}.btn{border-radius:.25rem !important;}.multiSelect{background-color:#4e5d6c;}.form-control-custom{background-color:#4e5d6c !important;color:#fff !important;border-radius:0;box-shadow:0 0 0 !important;}h1{font-size:3.5rem !important;font-weight:600 !important;}.request-title{margin-top:0 !important;font-size:1.9rem !important;}p{font-size:1.1rem !important;}label{display:inline-block !important;margin-bottom:.5rem !important;font-size:16px !important;}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{background:#4e5d6c;}.navbar .nav a .fa{font-size:130%;top:1px;position:relative;display:inline-block;margin-right:5px;}.btn-danger-outline{color:#d9534f !important;background-color:transparent;background-image:none;border-color:#d9534f !important;}.btn-danger-outline:focus,.btn-danger-outline.focus,.btn-danger-outline:active,.btn-danger-outline.active,.btn-danger-outline:hover,.open>.btn-danger-outline.dropdown-toggle{color:#fff !important;background-color:#d9534f !important;border-color:#d9534f !important;}.btn-primary-outline{color:#ff761b !important;background-color:transparent;background-image:none;border-color:#ff761b !important;}.btn-primary-outline:focus,.btn-primary-outline.focus,.btn-primary-outline:active,.btn-primary-outline.active,.btn-primary-outline:hover,.open>.btn-primary-outline.dropdown-toggle{color:#fff !important;background-color:#df691a !important;border-color:#df691a !important;}.btn-info-outline{color:#5bc0de !important;background-color:transparent;background-image:none;border-color:#5bc0de !important;}.btn-info-outline:focus,.btn-info-outline.focus,.btn-info-outline:active,.btn-info-outline.active,.btn-info-outline:hover,.open>.btn-info-outline.dropdown-toggle{color:#fff !important;background-color:#5bc0de !important;border-color:#5bc0de !important;}.btn-warning-outline{color:#f0ad4e !important;background-color:transparent;background-image:none;border-color:#f0ad4e !important;}.btn-warning-outline:focus,.btn-warning-outline.focus,.btn-warning-outline:active,.btn-warning-outline.active,.btn-warning-outline:hover,.open>.btn-warning-outline.dropdown-toggle{color:#fff !important;background-color:#f0ad4e !important;border-color:#f0ad4e !important;}.btn-success-outline{color:#5cb85c !important;background-color:transparent;background-image:none;border-color:#5cb85c !important;}.btn-success-outline:focus,.btn-success-outline.focus,.btn-success-outline:active,.btn-success-outline.active,.btn-success-outline:hover,.open>.btn-success-outline.dropdown-toggle{color:#fff !important;background-color:#5cb85c !important;border-color:#5cb85c !important;}#movieList .mix{display:none;}#tvList .mix{display:none;}.scroll-top-wrapper{position:fixed;opacity:0;visibility:hidden;overflow:hidden;text-align:center;z-index:99999999;background-color:#4e5d6c;color:#eee;width:50px;height:48px;line-height:48px;right:30px;bottom:30px;padding-top:2px;border-top-left-radius:10px;border-top-right-radius:10px;border-bottom-right-radius:10px;border-bottom-left-radius:10px;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;}.scroll-top-wrapper:hover{background-color:#637689;}.scroll-top-wrapper.show{visibility:visible;cursor:pointer;opacity:1;}.scroll-top-wrapper i.fa{line-height:inherit;}.no-search-results{text-align:center;}.no-search-results .no-search-results-icon{font-size:10em;color:#4e5d6c;}.no-search-results .no-search-results-text{margin:20px 0;color:#ccc;}

@ -6,7 +6,9 @@ $info-colour: #5bc0de;
$warning-colour: #f0ad4e;
$danger-colour: #d9534f;
$success-colour: #5cb85c;
$i:!important;
$i:
!important
;
@media (min-width: 768px ) {
.row {
@ -43,6 +45,8 @@ $i:!important;
.form-control-custom {
background-color: $form-color $i;
color: white $i;
border-radius: 0;
box-shadow: 0 0 0 !important;
}
@ -66,6 +70,20 @@ label {
font-size: 16px $i;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: #4e5d6c;
}
.navbar .nav a .fa {
font-size: 130%;
top: 1px;
position: relative;
display: inline-block;
margin-right: 5px;
}
.btn-danger-outline {
color: $danger-colour $i;
background-color: transparent;
@ -157,10 +175,11 @@ label {
border-color: $success-colour $i;
}
#movieList .mix{
#movieList .mix {
display: none;
}
#tvList .mix{
#tvList .mix {
display: none;
}
@ -202,4 +221,18 @@ $border-radius: 10px;
.scroll-top-wrapper i.fa {
line-height: inherit;
}
.no-search-results {
text-align: center;
}
.no-search-results .no-search-results-icon {
font-size: 10em;
color: $form-color;
}
.no-search-results .no-search-results-text {
margin: 20px 0;
color: #ccc;
}

@ -9,68 +9,122 @@ var searchSource = $("#search-template").html();
var searchTemplate = Handlebars.compile(searchSource);
var movieTimer = 0;
var tvimer = 0;
var mixItUpDefault = {
animation: { enable: true },
load: {
filter: 'all',
sort: 'requestorder:desc'
},
layout: {
display: 'block'
},
callbacks: {
onMixStart: function (state, futureState) {
$('.mix', this).removeAttr('data-bound').removeData('bound'); // fix for animation issues in other tabs
}
}
};
movieLoad();
tvLoad();
initLoad();
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr('href');
var activeState = "";
if (target === "#TvShowTab") {
if ($('#movieList').mixItUp('isLoaded')) {
activeState = $('#movieList').mixItUp('getState');
$('#movieList').mixItUp('destroy');
}
if (!$('#tvList').mixItUp('isLoaded')) {
$('#tvList').mixItUp({
load: {
filter: activeState.activeFilter || 'all',
sort: activeState.activeSort || 'default:asc'
},
layout: {
display: 'block'
}
});
var $ml = $('#movieList');
var $tvl = $('#tvList');
$('.approve-category').hide();
if (target === "#TvShowTab") {
$('#approveTVShows').show();
if ($ml.mixItUp('isLoaded')) {
activeState = $ml.mixItUp('getState');
$ml.mixItUp('destroy');
}
if ($tvl.mixItUp('isLoaded')) $tvl.mixItUp('destroy');
$tvl.mixItUp(mixItUpConfig(activeState)); // init or reinit
}
if (target === "#MoviesTab") {
if ($('#tvList').mixItUp('isLoaded')) {
activeState = $('#tvList').mixItUp('getState');
$('#tvList').mixItUp('destroy');
}
if (!$('#movieList').mixItUp('isLoaded')) {
$('#movieList').mixItUp({
load: {
filter: activeState.activeFilter || 'all',
sort: activeState.activeSort || 'default:asc'
},
layout: {
display: 'block'
}
});
$('#approveMovies').show();
if ($tvl.mixItUp('isLoaded')) {
activeState = $tvl.mixItUp('getState');
$tvl.mixItUp('destroy');
}
if ($ml.mixItUp('isLoaded')) $ml.mixItUp('destroy');
$ml.mixItUp(mixItUpConfig(activeState)); // init or reinit
}
//$('.mix[data-bound]').removeAttr('data-bound');
});
// Approve all
$('#approveAll').click(function () {
$('#approveMovies').click(function (e) {
e.preventDefault();
var buttonId = e.target.id;
var origHtml = $(this).html();
if ($('#' + buttonId).text() === " Loading...") {
return;
}
loadingButton(buttonId, "success");
$.ajax({
type: 'post',
url: '/approval/approveall',
url: '/approval/approveallmovies',
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
generateNotify("Success! All requests approved!", "success");
generateNotify("Success! All Movie requests approved!", "success");
movieLoad();
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
},
complete: function (e) {
finishLoading(buttonId, "success", origHtml)
}
});
});
$('#approveTVShows').click(function (e) {
e.preventDefault();
var buttonId = e.target.id;
var origHtml = $(this).html();
if ($('#' + buttonId).text() === " Loading...") {
return;
}
loadingButton(buttonId, "success");
$.ajax({
type: 'post',
url: '/approval/approvealltvshows',
dataType: "json",
success: function (response) {
if (checkJsonResponse(response)) {
generateNotify("Success! All TV Show requests approved!", "success");
tvLoad();
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
},
complete: function (e) {
finishLoading(buttonId, "success", origHtml)
}
});
});
// filtering/sorting
$('.filter,.sort', '.dropdown-menu').click(function (e) {
var $this = $(this);
$('.fa-square', $this.parents('.dropdown-menu:first')).removeClass('fa-square').addClass('fa-square-o');
$this.children('.fa').first().removeClass('fa-square-o').addClass('fa-square');
});
// Report Issue
@ -315,36 +369,54 @@ $(document).on("click", ".change", function (e) {
});
function mixItUpConfig(activeState) {
var conf = mixItUpDefault;
if (activeState) {
if (activeState.activeFilter) conf['load']['filter'] = activeState.activeFilter;
if (activeState.activeSort) conf['load']['sort'] = activeState.activeSort;
}
return conf;
};
function initLoad() {
movieLoad();
tvLoad();
}
function movieLoad() {
$("#movieList").html("");
var $ml = $('#movieList');
if ($ml.mixItUp('isLoaded')) {
activeState = $ml.mixItUp('getState');
$ml.mixItUp('destroy');
}
$ml.html("");
$.ajax("/requests/movies/").success(function (results) {
results.forEach(function (result) {
var context = buildRequestContext(result, "movie");
var html = searchTemplate(context);
$("#movieList").append(html);
});
$('#movieList').mixItUp({
layout: {
display: 'block'
},
load: {
filter: 'all'
}
$ml.append(html);
});
$ml.mixItUp(mixItUpConfig());
});
};
function tvLoad() {
$("#tvList").html("");
var $tvl = $('#tvList');
if ($tvl.mixItUp('isLoaded')) {
activeState = $tvl.mixItUp('getState');
$tvl.mixItUp('destroy');
}
$tvl.html("");
$.ajax("/requests/tvshows/").success(function (results) {
results.forEach(function (result) {
var context = buildRequestContext(result, "tv");
var html = searchTemplate(context);
$("#tvList").append(html);
$tvl.append(html);
});
$tvl.mixItUp(mixItUpConfig());
});
};
@ -359,9 +431,11 @@ function buildRequestContext(result, type) {
type: type,
status: result.status,
releaseDate: result.releaseDate,
releaseDateTicks: result.releaseDateTicks,
approved: result.approved,
requestedBy: result.requestedBy,
requestedUsers: result.requestedUsers ? result.requestedUsers.join(', ') : '',
requestedDate: result.requestedDate,
requestedDateTicks: result.requestedDateTicks,
available: result.available,
admin: result.admin,
issues: result.issues,
@ -373,16 +447,4 @@ function buildRequestContext(result, type) {
};
return context;
}
function startFilter(elementId) {
$('#'+element).mixItUp({
load: {
filter: activeState.activeFilter || 'all',
sort: activeState.activeSort || 'default:asc'
},
layout: {
display: 'block'
}
});
}

@ -7,6 +7,8 @@
var searchSource = $("#search-template").html();
var searchTemplate = Handlebars.compile(searchSource);
var noResultsHtml = "<div class='no-search-results'>" +
"<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div></div>";
var movieTimer = 0;
var tvimer = 0;
@ -124,6 +126,9 @@ function movieSearch() {
$("#movieList").append(html);
});
}
else {
$("#movieList").html(noResultsHtml);
}
$('#movieSearchButton').attr("class","fa fa-search");
});
};
@ -140,6 +145,9 @@ function tvSearch() {
$("#tvList").append(html);
});
}
else {
$("#tvList").html(noResultsHtml);
}
$('#tvSearchButton').attr("class", "fa fa-search");
});
};

@ -37,11 +37,13 @@ namespace PlexRequests.UI.Models
public string Title { get; set; }
public string PosterPath { get; set; }
public string ReleaseDate { get; set; }
public long ReleaseDateTicks { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
public string RequestedBy { get; set; }
public string[] RequestedUsers { get; set; }
public string RequestedDate { get; set; }
public long RequestedDateTicks { get; set; }
public string ReleaseYear { get; set; }
public bool Available { get; set; }
public bool Admin { get; set; }

@ -53,6 +53,7 @@ using PlexRequests.Store.Models;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
using System;
namespace PlexRequests.UI.Modules
{
@ -144,13 +145,16 @@ namespace PlexRequests.UI.Modules
Get["/emailnotification"] = _ => EmailNotifications();
Post["/emailnotification"] = _ => SaveEmailNotifications();
Post["/testemailnotification"] = _ => TestEmailNotifications();
Get["/status"] = _ => Status();
Get["/pushbulletnotification"] = _ => PushbulletNotifications();
Post["/pushbulletnotification"] = _ => SavePushbulletNotifications();
Post["/testpushbulletnotification"] = _ => TestPushbulletNotifications();
Get["/pushovernotification"] = _ => PushoverNotifications();
Post["/pushovernotification"] = _ => SavePushoverNotifications();
Post["/testpushovernotification"] = _ => TestPushoverNotifications();
Get["/logs"] = _ => Logs();
Get["/loglevel"] = _ => GetLogLevels();
@ -380,6 +384,37 @@ namespace PlexRequests.UI.Modules
return View["EmailNotifications", settings];
}
private Response TestEmailNotifications()
{
var settings = this.Bind<EmailNotificationSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var notificationModel = new NotificationModel
{
NotificationType = NotificationType.Test,
DateTime = DateTime.Now
};
try
{
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
settings.Enabled = true;
NotificationService.Publish(notificationModel, settings);
Log.Info("Sent email notification test");
}
catch (Exception)
{
Log.Error("Failed to subscribe and publish test Email Notification");
}
finally
{
NotificationService.UnSubscribe(new EmailMessageNotification(EmailService));
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Email Notification!" });
}
private Response SaveEmailNotifications()
{
var settings = this.Bind<EmailNotificationSettings>();
@ -448,6 +483,37 @@ namespace PlexRequests.UI.Modules
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Response TestPushbulletNotifications()
{
var settings = this.Bind<PushbulletNotificationSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var notificationModel = new NotificationModel
{
NotificationType = NotificationType.Test,
DateTime = DateTime.Now
};
try
{
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
settings.Enabled = true;
NotificationService.Publish(notificationModel, settings);
Log.Info("Sent pushbullet notification test");
}
catch (Exception)
{
Log.Error("Failed to subscribe and publish test Pushbullet Notification");
}
finally
{
NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushbullet Notification!" });
}
private Negotiator PushoverNotifications()
{
var settings = PushoverService.GetSettings();
@ -480,6 +546,37 @@ namespace PlexRequests.UI.Modules
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Response TestPushoverNotifications()
{
var settings = this.Bind<PushoverNotificationSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var notificationModel = new NotificationModel
{
NotificationType = NotificationType.Test,
DateTime = DateTime.Now
};
try
{
NotificationService.Subscribe(new PushoverNotification(PushoverApi, PushoverService));
settings.Enabled = true;
NotificationService.Publish(notificationModel, settings);
Log.Info("Sent pushover notification test");
}
catch (Exception)
{
Log.Error("Failed to subscribe and publish test Pushover Notification");
}
finally
{
NotificationService.UnSubscribe(new PushoverNotification(PushoverApi, PushoverService));
}
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully sent a test Pushover Notification!" });
}
private Response GetCpProfiles()
{
var settings = this.Bind<CouchPotatoSettings>();

@ -61,6 +61,8 @@ namespace PlexRequests.UI.Modules
Post["/approve"] = parameters => Approve((int)Request.Form.requestid);
Post["/approveall"] = x => ApproveAll();
Post["/approveallmovies"] = x => ApproveAllMovies();
Post["/approvealltvshows"] = x => ApproveAllTVShows();
}
private IRequestService Service { get; }
@ -216,6 +218,56 @@ namespace PlexRequests.UI.Modules
});
}
private Response ApproveAllMovies()
{
if (!Context.CurrentUser.IsAuthenticated())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
}
var requests = Service.GetAll().Where(x => x.CanApprove && x.Type == RequestType.Movie);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no movie requests to approve. Please refresh." });
}
try
{
return UpdateRequests(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private Response ApproveAllTVShows()
{
if (!Context.CurrentUser.IsAuthenticated())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
}
var requests = Service.GetAll().Where(x => x.CanApprove && x.Type == RequestType.TvShow);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no tv show requests to approve. Please refresh." });
}
try
{
return UpdateRequests(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
/// <summary>
/// Approves all.
/// </summary>
@ -227,23 +279,35 @@ namespace PlexRequests.UI.Modules
return Response.AsJson(new JsonResponseModel { Result = false, Message = "You are not an Admin, so you cannot approve any requests." });
}
var requests = Service.GetAll().Where(x => x.Approved == false);
var requests = Service.GetAll().Where(x => x.CanApprove);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
}
var cpSettings = CpService.GetSettings();
try
{
return UpdateRequests(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private Response UpdateRequests(RequestedModel[] requestedModels)
{
var cpSettings = CpService.GetSettings();
var updatedRequests = new List<RequestedModel>();
foreach (var r in requestedModels)
{
if (r.Type == RequestType.Movie)
{
var result = SendMovie(cpSettings, r, CpApi);
if (result)
var res = SendMovie(cpSettings, r, CpApi);
if (res)
{
r.Approved = true;
updatedRequests.Add(r);
@ -260,8 +324,8 @@ namespace PlexRequests.UI.Modules
var sonarr = SonarrSettings.GetSettings();
if (sr.Enabled)
{
var result = sender.SendToSickRage(sr, r);
if (result?.result == "success")
var res = sender.SendToSickRage(sr, r);
if (res?.result == "success")
{
r.Approved = true;
updatedRequests.Add(r);
@ -269,14 +333,14 @@ namespace PlexRequests.UI.Modules
else
{
Log.Error("Could not approve and send the TV {0} to SickRage!", r.Title);
Log.Error("SickRage Message: {0}", result?.message);
Log.Error("SickRage Message: {0}", res?.message);
}
}
if (sonarr.Enabled)
{
var result = sender.SendToSonarr(sonarr, r);
if (!string.IsNullOrEmpty(result?.title))
var res = sender.SendToSonarr(sonarr, r);
if (!string.IsNullOrEmpty(res?.title))
{
r.Approved = true;
updatedRequests.Add(r);
@ -284,7 +348,7 @@ namespace PlexRequests.UI.Modules
else
{
Log.Error("Could not approve and send the TV {0} to Sonarr!", r.Title);
Log.Error("Error message: {0}", result?.ErrorMessage);
Log.Error("Error message: {0}", res?.ErrorMessage);
}
}
}
@ -292,17 +356,16 @@ namespace PlexRequests.UI.Modules
try
{
var result = Service.BatchUpdate(updatedRequests); return Response.AsJson(result
var result = Service.BatchUpdate(updatedRequests);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not approve all of the requests. Please try again or check the logs." });
}
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)

@ -33,16 +33,30 @@ namespace PlexRequests.UI.Modules
{
public class BaseModule : NancyModule
{
private string _username;
protected string Username
{
get
{
if (string.IsNullOrEmpty(_username))
{
_username = Session[SessionKeys.UsernameKey].ToString();
}
return _username;
}
}
public BaseModule()
{
Before += (ctx)=> CheckAuth();
Before += (ctx) => CheckAuth();
}
public BaseModule(string modulePath) : base(modulePath)
{
Before += (ctx) => CheckAuth();
}
private Response CheckAuth()
{

@ -79,28 +79,38 @@ namespace PlexRequests.UI.Modules
private Response GetMovies()
{
var settings = PrSettings.GetSettings();
var isAdmin = Context.CurrentUser.IsAuthenticated();
var dbMovies = Service.GetAll().Where(x => x.Type == RequestType.Movie);
var viewModel = dbMovies.Select(movie => new RequestViewModel
if (settings.UsersCanViewOnlyOwnRequests && !isAdmin)
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate.Humanize(),
RequestedDate = movie.RequestedDate.Humanize(),
Approved = movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedBy = movie.RequestedBy,
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = isAdmin,
Issues = movie.Issues.Humanize(LetterCasing.Title),
OtherMessage = movie.OtherMessage,
AdminNotes = movie.AdminNote
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username));
}
var viewModel = dbMovies.Select(movie => {
return new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate.Humanize(),
ReleaseDateTicks = movie.ReleaseDate.Ticks,
RequestedDate = movie.RequestedDate.Humanize(),
RequestedDateTicks = movie.RequestedDate.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,
Issues = movie.Issues.Humanize(LetterCasing.Title),
OtherMessage = movie.OtherMessage,
AdminNotes = movie.AdminNote
};
}).ToList();
return Response.AsJson(viewModel);
@ -108,29 +118,39 @@ namespace PlexRequests.UI.Modules
private Response GetTvShows()
{
var settings = PrSettings.GetSettings();
var isAdmin = Context.CurrentUser.IsAuthenticated();
var dbTv = Service.GetAll().Where(x => x.Type == RequestType.TvShow);
var viewModel = dbTv.Select(tv => new RequestViewModel
if (settings.UsersCanViewOnlyOwnRequests && !isAdmin)
{
ProviderId = tv.ProviderId,
Type = tv.Type,
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath,
ReleaseDate = tv.ReleaseDate.Humanize(),
RequestedDate = tv.RequestedDate.Humanize(),
Approved = tv.Approved,
Title = tv.Title,
Overview = tv.Overview,
RequestedBy = tv.RequestedBy,
ReleaseYear = tv.ReleaseDate.Year.ToString(),
Available = tv.Available,
Admin = isAdmin,
Issues = tv.Issues.Humanize(LetterCasing.Title),
OtherMessage = tv.OtherMessage,
AdminNotes = tv.AdminNote,
TvSeriesRequestType = tv.SeasonsRequested
dbTv = dbTv.Where(x => x.UserHasRequested(Username));
}
var viewModel = dbTv.Select(tv => {
return new RequestViewModel
{
ProviderId = tv.ProviderId,
Type = tv.Type,
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath,
ReleaseDate = tv.ReleaseDate.Humanize(),
ReleaseDateTicks = tv.ReleaseDate.Ticks,
RequestedDate = tv.RequestedDate.Humanize(),
RequestedDateTicks = tv.RequestedDate.Ticks,
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,
Issues = tv.Issues.Humanize(LetterCasing.Title),
OtherMessage = tv.OtherMessage,
AdminNotes = tv.AdminNote,
TvSeriesRequestType = tv.SeasonsRequested
};
}).ToList();
return Response.AsJson(viewModel);
@ -165,7 +185,7 @@ namespace PlexRequests.UI.Modules
}
originalRequest.Issues = issue;
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
? $"{Session[SessionKeys.UsernameKey]} - {comment}"
? $"{Username} - {comment}"
: string.Empty;
@ -173,7 +193,7 @@ namespace PlexRequests.UI.Modules
var model = new NotificationModel
{
User = Session[SessionKeys.UsernameKey].ToString(),
User = Username,
NotificationType = NotificationType.Issue,
Title = originalRequest.Title,
DateTime = DateTime.Now,

@ -179,11 +179,20 @@ namespace PlexRequests.UI.Modules
Log.Trace(movieInfo.DumpJson);
//#if !DEBUG
var settings = PrService.GetSettings();
// check if the movie has already been requested
Log.Info("Requesting movie with id {0}", movieId);
if (RequestService.CheckRequest(movieId))
var existingRequest = RequestService.CheckRequest(movieId);
if (existingRequest != null)
{
Log.Trace("movie with id {0} exists", movieId);
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} has already been requested!" });
// check if the current user is already marked as a requester for this movie, if not, add them
if (!existingRequest.UserHasRequested(Username))
{
existingRequest.RequestedUsers.Add(Username);
RequestService.UpdateRequest(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" });
}
Log.Debug("movie with id {0} doesnt exists", movieId);
@ -213,14 +222,12 @@ namespace PlexRequests.UI.Modules
Status = movieInfo.Status,
RequestedDate = DateTime.Now,
Approved = false,
RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
RequestedUsers = new List<string>() { Username },
Issues = IssueState.None,
};
var settings = PrService.GetSettings();
Log.Trace(settings.DumpJson());
if (!settings.RequireMovieApproval)
if (!settings.RequireMovieApproval || settings.NoApprovalUserList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase)))
{
var cpSettings = CpService.GetSettings();
@ -247,7 +254,7 @@ namespace PlexRequests.UI.Modules
};
NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel {Result = true});
return Response.AsJson(new JsonResponseModel {Result = true, Message = $"{fullMovieName} was successfully added!" });
}
return
Response.AsJson(new JsonResponseModel
@ -272,7 +279,7 @@ namespace PlexRequests.UI.Modules
};
NotificationService.Publish(notificationModel);
return Response.AsJson(new JsonResponseModel { Result = true });
return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" });
}
}
@ -310,9 +317,20 @@ namespace PlexRequests.UI.Modules
string fullShowName = $"{showInfo.name} ({firstAir.Year})";
//#if !DEBUG
if (RequestService.CheckRequest(showId))
var settings = PrService.GetSettings();
// check if the show has already been requested
Log.Info("Requesting tv show with id {0}", showId);
var existingRequest = RequestService.CheckRequest(showId);
if (existingRequest != null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} has already been requested!" });
// check if the current user is already marked as a requester for this show, if not, add them
if (!existingRequest.UserHasRequested(Username))
{
existingRequest.RequestedUsers.Add(Username);
RequestService.UpdateRequest(existingRequest);
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" });
}
try
@ -340,7 +358,7 @@ namespace PlexRequests.UI.Modules
Status = showInfo.status,
RequestedDate = DateTime.Now,
Approved = false,
RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
RequestedUsers = new List<string>() { Username },
Issues = IssueState.None,
ImdbId = showInfo.externals?.imdb ?? string.Empty,
SeasonCount = showInfo.seasonCount
@ -363,8 +381,7 @@ namespace PlexRequests.UI.Modules
model.SeasonList = seasonsList.ToArray();
var settings = PrService.GetSettings();
if (!settings.RequireTvShowApproval)
if (!settings.RequireTvShowApproval || settings.NoApprovalUserList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase)))
{
var sonarrSettings = SonarrService.GetSettings();
var sender = new TvSender(SonarrApi, SickrageApi);

@ -69,7 +69,7 @@ namespace PlexRequests.UI
if (port == -1)
port = GetStartupPort();
var options = new StartOptions( $"http://+:{port}")
var options = new StartOptions(Debugger.IsAttached ? $"http://localhost:{port}" : $"http://+:{port}")
{
ServerFactory = "Microsoft.Owin.Host.HttpListener"
};

@ -88,6 +88,11 @@
</div>
</div>
<div class="form-group">
<div>
<button id="testEmail" type="submit" class="btn btn-primary-outline">Test</button>
</div>
</div>
<div class="form-group">
<div>
@ -128,7 +133,32 @@
});
});
$('#testEmail').click(function (e) {
e.preventDefault();
var port = $('#EmailPort').val();
if (isNaN(port)) {
generateNotify("You must specify a valid Port.", "warning");
return;
}
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: '/admin/testemailnotification',
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});

@ -36,6 +36,12 @@
</div>
</div>
<div class="form-group">
<div>
<button id="testPushbullet" type="submit" class="btn btn-primary-outline">Test</button>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
@ -70,5 +76,28 @@
}
});
});
$('#testPushbullet').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: '/admin/testpushbulletnotification',
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -36,6 +36,12 @@
</div>
</div>
<div class="form-group">
<div>
<button id="testPushover" type="submit" class="btn btn-primary-outline">Test</button>
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
@ -70,5 +76,28 @@
}
});
});
$('#testPushover').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: '/admin/testpushovernotification',
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -111,7 +111,31 @@
</div>
</div>
<p class="form-group">A comma separated list of users whose requests do not require approval.</p>
<div class="form-group">
<label for="noApprovalUsers" class="control-label">Users</label>
<div>
<input type="text" class="form-control-custom form-control " id="NoApprovalUsers" name="NoApprovalUsers" placeholder="e.g. John, Bobby" value="@Model.NoApprovalUsers">
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.UsersCanViewOnlyOwnRequests)
{
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests" checked="checked"><text>Users can view their own requests only</text>
}
else
{
<input type="checkbox" id="UsersCanViewOnlyOwnRequests" name="UsersCanViewOnlyOwnRequests"><text>Users can view their own requests only</text>
}
</label>
</div>
</div>
@*<div class="form-group">
<label for="WeeklyRequestLimit" class="control-label">Weekly Request Limit</label>
<div>
@ -131,4 +155,3 @@
</fieldset>
</form>
</div>

@ -4,7 +4,7 @@
Password <input class="form-control form-control-custom" name="Password" type="password"/>
<br/>
Remember Me <input name="RememberMe" type="checkbox" value="True"/>
<br/>
<br/><br/>
<input class="btn btn-success-outline" type="submit" value="Login"/>
</form>
@if (!Model.AdminExists)

@ -2,12 +2,8 @@
<div>
<h1>Requests</h1>
<h4>Below you can see yours and all other requests, as well as their download and approval status.</h4>
@if (Context.CurrentUser.IsAuthenticated())
{
<button id="approveAll" class="btn btn-success-outline" type="submit"><i class="fa fa-plus"></i> Approve All</button>
<br />
<br />
}
<br />
<!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies)
@ -20,40 +16,62 @@
}
</ul>
<br />
<!-- Tab panes -->
<div class="tab-content contentList">
<div class="btn-group col-sm-push-10">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Filter
<i class="fa fa-filter"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="filter" data-filter="all">All</a></li>
<li><a href="#" class="filter" data-filter=".approved-true">Approved</a></li>
<li><a href="#" class="filter" data-filter=".approved-false">Not Approved</a></li>
<li><a href="#" class="filter" data-filter=".available-true">Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false">Not Available</a></li>
</ul>
</div>
<div class="btn-group col-sm-push-10">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Order
<i class="fa fa-sort"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="sort" data-sort="default">Default</a></li>
<li><a href="#" class="sort" data-sort="requestorder:asc">Requested Date</a></li>
</ul>
<div class="row">
<div class="col-sm-12">
<div class="pull-right">
<div class="btn-group">
@if (Context.CurrentUser.IsAuthenticated())
{
@if (Model.SearchForMovies)
{
<button id="approveMovies" class="btn btn-success-outline approve-category" type="submit"><i class="fa fa-plus"></i> Approve Movies</button>
}
@if (Model.SearchForTvShows)
{
<button id="approveTVShows" class="btn btn-success-outline approve-category" type="submit" style="display: none;"><i class="fa fa-plus"></i> Approve TV Shows</button>
}
}
</div>
<div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Filter
<i class="fa fa-filter"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="filter" data-filter="all"><i class="fa fa-square"></i> All</a></li>
<li><a href="#" class="filter" data-filter=".approved-true"><i class="fa fa-square-o"></i> Approved</a></li>
<li><a href="#" class="filter" data-filter=".approved-false"><i class="fa fa-square-o"></i> Not Approved</a></li>
<li><a href="#" class="filter" data-filter=".available-true"><i class="fa fa-square-o"></i> Available</a></li>
<li><a href="#" class="filter" data-filter=".available-false"><i class="fa fa-square-o"></i> Not Available</a></li>
</ul>
</div>
<div class="btn-group">
<a href="#" class="btn btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Order
<i class="fa fa-sort"></i>
</a>
<ul class="dropdown-menu">
<li><a href="#" class="sort" data-sort="requestorder:desc"><i class="fa fa-square"></i> Recent Requests</a></li>
<li><a href="#" class="sort" data-sort="requestorder:asc"><i class="fa fa-square-o"></i> Older Requests</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:desc"><i class="fa fa-square-o"></i> Recent Releases</a></li>
<li><a href="#" class="sort" data-sort="releaseorder:asc"><i class="fa fa-square-o"></i> Older Releases</a></li>
</ul>
</div>
</div>
</div>
</div>
@if (Model.SearchForMovies)
{
{
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<br/>
<br/>
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
@ -61,12 +79,12 @@
}
@if (Model.SearchForTvShows)
{
{
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<br/>
<br/>
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
@ -78,7 +96,7 @@
<script id="search-template" type="text/x-handlebars-template">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}">
<div id="{{requestId}}Template" class="mix available-{{available}} approved-{{approved}}" data-requestorder="{{requestedDateTicks}}" data-releaseorder="{{releaseDateTicks}}">
<div class="row">
<div class="col-sm-2">
{{#if_eq type "movie"}}
@ -122,7 +140,7 @@
{{#if_eq type "tv"}}
<div>Series Requested: {{seriesRequested}}</div>
{{/if_eq}}
<div>Requested By: {{requestedBy}}</div>
<div>Requested By: {{requestedUsers}}</div>
<div>Requested Date: {{requestedDate}}</div>
<div id="issueArea{{requestId}}">
{{#if otherMessage}}
@ -181,7 +199,7 @@
<li><a id="{{requestId}}" issue-select="4" class="dropdownIssue" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#myModal">Other</a></li>
{{#if_eq admin true}}
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
<li><a id="{{requestId}}" issue-select="4" class="note" data-identifier="{{requestId}}" href="#" data-toggle="modal" data-target="#noteModal">Add Note</a></li>
{{/if_eq}}
</ul>
</div>

@ -1,6 +1,7 @@
<div>
<h1>Search</h1>
<h4>Want to watch something that is not currently on Plex?! No problem! Just search for it below and request it!</h4>
<br />
<!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
@if (Model.SearchForMovies)
@ -57,7 +58,6 @@
</div>
<script id="search-template" type="text/x-handlebars-template">
<div class="row">
<div class="col-sm-2">

@ -7,8 +7,8 @@
<title>Plex Requests</title>
<!-- Styles -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="~/Content/custom.min.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/bootstrap.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/custom.min.css" type="text/css" />
<link rel="stylesheet" href="~/Content/font-awesome.css" type="text/css"/>
<link rel="stylesheet" href="~/Content/pace.min.css" type="text/css"/>

Loading…
Cancel
Save