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