From 96f27f8f1a300fb2625fa3913d1384f953844382 Mon Sep 17 00:00:00 2001 From: Drewster727 Date: Tue, 21 Jun 2016 19:43:31 -0500 Subject: [PATCH 1/8] add an option to stop sending notifications for requests that don't require approval #345 --- .../SettingModels/PlexRequestSettings.cs | 155 +- .../Notification/EmailMessageNotification.cs | 400 ++-- PlexRequests.UI/Modules/SearchModule.cs | 1966 ++++++++--------- PlexRequests.UI/Views/Admin/Settings.cshtml | 601 ++--- appveyor.yml | 52 +- 5 files changed, 1595 insertions(+), 1579 deletions(-) diff --git a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs index 493d8bf39..4e986c555 100644 --- a/PlexRequests.Core/SettingModels/PlexRequestSettings.cs +++ b/PlexRequests.Core/SettingModels/PlexRequestSettings.cs @@ -1,77 +1,78 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PlexRequestSettings.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using Newtonsoft.Json; -using System; -using System.Collections.Generic; - -namespace PlexRequests.Core.SettingModels -{ - public class PlexRequestSettings : Settings - { - public int Port { get; set; } - public string BaseUrl { get; set; } - public bool SearchForMovies { get; set; } - public bool SearchForTvShows { get; set; } - public bool SearchForMusic { get; set; } - public bool RequireMovieApproval { get; set; } - public bool RequireTvShowApproval { get; set; } - public bool RequireMusicApproval { get; set; } - public bool UsersCanViewOnlyOwnRequests { get; set; } - public bool UsersCanViewOnlyOwnIssues { get; set; } - public int WeeklyRequestLimit { get; set; } - public string NoApprovalUsers { get; set; } - public bool CollectAnalyticData { get; set; } - - /// - /// The CSS name of the theme we want - /// - public string ThemeName { get; set; } - - public string ApiKey { get; set; } - - [JsonIgnore] - public List ApprovalWhiteList - { - get - { - var users = new List(); - if (string.IsNullOrEmpty(NoApprovalUsers)) - { - return users; - } - - var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var user in splitUsers) - { - if (!string.IsNullOrWhiteSpace(user)) - users.Add(user.Trim()); - } - return users; - } - } - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PlexRequestSettings.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace PlexRequests.Core.SettingModels +{ + public class PlexRequestSettings : Settings + { + public int Port { get; set; } + public string BaseUrl { get; set; } + public bool SearchForMovies { get; set; } + public bool SearchForTvShows { get; set; } + public bool SearchForMusic { get; set; } + public bool RequireMovieApproval { get; set; } + public bool RequireTvShowApproval { get; set; } + public bool RequireMusicApproval { get; set; } + public bool UsersCanViewOnlyOwnRequests { get; set; } + public bool UsersCanViewOnlyOwnIssues { get; set; } + public int WeeklyRequestLimit { get; set; } + public string NoApprovalUsers { get; set; } + public bool CollectAnalyticData { get; set; } + public bool IgnoreNotifyForAutoApprovedRequests { get; set; } + + /// + /// The CSS name of the theme we want + /// + public string ThemeName { get; set; } + + public string ApiKey { get; set; } + + [JsonIgnore] + public List ApprovalWhiteList + { + get + { + var users = new List(); + if (string.IsNullOrEmpty(NoApprovalUsers)) + { + return users; + } + + var splitUsers = NoApprovalUsers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var user in splitUsers) + { + if (!string.IsNullOrWhiteSpace(user)) + users.Add(user.Trim()); + } + return users; + } + } + } +} diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 20ef3f612..080091504 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -1,201 +1,201 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: EmailMessageNotification.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Threading.Tasks; - -using MailKit.Security; - -using MimeKit; -using NLog; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using SmtpClient = MailKit.Net.Smtp.SmtpClient; - -namespace PlexRequests.Services.Notification -{ - public class EmailMessageNotification : INotification - { - public EmailMessageNotification(ISettingsService settings) - { - EmailNotificationSettings = settings; - } - - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private ISettingsService EmailNotificationSettings { get; } - public string NotificationName => "EmailMessageNotification"; - - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetConfiguration(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var emailSettings = (EmailNotificationSettings)settings; - - if (!ValidateConfiguration(emailSettings)) - { - return; - } - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await EmailNewRequest(model, emailSettings); - break; - case NotificationType.Issue: - await EmailIssue(model, emailSettings); - break; - case NotificationType.RequestAvailable: - await EmailAvailableRequest(model, emailSettings); - break; - case NotificationType.RequestApproved: - throw new NotImplementedException(); - - case NotificationType.AdminNote: - throw new NotImplementedException(); - - case NotificationType.Test: - await EmailTest(model, emailSettings); - break; - } - - } - - private EmailNotificationSettings GetConfiguration() - { - var settings = EmailNotificationSettings.GetSettings(); - return settings; - } - - private bool ValidateConfiguration(EmailNotificationSettings settings) - { - if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) - { - return false; - } - - if (!settings.EnableUserEmailNotifications) - { - if (!settings.Enabled) - { - return false; - } - } - - return true; - } - - private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, - Subject = $"Plex Requests: New request for {model.Title}!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - - await Send(message, settings); - } - - private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" }, - Subject = $"Plex Requests: New issue for {model.Title}!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - - await Send(message, settings); - } - - private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) - { - if (!settings.EnableUserEmailNotifications) - { - await Task.FromResult(false); - } - - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }, - Subject = $"Plex Requests: {model.Title} is now available!" - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); - - await Send(message, settings); - } - - private async Task Send(MimeMessage message, EmailNotificationSettings settings) - { - try - { - using (var client = new SmtpClient()) - { - client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions. - - // Note: since we don't have an OAuth2 token, disable - // the XOAUTH2 authentication mechanism. - client.AuthenticationMechanisms.Remove("XOAUTH2"); - - client.Authenticate(settings.EmailUsername, settings.EmailPassword); - - await client.SendAsync(message); - await client.DisconnectAsync(true); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - - private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings) - { - var message = new MimeMessage - { - Body = new TextPart("plain") { Text = "This is just a test! Success!" }, - Subject = "Plex Requests: Test Message!", - }; - message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); - message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); - - await Send(message, settings); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: EmailMessageNotification.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Threading.Tasks; + +using MailKit.Security; + +using MimeKit; +using NLog; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using SmtpClient = MailKit.Net.Smtp.SmtpClient; + +namespace PlexRequests.Services.Notification +{ + public class EmailMessageNotification : INotification + { + public EmailMessageNotification(ISettingsService settings) + { + EmailNotificationSettings = settings; + } + + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private ISettingsService EmailNotificationSettings { get; } + public string NotificationName => "EmailMessageNotification"; + + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetConfiguration(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var emailSettings = (EmailNotificationSettings)settings; + + if (!ValidateConfiguration(emailSettings)) + { + return; + } + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await EmailNewRequest(model, emailSettings); + break; + case NotificationType.Issue: + await EmailIssue(model, emailSettings); + break; + case NotificationType.RequestAvailable: + await EmailAvailableRequest(model, emailSettings); + break; + case NotificationType.RequestApproved: + throw new NotImplementedException(); + + case NotificationType.AdminNote: + throw new NotImplementedException(); + + case NotificationType.Test: + await EmailTest(model, emailSettings); + break; + } + + } + + private EmailNotificationSettings GetConfiguration() + { + var settings = EmailNotificationSettings.GetSettings(); + return settings; + } + + private bool ValidateConfiguration(EmailNotificationSettings settings) + { + if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername) || string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail) || string.IsNullOrEmpty(settings.EmailPort.ToString())) + { + return false; + } + + if (!settings.EnableUserEmailNotifications) + { + if (!settings.Enabled) + { + return false; + } + } + + return true; + } + + private async Task EmailNewRequest(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, + Subject = $"Plex Requests: New request for {model.Title}!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + + private async Task EmailIssue(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has reported a new issue {model.Body} for the title {model.Title}!" }, + Subject = $"Plex Requests: New issue for {model.Title}!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + + await Send(message, settings); + } + + private async Task EmailAvailableRequest(NotificationModel model, EmailNotificationSettings settings) + { + if (!settings.EnableUserEmailNotifications) + { + await Task.FromResult(false); + } + + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = $"Hello! You requested {model.Title} on PlexRequests! This is now available on Plex! :)" }, + Subject = $"Plex Requests: {model.Title} is now available!" + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(model.UserEmail, model.UserEmail)); + + await Send(message, settings); + } + + private async Task Send(MimeMessage message, EmailNotificationSettings settings) + { + try + { + using (var client = new SmtpClient()) + { + client.Connect(settings.EmailHost, settings.EmailPort, SecureSocketOptions.Auto); // Let MailKit figure out the correct SecureSocketOptions. + + // Note: since we don't have an OAuth2 token, disable + // the XOAUTH2 authentication mechanism. + client.AuthenticationMechanisms.Remove("XOAUTH2"); + + client.Authenticate(settings.EmailUsername, settings.EmailPassword); + + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + + private async Task EmailTest(NotificationModel model, EmailNotificationSettings settings) + { + var message = new MimeMessage + { + Body = new TextPart("plain") { Text = "This is just a test! Success!" }, + Subject = "Plex Requests: Test Message!", + }; + message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); + message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); + + await Send(message, settings); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index 380df096f..e42ae0739 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -1,983 +1,983 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: SearchModule.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -using Nancy; -using Nancy.Responses.Negotiation; - -using NLog; - -using PlexRequests.Api; -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Music; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.Helpers.Exceptions; -using PlexRequests.Services.Interfaces; -using PlexRequests.Services.Notification; -using PlexRequests.Store; -using PlexRequests.UI.Helpers; -using PlexRequests.UI.Models; -using System.Threading.Tasks; - -using Nancy.Extensions; -using Nancy.Responses; - -using PlexRequests.Api.Models.Tv; -using PlexRequests.Core.Models; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; - -using TMDbLib.Objects.General; - -namespace PlexRequests.UI.Modules -{ - public class SearchModule : BaseAuthModule - { - public SearchModule(ICacheProvider cache, ISettingsService cpSettings, - ISettingsService prSettings, IAvailabilityChecker checker, - IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, - ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, - INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, - ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, - ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, - IIssueService issue) : base("search", prSettings) - { - Auth = auth; - PlexService = plexService; - PlexApi = plexApi; - CpService = cpSettings; - PrService = prSettings; - MovieApi = new TheMovieDbApi(); - Cache = cache; - Checker = checker; - CpCacher = cpCacher; - SonarrCacher = sonarrCacher; - SickRageCacher = sickRageCacher; - RequestService = request; - SonarrApi = sonarrApi; - SonarrService = sonarrSettings; - CouchPotatoApi = cpApi; - SickRageService = sickRageService; - SickrageApi = srApi; - NotificationService = notify; - MusicBrainzApi = mbApi; - HeadphonesApi = hpApi; - HeadphonesService = hpService; - UsersToNotifyRepo = u; - EmailNotificationSettings = email; - IssueService = issue; - - - Get["/", true] = async (x, ct) => await RequestLoad(); - - Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); - Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); - Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); - Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); - - Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); - Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); - - Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); - Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); - Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); - - Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); - Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings(); - - Get["/seasons"] = x => GetSeasons(); - } - private IPlexApi PlexApi { get; } - private TheMovieDbApi MovieApi { get; } - private INotificationService NotificationService { get; } - private ICouchPotatoApi CouchPotatoApi { get; } - private ISonarrApi SonarrApi { get; } - private ISickRageApi SickrageApi { get; } - private IRequestService RequestService { get; } - private ICacheProvider Cache { get; } - private ISettingsService Auth { get; } - private ISettingsService PlexService { get; } - private ISettingsService CpService { get; } - private ISettingsService PrService { get; } - private ISettingsService SonarrService { get; } - private ISettingsService SickRageService { get; } - private ISettingsService HeadphonesService { get; } - private ISettingsService EmailNotificationSettings { get; } - private IAvailabilityChecker Checker { get; } - private ICouchPotatoCacher CpCacher { get; } - private ISonarrCacher SonarrCacher { get; } - private ISickRageCacher SickRageCacher { get; } - private IMusicBrainzApi MusicBrainzApi { get; } - private IHeadphonesApi HeadphonesApi { get; } - private IRepository UsersToNotifyRepo { get; } - private IIssueService IssueService { get; } - private static Logger Log = LogManager.GetCurrentClassLogger(); - - private async Task RequestLoad() - { - var settings = await PrService.GetSettingsAsync(); - - Log.Trace("Loading Index"); - return View["Search/Index", settings]; - } - - private async Task UpcomingMovies() - { - Log.Trace("Loading upcoming movies"); - return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); - } - - private async Task CurrentlyPlayingMovies() - { - Log.Trace("Loading currently playing movies"); - return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); - } - - private async Task SearchMovie(string searchTerm) - { - Log.Trace("Searching for Movie {0}", searchTerm); - return await ProcessMovies(MovieSearchType.Search, searchTerm); - } - - private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) - { - var apiMovies = new List(); - await Task.Factory.StartNew( - () => - { - switch (searchType) - { - case MovieSearchType.Search: - return - MovieApi.SearchMovie(searchTerm) - .Result.Select( - x => - new MovieResult() - { - Adult = x.Adult, - BackdropPath = x.BackdropPath, - GenreIds = x.GenreIds, - Id = x.Id, - OriginalLanguage = x.OriginalLanguage, - OriginalTitle = x.OriginalTitle, - Overview = x.Overview, - Popularity = x.Popularity, - PosterPath = x.PosterPath, - ReleaseDate = x.ReleaseDate, - Title = x.Title, - Video = x.Video, - VoteAverage = x.VoteAverage, - VoteCount = x.VoteCount - }) - .ToList(); - case MovieSearchType.CurrentlyPlaying: - return MovieApi.GetCurrentPlayingMovies().Result.ToList(); - case MovieSearchType.Upcoming: - return MovieApi.GetUpcomingMovies().Result.ToList(); - default: - return new List(); - } - }).ContinueWith( - (t) => - { - apiMovies = t.Result; - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Movie); - - var distinctResults = allResults.DistinctBy(x => x.ProviderId); - var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); - - - var cpCached = CpCacher.QueuedIds(); - var plexMovies = Checker.GetPlexMovies(); - var settings = await PrService.GetSettingsAsync(); - var viewMovies = new List(); - foreach (MovieResult movie in apiMovies) - { - var viewMovie = new SearchMovieViewModel - { - Adult = movie.Adult, - BackdropPath = movie.BackdropPath, - GenreIds = movie.GenreIds, - Id = movie.Id, - OriginalLanguage = movie.OriginalLanguage, - OriginalTitle = movie.OriginalTitle, - Overview = movie.Overview, - Popularity = movie.Popularity, - PosterPath = movie.PosterPath, - ReleaseDate = movie.ReleaseDate, - Title = movie.Title, - Video = movie.Video, - VoteAverage = movie.VoteAverage, - VoteCount = movie.VoteCount - }; - var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); - if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) - { - viewMovie.Available = true; - } - else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db - { - var dbm = dbMovies[movie.Id]; - - viewMovie.Requested = true; - viewMovie.Approved = dbm.Approved; - viewMovie.Available = dbm.Available; - } - else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db - { - viewMovie.Requested = true; - } - - viewMovies.Add(viewMovie); - } - - return Response.AsJson(viewMovies); - } - - private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) - { - if (usersCanViewOnlyOwnRequests) - { - var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); - return result.Value == null || result.Value.UserHasRequested(Username); - } - - return true; - } - - private async Task SearchTvShow(string searchTerm) - { - var plexSettings = await PlexService.GetSettingsAsync(); - Log.Trace("Searching for TV Show {0}", searchTerm); - - var apiTv = new List(); - await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => - { - apiTv = t.Result; - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.TvShow); - - var dbTv = allResults.ToDictionary(x => x.ProviderId); - - if (!apiTv.Any()) - { - Log.Trace("TV Show data is null"); - return Response.AsJson(""); - } - - var sonarrCached = SonarrCacher.QueuedIds(); - var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays - var plexTvShows = Checker.GetPlexTvShows(); - - var viewTv = new List(); - foreach (var t in apiTv) - { - var banner = t.show.image?.medium; - if (!string.IsNullOrEmpty(banner)) - { - banner = banner.Replace("http", "https"); - } - - var viewT = new SearchTvShowViewModel - { - Banner = banner, - FirstAired = t.show.premiered, - Id = t.show.externals?.thetvdb ?? 0, - ImdbId = t.show.externals?.imdb, - Network = t.show.network?.name, - NetworkId = t.show.network?.id.ToString(), - Overview = t.show.summary.RemoveHtml(), - Rating = t.score.ToString(CultureInfo.CurrentUICulture), - Runtime = t.show.runtime.ToString(), - SeriesId = t.show.id, - SeriesName = t.show.name, - Status = t.show.status - }; - - - var providerId = string.Empty; - - if (plexSettings.AdvancedSearch) - { - providerId = viewT.Id.ToString(); - } - - if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) - { - viewT.Available = true; - } - else if (t.show?.externals?.thetvdb != null) - { - int tvdbid = (int)t.show.externals.thetvdb; - - if (dbTv.ContainsKey(tvdbid)) - { - var dbt = dbTv[tvdbid]; - - viewT.Requested = true; - viewT.Approved = dbt.Approved; - viewT.Available = dbt.Available; - } - else if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db - { - viewT.Requested = true; - } - } - - viewTv.Add(viewT); - } - - return Response.AsJson(viewTv); - } - - private async Task SearchMusic(string searchTerm) - { - var apiAlbums = new List(); - await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => - { - apiAlbums = t.Result.releases ?? new List(); - }); - - var allResults = await RequestService.GetAllAsync(); - allResults = allResults.Where(x => x.Type == RequestType.Album); - - var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); - - var plexAlbums = Checker.GetPlexAlbums(); - - var viewAlbum = new List(); - foreach (var a in apiAlbums) - { - var viewA = new SearchMusicViewModel - { - Title = a.title, - Id = a.id, - Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), - Overview = a.disambiguation, - ReleaseDate = a.date, - TrackCount = a.TrackCount, - ReleaseType = a.status, - Country = a.country - }; - - DateTime release; - DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); - var artist = a.ArtistCredit?.FirstOrDefault()?.artist; - if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) - { - viewA.Available = true; - } - if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) - { - var dba = dbAlbum[a.id]; - - viewA.Requested = true; - viewA.Approved = dba.Approved; - viewA.Available = dba.Available; - } - - viewAlbum.Add(viewA); - } - return Response.AsJson(viewAlbum); - } - - private async Task RequestMovie(int movieId) - { - var movieInfo = MovieApi.GetMovieInformation(movieId).Result; - var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; - Log.Trace("Getting movie info from TheMovieDb"); - - var settings = await PrService.GetSettingsAsync(); - - // check if the movie has already been requested - Log.Info("Requesting movie with id {0}", movieId); - var existingRequest = await RequestService.CheckRequestAsync(movieId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this movie, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" }); - } - - Log.Debug("movie with id {0} doesnt exists", movieId); - - try - { - var movies = Checker.GetPlexMovies(); - if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); - } - } - catch (Exception e) - { - Log.Error(e); - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" }); - } - //#endif - - var model = new RequestedModel - { - ProviderId = movieInfo.Id, - Type = RequestType.Movie, - Overview = movieInfo.Overview, - ImdbId = movieInfo.ImdbId, - PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath, - Title = movieInfo.Title, - ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, - Status = movieInfo.Status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - - }; - - if (ShouldAutoApprove(RequestType.Movie, settings)) - { - var cpSettings = await CpService.GetSettingsAsync(); - - if (cpSettings.Enabled) - { - Log.Info("Adding movie to CP (No approval required)"); - var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, - cpSettings.FullUri, cpSettings.ProfileId); - Log.Debug("Adding movie to CP result {0}", result); - if (result) - { - model.Approved = true; - Log.Info("Adding movie to database (No approval required)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notificationModel); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - return - Response.AsJson(new JsonResponseModel - { - Result = false, - Message = - "Something went wrong adding the movie to CouchPotato! Please check your settings." - }); - } - else - { - model.Approved = true; - Log.Info("Adding movie to database (No approval required)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notificationModel = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notificationModel); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - } - - try - { - Log.Info("Adding movie to database"); - var id = await RequestService.AddRequestAsync(model); - - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; - await NotificationService.Publish(notificationModel); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); - } - catch (Exception e) - { - Log.Fatal(e); - - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); - } - } - - /// - /// Requests the tv show. - /// - /// The show identifier. - /// The seasons. - /// - private async Task RequestTvShow(int showId, string seasons) - { - var tvApi = new TvMazeApi(); - - var showInfo = tvApi.ShowLookupByTheTvDbId(showId); - DateTime firstAir; - DateTime.TryParse(showInfo.premiered, out firstAir); - string fullShowName = $"{showInfo.name} ({firstAir.Year})"; - //#if !DEBUG - - var settings = await PrService.GetSettingsAsync(); - - // check if the show has already been requested - Log.Info("Requesting tv show with id {0}", showId); - var existingRequest = await RequestService.CheckRequestAsync(showId); - if (existingRequest != null) - { - // check if the current user is already marked as a requester for this show, if not, add them - if (!existingRequest.UserHasRequested(Username)) - { - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" }); - } - - try - { - var shows = Checker.GetPlexTvShows(); - var providerId = string.Empty; - var plexSettings = await PlexService.GetSettingsAsync(); - if (plexSettings.AdvancedSearch) - { - providerId = showId.ToString(); - } - if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); - } - } - catch (Exception) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" }); - } - //#endif - - - var model = new RequestedModel - { - ProviderId = showInfo.externals?.thetvdb ?? 0, - Type = RequestType.TvShow, - Overview = showInfo.summary.RemoveHtml(), - PosterPath = showInfo.image?.medium, - Title = showInfo.name, - ReleaseDate = firstAir, - Status = showInfo.status, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUsers = new List { Username }, - Issues = IssueState.None, - ImdbId = showInfo.externals?.imdb ?? string.Empty, - SeasonCount = showInfo.seasonCount, - TvDbId = showId.ToString() - }; - - var seasonsList = new List(); - switch (seasons) - { - case "first": - seasonsList.Add(1); - model.SeasonsRequested = "First"; - break; - case "latest": - seasonsList.Add(model.SeasonCount); - model.SeasonsRequested = "Latest"; - break; - case "all": - model.SeasonsRequested = "All"; - break; - default: - model.SeasonsRequested = seasons; - var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var seasonsCount = new int[split.Length]; - for (var i = 0; i < split.Length; i++) - { - int tryInt; - int.TryParse(split[i], out tryInt); - seasonsCount[i] = tryInt; - } - seasonsList.AddRange(seasonsCount); - break; - } - - model.SeasonList = seasonsList.ToArray(); - - if (ShouldAutoApprove(RequestType.TvShow, settings)) - { - var sonarrSettings = await SonarrService.GetSettingsAsync(); - var sender = new TvSender(SonarrApi, SickrageApi); - if (sonarrSettings.Enabled) - { - var result = sender.SendToSonarr(sonarrSettings, model); - if (!string.IsNullOrEmpty(result?.title)) - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notify1 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify1); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - - return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); - - } - - var srSettings = SickRageService.GetSettings(); - if (srSettings.Enabled) - { - var result = sender.SendToSickRage(srSettings, model); - if (result?.result == "success") - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required & SickRage)"); - await RequestService.AddRequestAsync(model); - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "Message From SickRage: " + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." }); - } - - if (!srSettings.Enabled && !sonarrSettings.Enabled) - { - model.Approved = true; - Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup"); - await RequestService.AddRequestAsync(model); - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); - - } - - await RequestService.AddRequestAsync(model); - - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; - await NotificationService.Publish(notificationModel); - - return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); - } - - private bool ShouldSendNotification() - { - var sendNotification = true; - var claims = Context.CurrentUser?.Claims; - if (claims != null) - { - var enumerable = claims as string[] ?? claims.ToArray(); - if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser)) - { - sendNotification = false; // Don't bother sending a notification if the user is an admin - } - } - return sendNotification; - } - - - private async Task RequestAlbum(string releaseId) - { - var settings = await PrService.GetSettingsAsync(); - var existingRequest = await RequestService.CheckRequestAsync(releaseId); - Log.Debug("Checking for an existing request"); - - if (existingRequest != null) - { - Log.Debug("We do have an existing album request"); - if (!existingRequest.UserHasRequested(Username)) - { - - Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username); - existingRequest.RequestedUsers.Add(Username); - await RequestService.UpdateRequestAsync(existingRequest); - } - return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" }); - } - - Log.Debug("This is a new request"); - - var albumInfo = MusicBrainzApi.GetAlbum(releaseId); - DateTime release; - DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); - - var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; - if (artist == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" }); - } - - var albums = Checker.GetPlexAlbums(); - var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); - - if (alreadyInPlex) - { - return Response.AsJson(new JsonResponseModel - { - Result = false, - Message = $"{albumInfo.title} is already in Plex!" - }); - } - - var img = GetMusicBrainzCoverArt(albumInfo.id); - - var model = new RequestedModel - { - Title = albumInfo.title, - MusicBrainzId = albumInfo.id, - Overview = albumInfo.disambiguation, - PosterPath = img, - Type = RequestType.Album, - ProviderId = 0, - RequestedUsers = new List { Username }, - Status = albumInfo.status, - Issues = IssueState.None, - RequestedDate = DateTime.UtcNow, - ReleaseDate = release, - ArtistName = artist.name, - ArtistId = artist.id - }; - - if (ShouldAutoApprove(RequestType.Album, settings)) - { - Log.Debug("We don't require approval OR the user is in the whitelist"); - var hpSettings = HeadphonesService.GetSettings(); - - if (!hpSettings.Enabled) - { - await RequestService.AddRequestAsync(model); - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); - await sender.AddAlbum(model); - model.Approved = true; - await RequestService.AddRequestAsync(model); - - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - - return - Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - if (ShouldSendNotification()) - { - var notify2 = new NotificationModel - { - Title = model.Title, - User = Username, - DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest - }; - await NotificationService.Publish(notify2); - } - await RequestService.AddRequestAsync(model); - return Response.AsJson(new JsonResponseModel - { - Result = true, - Message = $"{model.Title} was successfully added!" - }); - } - - private string GetMusicBrainzCoverArt(string id) - { - var coverArt = MusicBrainzApi.GetCoverArt(id); - var firstImage = coverArt?.images?.FirstOrDefault(); - var img = string.Empty; - - if (firstImage != null) - { - img = firstImage.thumbnails?.small ?? firstImage.image; - } - - return img; - } - - private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) - { - // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval - if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; - - // check by request type if the category requires approval or not - switch (requestType) - { - case RequestType.Movie: - return !prSettings.RequireMovieApproval; - case RequestType.TvShow: - return !prSettings.RequireTvShowApproval; - case RequestType.Album: - return !prSettings.RequireMusicApproval; - default: - return false; - } - } - - private async Task NotifyUser(bool notify) - { - var authSettings = await Auth.GetSettingsAsync(); - var auth = authSettings.UserAuthentication; - var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); - var email = emailSettings.EnableUserEmailNotifications; - if (!auth) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" }); - } - if (!email) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." }); - } - var username = Username; - var originalList = await UsersToNotifyRepo.GetAllAsync(); - if (!notify) - { - if (originalList == null) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" }); - } - var userToRemove = originalList.FirstOrDefault(x => x.Username == username); - if (userToRemove != null) - { - await UsersToNotifyRepo.DeleteAsync(userToRemove); - } - return Response.AsJson(new JsonResponseModel { Result = true }); - } - - - if (originalList == null) - { - var userModel = new UsersToNotify { Username = username }; - var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); - return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); - } - - var existingUser = originalList.FirstOrDefault(x => x.Username == username); - if (existingUser != null) - { - return Response.AsJson(new JsonResponseModel { Result = true }); // It's already enabled - } - else - { - var userModel = new UsersToNotify { Username = username }; - var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); - return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); - } - - } - private async Task GetUserNotificationSettings() - { - var all = await UsersToNotifyRepo.GetAllAsync(); - var retVal = all.FirstOrDefault(x => x.Username == Username); - return Response.AsJson(retVal != null); - } - - private Response GetSeasons() - { - var tv = new TvMazeApi(); - var seriesId = (int)Request.Query.tvId; - var show = tv.ShowLookupByTheTvDbId(seriesId); - var seasons = tv.GetSeasons(show.id); - var model = seasons.Select(x => x.number); - return Response.AsJson(model); - } - - - } -} +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: SearchModule.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +using Nancy; +using Nancy.Responses.Negotiation; + +using NLog; + +using PlexRequests.Api; +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Music; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Exceptions; +using PlexRequests.Services.Interfaces; +using PlexRequests.Services.Notification; +using PlexRequests.Store; +using PlexRequests.UI.Helpers; +using PlexRequests.UI.Models; +using System.Threading.Tasks; + +using Nancy.Extensions; +using Nancy.Responses; + +using PlexRequests.Api.Models.Tv; +using PlexRequests.Core.Models; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; + +using TMDbLib.Objects.General; + +namespace PlexRequests.UI.Modules +{ + public class SearchModule : BaseAuthModule + { + public SearchModule(ICacheProvider cache, ISettingsService cpSettings, + ISettingsService prSettings, IAvailabilityChecker checker, + IRequestService request, ISonarrApi sonarrApi, ISettingsService sonarrSettings, + ISettingsService sickRageService, ICouchPotatoApi cpApi, ISickRageApi srApi, + INotificationService notify, IMusicBrainzApi mbApi, IHeadphonesApi hpApi, ISettingsService hpService, + ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi, + ISettingsService plexService, ISettingsService auth, IRepository u, ISettingsService email, + IIssueService issue) : base("search", prSettings) + { + Auth = auth; + PlexService = plexService; + PlexApi = plexApi; + CpService = cpSettings; + PrService = prSettings; + MovieApi = new TheMovieDbApi(); + Cache = cache; + Checker = checker; + CpCacher = cpCacher; + SonarrCacher = sonarrCacher; + SickRageCacher = sickRageCacher; + RequestService = request; + SonarrApi = sonarrApi; + SonarrService = sonarrSettings; + CouchPotatoApi = cpApi; + SickRageService = sickRageService; + SickrageApi = srApi; + NotificationService = notify; + MusicBrainzApi = mbApi; + HeadphonesApi = hpApi; + HeadphonesService = hpService; + UsersToNotifyRepo = u; + EmailNotificationSettings = email; + IssueService = issue; + + + Get["/", true] = async (x, ct) => await RequestLoad(); + + Get["movie/{searchTerm}", true] = async (x, ct) => await SearchMovie((string)x.searchTerm); + Get["tv/{searchTerm}", true] = async (x, ct) => await SearchTvShow((string)x.searchTerm); + Get["music/{searchTerm}", true] = async (x, ct) => await SearchMusic((string)x.searchTerm); + Get["music/coverArt/{id}"] = p => GetMusicBrainzCoverArt((string)p.id); + + Get["movie/upcoming", true] = async (x, ct) => await UpcomingMovies(); + Get["movie/playing", true] = async (x, ct) => await CurrentlyPlayingMovies(); + + Post["request/movie", true] = async (x, ct) => await RequestMovie((int)Request.Form.movieId); + Post["request/tv", true] = async (x, ct) => await RequestTvShow((int)Request.Form.tvId, (string)Request.Form.seasons); + Post["request/album", true] = async (x, ct) => await RequestAlbum((string)Request.Form.albumId); + + Post["/notifyuser", true] = async (x, ct) => await NotifyUser((bool)Request.Form.notify); + Get["/notifyuser", true] = async (x, ct) => await GetUserNotificationSettings(); + + Get["/seasons"] = x => GetSeasons(); + } + private IPlexApi PlexApi { get; } + private TheMovieDbApi MovieApi { get; } + private INotificationService NotificationService { get; } + private ICouchPotatoApi CouchPotatoApi { get; } + private ISonarrApi SonarrApi { get; } + private ISickRageApi SickrageApi { get; } + private IRequestService RequestService { get; } + private ICacheProvider Cache { get; } + private ISettingsService Auth { get; } + private ISettingsService PlexService { get; } + private ISettingsService CpService { get; } + private ISettingsService PrService { get; } + private ISettingsService SonarrService { get; } + private ISettingsService SickRageService { get; } + private ISettingsService HeadphonesService { get; } + private ISettingsService EmailNotificationSettings { get; } + private IAvailabilityChecker Checker { get; } + private ICouchPotatoCacher CpCacher { get; } + private ISonarrCacher SonarrCacher { get; } + private ISickRageCacher SickRageCacher { get; } + private IMusicBrainzApi MusicBrainzApi { get; } + private IHeadphonesApi HeadphonesApi { get; } + private IRepository UsersToNotifyRepo { get; } + private IIssueService IssueService { get; } + private static Logger Log = LogManager.GetCurrentClassLogger(); + + private async Task RequestLoad() + { + var settings = await PrService.GetSettingsAsync(); + + Log.Trace("Loading Index"); + return View["Search/Index", settings]; + } + + private async Task UpcomingMovies() + { + Log.Trace("Loading upcoming movies"); + return await ProcessMovies(MovieSearchType.Upcoming, string.Empty); + } + + private async Task CurrentlyPlayingMovies() + { + Log.Trace("Loading currently playing movies"); + return await ProcessMovies(MovieSearchType.CurrentlyPlaying, string.Empty); + } + + private async Task SearchMovie(string searchTerm) + { + Log.Trace("Searching for Movie {0}", searchTerm); + return await ProcessMovies(MovieSearchType.Search, searchTerm); + } + + private async Task ProcessMovies(MovieSearchType searchType, string searchTerm) + { + var apiMovies = new List(); + await Task.Factory.StartNew( + () => + { + switch (searchType) + { + case MovieSearchType.Search: + return + MovieApi.SearchMovie(searchTerm) + .Result.Select( + x => + new MovieResult() + { + Adult = x.Adult, + BackdropPath = x.BackdropPath, + GenreIds = x.GenreIds, + Id = x.Id, + OriginalLanguage = x.OriginalLanguage, + OriginalTitle = x.OriginalTitle, + Overview = x.Overview, + Popularity = x.Popularity, + PosterPath = x.PosterPath, + ReleaseDate = x.ReleaseDate, + Title = x.Title, + Video = x.Video, + VoteAverage = x.VoteAverage, + VoteCount = x.VoteCount + }) + .ToList(); + case MovieSearchType.CurrentlyPlaying: + return MovieApi.GetCurrentPlayingMovies().Result.ToList(); + case MovieSearchType.Upcoming: + return MovieApi.GetUpcomingMovies().Result.ToList(); + default: + return new List(); + } + }).ContinueWith( + (t) => + { + apiMovies = t.Result; + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Movie); + + var distinctResults = allResults.DistinctBy(x => x.ProviderId); + var dbMovies = distinctResults.ToDictionary(x => x.ProviderId); + + + var cpCached = CpCacher.QueuedIds(); + var plexMovies = Checker.GetPlexMovies(); + var settings = await PrService.GetSettingsAsync(); + var viewMovies = new List(); + foreach (MovieResult movie in apiMovies) + { + var viewMovie = new SearchMovieViewModel + { + Adult = movie.Adult, + BackdropPath = movie.BackdropPath, + GenreIds = movie.GenreIds, + Id = movie.Id, + OriginalLanguage = movie.OriginalLanguage, + OriginalTitle = movie.OriginalTitle, + Overview = movie.Overview, + Popularity = movie.Popularity, + PosterPath = movie.PosterPath, + ReleaseDate = movie.ReleaseDate, + Title = movie.Title, + Video = movie.Video, + VoteAverage = movie.VoteAverage, + VoteCount = movie.VoteCount + }; + var canSee = CanUserSeeThisRequest(viewMovie.Id, settings.UsersCanViewOnlyOwnRequests, dbMovies); + if (Checker.IsMovieAvailable(plexMovies.ToArray(), movie.Title, movie.ReleaseDate?.Year.ToString())) + { + viewMovie.Available = true; + } + else if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db + { + var dbm = dbMovies[movie.Id]; + + viewMovie.Requested = true; + viewMovie.Approved = dbm.Approved; + viewMovie.Available = dbm.Available; + } + else if (cpCached.Contains(movie.Id) && canSee) // compare to the couchpotato db + { + viewMovie.Requested = true; + } + + viewMovies.Add(viewMovie); + } + + return Response.AsJson(viewMovies); + } + + private bool CanUserSeeThisRequest(int movieId, bool usersCanViewOnlyOwnRequests, Dictionary moviesInDb) + { + if (usersCanViewOnlyOwnRequests) + { + var result = moviesInDb.FirstOrDefault(x => x.Value.ProviderId == movieId); + return result.Value == null || result.Value.UserHasRequested(Username); + } + + return true; + } + + private async Task SearchTvShow(string searchTerm) + { + var plexSettings = await PlexService.GetSettingsAsync(); + Log.Trace("Searching for TV Show {0}", searchTerm); + + var apiTv = new List(); + await Task.Factory.StartNew(() => new TvMazeApi().Search(searchTerm)).ContinueWith((t) => + { + apiTv = t.Result; + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.TvShow); + + var dbTv = allResults.ToDictionary(x => x.ProviderId); + + if (!apiTv.Any()) + { + Log.Trace("TV Show data is null"); + return Response.AsJson(""); + } + + var sonarrCached = SonarrCacher.QueuedIds(); + var sickRageCache = SickRageCacher.QueuedIds(); // consider just merging sonarr/sickrage arrays + var plexTvShows = Checker.GetPlexTvShows(); + + var viewTv = new List(); + foreach (var t in apiTv) + { + var banner = t.show.image?.medium; + if (!string.IsNullOrEmpty(banner)) + { + banner = banner.Replace("http", "https"); + } + + var viewT = new SearchTvShowViewModel + { + Banner = banner, + FirstAired = t.show.premiered, + Id = t.show.externals?.thetvdb ?? 0, + ImdbId = t.show.externals?.imdb, + Network = t.show.network?.name, + NetworkId = t.show.network?.id.ToString(), + Overview = t.show.summary.RemoveHtml(), + Rating = t.score.ToString(CultureInfo.CurrentUICulture), + Runtime = t.show.runtime.ToString(), + SeriesId = t.show.id, + SeriesName = t.show.name, + Status = t.show.status + }; + + + var providerId = string.Empty; + + if (plexSettings.AdvancedSearch) + { + providerId = viewT.Id.ToString(); + } + + if (Checker.IsTvShowAvailable(plexTvShows.ToArray(), t.show.name, t.show.premiered?.Substring(0, 4), providerId)) + { + viewT.Available = true; + } + else if (t.show?.externals?.thetvdb != null) + { + int tvdbid = (int)t.show.externals.thetvdb; + + if (dbTv.ContainsKey(tvdbid)) + { + var dbt = dbTv[tvdbid]; + + viewT.Requested = true; + viewT.Approved = dbt.Approved; + viewT.Available = dbt.Available; + } + else if (sonarrCached.Contains(tvdbid) || sickRageCache.Contains(tvdbid)) // compare to the sonarr/sickrage db + { + viewT.Requested = true; + } + } + + viewTv.Add(viewT); + } + + return Response.AsJson(viewTv); + } + + private async Task SearchMusic(string searchTerm) + { + var apiAlbums = new List(); + await Task.Run(() => MusicBrainzApi.SearchAlbum(searchTerm)).ContinueWith((t) => + { + apiAlbums = t.Result.releases ?? new List(); + }); + + var allResults = await RequestService.GetAllAsync(); + allResults = allResults.Where(x => x.Type == RequestType.Album); + + var dbAlbum = allResults.ToDictionary(x => x.MusicBrainzId); + + var plexAlbums = Checker.GetPlexAlbums(); + + var viewAlbum = new List(); + foreach (var a in apiAlbums) + { + var viewA = new SearchMusicViewModel + { + Title = a.title, + Id = a.id, + Artist = a.ArtistCredit?.Select(x => x.artist?.name).FirstOrDefault(), + Overview = a.disambiguation, + ReleaseDate = a.date, + TrackCount = a.TrackCount, + ReleaseType = a.status, + Country = a.country + }; + + DateTime release; + DateTimeHelper.CustomParse(a.ReleaseEvents?.FirstOrDefault()?.date, out release); + var artist = a.ArtistCredit?.FirstOrDefault()?.artist; + if (Checker.IsAlbumAvailable(plexAlbums.ToArray(), a.title, release.ToString("yyyy"), artist?.name)) + { + viewA.Available = true; + } + if (!string.IsNullOrEmpty(a.id) && dbAlbum.ContainsKey(a.id)) + { + var dba = dbAlbum[a.id]; + + viewA.Requested = true; + viewA.Approved = dba.Approved; + viewA.Available = dba.Available; + } + + viewAlbum.Add(viewA); + } + return Response.AsJson(viewAlbum); + } + + private async Task RequestMovie(int movieId) + { + var movieInfo = MovieApi.GetMovieInformation(movieId).Result; + var fullMovieName = $"{movieInfo.Title}{(movieInfo.ReleaseDate.HasValue ? $" ({movieInfo.ReleaseDate.Value.Year})" : string.Empty)}"; + Log.Trace("Getting movie info from TheMovieDb"); + + var settings = await PrService.GetSettingsAsync(); + + // check if the movie has already been requested + Log.Info("Requesting movie with id {0}", movieId); + var existingRequest = await RequestService.CheckRequestAsync(movieId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this movie, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullMovieName} was successfully added!" : $"{fullMovieName} has already been requested!" }); + } + + Log.Debug("movie with id {0} doesnt exists", movieId); + + try + { + var movies = Checker.GetPlexMovies(); + if (Checker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullMovieName} is already in Plex!" }); + } + } + catch (Exception e) + { + Log.Error(e); + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullMovieName} is in Plex, are you sure it's correctly setup?" }); + } + //#endif + + var model = new RequestedModel + { + ProviderId = movieInfo.Id, + Type = RequestType.Movie, + Overview = movieInfo.Overview, + ImdbId = movieInfo.ImdbId, + PosterPath = "https://image.tmdb.org/t/p/w150/" + movieInfo.PosterPath, + Title = movieInfo.Title, + ReleaseDate = movieInfo.ReleaseDate ?? DateTime.MinValue, + Status = movieInfo.Status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + + }; + + if (ShouldAutoApprove(RequestType.Movie, settings)) + { + var cpSettings = await CpService.GetSettingsAsync(); + + if (cpSettings.Enabled) + { + Log.Info("Adding movie to CP (No approval required)"); + var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, + cpSettings.FullUri, cpSettings.ProfileId); + Log.Debug("Adding movie to CP result {0}", result); + if (result) + { + model.Approved = true; + Log.Info("Adding movie to database (No approval required)"); + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notificationModel); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); + } + return + Response.AsJson(new JsonResponseModel + { + Result = false, + Message = + "Something went wrong adding the movie to CouchPotato! Please check your settings." + }); + } + else + { + model.Approved = true; + Log.Info("Adding movie to database (No approval required)"); + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notificationModel = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notificationModel); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); + } + } + + try + { + Log.Info("Adding movie to database"); + var id = await RequestService.AddRequestAsync(model); + + var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + await NotificationService.Publish(notificationModel); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); + } + catch (Exception e) + { + Log.Fatal(e); + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." }); + } + } + + /// + /// Requests the tv show. + /// + /// The show identifier. + /// The seasons. + /// + private async Task RequestTvShow(int showId, string seasons) + { + var tvApi = new TvMazeApi(); + + var showInfo = tvApi.ShowLookupByTheTvDbId(showId); + DateTime firstAir; + DateTime.TryParse(showInfo.premiered, out firstAir); + string fullShowName = $"{showInfo.name} ({firstAir.Year})"; + //#if !DEBUG + + var settings = await PrService.GetSettingsAsync(); + + // check if the show has already been requested + Log.Info("Requesting tv show with id {0}", showId); + var existingRequest = await RequestService.CheckRequestAsync(showId); + if (existingRequest != null) + { + // check if the current user is already marked as a requester for this show, if not, add them + if (!existingRequest.UserHasRequested(Username)) + { + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{fullShowName} was successfully added!" : $"{fullShowName} has already been requested!" }); + } + + try + { + var shows = Checker.GetPlexTvShows(); + var providerId = string.Empty; + var plexSettings = await PlexService.GetSettingsAsync(); + if (plexSettings.AdvancedSearch) + { + providerId = showId.ToString(); + } + if (Checker.IsTvShowAvailable(shows.ToArray(), showInfo.name, showInfo.premiered?.Substring(0, 4), providerId)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{fullShowName} is already in Plex!" }); + } + } + catch (Exception) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {fullShowName} is in Plex, are you sure it's correctly setup?" }); + } + //#endif + + + var model = new RequestedModel + { + ProviderId = showInfo.externals?.thetvdb ?? 0, + Type = RequestType.TvShow, + Overview = showInfo.summary.RemoveHtml(), + PosterPath = showInfo.image?.medium, + Title = showInfo.name, + ReleaseDate = firstAir, + Status = showInfo.status, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUsers = new List { Username }, + Issues = IssueState.None, + ImdbId = showInfo.externals?.imdb ?? string.Empty, + SeasonCount = showInfo.seasonCount, + TvDbId = showId.ToString() + }; + + var seasonsList = new List(); + switch (seasons) + { + case "first": + seasonsList.Add(1); + model.SeasonsRequested = "First"; + break; + case "latest": + seasonsList.Add(model.SeasonCount); + model.SeasonsRequested = "Latest"; + break; + case "all": + model.SeasonsRequested = "All"; + break; + default: + model.SeasonsRequested = seasons; + var split = seasons.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var seasonsCount = new int[split.Length]; + for (var i = 0; i < split.Length; i++) + { + int tryInt; + int.TryParse(split[i], out tryInt); + seasonsCount[i] = tryInt; + } + seasonsList.AddRange(seasonsCount); + break; + } + + model.SeasonList = seasonsList.ToArray(); + + if (ShouldAutoApprove(RequestType.TvShow, settings)) + { + var sonarrSettings = await SonarrService.GetSettingsAsync(); + var sender = new TvSender(SonarrApi, SickrageApi); + if (sonarrSettings.Enabled) + { + var result = sender.SendToSonarr(sonarrSettings, model); + if (!string.IsNullOrEmpty(result?.title)) + { + model.Approved = true; + Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notify1 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify1); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + + + return Response.AsJson(ValidationHelper.SendSonarrError(result?.ErrorMessages)); + + } + + var srSettings = SickRageService.GetSettings(); + if (srSettings.Enabled) + { + var result = sender.SendToSickRage(srSettings, model); + if (result?.result == "success") + { + model.Approved = true; + Log.Debug("Adding tv to database requests (No approval required & SickRage)"); + await RequestService.AddRequestAsync(model); + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + return Response.AsJson(new JsonResponseModel { Result = false, Message = result?.message != null ? "Message From SickRage: " + result.message : "Something went wrong adding the movie to SickRage! Please check your settings." }); + } + + if (!srSettings.Enabled && !sonarrSettings.Enabled) + { + model.Approved = true; + Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup"); + await RequestService.AddRequestAsync(model); + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + + return Response.AsJson(new JsonResponseModel { Result = false, Message = "The request of TV Shows is not correctly set up. Please contact your admin." }); + + } + + await RequestService.AddRequestAsync(model); + + var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + await NotificationService.Publish(notificationModel); + + return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); + } + + private bool ShouldSendNotification(PlexRequestSettings prSettings) + { + var sendNotification = !prSettings.IgnoreNotifyForAutoApprovedRequests; + var claims = Context.CurrentUser?.Claims; + if (claims != null) + { + var enumerable = claims as string[] ?? claims.ToArray(); + if (enumerable.Contains(UserClaims.Admin) || enumerable.Contains(UserClaims.PowerUser)) + { + sendNotification = false; // Don't bother sending a notification if the user is an admin + } + } + return sendNotification; + } + + + private async Task RequestAlbum(string releaseId) + { + var settings = await PrService.GetSettingsAsync(); + var existingRequest = await RequestService.CheckRequestAsync(releaseId); + Log.Debug("Checking for an existing request"); + + if (existingRequest != null) + { + Log.Debug("We do have an existing album request"); + if (!existingRequest.UserHasRequested(Username)) + { + + Log.Debug("Not in the requested list so adding them and updating the request. User: {0}", Username); + existingRequest.RequestedUsers.Add(Username); + await RequestService.UpdateRequestAsync(existingRequest); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = settings.UsersCanViewOnlyOwnRequests ? $"{existingRequest.Title} was successfully added!" : $"{existingRequest.Title} has already been requested!" }); + } + + Log.Debug("This is a new request"); + + var albumInfo = MusicBrainzApi.GetAlbum(releaseId); + DateTime release; + DateTimeHelper.CustomParse(albumInfo.ReleaseEvents?.FirstOrDefault()?.date, out release); + + var artist = albumInfo.ArtistCredits?.FirstOrDefault()?.artist; + if (artist == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not find the artist on MusicBrainz. Please try again later or contact your admin" }); + } + + var albums = Checker.GetPlexAlbums(); + var alreadyInPlex = Checker.IsAlbumAvailable(albums.ToArray(), albumInfo.title, release.ToString("yyyy"), artist.name); + + if (alreadyInPlex) + { + return Response.AsJson(new JsonResponseModel + { + Result = false, + Message = $"{albumInfo.title} is already in Plex!" + }); + } + + var img = GetMusicBrainzCoverArt(albumInfo.id); + + var model = new RequestedModel + { + Title = albumInfo.title, + MusicBrainzId = albumInfo.id, + Overview = albumInfo.disambiguation, + PosterPath = img, + Type = RequestType.Album, + ProviderId = 0, + RequestedUsers = new List { Username }, + Status = albumInfo.status, + Issues = IssueState.None, + RequestedDate = DateTime.UtcNow, + ReleaseDate = release, + ArtistName = artist.name, + ArtistId = artist.id + }; + + if (ShouldAutoApprove(RequestType.Album, settings)) + { + Log.Debug("We don't require approval OR the user is in the whitelist"); + var hpSettings = HeadphonesService.GetSettings(); + + if (!hpSettings.Enabled) + { + await RequestService.AddRequestAsync(model); + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + var sender = new HeadphonesSender(HeadphonesApi, hpSettings, RequestService); + await sender.AddAlbum(model); + model.Approved = true; + await RequestService.AddRequestAsync(model); + + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + + return + Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + if (ShouldSendNotification(settings)) + { + var notify2 = new NotificationModel + { + Title = model.Title, + User = Username, + DateTime = DateTime.Now, + NotificationType = NotificationType.NewRequest + }; + await NotificationService.Publish(notify2); + } + await RequestService.AddRequestAsync(model); + return Response.AsJson(new JsonResponseModel + { + Result = true, + Message = $"{model.Title} was successfully added!" + }); + } + + private string GetMusicBrainzCoverArt(string id) + { + var coverArt = MusicBrainzApi.GetCoverArt(id); + var firstImage = coverArt?.images?.FirstOrDefault(); + var img = string.Empty; + + if (firstImage != null) + { + img = firstImage.thumbnails?.small ?? firstImage.image; + } + + return img; + } + + private bool ShouldAutoApprove(RequestType requestType, PlexRequestSettings prSettings) + { + // if the user is an admin or they are whitelisted, they go ahead and allow auto-approval + if (IsAdmin || prSettings.ApprovalWhiteList.Any(x => x.Equals(Username, StringComparison.OrdinalIgnoreCase))) return true; + + // check by request type if the category requires approval or not + switch (requestType) + { + case RequestType.Movie: + return !prSettings.RequireMovieApproval; + case RequestType.TvShow: + return !prSettings.RequireTvShowApproval; + case RequestType.Album: + return !prSettings.RequireMusicApproval; + default: + return false; + } + } + + private async Task NotifyUser(bool notify) + { + var authSettings = await Auth.GetSettingsAsync(); + var auth = authSettings.UserAuthentication; + var emailSettings = await EmailNotificationSettings.GetSettingsAsync(); + var email = emailSettings.EnableUserEmailNotifications; + if (!auth) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but this functionality is currently only for users with Plex accounts" }); + } + if (!email) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, but your administrator has not yet enabled this functionality." }); + } + var username = Username; + var originalList = await UsersToNotifyRepo.GetAllAsync(); + if (!notify) + { + if (originalList == null) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "We could not remove this notification because you never had it!" }); + } + var userToRemove = originalList.FirstOrDefault(x => x.Username == username); + if (userToRemove != null) + { + await UsersToNotifyRepo.DeleteAsync(userToRemove); + } + return Response.AsJson(new JsonResponseModel { Result = true }); + } + + + if (originalList == null) + { + var userModel = new UsersToNotify { Username = username }; + var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); + return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); + } + + var existingUser = originalList.FirstOrDefault(x => x.Username == username); + if (existingUser != null) + { + return Response.AsJson(new JsonResponseModel { Result = true }); // It's already enabled + } + else + { + var userModel = new UsersToNotify { Username = username }; + var insertResult = await UsersToNotifyRepo.InsertAsync(userModel); + return Response.AsJson(insertResult != -1 ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "Could not save, please try again" }); + } + + } + private async Task GetUserNotificationSettings() + { + var all = await UsersToNotifyRepo.GetAllAsync(); + var retVal = all.FirstOrDefault(x => x.Username == Username); + return Response.AsJson(retVal != null); + } + + private Response GetSeasons() + { + var tv = new TvMazeApi(); + var seriesId = (int)Request.Query.tvId; + var show = tv.ShowLookupByTheTvDbId(seriesId); + var seasons = tv.GetSeasons(show.id); + var model = seasons.Select(x => x.number); + return Response.AsJson(model); + } + + + } +} diff --git a/PlexRequests.UI/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index b6ad5d09d..423b98502 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -1,294 +1,309 @@ -@using PlexRequests.UI.Helpers -@inherits Nancy.ViewEngines.Razor.NancyRazorViewBase -@Html.Partial("_Sidebar") -@{ - int port; - if (Model.Port == 0) - { - port = 3579; - } - else - { - port = Model.Port; - } - - - var baseUrl = Html.GetBaseUrl(); - var formAction = "/admin"; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - formAction = "/" + baseUrl.ToHtmlString() + formAction; - } - var plexTheme = string.Empty; - var originalTheme = string.Empty; - - if (!string.IsNullOrEmpty(Model.ThemeName)) - { - plexTheme = Model.ThemeName.Equals(Themes.PlexTheme) ? "selected=\"selected\"" : string.Empty; - originalTheme = Model.ThemeName.Equals(Themes.OriginalTheme) ? "selected=\"selected\"" : string.Empty; - } - else - { - plexTheme = "selected=\"selected\""; - } -} -
-
-
- Plex Request Settings -
- - -
- -
-
- You will have to restart after changing the port. - -
- - -
- -
-
- You will have to restart after changing the url base. - -
- -
- - -
-
-
-
-
- -
- -
- -
-
- -
-
- - @if (Model.SearchForMovies) - { - - } - else - { - - } - -
-
- -
-
- - @if (Model.SearchForTvShows) - { - - } - else - { - - } - -
-
-
-
- - @if (Model.SearchForMusic) - { - - } - else - { - - } - -
-
-
-
- - @if (Model.RequireMovieApproval) - { - - } - else - { - - } -
-
-
-
- - @if (Model.RequireTvShowApproval) - { - - } - else - { - - } - - -
-
-
-
- - @if (Model.RequireMusicApproval) - { - - } - else - { - - } - - -
-
- -
-
- - @if (Model.UsersCanViewOnlyOwnRequests) - { - - - } - else - { - - } - - -
-
- -
-
- - @if (Model.UsersCanViewOnlyOwnIssues) - { - - - } - else - { - - } -
-
- -
-
- - @if (Model.CollectAnalyticData) - { - - - } - else - { - - } -
-
- - - -

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

-
- -
- -
-
- - @*
- -
- -
-
//TODO: Need to implement this*@ - -
-
-
-
- -
-
-
-
-
- - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 6771fa74b..cfef4c77b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,26 +1,26 @@ -version: 1.8.{build} -configuration: Release -assembly_info: - patch: true - file: '**\AssemblyInfo.*' - assembly_version: '1.8.1' - assembly_file_version: '{version}' - assembly_informational_version: '1.8.1' -before_build: -- cmd: appveyor-retry nuget restore -build: - verbosity: minimal -after_build: -- cmd: >- - 7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\ - - appveyor PushArtifact PlexRequests.zip - -deploy: -- provider: GitHub - release: PlexRequests v$(appveyor_build_version) - auth_token: - secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp - draft: true - on: - branch: master +version: 1.8.{build} +configuration: Release +assembly_info: + patch: true + file: '**\AssemblyInfo.*' + assembly_version: '1.8.1' + assembly_file_version: '{version}' + assembly_informational_version: '1.8.1' +before_build: +- cmd: appveyor-retry nuget restore +build: + verbosity: minimal +after_build: +- cmd: >- + 7z a PlexRequests.zip %APPVEYOR_BUILD_FOLDER%\PlexRequests.UI\bin\Release\ + + appveyor PushArtifact PlexRequests.zip + +deploy: +- provider: GitHub + release: PlexRequests v$(appveyor_build_version) + auth_token: + secure: jDpp1/WUQl3uN41fNI3VeZoRZbDiDfs3GPQ1v+C5ZNE3cWdnUvuJfCCfUbYUV1Rp + draft: true + on: + branch: master From f76e54408eca5d4249db438b1d132082e21a1f70 Mon Sep 17 00:00:00 2001 From: Drewster727 Date: Tue, 21 Jun 2016 20:20:44 -0500 Subject: [PATCH 2/8] show request type in notifications #346 and fix an issue from previous commit for #345 --- .../Notification/EmailMessageNotification.cs | 5 +- .../Notification/NotificationModel.cs | 80 ++--- .../Notification/PushbulletNotification.cs | 283 +++++++++--------- .../Notification/PushoverNotification.cs | 277 ++++++++--------- PlexRequests.Store/RequestedModel.cs | 198 ++++++------ PlexRequests.UI/Modules/SearchModule.cs | 43 +-- 6 files changed, 458 insertions(+), 428 deletions(-) diff --git a/PlexRequests.Services/Notification/EmailMessageNotification.cs b/PlexRequests.Services/Notification/EmailMessageNotification.cs index 080091504..466ff7a24 100644 --- a/PlexRequests.Services/Notification/EmailMessageNotification.cs +++ b/PlexRequests.Services/Notification/EmailMessageNotification.cs @@ -36,6 +36,7 @@ using PlexRequests.Core.SettingModels; using PlexRequests.Services.Interfaces; using SmtpClient = MailKit.Net.Smtp.SmtpClient; +using PlexRequests.Store; namespace PlexRequests.Services.Notification { @@ -119,8 +120,8 @@ private async Task EmailNewRequest(NotificationModel model, EmailNotificationSet { var message = new MimeMessage { - Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested {model.Title}! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, - Subject = $"Plex Requests: New request for {model.Title}!" + Body = new TextPart("plain") { Text = $"Hello! The user '{model.User}' has requested the {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} '{model.Title}'! Please log in to approve this request. Request Date: {model.DateTime.ToString("f")}" }, + Subject = $"Plex Requests: New {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} request for {model.Title}!" }; message.From.Add(new MailboxAddress(settings.EmailSender, settings.EmailSender)); message.To.Add(new MailboxAddress(settings.RecipientEmail, settings.RecipientEmail)); diff --git a/PlexRequests.Services/Notification/NotificationModel.cs b/PlexRequests.Services/Notification/NotificationModel.cs index 2cc4e7fdb..e74f90dd4 100644 --- a/PlexRequests.Services/Notification/NotificationModel.cs +++ b/PlexRequests.Services/Notification/NotificationModel.cs @@ -1,40 +1,42 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: NotificationModel.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; - -namespace PlexRequests.Services.Notification -{ - public class NotificationModel - { - public string Title { get; set; } - public string Body { get; set; } - public DateTime DateTime { get; set; } - public NotificationType NotificationType { get; set; } - public string User { get; set; } - public string UserEmail { get; set; } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: NotificationModel.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using PlexRequests.Store; +using System; + +namespace PlexRequests.Services.Notification +{ + public class NotificationModel + { + public string Title { get; set; } + public string Body { get; set; } + public DateTime DateTime { get; set; } + public NotificationType NotificationType { get; set; } + public string User { get; set; } + public string UserEmail { get; set; } + public RequestType RequestType { get; set; } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/PushbulletNotification.cs b/PlexRequests.Services/Notification/PushbulletNotification.cs index 70f318331..8eda8d0b9 100644 --- a/PlexRequests.Services/Notification/PushbulletNotification.cs +++ b/PlexRequests.Services/Notification/PushbulletNotification.cs @@ -1,142 +1,143 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PushbulletNotification.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Threading.Tasks; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; - -namespace PlexRequests.Services.Notification -{ - public class PushbulletNotification : INotification - { - public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService settings) - { - PushbulletApi = pushbulletApi; - SettingsService = settings; - } - private IPushbulletApi PushbulletApi { get; } - private ISettingsService SettingsService { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - public string NotificationName => "PushbulletNotification"; - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetSettings(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var pushSettings = (PushbulletNotificationSettings)settings; - - if (!ValidateConfiguration(pushSettings)) return; - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await PushNewRequestAsync(model, pushSettings); - break; - case NotificationType.Issue: - await PushIssueAsync(model, pushSettings); - break; - case NotificationType.RequestAvailable: - break; - case NotificationType.RequestApproved: - break; - case NotificationType.AdminNote: - break; - case NotificationType.Test: - await PushTestAsync(pushSettings); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - private bool ValidateConfiguration(PushbulletNotificationSettings settings) - { - if (!settings.Enabled) - { - return false; - } - if (string.IsNullOrEmpty(settings.AccessToken)) - { - return false; - } - return true; - } - - private PushbulletNotificationSettings GetSettings() - { - return SettingsService.GetSettings(); - } - - private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings) - { - var message = $"{model.Title} has been requested by user: {model.User}"; - var pushTitle = $"Plex Requests: {model.Title} has been requested!"; - await Push(settings, message, pushTitle); - } - - private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings) - { - var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; - var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}"; - await Push(settings, message, pushTitle); - } - - private async Task PushTestAsync(PushbulletNotificationSettings settings) - { - var message = "This is just a test! Success!"; - var pushTitle = "Plex Requests: Test Message!"; - await Push(settings, message, pushTitle); - } - - private async Task Push(PushbulletNotificationSettings settings, string message, string title) - { - try - { - var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier); - if (result != null) - { - Log.Error("Pushbullet api returned a null value, the notification did not get pushed"); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletNotification.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; + +namespace PlexRequests.Services.Notification +{ + public class PushbulletNotification : INotification + { + public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService settings) + { + PushbulletApi = pushbulletApi; + SettingsService = settings; + } + private IPushbulletApi PushbulletApi { get; } + private ISettingsService SettingsService { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public string NotificationName => "PushbulletNotification"; + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetSettings(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var pushSettings = (PushbulletNotificationSettings)settings; + + if (!ValidateConfiguration(pushSettings)) return; + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await PushNewRequestAsync(model, pushSettings); + break; + case NotificationType.Issue: + await PushIssueAsync(model, pushSettings); + break; + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + case NotificationType.Test: + await PushTestAsync(pushSettings); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private bool ValidateConfiguration(PushbulletNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.AccessToken)) + { + return false; + } + return true; + } + + private PushbulletNotificationSettings GetSettings() + { + return SettingsService.GetSettings(); + } + + private async Task PushNewRequestAsync(NotificationModel model, PushbulletNotificationSettings settings) + { + var message = $"The {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} '{model.Title}' has been requested by user: {model.User}"; + var pushTitle = $"Plex Requests: The {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} {model.Title} has been requested!"; + await Push(settings, message, pushTitle); + } + + private async Task PushIssueAsync(NotificationModel model, PushbulletNotificationSettings settings) + { + var message = $"A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + var pushTitle = $"Plex Requests: A new issue has been reported for {model.Title}"; + await Push(settings, message, pushTitle); + } + + private async Task PushTestAsync(PushbulletNotificationSettings settings) + { + var message = "This is just a test! Success!"; + var pushTitle = "Plex Requests: Test Message!"; + await Push(settings, message, pushTitle); + } + + private async Task Push(PushbulletNotificationSettings settings, string message, string title) + { + try + { + var result = await PushbulletApi.PushAsync(settings.AccessToken, title, message, settings.DeviceIdentifier); + if (result != null) + { + Log.Error("Pushbullet api returned a null value, the notification did not get pushed"); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Services/Notification/PushoverNotification.cs b/PlexRequests.Services/Notification/PushoverNotification.cs index d39492563..9831e6562 100644 --- a/PlexRequests.Services/Notification/PushoverNotification.cs +++ b/PlexRequests.Services/Notification/PushoverNotification.cs @@ -1,139 +1,140 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: PushbulletNotification.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Threading.Tasks; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; - -namespace PlexRequests.Services.Notification -{ - public class PushoverNotification : INotification - { - public PushoverNotification(IPushoverApi pushoverApi, ISettingsService settings) - { - PushoverApi = pushoverApi; - SettingsService = settings; - } - private IPushoverApi PushoverApi { get; } - private ISettingsService SettingsService { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - public string NotificationName => "PushoverNotification"; - public async Task NotifyAsync(NotificationModel model) - { - var configuration = GetSettings(); - await NotifyAsync(model, configuration); - } - - public async Task NotifyAsync(NotificationModel model, Settings settings) - { - if (settings == null) await NotifyAsync(model); - - var pushSettings = (PushoverNotificationSettings)settings; - - if (!ValidateConfiguration(pushSettings)) return; - - switch (model.NotificationType) - { - case NotificationType.NewRequest: - await PushNewRequestAsync(model, pushSettings); - break; - case NotificationType.Issue: - await PushIssueAsync(model, pushSettings); - break; - case NotificationType.RequestAvailable: - break; - case NotificationType.RequestApproved: - break; - case NotificationType.AdminNote: - break; - case NotificationType.Test: - await PushTestAsync(model, pushSettings); - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - private bool ValidateConfiguration(PushoverNotificationSettings settings) - { - if (!settings.Enabled) - { - return false; - } - if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken)) - { - return false; - } - return true; - } - - private PushoverNotificationSettings GetSettings() - { - return SettingsService.GetSettings(); - } - - private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings) - { - var message = $"Plex Requests: {model.Title} has been requested by user: {model.User}"; - await Push(settings, message); - } - - private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings) - { - var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; - await Push(settings, message); - } - - private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings) - { - var message = $"Plex Requests: Test Message!"; - await Push(settings, message); - } - - private async Task Push(PushoverNotificationSettings settings, string message) - { - try - { - var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken); - if (result?.status != 1) - { - Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed"); - } - } - catch (Exception e) - { - Log.Error(e); - } - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: PushbulletNotification.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Threading.Tasks; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store; + +namespace PlexRequests.Services.Notification +{ + public class PushoverNotification : INotification + { + public PushoverNotification(IPushoverApi pushoverApi, ISettingsService settings) + { + PushoverApi = pushoverApi; + SettingsService = settings; + } + private IPushoverApi PushoverApi { get; } + private ISettingsService SettingsService { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + public string NotificationName => "PushoverNotification"; + public async Task NotifyAsync(NotificationModel model) + { + var configuration = GetSettings(); + await NotifyAsync(model, configuration); + } + + public async Task NotifyAsync(NotificationModel model, Settings settings) + { + if (settings == null) await NotifyAsync(model); + + var pushSettings = (PushoverNotificationSettings)settings; + + if (!ValidateConfiguration(pushSettings)) return; + + switch (model.NotificationType) + { + case NotificationType.NewRequest: + await PushNewRequestAsync(model, pushSettings); + break; + case NotificationType.Issue: + await PushIssueAsync(model, pushSettings); + break; + case NotificationType.RequestAvailable: + break; + case NotificationType.RequestApproved: + break; + case NotificationType.AdminNote: + break; + case NotificationType.Test: + await PushTestAsync(model, pushSettings); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private bool ValidateConfiguration(PushoverNotificationSettings settings) + { + if (!settings.Enabled) + { + return false; + } + if (string.IsNullOrEmpty(settings.AccessToken) || string.IsNullOrEmpty(settings.UserToken)) + { + return false; + } + return true; + } + + private PushoverNotificationSettings GetSettings() + { + return SettingsService.GetSettings(); + } + + private async Task PushNewRequestAsync(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Plex Requests: The {RequestTypeDisplay.Get(model.RequestType)?.ToLower()} '{model.Title}' has been requested by user: {model.User}"; + await Push(settings, message); + } + + private async Task PushIssueAsync(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Plex Requests: A new issue: {model.Body} has been reported by user: {model.User} for the title: {model.Title}"; + await Push(settings, message); + } + + private async Task PushTestAsync(NotificationModel model, PushoverNotificationSettings settings) + { + var message = $"Plex Requests: Test Message!"; + await Push(settings, message); + } + + private async Task Push(PushoverNotificationSettings settings, string message) + { + try + { + var result = await PushoverApi.PushAsync(settings.AccessToken, message, settings.UserToken); + if (result?.status != 1) + { + Log.Error("Pushover api returned a status that was not 1, the notification did not get pushed"); + } + } + catch (Exception e) + { + Log.Error(e); + } + } + } } \ No newline at end of file diff --git a/PlexRequests.Store/RequestedModel.cs b/PlexRequests.Store/RequestedModel.cs index c63ede547..254b4f7be 100644 --- a/PlexRequests.Store/RequestedModel.cs +++ b/PlexRequests.Store/RequestedModel.cs @@ -1,90 +1,108 @@ -using System; -using Dapper.Contrib.Extensions; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; - -namespace PlexRequests.Store -{ - [Table("Requested")] - public class RequestedModel : Entity - { - public RequestedModel() - { - RequestedUsers = new List(); - } - - // ReSharper disable once IdentifierTypo - public int ProviderId { get; set; } - public string ImdbId { get; set; } - public string TvDbId { get; set; } - public string Overview { get; set; } - public string Title { get; set; } - public string PosterPath { get; set; } - public DateTime ReleaseDate { get; set; } - public RequestType Type { get; set; } - public string Status { get; set; } - public bool Approved { get; set; } - - [Obsolete("Use RequestedUsers")] - public string RequestedBy { get; set; } - - public DateTime RequestedDate { get; set; } - public bool Available { get; set; } - public IssueState Issues { get; set; } - public string OtherMessage { get; set; } - public string AdminNote { get; set; } - public int[] SeasonList { get; set; } - public int SeasonCount { get; set; } - public string SeasonsRequested { get; set; } - public string MusicBrainzId { get; set; } - public List RequestedUsers { get; set; } - public string ArtistName { get; set; } - public string ArtistId { get; set; } - public int IssueId { get; set; } - - [JsonIgnore] - public List AllUsers - { - get - { - var u = new List(); - if (!string.IsNullOrEmpty(RequestedBy)) - { - u.Add(RequestedBy); - } - - if (RequestedUsers.Any()) - { - u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy)); - } - return u; - } - } - - [JsonIgnore] - public bool CanApprove => !Approved && !Available; - - public bool UserHasRequested(string username) - { - return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); - } - } - - public enum RequestType - { - Movie, - TvShow, - Album - } - - public enum IssueState - { - None = 99, - WrongAudio = 0, - NoSubtitles = 1, - WrongContent = 2, - PlaybackIssues = 3, - Other = 4, // Provide a message - } -} +using System; +using Dapper.Contrib.Extensions; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace PlexRequests.Store +{ + [Table("Requested")] + public class RequestedModel : Entity + { + public RequestedModel() + { + RequestedUsers = new List(); + } + + // ReSharper disable once IdentifierTypo + public int ProviderId { get; set; } + public string ImdbId { get; set; } + public string TvDbId { get; set; } + public string Overview { get; set; } + public string Title { get; set; } + public string PosterPath { get; set; } + public DateTime ReleaseDate { get; set; } + public RequestType Type { get; set; } + public string Status { get; set; } + public bool Approved { get; set; } + + [Obsolete("Use RequestedUsers")] + public string RequestedBy { get; set; } + + public DateTime RequestedDate { get; set; } + public bool Available { get; set; } + public IssueState Issues { get; set; } + public string OtherMessage { get; set; } + public string AdminNote { get; set; } + public int[] SeasonList { get; set; } + public int SeasonCount { get; set; } + public string SeasonsRequested { get; set; } + public string MusicBrainzId { get; set; } + public List RequestedUsers { get; set; } + public string ArtistName { get; set; } + public string ArtistId { get; set; } + public int IssueId { get; set; } + + [JsonIgnore] + public List AllUsers + { + get + { + var u = new List(); + if (!string.IsNullOrEmpty(RequestedBy)) + { + u.Add(RequestedBy); + } + + if (RequestedUsers.Any()) + { + u.AddRange(RequestedUsers.Where(requestedUser => requestedUser != RequestedBy)); + } + return u; + } + } + + [JsonIgnore] + public bool CanApprove => !Approved && !Available; + + public bool UserHasRequested(string username) + { + return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase)); + } + } + + public enum RequestType + { + Movie, + TvShow, + Album + } + + public static class RequestTypeDisplay + { + public static string Get(RequestType type) + { + switch (type) + { + case RequestType.Movie: + return "Movie"; + case RequestType.TvShow: + return "TV Show"; + case RequestType.Album: + return "Album"; + default: + return string.Empty; + } + } + } + + public enum IssueState + { + None = 99, + WrongAudio = 0, + NoSubtitles = 1, + WrongContent = 2, + PlaybackIssues = 3, + Other = 4, // Provide a message + } +} diff --git a/PlexRequests.UI/Modules/SearchModule.cs b/PlexRequests.UI/Modules/SearchModule.cs index e42ae0739..f02a417ea 100644 --- a/PlexRequests.UI/Modules/SearchModule.cs +++ b/PlexRequests.UI/Modules/SearchModule.cs @@ -488,14 +488,15 @@ private async Task RequestMovie(int movieId) Log.Info("Adding movie to database (No approval required)"); await RequestService.AddRequestAsync(model); - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.Movie, settings)) { var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.Movie }; await NotificationService.Publish(notificationModel); } @@ -515,14 +516,15 @@ private async Task RequestMovie(int movieId) Log.Info("Adding movie to database (No approval required)"); await RequestService.AddRequestAsync(model); - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.Movie, settings)) { var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.Movie }; await NotificationService.Publish(notificationModel); } @@ -536,7 +538,7 @@ private async Task RequestMovie(int movieId) Log.Info("Adding movie to database"); var id = await RequestService.AddRequestAsync(model); - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest, RequestType = RequestType.Movie }; await NotificationService.Publish(notificationModel); return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullMovieName} was successfully added!" }); @@ -663,14 +665,15 @@ private async Task RequestTvShow(int showId, string seasons) Log.Debug("Adding tv to database requests (No approval required & Sonarr)"); await RequestService.AddRequestAsync(model); - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.TvShow, settings)) { var notify1 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.TvShow }; await NotificationService.Publish(notify1); } @@ -691,14 +694,15 @@ private async Task RequestTvShow(int showId, string seasons) model.Approved = true; Log.Debug("Adding tv to database requests (No approval required & SickRage)"); await RequestService.AddRequestAsync(model); - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.TvShow, settings)) { var notify2 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.TvShow }; await NotificationService.Publish(notify2); } @@ -713,14 +717,15 @@ private async Task RequestTvShow(int showId, string seasons) model.Approved = true; Log.Debug("Adding tv to database requests (No approval required) and Sonarr/Sickrage not setup"); await RequestService.AddRequestAsync(model); - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.TvShow, settings)) { var notify2 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.TvShow }; await NotificationService.Publish(notify2); } @@ -733,15 +738,15 @@ private async Task RequestTvShow(int showId, string seasons) await RequestService.AddRequestAsync(model); - var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest }; + var notificationModel = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, NotificationType = NotificationType.NewRequest, RequestType = RequestType.TvShow }; await NotificationService.Publish(notificationModel); return Response.AsJson(new JsonResponseModel { Result = true, Message = $"{fullShowName} was successfully added!" }); } - private bool ShouldSendNotification(PlexRequestSettings prSettings) + private bool ShouldSendNotification(RequestType type, PlexRequestSettings prSettings) { - var sendNotification = !prSettings.IgnoreNotifyForAutoApprovedRequests; + var sendNotification = ShouldAutoApprove(type, prSettings) ? !prSettings.IgnoreNotifyForAutoApprovedRequests : true; var claims = Context.CurrentUser?.Claims; if (claims != null) { @@ -838,14 +843,15 @@ private async Task RequestAlbum(string releaseId) model.Approved = true; await RequestService.AddRequestAsync(model); - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.Album, settings)) { var notify2 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.Album }; await NotificationService.Publish(notify2); } @@ -858,14 +864,15 @@ private async Task RequestAlbum(string releaseId) }); } - if (ShouldSendNotification(settings)) + if (ShouldSendNotification(RequestType.Album, settings)) { var notify2 = new NotificationModel { Title = model.Title, User = Username, DateTime = DateTime.Now, - NotificationType = NotificationType.NewRequest + NotificationType = NotificationType.NewRequest, + RequestType = RequestType.Album }; await NotificationService.Publish(notify2); } From ffc0ec34c40c48c7859414c24fb5b498736fc1d3 Mon Sep 17 00:00:00 2001 From: Drewster727 Date: Tue, 21 Jun 2016 21:11:36 -0500 Subject: [PATCH 3/8] null provider check for movies --- PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 253ba7af3..68b8da01b 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -179,7 +179,8 @@ public bool IsMovieAvailable(PlexMovie[] plexMovies, string title, string year, { if (advanced) { - if (movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) + if (!string.IsNullOrEmpty(movie.ProviderId) && + movie.ProviderId.Equals(providerId, StringComparison.InvariantCultureIgnoreCase)) { return true; } From 7b57e3fffc8d6c41e9e1ff5fa5bd898ee29c00f5 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 22 Jun 2016 09:33:20 +0100 Subject: [PATCH 4/8] Fixed #353 #354 #355 --- PlexRequests.Core/Setup.cs | 4 - PlexRequests.UI/Content/issue-details.js | 38 +- PlexRequests.UI/Modules/IssuesModule.cs | 18 +- PlexRequests.UI/Views/Issues/Details.cshtml | 2 +- PlexRequests.UI/Views/Landing/Index.cshtml | 11 +- PlexRequests.UI/Views/Requests/Index.cshtml | 787 ++++++++++---------- 6 files changed, 432 insertions(+), 428 deletions(-) diff --git a/PlexRequests.Core/Setup.cs b/PlexRequests.Core/Setup.cs index 85a72d7f9..e66a15cec 100644 --- a/PlexRequests.Core/Setup.cs +++ b/PlexRequests.Core/Setup.cs @@ -57,10 +57,6 @@ public string SetupDb(string urlBase) var version = CheckSchema(); if (version > 0) { - if (version > 1700 && version <= 1799) - { - MigrateToVersion1700(); - } if (version > 1799 && version <= 1800) { MigrateToVersion1800(); diff --git a/PlexRequests.UI/Content/issue-details.js b/PlexRequests.UI/Content/issue-details.js index 28b10c997..92693ac94 100644 --- a/PlexRequests.UI/Content/issue-details.js +++ b/PlexRequests.UI/Content/issue-details.js @@ -27,19 +27,43 @@ $(".theNoteSaveButton").click(function (e) { }); }); // Update the note modal -$('#noteModal').on('show.bs.modal', function (event) { - var button = $(event.relatedTarget); // Button that triggered the modal - var id = button.data('identifier'); // Extract info from data-* attributes +$('#noteModal').on('show.bs.modal', function (event) { + var button = $(event.relatedTarget); // Button that triggered the modal + var id = button.data('identifier'); // Extract info from data-* attributes + var issue = button.data('issue'); - var modal = $(this); - modal.find('.theNoteSaveButton').val(id); // Add ID to the button - var requestField = modal.find('.noteId'); - requestField.val(id); // Add ID to the hidden field + var modal = $(this); + modal.find('.theNoteSaveButton').val(id); // Add ID to the button + var requestField = modal.find('.noteId'); + requestField.val(id); // Add ID to the hidden field + var noteType = modal.find('.issue'); + noteType.val(issue); + }); + + + $('.delete').click(function(e) { + e.preventDefault(); + var url = createBaseUrl(base, "/issues"); + var $form = $("#removeForm"); + $.ajax({ + type: $form.prop("method"), + url: $form.prop("action"), + data: $form.serialize(), + dataType: "json", + success: function (response) { + if (checkJsonResponse(response)) { window.location.replace(url); } + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); + }); diff --git a/PlexRequests.UI/Modules/IssuesModule.cs b/PlexRequests.UI/Modules/IssuesModule.cs index 1c66831e0..d5129f303 100644 --- a/PlexRequests.UI/Modules/IssuesModule.cs +++ b/PlexRequests.UI/Modules/IssuesModule.cs @@ -48,7 +48,7 @@ public IssuesModule(ISettingsService pr, IIssueService issu Get["/issuecount", true] = async (x, ct) => await IssueCount(); Get["/tabCount", true] = async (x, ct) => await TabCount(); - Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.provierId, IssueState.Other, (string)Request.Form.commentArea); + Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea); Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null); @@ -369,16 +369,22 @@ private async Task RemoveIssue(int issueId) this.RequiresClaims(UserClaims.Admin); var issue = await IssuesService.GetAsync(issueId); var request = await RequestService.GetAsync(issue.RequestId); + if (request.Id > 0) + { + request.IssueId = 0; // No issue; - request.IssueId = 0; // No issue; - - var result = await RequestService.UpdateRequestAsync(request); - if (result) + var result = await RequestService.UpdateRequestAsync(request); + if (result) + { + await IssuesService.DeleteIssueAsync(issueId); + } + } + else { await IssuesService.DeleteIssueAsync(issueId); } - return Response.AsJson(new JsonResponseModel() { Result = true }); + return Response.AsJson(new JsonResponseModel { Result = true }); } catch (Exception e) diff --git a/PlexRequests.UI/Views/Issues/Details.cshtml b/PlexRequests.UI/Views/Issues/Details.cshtml index 53a6c8994..4c4f06178 100644 --- a/PlexRequests.UI/Views/Issues/Details.cshtml +++ b/PlexRequests.UI/Views/Issues/Details.cshtml @@ -42,7 +42,7 @@ } @if (Model.IssueStatus == IssueStatus.ResolvedIssue) { -
+
diff --git a/PlexRequests.UI/Views/Landing/Index.cshtml b/PlexRequests.UI/Views/Landing/Index.cshtml index 8e0cd90a0..2a3ff8945 100644 --- a/PlexRequests.UI/Views/Landing/Index.cshtml +++ b/PlexRequests.UI/Views/Landing/Index.cshtml @@ -1,8 +1,15 @@  @using PlexRequests.UI.Helpers @inherits PlexRequests.UI.Helpers.EmptyViewBase - - +@{ + var baseUrl = Html.GetBaseUrl(); + var formAction = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + formAction = "/" + baseUrl.ToHtmlString(); + } +} +
@if (Model.NoticeEnable && (!Model.EnabledNoticeTime || Model.NoticeActive)) { diff --git a/PlexRequests.UI/Views/Requests/Index.cshtml b/PlexRequests.UI/Views/Requests/Index.cshtml index 95db35a11..e0e36f5ed 100644 --- a/PlexRequests.UI/Views/Requests/Index.cshtml +++ b/PlexRequests.UI/Views/Requests/Index.cshtml @@ -1,408 +1,379 @@ -@using Nancy.Security -@using PlexRequests.UI.Helpers -@{ - var baseUrl = Html.GetBaseUrl(); - var formAction = string.Empty; - if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) - { - formAction = "/" + baseUrl.ToHtmlString(); - } -} -
-

Requests

-

Below you can see yours and all other requests, as well as their download and approval status.

-
- - - -
- - -
-
-
-
-
- @if (Context.CurrentUser.IsAuthenticated()) //TODO replace with IsAdmin - { - @if (Model.SearchForMovies) - { - - - } - @if (Model.SearchForTvShows) - { - - - } - @if (Model.SearchForMusic) - { - - - } - } -
- - -
-
-
- @if (Model.SearchForMovies) - { - - -
- -
-
- -
-
-
- } - - @if (Model.SearchForTvShows) - { - -
- -
-
- -
-
-
- } - - @if (Model.SearchForMusic) - { - -
- -
-
- -
-
-
- } -
- -
- - - - - - - - - - -@Html.LoadRequestAssets() - - +@using Nancy.Security +@using PlexRequests.UI.Helpers +@{ + var baseUrl = Html.GetBaseUrl(); + var formAction = string.Empty; + if (!string.IsNullOrEmpty(baseUrl.ToHtmlString())) + { + formAction = "/" + baseUrl.ToHtmlString(); + } +} +
+

Requests

+

Below you can see yours and all other requests, as well as their download and approval status.

+
+ + + +
+ + +
+
+
+
+
+ @if (Context.CurrentUser.IsAuthenticated()) //TODO replace with IsAdmin + { + @if (Model.SearchForMovies) + { + + + } + @if (Model.SearchForTvShows) + { + + + } + @if (Model.SearchForMusic) + { + + + } + } +
+ + +
+
+
+ @if (Model.SearchForMovies) + { + + +
+ +
+
+ +
+
+
+ } + + @if (Model.SearchForTvShows) + { + +
+ +
+
+ +
+
+
+ } + + @if (Model.SearchForMusic) + { + +
+ +
+
+ +
+
+
+ } +
+ +
+ + + + + + + + +@Html.LoadRequestAssets() + + From 63e0d0e531fb0511c18e8a5d20696e7d00232eca Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 22 Jun 2016 12:59:01 +0100 Subject: [PATCH 5/8] Small changes around how we work with custom events in the analytics --- .../CookieHelperTests.cs | 52 ++ .../PlexRequests.Helpers.Tests.csproj | 237 ++--- PlexRequests.Helpers/Analytics/Action.cs | 3 +- PlexRequests.Helpers/Analytics/Analytics.cs | 53 +- PlexRequests.Helpers/Analytics/IAnalytics.cs | 18 +- PlexRequests.Helpers/CookieHelper.cs | 57 ++ .../PlexRequests.Helpers.csproj | 203 ++-- PlexRequests.UI.Tests/AdminModuleTests.cs | 724 ++++++++------- PlexRequests.UI.Tests/UserLoginModuleTests.cs | 876 +++++++++--------- PlexRequests.UI/Modules/AdminModule.cs | 11 +- PlexRequests.UI/Modules/BaseModule.cs | 148 +-- PlexRequests.UI/Modules/UserLoginModule.cs | 453 ++++----- .../Validators/PlexRequestsValidator.cs | 1 + PlexRequests.UI/Views/Shared/Blank.cshtml | 2 +- 14 files changed, 1498 insertions(+), 1340 deletions(-) create mode 100644 PlexRequests.Helpers.Tests/CookieHelperTests.cs create mode 100644 PlexRequests.Helpers/CookieHelper.cs diff --git a/PlexRequests.Helpers.Tests/CookieHelperTests.cs b/PlexRequests.Helpers.Tests/CookieHelperTests.cs new file mode 100644 index 000000000..c9a8205fb --- /dev/null +++ b/PlexRequests.Helpers.Tests/CookieHelperTests.cs @@ -0,0 +1,52 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: DateTimeHelperTests.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System.Collections.Generic; + +using NUnit.Framework; + +namespace PlexRequests.Helpers.Tests +{ + [TestFixture] + public class CookieHelperTests + { + [TestCaseSource(nameof(GetAnalyticsClientId))] + public string TestGetAnalyticsClientId(Dictionary cookies) + { + return CookieHelper.GetAnalyticClientId(cookies); + } + + private static IEnumerable GetAnalyticsClientId + { + get + { + yield return new TestCaseData(new Dictionary()).Returns(string.Empty); + yield return new TestCaseData(new Dictionary { { "_ga", "GA1.1.306549087.1464005217" } }).Returns("306549087.1464005217"); + yield return new TestCaseData(new Dictionary { {"_ga", "GA1.1.306549087" } }).Returns(string.Empty); + } + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj index 308e966e7..0789e013c 100644 --- a/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj +++ b/PlexRequests.Helpers.Tests/PlexRequests.Helpers.Tests.csproj @@ -1,119 +1,120 @@ - - - - Debug - AnyCPU - {0E6395D3-B074-49E8-898D-0EB99E507E0E} - Library - Properties - PlexRequests.Helpers.Tests - PlexRequests.Helpers.Tests - v4.5 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - True - - - ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll - True - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll - True - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {1252336d-42a3-482a-804c-836e60173dfa} - PlexRequests.Helpers - - - - - - - False - - - False - - - False - - - False - - - - - - - + + + + Debug + AnyCPU + {0E6395D3-B074-49E8-898D-0EB99E507E0E} + Library + Properties + PlexRequests.Helpers.Tests + PlexRequests.Helpers.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NUnit.3.0.1\lib\net45\nunit.framework.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {1252336d-42a3-482a-804c-836e60173dfa} + PlexRequests.Helpers + + + + + + + False + + + False + + + False + + + False + + + + + + + \ No newline at end of file diff --git a/PlexRequests.Helpers/Analytics/Action.cs b/PlexRequests.Helpers/Analytics/Action.cs index a353dcc33..4a42a45be 100644 --- a/PlexRequests.Helpers/Analytics/Action.cs +++ b/PlexRequests.Helpers/Analytics/Action.cs @@ -34,6 +34,7 @@ public enum Action Create, Save, Update, - Start + Start, + View } } \ No newline at end of file diff --git a/PlexRequests.Helpers/Analytics/Analytics.cs b/PlexRequests.Helpers/Analytics/Analytics.cs index 4b4d74ed9..764295fa3 100644 --- a/PlexRequests.Helpers/Analytics/Analytics.cs +++ b/PlexRequests.Helpers/Analytics/Analytics.cs @@ -47,52 +47,51 @@ public class Analytics : IAnalytics private static Logger Log = LogManager.GetCurrentClassLogger(); - public void TrackEvent(Category category, Action action, string label, string username, int? value = null) + public void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - Track(HitType.@event, username, cat, act, label, value); + Track(HitType.@event, username, cat, act, label, clientId, value); } - public async Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null) + public async Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - await TrackAsync(HitType.@event, username, cat, act, label, value); + await TrackAsync(HitType.@event, username, cat, act, clientId, label, value); } - public void TrackPageview(Category category, Action action, string label, string username, int? value = null) + public void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - Track(HitType.@pageview, username, cat, act, label, value); + Track(HitType.@pageview, username, cat, act, clientId, label, value); } - public async Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null) + public async Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null) { var cat = category.ToString(); var act = action.ToString(); - await TrackAsync(HitType.@pageview, username, cat, act, label, value); + await TrackAsync(HitType.@pageview, username, cat, act, clientId, label, value); } - public void TrackException(string message, string username, bool fatal) + public void TrackException(string message, string username, string clientId, bool fatal) { var fatalInt = fatal ? 1 : 0; - Track(HitType.exception, message, fatalInt, username); + Track(HitType.exception, message, fatalInt, username, clientId); } - public async Task TrackExceptionAsync(string message, string username, bool fatal) + public async Task TrackExceptionAsync(string message, string username, string clientId, bool fatal) { var fatalInt = fatal ? 1 : 0; - await TrackAsync(HitType.exception, message, fatalInt, username); + await TrackAsync(HitType.exception, message, fatalInt, username, clientId); } - private void Track(HitType type, string username, string category, string action, string label, int? value = null) + private void Track(HitType type, string username, string category, string action, string clientId, string label, int? value = null) { if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category)); if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action)); - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, category, action, label, value, null, null); + var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -101,13 +100,12 @@ private void Track(HitType type, string username, string category, string action SendRequest(postDataString); } - private async Task TrackAsync(HitType type, string username, string category, string action, string label, int? value = null) + private async Task TrackAsync(HitType type, string username, string category, string action, string clientId, string label, int? value = null) { if (string.IsNullOrEmpty(category)) throw new ArgumentNullException(nameof(category)); if (string.IsNullOrEmpty(action)) throw new ArgumentNullException(nameof(action)); - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, category, action, label, value, null, null); + var postData = BuildRequestData(type, username, category, action, clientId, label, value, null, null); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -115,12 +113,11 @@ private async Task TrackAsync(HitType type, string username, string category, st await SendRequestAsync(postDataString); } - private async Task TrackAsync(HitType type, string message, int fatal, string username) + private async Task TrackAsync(HitType type, string message, int fatal, string username, string clientId) { if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, null, null, null, null, message, fatal); + var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -129,12 +126,12 @@ private async Task TrackAsync(HitType type, string message, int fatal, string us await SendRequestAsync(postDataString); } - private void Track(HitType type, string message, int fatal, string username) + private void Track(HitType type, string message, int fatal, string username, string clientId) { if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - var postData = BuildRequestData(type, username, null, null, null, null, message, fatal); + var postData = BuildRequestData(type, username, null, null, null, clientId, null, message, fatal); var postDataString = postData .Aggregate("", (data, next) => string.Format($"{data}&{next.Key}={HttpUtility.UrlEncode(next.Value)}")) @@ -196,20 +193,24 @@ private async Task SendRequestAsync(string postDataString) } } - private Dictionary BuildRequestData(HitType type, string username, string category, string action, string label, int? value, string exceptionDescription, int? fatal) + private Dictionary BuildRequestData(HitType type, string username, string category, string action, string clientId, string label, int? value, string exceptionDescription, int? fatal) { var postData = new Dictionary { { "v", "1" }, { "tid", TrackingId }, - { "t", type.ToString() }, - {"cid", Guid.NewGuid().ToString() } + { "t", type.ToString() } }; if (!string.IsNullOrEmpty(username)) { postData.Add("uid", username); } + + postData.Add("cid", !string.IsNullOrEmpty(clientId) + ? clientId + : Guid.NewGuid().ToString()); + if (!string.IsNullOrEmpty(label)) { postData.Add("el", label); diff --git a/PlexRequests.Helpers/Analytics/IAnalytics.cs b/PlexRequests.Helpers/Analytics/IAnalytics.cs index 4e228f545..d21dd18d1 100644 --- a/PlexRequests.Helpers/Analytics/IAnalytics.cs +++ b/PlexRequests.Helpers/Analytics/IAnalytics.cs @@ -37,8 +37,9 @@ public interface IAnalytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. - void TrackEvent(Category category, Action action, string label, string username, int? value = null); + void TrackEvent(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the event asynchronous. @@ -47,9 +48,10 @@ public interface IAnalytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. /// - Task TrackEventAsync(Category category, Action action, string label, string username, int? value = null); + Task TrackEventAsync(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the page view. @@ -58,8 +60,9 @@ public interface IAnalytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. - void TrackPageview(Category category, Action action, string label, string username, int? value = null); + void TrackPageview(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the page view asynchronous. @@ -68,25 +71,28 @@ public interface IAnalytics /// The action. /// The label. /// The username. + /// The client identifier. /// The value. /// - Task TrackPageviewAsync(Category category, Action action, string label, string username, int? value = null); + Task TrackPageviewAsync(Category category, Action action, string label, string username, string clientId, int? value = null); /// /// Tracks the exception. /// /// The message. /// The username. + /// The client identifier. /// if set to true [fatal]. - void TrackException(string message, string username, bool fatal); + void TrackException(string message, string username, string clientId, bool fatal); /// /// Tracks the exception asynchronous. /// /// The message. /// The username. + /// The client identifier. /// if set to true [fatal]. /// - Task TrackExceptionAsync(string message, string username, bool fatal); + Task TrackExceptionAsync(string message, string username, string clientId, bool fatal); } } \ No newline at end of file diff --git a/PlexRequests.Helpers/CookieHelper.cs b/PlexRequests.Helpers/CookieHelper.cs new file mode 100644 index 000000000..5d4b97a60 --- /dev/null +++ b/PlexRequests.Helpers/CookieHelper.cs @@ -0,0 +1,57 @@ +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: CookieHelper.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Generic; + +namespace PlexRequests.Helpers +{ + public static class CookieHelper + { + private const string GaCookie = "_ga"; + + /// + /// Gets the analytic client identifier. + /// Example: Value = "GA1.1.306549087.1464005217" + /// + /// The cookies. + /// + public static string GetAnalyticClientId(IDictionary cookies) + { + var outString = string.Empty; + + if (cookies.TryGetValue(GaCookie, out outString)) + { + var split = outString.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + + return split.Length < 4 + ? string.Empty + : $"{split[2]}.{split[3]}"; + } + return string.Empty; + } + } +} \ No newline at end of file diff --git a/PlexRequests.Helpers/PlexRequests.Helpers.csproj b/PlexRequests.Helpers/PlexRequests.Helpers.csproj index 8bdfb5ed8..df0ffcf77 100644 --- a/PlexRequests.Helpers/PlexRequests.Helpers.csproj +++ b/PlexRequests.Helpers/PlexRequests.Helpers.csproj @@ -1,102 +1,103 @@ - - - - - Debug - AnyCPU - {1252336D-42A3-482A-804C-836E60173DFA} - Library - Properties - PlexRequests.Helpers - PlexRequests.Helpers - v4.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\Hangfire.Core.1.5.7\lib\net45\Hangfire.Core.dll - True - - - ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll - True - - - ..\packages\NLog.4.3.4\lib\net45\NLog.dll - True - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - True - - - - - - - - - - - - - ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + AnyCPU + {1252336D-42A3-482A-804C-836E60173DFA} + Library + Properties + PlexRequests.Helpers + PlexRequests.Helpers + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Hangfire.Core.1.5.7\lib\net45\Hangfire.Core.dll + True + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + True + + + ..\packages\NLog.4.3.4\lib\net45\NLog.dll + True + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PlexRequests.UI.Tests/AdminModuleTests.cs b/PlexRequests.UI.Tests/AdminModuleTests.cs index 66245d649..198be036f 100644 --- a/PlexRequests.UI.Tests/AdminModuleTests.cs +++ b/PlexRequests.UI.Tests/AdminModuleTests.cs @@ -1,361 +1,365 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UserLoginModuleTests.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System.Collections.Generic; -using System.Linq; - -using Moq; - -using Nancy; -using Nancy.Testing; - -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -using NUnit.Framework; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Services.Interfaces; -using PlexRequests.Store.Models; -using PlexRequests.Store.Repository; -using PlexRequests.UI.Models; -using PlexRequests.UI.Modules; -using PlexRequests.Helpers; -using PlexRequests.UI.Helpers; - -namespace PlexRequests.UI.Tests -{ - [TestFixture] - public class AdminModuleTests - { - private Mock> PlexRequestMock { get; set; } - private Mock> CpMock { get; set; } - private Mock> AuthMock { get; set; } - private Mock> PlexSettingsMock { get; set; } - private Mock> SonarrSettingsMock { get; set; } - private Mock> SickRageSettingsMock { get; set; } - private Mock> ScheduledJobsSettingsMock { get; set; } - private Mock> EmailMock { get; set; } - private Mock> PushbulletSettings { get; set; } - private Mock> PushoverSettings { get; set; } - private Mock> HeadphonesSettings { get; set; } - private Mock PlexMock { get; set; } - private Mock SonarrApiMock { get; set; } - private Mock PushbulletApi { get; set; } - private Mock PushoverApi { get; set; } - private Mock CpApi { get; set; } - private Mock RecorderMock { get; set; } - private Mock> LogRepo { get; set; } - private Mock NotificationService { get; set; } - private Mock Cache { get; set; } - private Mock> Log { get; set; } - private Mock> SlackSettings { get; set; } - private Mock> LandingPageSettings { get; set; } - private Mock SlackApi { get; set; } - - private ConfigurableBootstrapper Bootstrapper { get; set; } - - [SetUp] - public void Setup() - { - AuthMock = new Mock>(); - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - PlexMock = new Mock(); - PlexMock.Setup(x => x.SignIn("Username1", "Password1")) - .Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } }); - - PlexRequestMock = new Mock>(); - PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); - CpMock = new Mock>(); - PlexSettingsMock = new Mock>(); - SonarrApiMock = new Mock(); - SonarrSettingsMock = new Mock>(); - EmailMock = new Mock>(); - PushbulletApi = new Mock(); - PushbulletSettings = new Mock>(); - CpApi = new Mock(); - SickRageSettingsMock = new Mock>(); - LogRepo = new Mock>(); - PushoverSettings = new Mock>(); - PushoverApi = new Mock(); - NotificationService = new Mock(); - HeadphonesSettings = new Mock>(); - Cache = new Mock(); - Log = new Mock>(); - SlackApi = new Mock(); - SlackSettings = new Mock>(); - LandingPageSettings = new Mock>(); - ScheduledJobsSettingsMock = new Mock>(); - RecorderMock = new Mock(); - - - Bootstrapper = new ConfigurableBootstrapper(with => - { - with.Module(); - with.Dependency(AuthMock.Object); - with.Dependency(PlexRequestMock.Object); - with.Dependency(CpMock.Object); - with.Dependency(PlexSettingsMock.Object); - with.Dependency(SonarrApiMock.Object); - with.Dependency(SonarrSettingsMock.Object); - with.Dependency(PlexMock.Object); - with.Dependency(EmailMock.Object); - with.Dependency(PushbulletApi.Object); - with.Dependency(PushbulletSettings.Object); - with.Dependency(CpApi.Object); - with.Dependency(SickRageSettingsMock.Object); - with.Dependency(LogRepo.Object); - with.Dependency(PushoverSettings.Object); - with.Dependency(PushoverApi.Object); - with.Dependency(NotificationService.Object); - with.Dependency(HeadphonesSettings.Object); - with.Dependency(Cache.Object); - with.Dependency(Log.Object); - with.Dependency(SlackApi.Object); - with.Dependency(LandingPageSettings.Object); - with.Dependency(SlackSettings.Object); - with.Dependency(ScheduledJobsSettingsMock.Object); - with.Dependency(RecorderMock.Object); - with.RootPathProvider(); - with.RequestStartup((container, pipelines, context) => - { - context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List {"Admin"} }; - }); - }); - - Bootstrapper.WithSession(new Dictionary()); - } - - [Test] - public void RequestAuthTokenTestNewSettings() - { - var browser = new Browser(Bootstrapper); - - var result = browser.Post("/admin/requestauth", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Username1"); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Once); - } - - [Test] - public void RequestAuthTokenTestEmptyCredentials() - { - var browser = new Browser(Bootstrapper); - - var result = browser.Post("/admin/requestauth", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", string.Empty); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - - PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Never); - AuthMock.Verify(x => x.GetSettings(), Times.Never); - AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Never); - } - - [Test] - public void RequestAuthTokenTesPlexSignInFail() - { - var browser = new Browser(Bootstrapper); - - var result = browser.Post("/admin/requestauth", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Badusername"); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - - PlexMock.Verify(x => x.SignIn("Badusername", "Password1"), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Never); - AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Never); - } - - [Test] - public void RequestAuthTokenTestExistingSettings() - { - AuthMock.Setup(x => x.GetSettings()).Returns(() => null); - var browser = new Browser(Bootstrapper); - - var result = browser.Post("/admin/requestauth", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Username1"); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - - PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Once); - } - - [Test] - public void GetUsersSuccessfully() - { - var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } }; - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(users); - var browser = new Browser(Bootstrapper); - - var result = browser.Get("/admin/getusers", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Username1"); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - var user = body["users"]; - Assert.That(body, Is.Not.Null); - Assert.That(user.ToString().Contains("abc"), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - - [Test] - public void GetUsersReturnsNoUsers() - { - var users = new PlexFriends(); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(users); - var browser = new Browser(Bootstrapper); - - var result = browser.Get("/admin/getusers", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Username1"); - with.FormValue("password", "Password1"); - - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body, Is.Not.Null); - Assert.That(string.IsNullOrWhiteSpace(body), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - - [Test] - public void GetUsersReturnsNull() - { - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(() => null); - var browser = new Browser(Bootstrapper); - - var result = browser.Get("/admin/getusers", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Username1"); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body, Is.Not.Null); - Assert.That(string.IsNullOrWhiteSpace(body), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - - [Test] - public void GetUsersTokenIsNull() - { - AuthMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings()); - var browser = new Browser(Bootstrapper); - - var result = browser.Get("/admin/getusers", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("username", "Username1"); - with.FormValue("password", "Password1"); - - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - var user = (string)body["users"]; - Assert.That(body, Is.Not.Null); - Assert.That(string.IsNullOrWhiteSpace(user), Is.True); - - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserLoginModuleTests.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System.Collections.Generic; +using System.Linq; + +using Moq; + +using Nancy; +using Nancy.Testing; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using NUnit.Framework; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Services.Interfaces; +using PlexRequests.Store.Models; +using PlexRequests.Store.Repository; +using PlexRequests.UI.Models; +using PlexRequests.UI.Modules; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; +using PlexRequests.UI.Helpers; + +namespace PlexRequests.UI.Tests +{ + [TestFixture] + public class AdminModuleTests + { + private Mock> PlexRequestMock { get; set; } + private Mock> CpMock { get; set; } + private Mock> AuthMock { get; set; } + private Mock> PlexSettingsMock { get; set; } + private Mock> SonarrSettingsMock { get; set; } + private Mock> SickRageSettingsMock { get; set; } + private Mock> ScheduledJobsSettingsMock { get; set; } + private Mock> EmailMock { get; set; } + private Mock> PushbulletSettings { get; set; } + private Mock> PushoverSettings { get; set; } + private Mock> HeadphonesSettings { get; set; } + private Mock PlexMock { get; set; } + private Mock SonarrApiMock { get; set; } + private Mock PushbulletApi { get; set; } + private Mock PushoverApi { get; set; } + private Mock CpApi { get; set; } + private Mock RecorderMock { get; set; } + private Mock> LogRepo { get; set; } + private Mock NotificationService { get; set; } + private Mock Cache { get; set; } + private Mock> Log { get; set; } + private Mock> SlackSettings { get; set; } + private Mock> LandingPageSettings { get; set; } + private Mock SlackApi { get; set; } + private Mock IAnalytics { get; set; } + + private ConfigurableBootstrapper Bootstrapper { get; set; } + + [SetUp] + public void Setup() + { + AuthMock = new Mock>(); + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + PlexMock = new Mock(); + PlexMock.Setup(x => x.SignIn("Username1", "Password1")) + .Returns(new PlexAuthentication { user = new User { authentication_token = "abc", title = "Username1" } }); + + PlexRequestMock = new Mock>(); + PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); + CpMock = new Mock>(); + PlexSettingsMock = new Mock>(); + SonarrApiMock = new Mock(); + SonarrSettingsMock = new Mock>(); + EmailMock = new Mock>(); + PushbulletApi = new Mock(); + PushbulletSettings = new Mock>(); + CpApi = new Mock(); + SickRageSettingsMock = new Mock>(); + LogRepo = new Mock>(); + PushoverSettings = new Mock>(); + PushoverApi = new Mock(); + NotificationService = new Mock(); + HeadphonesSettings = new Mock>(); + Cache = new Mock(); + Log = new Mock>(); + SlackApi = new Mock(); + SlackSettings = new Mock>(); + LandingPageSettings = new Mock>(); + ScheduledJobsSettingsMock = new Mock>(); + RecorderMock = new Mock(); + IAnalytics = new Mock(); + + + Bootstrapper = new ConfigurableBootstrapper(with => + { + with.Module(); + with.Dependency(AuthMock.Object); + with.Dependency(PlexRequestMock.Object); + with.Dependency(CpMock.Object); + with.Dependency(PlexSettingsMock.Object); + with.Dependency(SonarrApiMock.Object); + with.Dependency(SonarrSettingsMock.Object); + with.Dependency(PlexMock.Object); + with.Dependency(EmailMock.Object); + with.Dependency(PushbulletApi.Object); + with.Dependency(PushbulletSettings.Object); + with.Dependency(CpApi.Object); + with.Dependency(SickRageSettingsMock.Object); + with.Dependency(LogRepo.Object); + with.Dependency(PushoverSettings.Object); + with.Dependency(PushoverApi.Object); + with.Dependency(NotificationService.Object); + with.Dependency(IAnalytics.Object); + with.Dependency(HeadphonesSettings.Object); + with.Dependency(Cache.Object); + with.Dependency(Log.Object); + with.Dependency(SlackApi.Object); + with.Dependency(LandingPageSettings.Object); + with.Dependency(SlackSettings.Object); + with.Dependency(ScheduledJobsSettingsMock.Object); + with.Dependency(RecorderMock.Object); + with.RootPathProvider(); + with.RequestStartup((container, pipelines, context) => + { + context.CurrentUser = new UserIdentity { UserName = "user", Claims = new List {"Admin"} }; + }); + }); + + Bootstrapper.WithSession(new Dictionary()); + } + + [Test] + public void RequestAuthTokenTestNewSettings() + { + var browser = new Browser(Bootstrapper); + + var result = browser.Post("/admin/requestauth", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Username1"); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Once); + } + + [Test] + public void RequestAuthTokenTestEmptyCredentials() + { + var browser = new Browser(Bootstrapper); + + var result = browser.Post("/admin/requestauth", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", string.Empty); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + + PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Never); + AuthMock.Verify(x => x.GetSettings(), Times.Never); + AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Never); + } + + [Test] + public void RequestAuthTokenTesPlexSignInFail() + { + var browser = new Browser(Bootstrapper); + + var result = browser.Post("/admin/requestauth", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Badusername"); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + + PlexMock.Verify(x => x.SignIn("Badusername", "Password1"), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Never); + AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Never); + } + + [Test] + public void RequestAuthTokenTestExistingSettings() + { + AuthMock.Setup(x => x.GetSettings()).Returns(() => null); + var browser = new Browser(Bootstrapper); + + var result = browser.Post("/admin/requestauth", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Username1"); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + + PlexMock.Verify(x => x.SignIn("Username1", "Password1"), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + AuthMock.Verify(x => x.SaveSettings(It.IsAny()), Times.Once); + } + + [Test] + public void GetUsersSuccessfully() + { + var users = new PlexFriends { User = new[] { new UserFriends { Title = "abc2" }, } }; + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(users); + var browser = new Browser(Bootstrapper); + + var result = browser.Get("/admin/getusers", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Username1"); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + var user = body["users"]; + Assert.That(body, Is.Not.Null); + Assert.That(user.ToString().Contains("abc"), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + + [Test] + public void GetUsersReturnsNoUsers() + { + var users = new PlexFriends(); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(users); + var browser = new Browser(Bootstrapper); + + var result = browser.Get("/admin/getusers", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Username1"); + with.FormValue("password", "Password1"); + + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body, Is.Not.Null); + Assert.That(string.IsNullOrWhiteSpace(body), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + + [Test] + public void GetUsersReturnsNull() + { + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(() => null); + var browser = new Browser(Bootstrapper); + + var result = browser.Get("/admin/getusers", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Username1"); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body, Is.Not.Null); + Assert.That(string.IsNullOrWhiteSpace(body), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + + [Test] + public void GetUsersTokenIsNull() + { + AuthMock.Setup(x => x.GetSettings()).Returns(new AuthenticationSettings()); + var browser = new Browser(Bootstrapper); + + var result = browser.Get("/admin/getusers", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("username", "Username1"); + with.FormValue("password", "Password1"); + + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + var user = (string)body["users"]; + Assert.That(body, Is.Not.Null); + Assert.That(string.IsNullOrWhiteSpace(user), Is.True); + + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI.Tests/UserLoginModuleTests.cs b/PlexRequests.UI.Tests/UserLoginModuleTests.cs index 22d000575..c962fa335 100644 --- a/PlexRequests.UI.Tests/UserLoginModuleTests.cs +++ b/PlexRequests.UI.Tests/UserLoginModuleTests.cs @@ -1,437 +1,441 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UserLoginModuleTests.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System.Collections.Generic; -using System.Threading.Tasks; - -using Moq; - -using Nancy; -using Nancy.Testing; - -using Newtonsoft.Json; - -using NUnit.Framework; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.UI.Models; -using PlexRequests.UI.Modules; - -namespace PlexRequests.UI.Tests -{ - [TestFixture] - public class UserLoginModuleTests - { - private Mock> AuthMock { get; set; } - private Mock> PlexRequestMock { get; set; } - private Mock> LandingPageMock { get; set; } - private ConfigurableBootstrapper Bootstrapper { get; set; } - private Mock PlexMock { get; set; } - - [SetUp] - public void Setup() - { - AuthMock = new Mock>(); - PlexMock = new Mock(); - LandingPageMock = new Mock>(); - PlexRequestMock = new Mock>(); - PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); - PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings())); - LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings()); - Bootstrapper = new ConfigurableBootstrapper(with => - { - with.Module(); - with.Dependency(PlexRequestMock.Object); - with.Dependency(AuthMock.Object); - with.Dependency(PlexMock.Object); - with.Dependency(LandingPageMock.Object); - with.RootPathProvider(); - }); - } - - [Test] - public void LoginWithoutAuthentication() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void LoginWithoutAuthenticationWithEmptyUsername() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", string.Empty); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - AuthMock.Verify(x => x.GetSettings(), Times.Never); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void LoginWithUsernameSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Title = "abc", - }, - } - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithUsernameUnSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Username = "aaaa", - }, - } - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithUsernameAndPasswordSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Title = "abc", - } - } - }; - var plexAuth = new PlexAuthentication - { - user = new User - { - authentication_token = "abc" - } - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - with.FormValue("Password", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithUsernameAndPasswordUnSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends - { - Username = "abc", - }, - } - }; - var plexAuth = new PlexAuthentication - { - user = null - }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); - - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - with.FormValue("Password", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void AttemptToLoginAsDeniedUser() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "abc"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(false)); - Assert.That(body.Message, Is.Not.Empty); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - - [Test] - public void Logout() - { - Bootstrapper.WithSession(new Dictionary { { SessionKeys.UsernameKey, "abc" } }); - - var browser = new Browser(Bootstrapper); - var result = browser.Get("/userlogin/logout", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - }); - - Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); - } - - [Test] - public void LoginWithOwnerUsernameSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends() - } - }; - - var account = new PlexAccount { Username = "Jamie" }; - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } }); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "Jamie"); - }); - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); - } - - [Test] - public void LoginWithOwnerUsernameAndPasswordSuccessfully() - { - var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; - var plexFriends = new PlexFriends - { - User = new[] - { - new UserFriends() - } - }; - var plexAuth = new PlexAuthentication - { - user = new User - { - authentication_token = "abc", - username = "Jamie" - } - }; - - var account = new PlexAccount { Username = "Jamie" }; - - AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); - PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); - PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); - PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); - - Bootstrapper.WithSession(new Dictionary()); - - var browser = new Browser(Bootstrapper); - var result = browser.Post("/userlogin", with => - { - with.HttpRequest(); - with.Header("Accept", "application/json"); - with.FormValue("Username", "jamie"); - with.FormValue("Password", "abc"); - }); - - - Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); - Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie")); - - var body = JsonConvert.DeserializeObject(result.Body.AsString()); - Assert.That(body.Result, Is.EqualTo(true)); - AuthMock.Verify(x => x.GetSettings(), Times.Once); - PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); - PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserLoginModuleTests.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System.Collections.Generic; +using System.Threading.Tasks; + +using Moq; + +using Nancy; +using Nancy.Testing; + +using Newtonsoft.Json; + +using NUnit.Framework; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers.Analytics; +using PlexRequests.UI.Models; +using PlexRequests.UI.Modules; + +namespace PlexRequests.UI.Tests +{ + [TestFixture] + public class UserLoginModuleTests + { + private Mock> AuthMock { get; set; } + private Mock> PlexRequestMock { get; set; } + private Mock> LandingPageMock { get; set; } + private ConfigurableBootstrapper Bootstrapper { get; set; } + private Mock PlexMock { get; set; } + private Mock IAnalytics { get; set; } + + [SetUp] + public void Setup() + { + AuthMock = new Mock>(); + PlexMock = new Mock(); + LandingPageMock = new Mock>(); + PlexRequestMock = new Mock>(); + PlexRequestMock.Setup(x => x.GetSettings()).Returns(new PlexRequestSettings()); + PlexRequestMock.Setup(x => x.GetSettingsAsync()).Returns(Task.FromResult(new PlexRequestSettings())); + LandingPageMock.Setup(x => x.GetSettings()).Returns(new LandingPageSettings()); + IAnalytics = new Mock(); + Bootstrapper = new ConfigurableBootstrapper(with => + { + with.Module(); + with.Dependency(PlexRequestMock.Object); + with.Dependency(AuthMock.Object); + with.Dependency(PlexMock.Object); + with.Dependency(LandingPageMock.Object); + with.Dependency(IAnalytics.Object); + with.RootPathProvider(); + }); + } + + [Test] + public void LoginWithoutAuthentication() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void LoginWithoutAuthenticationWithEmptyUsername() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", string.Empty); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + AuthMock.Verify(x => x.GetSettings(), Times.Never); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void LoginWithUsernameSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Title = "abc", + }, + } + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithUsernameUnSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Username = "aaaa", + }, + } + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithUsernameAndPasswordSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Title = "abc", + } + } + }; + var plexAuth = new PlexAuthentication + { + user = new User + { + authentication_token = "abc" + } + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(new PlexAccount()); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + with.FormValue("Password", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("abc")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithUsernameAndPasswordUnSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends + { + Username = "abc", + }, + } + }; + var plexAuth = new PlexAuthentication + { + user = null + }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); + + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + with.FormValue("Password", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void AttemptToLoginAsDeniedUser() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = false, DeniedUsers = "abc", PlexAuthToken = "abc" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "abc"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(false)); + Assert.That(body.Message, Is.Not.Empty); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + + [Test] + public void Logout() + { + Bootstrapper.WithSession(new Dictionary { { SessionKeys.UsernameKey, "abc" } }); + + var browser = new Browser(Bootstrapper); + var result = browser.Get("/userlogin/logout", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + }); + + Assert.That(HttpStatusCode.SeeOther, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.Null); + } + + [Test] + public void LoginWithOwnerUsernameSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends() + } + }; + + var account = new PlexAccount { Username = "Jamie" }; + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(new PlexAuthentication { user = new User { username = "Jamie" } }); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "Jamie"); + }); + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("Jamie")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Never); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Once); + } + + [Test] + public void LoginWithOwnerUsernameAndPasswordSuccessfully() + { + var expectedSettings = new AuthenticationSettings { UserAuthentication = true, UsePassword = true, PlexAuthToken = "abc" }; + var plexFriends = new PlexFriends + { + User = new[] + { + new UserFriends() + } + }; + var plexAuth = new PlexAuthentication + { + user = new User + { + authentication_token = "abc", + username = "Jamie" + } + }; + + var account = new PlexAccount { Username = "Jamie" }; + + AuthMock.Setup(x => x.GetSettings()).Returns(expectedSettings); + PlexMock.Setup(x => x.GetUsers(It.IsAny())).Returns(plexFriends); + PlexMock.Setup(x => x.SignIn(It.IsAny(), It.IsAny())).Returns(plexAuth); + PlexMock.Setup(x => x.GetAccount(It.IsAny())).Returns(account); + + Bootstrapper.WithSession(new Dictionary()); + + var browser = new Browser(Bootstrapper); + var result = browser.Post("/userlogin", with => + { + with.HttpRequest(); + with.Header("Accept", "application/json"); + with.FormValue("Username", "jamie"); + with.FormValue("Password", "abc"); + }); + + + Assert.That(HttpStatusCode.OK, Is.EqualTo(result.StatusCode)); + Assert.That(result.Context.Request.Session[SessionKeys.UsernameKey], Is.EqualTo("jamie")); + + var body = JsonConvert.DeserializeObject(result.Body.AsString()); + Assert.That(body.Result, Is.EqualTo(true)); + AuthMock.Verify(x => x.GetSettings(), Times.Once); + PlexMock.Verify(x => x.SignIn(It.IsAny(), It.IsAny()), Times.Once); + PlexMock.Verify(x => x.GetUsers(It.IsAny()), Times.Never); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index c22734b9f..5b8f0a103 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -32,7 +32,6 @@ using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; -using System.Dynamic; using System.Linq; using System.Net; @@ -52,6 +51,7 @@ using PlexRequests.Core; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; using PlexRequests.Helpers.Exceptions; using PlexRequests.Services.Interfaces; using PlexRequests.Services.Notification; @@ -60,6 +60,7 @@ using PlexRequests.UI.Helpers; using PlexRequests.UI.Models; +using Action = PlexRequests.Helpers.Analytics.Action; namespace PlexRequests.UI.Modules { @@ -89,6 +90,7 @@ public class AdminModule : BaseModule private ISettingsService ScheduledJobSettings { get; } private ISlackApi SlackApi { get; } private IJobRecord JobRecorder { get; } + private IAnalytics Analytics { get; } private static Logger Log = LogManager.GetCurrentClassLogger(); public AdminModule(ISettingsService prService, @@ -111,7 +113,7 @@ public AdminModule(ISettingsService prService, ISettingsService logs, ICacheProvider cache, ISettingsService slackSettings, ISlackApi slackApi, ISettingsService lp, - ISettingsService scheduler, IJobRecord rec) : base("admin", prService) + ISettingsService scheduler, IJobRecord rec, IAnalytics analytics) : base("admin", prService) { PrService = prService; CpService = cpService; @@ -137,6 +139,7 @@ public AdminModule(ISettingsService prService, LandingSettings = lp; ScheduledJobSettings = scheduler; JobRecorder = rec; + Analytics = analytics; this.RequiresClaims(UserClaims.Admin); @@ -237,7 +240,7 @@ private Negotiator Admin() return View["Settings", settings]; } - private Response SaveAdmin() + private async Task SaveAdmin() { var model = this.Bind(); var valid = this.Validate(model); @@ -254,6 +257,8 @@ private Response SaveAdmin() } } var result = PrService.SaveSettings(model); + + await Analytics.TrackEventAsync(Category.Admin, Action.Save, "PlexRequestSettings", Username, CookieHelper.GetAnalyticClientId(Cookies)); return Response.AsJson(result ? new JsonResponseModel { Result = true } : new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" }); diff --git a/PlexRequests.UI/Modules/BaseModule.cs b/PlexRequests.UI/Modules/BaseModule.cs index 2ebce8fa7..14b24141e 100644 --- a/PlexRequests.UI/Modules/BaseModule.cs +++ b/PlexRequests.UI/Modules/BaseModule.cs @@ -1,68 +1,69 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: BaseModule.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion -using System; -using System.Linq; - -using Nancy; - -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.Helpers; -using PlexRequests.UI.Models; - -namespace PlexRequests.UI.Modules -{ - public abstract class BaseModule : NancyModule - { - protected string BaseUrl { get; set; } - - protected BaseModule(ISettingsService settingsService) - { - var settings = settingsService.GetSettings(); - var baseUrl = settings.BaseUrl; - BaseUrl = baseUrl; - - var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl; - - ModulePath = modulePath; - } - - protected BaseModule(string modulePath, ISettingsService settingsService) - { - var settings = settingsService.GetSettings(); - var baseUrl = settings.BaseUrl; - BaseUrl = baseUrl; - - var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}"; - - ModulePath = settingModulePath; - } - - private int _dateTimeOffset = -1; +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: BaseModule.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion +using System; +using System.Collections.Generic; +using System.Linq; + +using Nancy; + +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.UI.Models; + +namespace PlexRequests.UI.Modules +{ + public abstract class BaseModule : NancyModule + { + protected string BaseUrl { get; set; } + + protected BaseModule(ISettingsService settingsService) + { + var settings = settingsService.GetSettings(); + var baseUrl = settings.BaseUrl; + BaseUrl = baseUrl; + + var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl; + + ModulePath = modulePath; + } + + protected BaseModule(string modulePath, ISettingsService settingsService) + { + var settings = settingsService.GetSettings(); + var baseUrl = settings.BaseUrl; + BaseUrl = baseUrl; + + var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}"; + + ModulePath = settingModulePath; + } + + private int _dateTimeOffset = -1; protected int DateTimeOffset { get @@ -73,7 +74,7 @@ protected int DateTimeOffset } return _dateTimeOffset; } - } + } private string _username; protected string Username @@ -82,12 +83,21 @@ protected string Username { if (string.IsNullOrEmpty(_username)) { - _username = Session[SessionKeys.UsernameKey].ToString(); + try + { + _username = Session[SessionKeys.UsernameKey].ToString(); + } + catch (Exception) + { + return string.Empty; + } } return _username; } } + protected IDictionary Cookies => Request.Cookies; + protected bool IsAdmin { get @@ -100,6 +110,6 @@ protected bool IsAdmin return claims.Contains(UserClaims.Admin) || claims.Contains(UserClaims.PowerUser); } } - - } + + } } \ No newline at end of file diff --git a/PlexRequests.UI/Modules/UserLoginModule.cs b/PlexRequests.UI/Modules/UserLoginModule.cs index cfa0c03a9..2cb97ff11 100644 --- a/PlexRequests.UI/Modules/UserLoginModule.cs +++ b/PlexRequests.UI/Modules/UserLoginModule.cs @@ -1,220 +1,235 @@ -#region Copyright -// /************************************************************************ -// Copyright (c) 2016 Jamie Rees -// File: UserLoginModule.cs -// Created By: Jamie Rees -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// ************************************************************************/ -#endregion - -using System; -using System.Linq; -using System.Threading.Tasks; - -using Nancy; -using Nancy.Extensions; -using Nancy.Responses.Negotiation; - -using NLog; - -using PlexRequests.Api.Interfaces; -using PlexRequests.Api.Models.Plex; -using PlexRequests.Core; -using PlexRequests.Core.SettingModels; -using PlexRequests.UI.Models; - -namespace PlexRequests.UI.Modules -{ - public class UserLoginModule : BaseModule - { - public UserLoginModule(ISettingsService auth, IPlexApi api, ISettingsService pr, ISettingsService lp) : base("userlogin", pr) - { - AuthService = auth; - LandingPageSettings = lp; - Api = api; - Get["/", true] = async (x, ct) => await Index(); - Post["/"] = x => LoginUser(); - Get["/logout"] = x => Logout(); - } - - private ISettingsService AuthService { get; } - private ISettingsService LandingPageSettings { get; } - private IPlexApi Api { get; } - - private static Logger Log = LogManager.GetCurrentClassLogger(); - - public async Task Index() - { - var query = Request.Query["landing"]; - var landingCheck = (bool?)query ?? true; - if (landingCheck) - { - var landingSettings = await LandingPageSettings.GetSettingsAsync(); - - if (landingSettings.Enabled) - { - if (landingSettings.BeforeLogin) - { - var model = new LandingPageViewModel - { - Enabled = landingSettings.Enabled, - Id = landingSettings.Id, - EnabledNoticeTime = landingSettings.EnabledNoticeTime, - NoticeEnable = landingSettings.NoticeEnable, - NoticeEnd = landingSettings.NoticeEnd, - NoticeMessage = landingSettings.NoticeMessage, - NoticeStart = landingSettings.NoticeStart, - ContinueUrl = landingSettings.BeforeLogin ? $"userlogin" : $"search" - }; - - return View["Landing/Index", model]; - } - } - } - var settings = await AuthService.GetSettingsAsync(); - return View["Index", settings]; - } - - private Response LoginUser() - { - var dateTimeOffset = Request.Form.DateTimeOffset; - var username = Request.Form.username.Value; - Log.Debug("Username \"{0}\" attempting to login", username); - if (string.IsNullOrWhiteSpace(username)) - { - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); - } - - var authenticated = false; - - var settings = AuthService.GetSettings(); - - if (IsUserInDeniedList(username, settings)) - { - Log.Debug("User is in denied list, not allowing them to authenticate"); - return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); - } - - var password = string.Empty; - if (settings.UsePassword) - { - Log.Debug("Using password"); - password = Request.Form.password.Value; - } - - - if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex - { - Log.Debug("Need to auth and also provide pass"); - var signedIn = (PlexAuthentication)Api.SignIn(username, password); - if (signedIn.user?.authentication_token != null) - { - Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); - if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username)) - { - Log.Debug("User is the account owner"); - authenticated = true; - } - else - { - authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); - Log.Debug("Friends list result = {0}", authenticated); - } - } - } - else if (settings.UserAuthentication) // Check against the users in Plex - { - Log.Debug("Need to auth"); - authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); - if (CheckIfUserIsOwner(settings.PlexAuthToken, username)) - { - Log.Debug("User is the account owner"); - authenticated = true; - } - Log.Debug("Friends list result = {0}", authenticated); - } - else if (!settings.UserAuthentication) // No auth, let them pass! - { - Log.Debug("No need to auth"); - authenticated = true; - } - - if (authenticated) - { - Log.Debug("We are authenticated! Setting session."); - // Add to the session (Used in the BaseModules) - Session[SessionKeys.UsernameKey] = (string)username; - } - - Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset; - - if (!authenticated) - { - return Response.AsJson(new JsonResponseModel {Result = false, Message = "Incorrect User or Password"}); - } - - var landingSettings = LandingPageSettings.GetSettings(); - - if (landingSettings.Enabled) - { - if (!landingSettings.BeforeLogin) - return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" }); - } - return Response.AsJson(new JsonResponseModel {Result = true, Message = "search" }); - } - - - - private Response Logout() - { - Log.Debug("Logging Out"); - if (Session[SessionKeys.UsernameKey] != null) - { - Session.Delete(SessionKeys.UsernameKey); - } - return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) - ? $"~/{BaseUrl}/userlogin" - : "~/userlogin"); - } - - private bool CheckIfUserIsOwner(string authToken, string userName) - { - var userAccount = Api.GetAccount(authToken); - if (userAccount == null) - { - return false; - } - return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase); - } - - private bool CheckIfUserIsInPlexFriends(string username, string authToken) - { - var users = Api.GetUsers(authToken); - var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title)); - return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)); - } - - private bool IsUserInDeniedList(string username, AuthenticationSettings settings) - { - return settings.DeniedUserList.Any(x => x.Equals(username)); - } - } +#region Copyright +// /************************************************************************ +// Copyright (c) 2016 Jamie Rees +// File: UserLoginModule.cs +// Created By: Jamie Rees +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// ************************************************************************/ +#endregion + +using System; +using System.Linq; +using System.Threading.Tasks; + +using Nancy; +using Nancy.Extensions; +using Nancy.Responses.Negotiation; + +using NLog; + +using PlexRequests.Api.Interfaces; +using PlexRequests.Api.Models.Plex; +using PlexRequests.Core; +using PlexRequests.Core.SettingModels; +using PlexRequests.Helpers; +using PlexRequests.Helpers.Analytics; +using PlexRequests.UI.Models; + +using Action = PlexRequests.Helpers.Analytics.Action; + +namespace PlexRequests.UI.Modules +{ + public class UserLoginModule : BaseModule + { + public UserLoginModule(ISettingsService auth, IPlexApi api, ISettingsService pr, ISettingsService lp, IAnalytics a) : base("userlogin", pr) + { + AuthService = auth; + LandingPageSettings = lp; + Analytics = a; + Api = api; + Get["/", true] = async (x, ct) => await Index(); + Post["/"] = x => LoginUser(); + Get["/logout"] = x => Logout(); + } + + private ISettingsService AuthService { get; } + private ISettingsService LandingPageSettings { get; } + private IPlexApi Api { get; } + private IAnalytics Analytics { get; } + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + public async Task Index() + { + var query = Request.Query["landing"]; + var landingCheck = (bool?)query ?? true; + if (landingCheck) + { + var landingSettings = await LandingPageSettings.GetSettingsAsync(); + + if (landingSettings.Enabled) + { + + if (landingSettings.BeforeLogin) + { + await + Analytics.TrackEventAsync( + Category.LandingPage, + Action.View, + "Going To LandingPage before login", + Username, + CookieHelper.GetAnalyticClientId(Cookies)); + + var model = new LandingPageViewModel + { + Enabled = landingSettings.Enabled, + Id = landingSettings.Id, + EnabledNoticeTime = landingSettings.EnabledNoticeTime, + NoticeEnable = landingSettings.NoticeEnable, + NoticeEnd = landingSettings.NoticeEnd, + NoticeMessage = landingSettings.NoticeMessage, + NoticeStart = landingSettings.NoticeStart, + ContinueUrl = landingSettings.BeforeLogin ? $"userlogin" : $"search" + }; + + return View["Landing/Index", model]; + } + } + } + var settings = await AuthService.GetSettingsAsync(); + return View["Index", settings]; + } + + private Response LoginUser() + { + var dateTimeOffset = Request.Form.DateTimeOffset; + var username = Request.Form.username.Value; + Log.Debug("Username \"{0}\" attempting to login", username); + if (string.IsNullOrWhiteSpace(username)) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); + } + + var authenticated = false; + + var settings = AuthService.GetSettings(); + + if (IsUserInDeniedList(username, settings)) + { + Log.Debug("User is in denied list, not allowing them to authenticate"); + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); + } + + var password = string.Empty; + if (settings.UsePassword) + { + Log.Debug("Using password"); + password = Request.Form.password.Value; + } + + + if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex + { + Log.Debug("Need to auth and also provide pass"); + var signedIn = (PlexAuthentication)Api.SignIn(username, password); + if (signedIn.user?.authentication_token != null) + { + Log.Debug("Correct credentials, checking if the user is account owner or in the friends list"); + if (CheckIfUserIsOwner(settings.PlexAuthToken, signedIn.user?.username)) + { + Log.Debug("User is the account owner"); + authenticated = true; + } + else + { + authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); + Log.Debug("Friends list result = {0}", authenticated); + } + } + } + else if (settings.UserAuthentication) // Check against the users in Plex + { + Log.Debug("Need to auth"); + authenticated = CheckIfUserIsInPlexFriends(username, settings.PlexAuthToken); + if (CheckIfUserIsOwner(settings.PlexAuthToken, username)) + { + Log.Debug("User is the account owner"); + authenticated = true; + } + Log.Debug("Friends list result = {0}", authenticated); + } + else if (!settings.UserAuthentication) // No auth, let them pass! + { + Log.Debug("No need to auth"); + authenticated = true; + } + + if (authenticated) + { + Log.Debug("We are authenticated! Setting session."); + // Add to the session (Used in the BaseModules) + Session[SessionKeys.UsernameKey] = (string)username; + } + + Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset; + + if (!authenticated) + { + return Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect User or Password" }); + } + + var landingSettings = LandingPageSettings.GetSettings(); + + if (landingSettings.Enabled) + { + if (!landingSettings.BeforeLogin) + return Response.AsJson(new JsonResponseModel { Result = true, Message = "landing" }); + } + return Response.AsJson(new JsonResponseModel { Result = true, Message = "search" }); + } + + + + private Response Logout() + { + Log.Debug("Logging Out"); + if (Session[SessionKeys.UsernameKey] != null) + { + Session.Delete(SessionKeys.UsernameKey); + } + return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl) + ? $"~/{BaseUrl}/userlogin" + : "~/userlogin"); + } + + private bool CheckIfUserIsOwner(string authToken, string userName) + { + var userAccount = Api.GetAccount(authToken); + if (userAccount == null) + { + return false; + } + return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase); + } + + private bool CheckIfUserIsInPlexFriends(string username, string authToken) + { + var users = Api.GetUsers(authToken); + var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title)); + return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)); + } + + private bool IsUserInDeniedList(string username, AuthenticationSettings settings) + { + return settings.DeniedUserList.Any(x => x.Equals(username)); + } + } } \ No newline at end of file diff --git a/PlexRequests.UI/Validators/PlexRequestsValidator.cs b/PlexRequests.UI/Validators/PlexRequestsValidator.cs index 6f8ba4d3e..c82817185 100644 --- a/PlexRequests.UI/Validators/PlexRequestsValidator.cs +++ b/PlexRequests.UI/Validators/PlexRequestsValidator.cs @@ -45,6 +45,7 @@ public PlexRequestsValidator() RuleFor(x => x.BaseUrl).NotEqual("updatechecker").WithMessage("You cannot use 'updatechecker' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("usermanagement").WithMessage("You cannot use 'usermanagement' as this is reserved by the application."); RuleFor(x => x.BaseUrl).NotEqual("api").WithMessage("You cannot use 'api' as this is reserved by the application."); + RuleFor(x => x.BaseUrl).NotEqual("landing").WithMessage("You cannot use 'landing' as this is reserved by the application."); } } } \ No newline at end of file diff --git a/PlexRequests.UI/Views/Shared/Blank.cshtml b/PlexRequests.UI/Views/Shared/Blank.cshtml index d5b1f73eb..cafdf0d63 100644 --- a/PlexRequests.UI/Views/Shared/Blank.cshtml +++ b/PlexRequests.UI/Views/Shared/Blank.cshtml @@ -17,7 +17,7 @@ Plex Requests - + @Html.LoadAnalytics() @Html.LoadAssets() From 73f02b8e879aee5748f7a143b9279b1ee94d2d4c Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 22 Jun 2016 13:37:18 +0100 Subject: [PATCH 6/8] Update appveyor.yml --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index cfef4c77b..f89de2ad2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,9 +3,9 @@ configuration: Release assembly_info: patch: true file: '**\AssemblyInfo.*' - assembly_version: '1.8.1' + assembly_version: '1.8.2' assembly_file_version: '{version}' - assembly_informational_version: '1.8.1' + assembly_informational_version: '1.8.2' before_build: - cmd: appveyor-retry nuget restore build: From f834e9635c77b2b65fb90dc5baca5252eab0b9c2 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 22 Jun 2016 13:50:17 +0100 Subject: [PATCH 7/8] Fixed a circular reference issue --- PlexRequests.UI/Modules/AdminModule.cs | 2 +- PlexRequests.UI/Views/Admin/Settings.cshtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PlexRequests.UI/Modules/AdminModule.cs b/PlexRequests.UI/Modules/AdminModule.cs index 5b8f0a103..8e0ff9363 100644 --- a/PlexRequests.UI/Modules/AdminModule.cs +++ b/PlexRequests.UI/Modules/AdminModule.cs @@ -148,7 +148,7 @@ public AdminModule(ISettingsService prService, Get["/authentication", true] = async (x, ct) => await Authentication(); Post["/authentication", true] = async (x, ct) => await SaveAuthentication(); - Post["/"] = _ => SaveAdmin(); + Post["/", true] = async (x, ct) => await SaveAdmin(); Post["/requestauth"] = _ => RequestAuthToken(); diff --git a/PlexRequests.UI/Views/Admin/Settings.cshtml b/PlexRequests.UI/Views/Admin/Settings.cshtml index 423b98502..91532b433 100644 --- a/PlexRequests.UI/Views/Admin/Settings.cshtml +++ b/PlexRequests.UI/Views/Admin/Settings.cshtml @@ -228,7 +228,7 @@

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

- +
From 901f5efb716e8df63e0f58852020f63d14768d0d Mon Sep 17 00:00:00 2001 From: tidusjar Date: Wed, 22 Jun 2016 13:52:06 +0100 Subject: [PATCH 8/8] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0153cfb7a..d5b424b76 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ I wanted to write a similar application in .Net! * Headphones integration! * Ability to run with a reverse proxy! -# Preview () +# Preview ![Preview](http://i.imgur.com/DgwkIsW.gif)