From d7e65dd088e2e173b46ecaa1d3880cf479f9b02d Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 20 Jun 2017 15:10:42 +1000 Subject: [PATCH] User groups can now be created/saved --- .../src/common/resources/users.resource.js | 4 +- .../src/common/services/util.service.js | 7 +- .../Editors/DataTypeValidateAttribute.cs | 6 +- .../Editors/UserGroupValidateAttribute.cs | 87 +++++++++++++++++++ src/Umbraco.Web/Editors/UsersController.cs | 36 +------- .../Models/ContentEditing/UserGroupBasic.cs | 2 + .../Models/ContentEditing/UserGroupDisplay.cs | 6 ++ .../Models/ContentEditing/UserGroupSave.cs | 21 ++++- .../Models/Mapping/UserModelMapper.cs | 3 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 10 files changed, 131 insertions(+), 42 deletions(-) create mode 100644 src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js index b77da610c3..100b5a398c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/users.resource.js @@ -174,13 +174,13 @@ "Failed to save user"); } - function saveUserGroup(userGroup) { + function saveUserGroup(userGroup, isNew) { if (!userGroup) { throw "userGroup not specified"; } //need to convert the user data into the correctly formatted save data - it is *not* the same and we don't want to over-post - var formattedSaveData = umbDataFormatter.formatUserGroupPostData(userGroup); + var formattedSaveData = umbDataFormatter.formatUserGroupPostData(userGroup, "save" + (isNew ? "New" : "")); return umbRequestHelper.resourcePromise( $http.post( diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 5b61bf15d4..ebab7497ca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -688,10 +688,15 @@ function umbDataFormatter() { }, /** formats the display model used to display the user group to the model used to save the user group*/ - formatUserGroupPostData: function (displayModel) { + formatUserGroupPostData: function (displayModel, action) { //create the save model from the display model var saveModel = _.pick(displayModel, 'id', 'alias', 'name', 'sections', 'users', 'startContentId', 'startMediaId'); + //set the action on the save model + saveModel.action = action; + if (!saveModel.id) { + saveModel.id = 0; + } //make sure the sections are just a string array var currSections = saveModel.sections; diff --git a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs index 7f513b35c7..0a25a4236a 100644 --- a/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs +++ b/src/Umbraco.Web/Editors/DataTypeValidateAttribute.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Editors private readonly IDataTypeService _dataTypeService; public DataTypeValidateAttribute() - { + { } public DataTypeValidateAttribute(IDataTypeService dataTypeService) @@ -88,8 +88,8 @@ namespace Umbraco.Web.Editors //Validate each field foreach (var preVal in dataType.PreValues) { - var postedValue = preVal.Value; - + var postedValue = preVal.Value; + foreach (var v in propertyEditor.PreValueEditor.Fields.Where(x => x.Key == preVal.Key).SelectMany(x => x.Validators)) { foreach (var result in v.Validate(postedValue, null, propertyEditor)) diff --git a/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs b/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs new file mode 100644 index 0000000000..a72d97df10 --- /dev/null +++ b/src/Umbraco.Web/Editors/UserGroupValidateAttribute.cs @@ -0,0 +1,87 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using AutoMapper; +using Umbraco.Core; +using Umbraco.Core.Models.Membership; +using Umbraco.Core.Services; +using Umbraco.Web.Models.ContentEditing; +using Umbraco.Web.WebApi; + +namespace Umbraco.Web.Editors +{ + internal sealed class UserGroupValidateAttribute : ActionFilterAttribute + { + private readonly IUserService _userService; + + public UserGroupValidateAttribute() + { + } + + public UserGroupValidateAttribute(IUserService userService) + { + if (_userService == null) throw new ArgumentNullException("userService"); + _userService = userService; + } + + private IUserService UserService + { + get { return _userService ?? ApplicationContext.Current.Services.UserService; } + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + var userGroupSave = (UserGroupSave)actionContext.ActionArguments["userGroupSave"]; + + userGroupSave.Name = userGroupSave.Name.CleanForXss('[', ']', '(', ')', ':'); + userGroupSave.Alias = userGroupSave.Name.CleanForXss('[', ']', '(', ')', ':'); + + //Validate the usergroup exists or create one if required + IUserGroup persisted; + switch (userGroupSave.Action) + { + case ContentSaveAction.Save: + persisted = UserService.GetUserGroupById(Convert.ToInt32(userGroupSave.Id)); + if (persisted == null) + { + var message = string.Format("User group with id: {0} was not found", userGroupSave.Id); + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, message); + return; + } + //map the model to the persisted instance + Mapper.Map(userGroupSave, persisted); + break; + case ContentSaveAction.SaveNew: + //create the persisted model from mapping the saved model + persisted = Mapper.Map(userGroupSave); + ((UserGroup)persisted).ResetIdentity(); + break; + default: + actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, new ArgumentOutOfRangeException()); + return; + } + + //now assign the persisted entity to the model so we can use it in the action + userGroupSave.PersistedUserGroup = persisted; + + var existing = UserService.GetUserGroupByAlias(userGroupSave.Alias); + if (existing != null && existing.Id != userGroupSave.PersistedUserGroup.Id) + { + actionContext.ModelState.AddModelError("Alias", "A user group with this alias already exists"); + } + + //TODO: Validate the name is unique? + + if (actionContext.ModelState.IsValid == false) + { + //if it is not valid, do not continue and return the model state + actionContext.Response = actionContext.Request.CreateValidationErrorResponse(actionContext.ModelState); + return; + } + + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/UsersController.cs b/src/Umbraco.Web/Editors/UsersController.cs index 193322e223..d95b50134d 100644 --- a/src/Umbraco.Web/Editors/UsersController.cs +++ b/src/Umbraco.Web/Editors/UsersController.cs @@ -453,42 +453,14 @@ namespace Umbraco.Web.Editors return display; } + [UserGroupValidate] public UserGroupDisplay PostSaveUserGroup(UserGroupSave userGroupSave) { if (userGroupSave == null) throw new ArgumentNullException("userGroupSave"); + + Services.UserService.Save(userGroupSave.PersistedUserGroup, userGroupSave.Users.ToArray()); - if (ModelState.IsValid == false) - { - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); - } - - var intId = userGroupSave.Id.TryConvertTo(); - if (intId.Success == false) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var found = Services.UserService.GetUserGroupById(intId.Result); - if (found == null) - throw new HttpResponseException(HttpStatusCode.NotFound); - - var hasErrors = false; - - var existing = Services.UserService.GetUserGroupByAlias(userGroupSave.Alias); - if (existing != null && existing.Id != userGroupSave.Id) - { - ModelState.AddModelError("Alias", "A user group with this alias already exists"); - hasErrors = true; - } - //TODO: Validate the name is unique? - - if (hasErrors) - throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState)); - - //merge the save data onto the user group - var userGroup = Mapper.Map(userGroupSave, found); - - Services.UserService.Save(userGroup, userGroupSave.Users.ToArray()); - - var display = Mapper.Map(userGroup); + var display = Mapper.Map(userGroupSave.PersistedUserGroup); display.AddSuccessNotification(Services.TextService.Localize("speechBubbles/operationSavedHeader"), Services.TextService.Localize("speechBubbles/editUserGroupSaved")); return display; diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs index d34ec6cf67..8696d2695a 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupBasic.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -9,6 +10,7 @@ namespace Umbraco.Web.Models.ContentEditing public UserGroupBasic() { Notifications = new List(); + Sections = Enumerable.Empty
(); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs index fdeab12e05..7be878d33d 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupDisplay.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Runtime.Serialization; namespace Umbraco.Web.Models.ContentEditing @@ -6,6 +7,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataContract(Name = "userGroup", Namespace = "")] public class UserGroupDisplay : UserGroupBasic { + public UserGroupDisplay() + { + Users = Enumerable.Empty(); + } + [DataMember(Name = "users")] public IEnumerable Users { get; set; } } diff --git a/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs b/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs index a594e756dd..fdb9aced0f 100644 --- a/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs +++ b/src/Umbraco.Web/Models/ContentEditing/UserGroupSave.cs @@ -1,16 +1,25 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Runtime.Serialization; +using Newtonsoft.Json; +using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Models.ContentEditing { [DataContract(Name = "userGroup", Namespace = "")] public class UserGroupSave : EntityBasic, IValidatableObject { - [DataMember(Name = "id", IsRequired = true)] + /// + /// The action to perform when saving this user group + /// + /// + /// If either of the Publish actions are specified an exception will be thrown. + /// + [DataMember(Name = "action", IsRequired = true)] [Required] - public new int Id { get; set; } - + public ContentSaveAction Action { get; set; } + [DataMember(Name = "alias", IsRequired = true)] [Required] public override string Alias { get; set; } @@ -27,6 +36,12 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "startMediaId")] public int StartMediaId { get; set; } + /// + /// The real persisted user group + /// + [JsonIgnore] + internal IUserGroup PersistedUserGroup { get; set; } + public IEnumerable Validate(ValidationContext validationContext) { //TODO: Add other server side validation diff --git a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs index 07697c2698..36774bdf85 100644 --- a/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/UserModelMapper.cs @@ -22,8 +22,9 @@ namespace Umbraco.Web.Models.Mapping { public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext) { - //Used for merging existing UserGroupSave to an existing IUserGroup instance - this will not create an IUserGroup instance! + config.CreateMap() + .ConstructUsing((UserGroupSave save) => new UserGroup() { CreateDate = DateTime.Now }) .IgnoreAllUnmapped() .ForMember(user => user.Alias, expression => expression.MapFrom(save => save.Alias)) .ForMember(user => user.Name, expression => expression.MapFrom(save => save.Name)) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 65172f3b1e..587e26f0fd 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -334,6 +334,7 @@ +