From d2d6b6654a93cd0736878ad14f43455eaa39a026 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 11 May 2017 13:11:41 +1000 Subject: [PATCH] Adds models and methods for the UserController --- src/Umbraco.Core/Constants-Applications.cs | 2 +- src/Umbraco.Web.UI/config/trees.config | 5 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- .../umbraco/config/lang/en_us.xml | 1 + src/Umbraco.Web/Editors/DataTypeController.cs | 1 + src/Umbraco.Web/Editors/UserController.cs | 95 +++++++++++++++++- .../Models/ContentEditing/UserBasic.cs | 11 ++- .../Models/ContentEditing/UserDetail.cs | 5 +- .../Models/ContentEditing/UserDisplay.cs | 59 +++++++++++ .../Models/ContentEditing/UserSave.cs | 51 ++++++++++ .../Models/Mapping/UserModelMapper.cs | 11 +++ ...reeController.cs => UserTreeController.cs} | 99 +++++++++++-------- src/Umbraco.Web/Umbraco.Web.csproj | 4 +- .../umbraco/Trees/loadUsers.cs | 3 +- 14 files changed, 292 insertions(+), 57 deletions(-) create mode 100644 src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs create mode 100644 src/Umbraco.Web/Models/ContentEditing/UserSave.cs rename src/Umbraco.Web/Trees/{UsersTreeController.cs => UserTreeController.cs} (77%) diff --git a/src/Umbraco.Core/Constants-Applications.cs b/src/Umbraco.Core/Constants-Applications.cs index 8b9be02f4f..b84446adc3 100644 --- a/src/Umbraco.Core/Constants-Applications.cs +++ b/src/Umbraco.Core/Constants-Applications.cs @@ -118,7 +118,7 @@ public const string Scripts = "scripts"; - public const string Users = "usersV2"; + public const string Users = "users"; //TODO: Fill in the rest! } diff --git a/src/Umbraco.Web.UI/config/trees.config b/src/Umbraco.Web.UI/config/trees.config index 13da9246b7..12f3110cd1 100644 --- a/src/Umbraco.Web.UI/config/trees.config +++ b/src/Umbraco.Web.UI/config/trees.config @@ -24,9 +24,10 @@ - + + @@ -40,5 +41,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index e46c065695..416d3b0361 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1370,7 +1370,7 @@ To manage your website, simply open the Umbraco back office and start adding con Templates XSLT Files Analytics - Users + Users New update ready diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 9a5ea92fd6..9cf2fcd92b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1367,6 +1367,7 @@ To manage your website, simply open the Umbraco back office and start adding con Templates XSLT Files Analytics + Users New update ready diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index b3e306a48c..522c1f2b48 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -23,6 +23,7 @@ using System.Text; namespace Umbraco.Web.Editors { + /// /// The API controller used for editing data types /// diff --git a/src/Umbraco.Web/Editors/UserController.cs b/src/Umbraco.Web/Editors/UserController.cs index 363940b924..1cdb243742 100644 --- a/src/Umbraco.Web/Editors/UserController.cs +++ b/src/Umbraco.Web/Editors/UserController.cs @@ -1,5 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; using System.Net; +using System.Net.Http; using System.Web.Http; +using AutoMapper; +using ClientDependency.Core; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; using Constants = Umbraco.Core.Constants; @@ -15,7 +26,7 @@ namespace Umbraco.Web.Editors /// public UserController() : this(UmbracoContext.Current) - { + { } /// @@ -24,7 +35,87 @@ namespace Umbraco.Web.Editors /// public UserController(UmbracoContext umbracoContext) : base(umbracoContext) - { + { + } + + /// + /// Gets a user by Id + /// + /// + /// + public UserDisplay GetById(int id) + { + var user = Services.UserService.GetUserById(id); + if (user == null) + { + throw new HttpResponseException(HttpStatusCode.NotFound); + } + return Mapper.Map(user); + } + + //TODO: This will probably not be UserDisplay objects since there's probably too much data in the display object for a grid + public PagedResult GetPagedUsers( + int id, + int pageNumber = 1, + int pageSize = 0, + string orderBy = "SortOrder", + Direction orderDirection = Direction.Ascending, + string filter = "") + { + + //TODO: Make this real, for now this is mock data + + var startId = 100 + ((pageNumber -1) * pageSize); + var numUsers = pageSize; + var users = new List(); + var userTypes = Services.UserService.GetAllUserTypes().ToDictionary(x => x.Alias, x => x.Name); + var cultures = Services.TextService.GetSupportedCultures().ToDictionary(x => x.Name, x => x.DisplayName); + for (int i = 0; i < numUsers; i++) + { + var display = new UserDisplay + { + Id = startId, + UserType = "writer", + AllowedSections = new[] {"content", "media"}, + AvailableUserTypes = userTypes, + Email = "test" + startId + "@test.com", + Name = "User " + startId, + Culture = "en-US", + AvailableCultures = cultures, + Path = "-1," + startId, + ParentId = -1, + StartContentId = -1, + StartMediaId = -1 + }; + users.Add(display); + startId++; + } + + return new PagedResult(100, pageNumber, pageSize) + { + Items = users + }; + } + + public UserDisplay PostSaveUser(UserSave userSave) + { + if (userSave == null) throw new ArgumentNullException("userSave"); + + if (ModelState.IsValid == false) + { + throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); + } + + var intId = userSave.Id.TryConvertTo(); + if (intId.Success == false) + throw new HttpResponseException(HttpStatusCode.NotFound); + + var found = Services.UserService.GetUserById(intId.Result); + if (found == null) + throw new HttpResponseException(HttpStatusCode.NotFound); + + //TODO: More validation, password changing logic, persisting + return Mapper.Map(found); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs b/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs index c1cf3ed5c6..07e800d647 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserBasic.cs @@ -1,14 +1,15 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Models.ContentEditing { /// - /// A basic structure the represents a user + /// A bare minimum structure that represents a user, usually attached to other objects /// [DataContract(Name = "user", Namespace = "")] - public class UserBasic : System.IComparable + public class UserBasic : IComparable { [DataMember(Name = "id", IsRequired = true)] [Required] @@ -19,9 +20,9 @@ namespace Umbraco.Web.Models.ContentEditing public string Name { get; set; } - int System.IComparable.CompareTo(object obj) + int IComparable.CompareTo(object obj) { - return Name.CompareTo(((UserBasic)obj).Name); + return String.Compare(Name, ((UserBasic)obj).Name, StringComparison.Ordinal); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs index d27736576f..0b4bd5132d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserDetail.cs @@ -3,7 +3,10 @@ using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing -{ +{ + /// + /// Represents information for the current user + /// [DataContract(Name = "user", Namespace = "")] public class UserDetail : UserBasic { diff --git a/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs new file mode 100644 index 0000000000..d77d9d2019 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserDisplay.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents a user that is being edited + /// + [DataContract(Name = "user", Namespace = "")] + public class UserDisplay : EntityBasic, INotificationModel + { + public UserDisplay() + { + Notifications = new List(); + } + + [DataMember(Name = "culture", IsRequired = true)] + public string Culture { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + public string Email { get; set; } + + [DataMember(Name = "userType")] + public string UserType { get; set; } + + /// + /// Gets the available user types (i.e. to populate a drop down) + /// The key is the Alias the value is the Name - the Alias is what is used in the UserType property and for persistence + /// + [DataMember(Name = "availableUserTypes")] + public IDictionary AvailableUserTypes { get; set; } + + /// + /// Gets the available cultures (i.e. to populate a drop down) + /// The key is the culture stored in the database, the value is the Name + /// + [DataMember(Name = "availableCultures")] + public IDictionary AvailableCultures { get; set; } + + [DataMember(Name = "startContentId")] + public int StartContentId { get; set; } + + [DataMember(Name = "startMediaId")] + public int StartMediaId { get; set; } + + /// + /// A list of sections the user is allowed to view. + /// + [DataMember(Name = "allowedSections")] + public IEnumerable AllowedSections { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/UserSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs new file mode 100644 index 0000000000..5fe57e0787 --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/UserSave.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + /// + /// Represents the data used to persist a user + /// + /// + /// This will be different from the model used to display a user and we don't want to "Overpost" data back to the server, + /// and there will most likely be different bits of data required for updating passwords which will be different from the + /// data used to display vs save + /// + [DataContract(Name = "user", Namespace = "")] + public class UserSave : EntityBasic, IValidatableObject + { + //TODO: There will be more information to save along with the structure for changing passwords + + [DataMember(Name = "locale", IsRequired = true)] + [Required] + public string Culture { get; set; } + + [DataMember(Name = "email", IsRequired = true)] + [Required] + [EmailAddress] + public string Email { get; set; } + + [DataMember(Name = "userType")] + [Required] + public string UserType { get; set; } + + [DataMember(Name = "startContentId")] + public int StartContentId { get; set; } + + [DataMember(Name = "startMediaId")] + public int StartMediaId { get; set; } + + [DataMember(Name = "allowedSections")] + public IEnumerable AllowedSections { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + //TODO: Add other server side validation + //if (CultureInfo.GetCultureInfo(Culture)) + // yield return new ValidationResult("The culture is invalid", new[] { "Culture" }); + + yield break; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 72093b19a8..025de43d40 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models.Mapping; @@ -15,6 +16,16 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { + config.CreateMap() + .ForMember(detail => detail.Id, opt => opt.MapFrom(user => user.Id)) + .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias)) + .ForMember(detail => detail.StartContentId, opt => opt.MapFrom(user => user.StartContentId)) + .ForMember(detail => detail.StartMediaId, opt => opt.MapFrom(user => user.StartMediaId)) + .ForMember(detail => detail.Culture, opt => opt.MapFrom(user => user.GetUserCulture(applicationContext.Services.TextService))) + .ForMember( + detail => detail.AvailableUserTypes, + opt => opt.MapFrom(user => applicationContext.Services.SectionService.GetSections().ToDictionary(x => x.Alias, x => x.Name))); + config.CreateMap() .ForMember(detail => detail.UserId, opt => opt.MapFrom(user => GetIntId(user.Id))) .ForMember(detail => detail.UserType, opt => opt.MapFrom(user => user.UserType.Alias)) diff --git a/src/Umbraco.Web/Trees/UsersTreeController.cs b/src/Umbraco.Web/Trees/UserTreeController.cs similarity index 77% rename from src/Umbraco.Web/Trees/UsersTreeController.cs rename to src/Umbraco.Web/Trees/UserTreeController.cs index 65c289006a..1318934559 100644 --- a/src/Umbraco.Web/Trees/UsersTreeController.cs +++ b/src/Umbraco.Web/Trees/UserTreeController.cs @@ -1,44 +1,57 @@ -using System.Net.Http.Formatting; -using Umbraco.Web.Models.Trees; -using Umbraco.Web.Mvc; -using Umbraco.Web.WebApi.Filters; -using Constants = Umbraco.Core.Constants; - -namespace Umbraco.Web.Trees -{ - [UmbracoTreeAuthorize(Constants.Trees.Users)] - [Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 3)] - [PluginController("UmbracoTrees")] - [CoreTree] - public class UsersTreeController : TreeController - { - /// - /// Helper method to create a root model for a tree - /// - /// - protected override TreeNode CreateRootNode(FormDataCollection queryStrings) - { - var root = base.CreateRootNode(queryStrings); - - //this will load in a custom UI instead of the dashboard for the root node - root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Users, Constants.Trees.Users, "overview"); - root.Icon = "icon-users"; - - return root; - } - - protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) - { - var baseUrl = Constants.Applications.Users + "/users/"; - - var nodes = new TreeNodeCollection(); - return nodes; - } - - protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) - { - var menu = new MenuItemCollection(); - return menu; - } - } +using System.Net.Http.Formatting; +using Umbraco.Web.Models.Trees; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; + +namespace Umbraco.Web.Trees +{ + [UmbracoTreeAuthorize(Constants.Trees.Users)] + [Tree(Constants.Applications.Users, Constants.Trees.Users, null, sortOrder: 3)] + [PluginController("UmbracoTrees")] + [CoreTree] + public class UserTreeController : TreeController + { + public UserTreeController() + { + } + + public UserTreeController(UmbracoContext umbracoContext) : base(umbracoContext) + { + } + + public UserTreeController(UmbracoContext umbracoContext, UmbracoHelper umbracoHelper) : base(umbracoContext, umbracoHelper) + { + } + + /// + /// Helper method to create a root model for a tree + /// + /// + protected override TreeNode CreateRootNode(FormDataCollection queryStrings) + { + var root = base.CreateRootNode(queryStrings); + + //this will load in a custom UI instead of the dashboard for the root node + root.RoutePath = string.Format("{0}/{1}/{2}", Constants.Applications.Users, Constants.Trees.Users, "overview"); + root.Icon = "icon-users"; + + root.HasChildren = false; + return root; + } + + protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) + { + var baseUrl = Constants.Applications.Users + "/users/"; + + var nodes = new TreeNodeCollection(); + return nodes; + } + + protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings) + { + var menu = new MenuItemCollection(); + return menu; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d534dc2ae4..0c651af1fe 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -366,6 +366,8 @@ + + @@ -433,7 +435,7 @@ - + diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs index 5db7a06dab..c749b6b58b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Trees/loadUsers.cs @@ -34,7 +34,8 @@ namespace umbraco /// /// Handles loading of all umbraco users into the users application tree /// - [Tree(Constants.Applications.Users, "users", "Users")] + [Tree(Constants.Applications.Users, "users_old", "Users (Legacy)")] + //TODO: Remove this tree when ready public class loadUsers : BaseTree { public loadUsers(string application) : base(application) { }