diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java index f6881005..ed15c8ee 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/GroupDao.java @@ -48,6 +48,23 @@ public class GroupDao { } } + /** + * Returns a group by ID. + * + * @param id Group ID + * @return Group + */ + public Group getActiveById(String id) { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null"); + q.setParameter("id", id); + try { + return (Group) q.getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + /** * Creates a new group. * @@ -99,6 +116,10 @@ public class GroupDao { q.setParameter("dateNow", dateNow); q.executeUpdate(); + q = em.createQuery("update Group g set g.parentId = null where g.parentId = :groupId and g.deleteDate is null"); + q.setParameter("groupId", groupDb.getId()); + q.executeUpdate(); + // Create audit log AuditLogUtil.create(groupDb, AuditLogType.DELETE, userId); } @@ -211,7 +232,7 @@ public class GroupDao { return groupDtoList; } - + /** * Recursively search group's parents. * @@ -232,5 +253,30 @@ public class GroupDao { } } } + + /** + * Update a group. + * + * @param group Group to update + * @param userId User ID + * @return Updated group + */ + public Group update(Group group, String userId) { + EntityManager em = ThreadLocalContext.get().getEntityManager(); + + // Get the group + Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null"); + q.setParameter("id", group.getId()); + Group groupFromDb = (Group) q.getSingleResult(); + + // Update the group + groupFromDb.setName(group.getName()); + groupFromDb.setParentId(group.getParentId()); + + // Create audit log + AuditLogUtil.create(groupFromDb, AuditLogType.UPDATE, userId); + + return groupFromDb; + } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java index 4beae0e6..778ba2e6 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/GroupResource.java @@ -9,6 +9,7 @@ import javax.json.JsonObjectBuilder; import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; +import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -83,6 +84,87 @@ public class GroupResource extends BaseResource { return Response.ok().entity(response.build()).build(); } + /** + * Update a group. + * + * @return Response + */ + @POST + @Path("{groupName: [a-zA-Z0-9_]+}") + public Response update(@PathParam("groupName") String groupName, + @FormParam("parent") String parentName, + @FormParam("name") String name) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + // Validate input + name = ValidationUtil.validateLength(name, "name", 1, 50, false); + ValidationUtil.validateAlphanumeric(name, "name"); + + // Get the group (by its old name) + GroupDao groupDao = new GroupDao(); + Group group = groupDao.getActiveByName(groupName); + if (group == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + // Avoid duplicates + Group existingGroup = groupDao.getActiveByName(name); + if (existingGroup != null && existingGroup.getId() != group.getId()) { + throw new ClientException("GroupAlreadyExists", MessageFormat.format("This group already exists: {0}", name)); + } + + // Validate parent + String parentId = null; + if (!Strings.isNullOrEmpty(parentName)) { + Group parentGroup = groupDao.getActiveByName(parentName); + if (parentGroup == null) { + throw new ClientException("ParentGroupNotFound", MessageFormat.format("This group does not exists: {0}", parentName)); + } + parentId = parentGroup.getId(); + } + + // Update the group + groupDao.update(group.setName(name) + .setParentId(parentId), principal.getId()); + + // Always return OK + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } + + /** + * Delete a group. + * + * @return Response + */ + @DELETE + @Path("{groupName: [a-zA-Z0-9_]+}") + public Response delete(@PathParam("groupName") String groupName) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + // Get the group + GroupDao groupDao = new GroupDao(); + Group group = groupDao.getActiveByName(groupName); + if (group == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + // Delete the group + groupDao.delete(group.getId(), principal.getId()); + + // Always return OK + JsonObjectBuilder response = Json.createObjectBuilder() + .add("status", "ok"); + return Response.ok().entity(response.build()).build(); + } + /** * Add a user to a group. * @@ -213,4 +295,40 @@ public class GroupResource extends BaseResource { .add("groups", groups); return Response.ok().entity(response.build()).build(); } + + /** + * Get a group. + * + * @param groupName Group name + * @return Response + */ + @GET + @Path("{groupName: [a-zA-Z0-9_]+}") + public Response get(@PathParam("groupName") String groupName) { + if (!authenticate()) { + throw new ForbiddenClientException(); + } + checkBaseFunction(BaseFunction.ADMIN); + + // Get the group + GroupDao groupDao = new GroupDao(); + Group group = groupDao.getActiveByName(groupName); + if (group == null) { + return Response.status(Status.NOT_FOUND).build(); + } + + // Build the response + JsonObjectBuilder response = Json.createObjectBuilder() + .add("name", group.getName()); + + // Get the parent + if (group.getParentId() != null) { + Group parentGroup = groupDao.getActiveById(group.getParentId()); + response.add("parent", parentGroup.getName()); + } + + // TODO Add members + + return Response.ok().entity(response.build()).build(); + } } diff --git a/docs-web/src/main/webapp/src/app/docs/app.js b/docs-web/src/main/webapp/src/app/docs/app.js index 1d29aee4..839420e3 100644 --- a/docs-web/src/main/webapp/src/app/docs/app.js +++ b/docs-web/src/main/webapp/src/app/docs/app.js @@ -106,6 +106,33 @@ angular.module('docs', } } }) + .state('settings.group', { + url: '/group', + views: { + 'settings': { + templateUrl: 'partial/docs/settings.group.html', + controller: 'SettingsGroup' + } + } + }) + .state('settings.group.edit', { + url: '/edit/:name', + views: { + 'group': { + templateUrl: 'partial/docs/settings.group.edit.html', + controller: 'SettingsGroupEdit' + } + } + }) + .state('settings.group.add', { + url: '/add', + views: { + 'group': { + templateUrl: 'partial/docs/settings.group.edit.html', + controller: 'SettingsGroupEdit' + } + } + }) .state('settings.vocabulary', { url: '/vocabulary', views: { diff --git a/docs-web/src/main/webapp/src/app/docs/controller/SettingsGroup.js b/docs-web/src/main/webapp/src/app/docs/controller/SettingsGroup.js new file mode 100644 index 00000000..2f28b1f9 --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/controller/SettingsGroup.js @@ -0,0 +1,24 @@ +'use strict'; + +/** + * Settings group page controller. + */ +angular.module('docs').controller('SettingsGroup', function($scope, $state, Restangular) { + /** + * Load groups from server. + */ + $scope.loadGroups = function() { + Restangular.one('group').get().then(function(data) { + $scope.groups = data.groups; + }); + }; + + $scope.loadGroups(); + + /** + * Edit a group. + */ + $scope.editGroup = function(group) { + $state.go('settings.group.edit', { name: group.name }); + }; +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/controller/SettingsGroupEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/SettingsGroupEdit.js new file mode 100644 index 00000000..3bb2f7f2 --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/controller/SettingsGroupEdit.js @@ -0,0 +1,87 @@ +'use strict'; + +/** + * Settings group edition page controller. + */ +angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog, $state, $stateParams, Restangular, $q) { + /** + * Returns true if in edit mode (false in add mode). + */ + $scope.isEdit = function() { + return $stateParams.name; + }; + + /** + * In edit mode, load the current group. + */ + if ($scope.isEdit()) { + Restangular.one('group', $stateParams.name).get().then(function(data) { + $scope.group = data; + }); + } + + /** + * Update the current group. + */ + $scope.edit = function() { + var promise = null; + var group = angular.copy($scope.group); + + if ($scope.isEdit()) { + promise = Restangular + .one('group', $stateParams.name) + .post('', group); + } else { + promise = Restangular + .one('group') + .put(group); + } + + promise.then(function() { + $scope.loadGroups(); + if ($scope.isEdit()) { + $state.go('settings.group'); + } else { + // Go to edit this group to add members + $state.go('settings.group.edit', { name: group.name }); + } + }); + }; + + /** + * Delete the current group. + */ + $scope.remove = function () { + var title = 'Delete group'; + var msg = 'Do you really want to delete this group?'; + var btns = [{result:'cancel', label: 'Cancel'}, {result:'ok', label: 'OK', cssClass: 'btn-primary'}]; + + $dialog.messageBox(title, msg, btns, function(result) { + if (result == 'ok') { + Restangular.one('group', $stateParams.name).remove().then(function() { + $scope.loadGroups(); + $state.go('settings.group'); + }, function () { + $state.go('settings.group'); + }); + } + }); + }; + + /** + * Returns a promise for typeahead group. + */ + $scope.getGroupTypeahead = function($viewValue) { + var deferred = $q.defer(); + Restangular.one('group') + .getList('', { + sort_column: 1, + asc: true + }).then(function(data) { + deferred.resolve(_.pluck(_.filter(data.groups, function(group) { + return group.name.indexOf($viewValue) !== -1; + }), 'name')); + }); + return deferred.promise; + }; +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js b/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js index 792990c2..44b93ea6 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/SettingsUserEdit.js @@ -31,12 +31,12 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog, if ($scope.isEdit()) { promise = Restangular - .one('user', $stateParams.username) - .post('', user); + .one('user', $stateParams.username) + .post('', user); } else { promise = Restangular - .one('user') - .put(user); + .one('user') + .put(user); } promise.then(function() { diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index c9623ec0..3793ffa0 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -60,6 +60,8 @@ + + @@ -109,7 +111,7 @@ Tags
Name | +
---|
{{ group.name }} | +