diff --git a/PlexRequests.Core.Migration/Migrations/Version1100.cs b/PlexRequests.Core.Migration/Migrations/Version1100.cs index d4365f357..525114ebb 100644 --- a/PlexRequests.Core.Migration/Migrations/Version1100.cs +++ b/PlexRequests.Core.Migration/Migrations/Version1100.cs @@ -76,6 +76,9 @@ namespace PlexRequests.Core.Migration.Migrations con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER"); con.AlterTable("Users", "ADD", "Features", true, "INTEGER"); + con.AlterTable("PlexUsers", "ADD", "Permissions", true, "INTEGER"); + con.AlterTable("PlexUsers", "ADD", "Features", true, "INTEGER"); + //https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg // UI = https://image.tmdb.org/t/p/w150/{{posterPath}} diff --git a/PlexRequests.Helpers/Permissions/Permissions.cs b/PlexRequests.Helpers/Permissions/Permissions.cs index b0c28db36..8b1d22954 100644 --- a/PlexRequests.Helpers/Permissions/Permissions.cs +++ b/PlexRequests.Helpers/Permissions/Permissions.cs @@ -50,5 +50,15 @@ namespace PlexRequests.Helpers.Permissions [Display(Name = "Read Only User")] ReadOnlyUser = 32, + + [Display(Name = "Auto Approve Movie Requests")] + AutoApproveMovie = 64, + + [Display(Name = "Auto Approve TV Show Requests")] + AutoApproveTv = 128, + + [Display(Name = "Auto Approve Album Requests")] + AutoApproveAlbum = 256 + } } \ No newline at end of file diff --git a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs index 8ecb00bb5..365a7efa0 100644 --- a/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs +++ b/PlexRequests.Services/Jobs/PlexAvailabilityChecker.cs @@ -471,10 +471,10 @@ namespace PlexRequests.Services.Jobs { var sections = PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); - List libs = new List(); + var libs = new List(); if (sections != null) { - foreach (var dir in sections.Directories) + foreach (var dir in sections.Directories ?? new List()) { var lib = PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.Key); if (lib != null) diff --git a/PlexRequests.Store/Models/PlexUsers.cs b/PlexRequests.Store/Models/PlexUsers.cs index 884fe1d4d..c860b9551 100644 --- a/PlexRequests.Store/Models/PlexUsers.cs +++ b/PlexRequests.Store/Models/PlexUsers.cs @@ -24,11 +24,17 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // ************************************************************************/ #endregion + +using Dapper.Contrib.Extensions; + namespace PlexRequests.Store.Models { + [Table(nameof(PlexUsers))] public class PlexUsers : Entity { - public int PlexUserId { get; set; } + public string PlexUserId { get; set; } public string UserAlias { get; set; } + public int Permissions { get; set; } + public int Features { get; set; } } } \ No newline at end of file diff --git a/PlexRequests.Store/SqlTables.sql b/PlexRequests.Store/SqlTables.sql index 4d180b1d0..f4a512346 100644 --- a/PlexRequests.Store/SqlTables.sql +++ b/PlexRequests.Store/SqlTables.sql @@ -114,8 +114,10 @@ CREATE UNIQUE INDEX IF NOT EXISTS RequestLimit_Id ON RequestLimit (Id); CREATE TABLE IF NOT EXISTS PlexUsers ( Id INTEGER PRIMARY KEY AUTOINCREMENT, - PlexUserId INTEGER NOT NULL, - UserAlias varchar(100) NOT NULL + PlexUserId varchar(100) NOT NULL, + UserAlias varchar(100) NOT NULL, + Permissions INTEGER, + Features INTEGER ); CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id); diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementController.js b/PlexRequests.UI/Content/app/userManagement/userManagementController.js index 3b67832aa..814df40d8 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementController.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementController.js @@ -120,29 +120,30 @@ $scope.updateUser = function () { var u = $scope.selectedUser; - userManagementService.updateUser(u.id, u.permissions, u.alias, u.emailAddress) - .then(function (data) { - if (data) { - $scope.selectedUser = data; - - if (open) { - open = false; - $("#wrapper").toggleClass("toggled"); + userManagementService.updateUser(u.id, u.permissions, u.features, u.alias, u.emailAddress) + .then(function success(data) { + if (data.data) { + $scope.selectedUser = data.data; + + closeSidebar(); + return successCallback("Updated User", "success"); } - return successCallback("Updated User", "success"); - } - }); + }, function errorCallback(response) { + successCallback(response, "danger"); + }); } $scope.deleteUser = function () { var u = $scope.selectedUser; - var result = userManagementService.deleteUser(u.id); - - result.success(function (data) { - if (data.result) { + userManagementService.deleteUser(u.id) + .then(function sucess(data) { + if (data.data.result) { removeUser(u.id, true); + closeSidebar(); return successCallback("Deleted User", "success"); } + }, function errorCallback(response) { + successCallback(response, "danger"); }); } @@ -170,6 +171,13 @@ $scope.selectedUser = null; } } + + function closeSidebar() { + if (open) { + open = false; + $("#wrapper").toggleClass("toggled"); + } + } } function successCallback(message, type) { diff --git a/PlexRequests.UI/Content/app/userManagement/userManagementService.js b/PlexRequests.UI/Content/app/userManagement/userManagementService.js index 72cd7f375..59dfe3571 100644 --- a/PlexRequests.UI/Content/app/userManagement/userManagementService.js +++ b/PlexRequests.UI/Content/app/userManagement/userManagementService.js @@ -28,11 +28,11 @@ return $http.get('/usermanagement/permissions'); } - var updateUser = function (id, permissions, alias, email) { + var updateUser = function (id, permissions, features, alias, email) { return $http({ url: '/usermanagement/updateUser', method: "POST", - data: { id: id, permissions: permissions, alias: alias, emailAddress: email } + data: { id: id, permissions: permissions, features: features, alias: alias, emailAddress: email }, }); } diff --git a/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs b/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs index aad003c05..cdc78a07a 100644 --- a/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs +++ b/PlexRequests.UI/Models/UserManagement/UserManagementUsersViewModel.cs @@ -75,6 +75,8 @@ namespace PlexRequests.UI.Models public string Id { get; set; } [JsonProperty("permissions")] public List Permissions { get; set; } + [JsonProperty("features")] + public List Features { get; set; } public string Alias { get; set; } public string EmailAddress { get; set; } } diff --git a/PlexRequests.UI/Modules/UserManagementModule.cs b/PlexRequests.UI/Modules/UserManagementModule.cs index ee9558d1f..9870d3533 100644 --- a/PlexRequests.UI/Modules/UserManagementModule.cs +++ b/PlexRequests.UI/Modules/UserManagementModule.cs @@ -6,15 +6,16 @@ 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.Api.Models.Plex; using PlexRequests.Core; using PlexRequests.Core.Models; using PlexRequests.Core.SettingModels; using PlexRequests.Helpers; using PlexRequests.Helpers.Permissions; using PlexRequests.Store; +using PlexRequests.Store.Models; using PlexRequests.Store.Repository; using PlexRequests.UI.Models; @@ -22,15 +23,16 @@ namespace PlexRequests.UI.Modules { public class UserManagementModule : BaseModule { - public UserManagementModule(ISettingsService pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService plex, IRepository userLogins) : base("usermanagement", pr) + public UserManagementModule(ISettingsService pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService plex, IRepository userLogins, IRepository plexRepo) : base("usermanagement", pr) { #if !DEBUG - this.RequiresAnyClaim(UserClaims.Admin); + Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); #endif UserMapper = m; PlexApi = plexApi; PlexSettings = plex; UserLoginsRepo = userLogins; + PlexUsersRepository = plexRepo; Get["/"] = x => Load(); @@ -40,7 +42,7 @@ namespace PlexRequests.UI.Modules Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id); Get["/permissions"] = x => GetEnum(); Get["/features"] = x => GetEnum(); - Post["/updateuser"] = x => UpdateUser(); + Post["/updateuser", true] = async (x, ct) => await UpdateUser(); Post["/deleteuser"] = x => DeleteUser(); } @@ -48,6 +50,7 @@ namespace PlexRequests.UI.Modules private IPlexApi PlexApi { get; } private ISettingsService PlexSettings { get; } private IRepository UserLoginsRepo { get; } + private IRepository PlexUsersRepository { get; } private Negotiator Load() { @@ -57,13 +60,14 @@ namespace PlexRequests.UI.Modules private async Task LoadUsers() { var localUsers = await UserMapper.GetUsersAsync(); + var plexDbUsers = await PlexUsersRepository.GetAllAsync(); var model = new List(); - var usersDb = UserLoginsRepo.GetAll().ToList(); + var userLogins = UserLoginsRepo.GetAll().ToList(); foreach (var user in localUsers) { - var userDb = usersDb.FirstOrDefault(x => x.UserId == user.UserGuid); + var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid); model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue)); } @@ -75,20 +79,18 @@ namespace PlexRequests.UI.Modules foreach (var u in plexUsers.User) { - var userDb = usersDb.FirstOrDefault(x => x.UserId == u.Id); - model.Add(new UserManagementUsersViewModel + var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == u.Id); + var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id); + // We don't have the user in the database yet + if (dbUser == null) { - Username = u.Username, - Type = UserType.PlexUser, - Id = u.Id, - FeaturesFormattedString = "Requestor", - EmailAddress = u.Email, - PlexInfo = new UserManagementPlexInformation - { - Thumb = u.Thumb - }, - LastLoggedIn = userDb?.LastLoggedIn ?? DateTime.MinValue, - }); + model.Add(MapPlexUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } + else + { + // The Plex User is in the database + model.Add(MapPlexUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue)); + } } } return Response.AsJson(model); @@ -128,7 +130,7 @@ namespace PlexRequests.UI.Modules permissionsVal += f; } - var user = UserMapper.CreateUser(model.Username, model.Password, featuresVal, permissionsVal, new UserProperties { EmailAddress = model.EmailAddress }); + var user = UserMapper.CreateUser(model.Username, model.Password, permissionsVal, featuresVal, new UserProperties { EmailAddress = model.EmailAddress }); if (user.HasValue) { return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue)); @@ -137,7 +139,7 @@ namespace PlexRequests.UI.Modules return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" }); } - private Response UpdateUser() + private async Task UpdateUser() { var body = Request.Body.AsString(); if (string.IsNullOrEmpty(body)) @@ -156,22 +158,68 @@ namespace PlexRequests.UI.Modules }); } - var val = model.Permissions.Where(c => c.Selected).Sum(c => c.Value); + var permissionsValue = model.Permissions.Where(c => c.Selected).Sum(c => c.Value); + var featuresValue = model.Features.Where(c => c.Selected).Sum(c => c.Value); + + Guid outId; + Guid.TryParse(model.Id, out outId); + var localUser = UserMapper.GetUser(outId); + + // Update Local User + if (localUser != null) + { + localUser.Permissions = permissionsValue; + localUser.Features = featuresValue; + + var currentProps = ByteConverterHelper.ReturnObject(localUser.UserProperties); + currentProps.UserAlias = model.Alias; + currentProps.EmailAddress = model.EmailAddress; + + localUser.UserProperties = ByteConverterHelper.ReturnBytes(currentProps); + + var user = UserMapper.EditUser(localUser); + var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid); + var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } + + var plexSettings = await PlexSettings.GetSettingsAsync(); + var plexDbUsers = await PlexUsersRepository.GetAllAsync(); + var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken); + var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id); + var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id); + var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id); + if (plexDbUser != null && plexUser != null) + { + // We have a user in the DB for this Plex Account + plexDbUser.Permissions = permissionsValue; + plexDbUser.Features = featuresValue; - var userFound = UserMapper.GetUser(new Guid(model.Id)); + plexDbUser.UserAlias = model.Alias; - userFound.Permissions = val; + await PlexUsersRepository.UpdateAsync(plexDbUser); + + var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } - var currentProps = ByteConverterHelper.ReturnObject(userFound.UserProperties); - currentProps.UserAlias = model.Alias; - currentProps.EmailAddress = model.EmailAddress; + // We have a Plex Account but he's not in the DB + if (plexUser != null) + { + var user = new PlexUsers + { + Permissions = permissionsValue, + Features = featuresValue, + UserAlias = model.Alias, + PlexUserId = plexUser.Id + }; - userFound.UserProperties = ByteConverterHelper.ReturnBytes(currentProps); + await PlexUsersRepository.InsertAsync(user); - var user = UserMapper.EditUser(userFound); - var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid); - var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue); - return Response.AsJson(retUser); + var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue); + return Response.AsJson(retUser); + } + return null; // We should never end up here. } private Response DeleteUser() @@ -248,8 +296,8 @@ namespace PlexRequests.UI.Modules private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn) { - var features = (Features) user.Features; - var permissions = (Permissions) user.Permissions; + var features = (Features)user.Features; + var permissions = (Permissions)user.Permissions; var userProps = ByteConverterHelper.ReturnObject(user.UserProperties); @@ -297,6 +345,64 @@ namespace PlexRequests.UI.Modules return m; } + + private UserManagementUsersViewModel MapPlexUser(UserFriends plexInfo, PlexUsers dbUser, DateTime lastLoggedIn) + { + if (dbUser == null) + { + dbUser = new PlexUsers(); + } + var features = (Features)dbUser?.Features; + var permissions = (Permissions)dbUser?.Permissions; + + var m = new UserManagementUsersViewModel + { + Id = plexInfo.Id, + PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(), + FeaturesFormattedString = features.ToString(), + Username = plexInfo.Username, + Type = UserType.PlexUser, + EmailAddress = plexInfo.Email, + Alias = dbUser?.UserAlias ?? string.Empty, + LastLoggedIn = lastLoggedIn, + PlexInfo = new UserManagementPlexInformation + { + Thumb = plexInfo.Thumb + }, + }; + + // Add permissions + foreach (var p in Enum.GetValues(typeof(Permissions))) + { + var perm = (Permissions)p; + var displayValue = EnumHelper.GetDisplayValue(perm); + var pm = new CheckBox + { + Name = displayValue, + Selected = permissions.HasFlag(perm), + Value = (int)perm + }; + + m.Permissions.Add(pm); + } + + // Add features + foreach (var p in Enum.GetValues(typeof(Features))) + { + var perm = (Features)p; + var displayValue = EnumHelper.GetDisplayValue(perm); + var pm = new CheckBox + { + Name = displayValue, + Selected = features.HasFlag(perm), + Value = (int)perm + }; + + m.Features.Add(pm); + } + + return m; + } } } diff --git a/PlexRequests.UI/Resources/UI.resx b/PlexRequests.UI/Resources/UI.resx index ca792000b..f82c34fce 100644 --- a/PlexRequests.UI/Resources/UI.resx +++ b/PlexRequests.UI/Resources/UI.resx @@ -470,4 +470,7 @@ A background process is currently running, so there might be some unexpected behavior. This shouldn't take too long. + + User Management + \ No newline at end of file diff --git a/PlexRequests.UI/Resources/UI1.Designer.cs b/PlexRequests.UI/Resources/UI1.Designer.cs index 0a57c2412..a5cf28b1f 100644 --- a/PlexRequests.UI/Resources/UI1.Designer.cs +++ b/PlexRequests.UI/Resources/UI1.Designer.cs @@ -402,6 +402,15 @@ namespace PlexRequests.UI.Resources { } } + /// + /// Looks up a localized string similar to User Management. + /// + public static string Layout_Usermanagement { + get { + return ResourceManager.GetString("Layout_Usermanagement", resourceCulture); + } + } + /// /// Looks up a localized string similar to Welcome. /// diff --git a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml index e4d75cc75..1f2f1b232 100644 --- a/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml +++ b/PlexRequests.UI/Views/Shared/Partial/_Navbar.cshtml @@ -35,6 +35,10 @@ @Html.GetNavbarUrl(Context, "/search", UI.Layout_Search, "search") @Html.GetNavbarUrl(Context, "/requests", UI.Layout_Requests, "plus-circle") @Html.GetNavbarUrl(Context, "/issues", UI.Layout_Issues, "exclamation", "") + @if (Html.IsAdmin()) + { + @Html.GetNavbarUrl(Context, "/usermanagement", UI.Layout_Usermanagement, "users") + } @*@if (Context.CurrentUser.IsAuthenticated()) // TODO replace with IsAdmin*@ @if (Html.IsAdmin()) { diff --git a/PlexRequests.UI/Views/UserManagement/Index.cshtml b/PlexRequests.UI/Views/UserManagement/Index.cshtml index 7002cb143..d0cd11dc1 100644 --- a/PlexRequests.UI/Views/UserManagement/Index.cshtml +++ b/PlexRequests.UI/Views/UserManagement/Index.cshtml @@ -15,30 +15,31 @@
Email Address:
-
- Permissions: -
-
- Features: -
User Type:


-
+
- Modify Roles: - + Modify Permissions: +
+ Modify Features: + +
+ + +
+ Email Address
- +
Alias