diff --git a/gms-ui/package-lock.json b/gms-ui/package-lock.json index f41f0cbc8b890d9a1afcf665f64a366ceeba8c3c..e988bbac737fe4b8cd3ca5ae0aa49607569bfbe9 100644 --- a/gms-ui/package-lock.json +++ b/gms-ui/package-lock.json @@ -2367,13 +2367,12 @@ "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, "bootstrap-vue": { - "version": "2.0.0-rc.24", - "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.24.tgz", - "integrity": "sha512-8rA/I9tOvpNVIuMKD3rdlrUqgVdPEw4vPI0X8OeFJcG2hHvCHeZDF7FmWqxSeehIrUHGDV17HlTGSuP/v1Sp5g==", + "version": "2.0.0-rc.27", + "resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.27.tgz", + "integrity": "sha512-gXdpt2IsKbmC3SU0bf/RgldWwgrXK7G47yslOtkk4OA1z6fOzb2orM2vU5L8NCNZQomYax9LapRucv+8PByfdA==", "requires": { "@nuxt/opencollective": "^0.2.2", "bootstrap": "^4.3.1", - "core-js": ">=2.6.5 <3.0.0", "popper.js": "^1.15.0", "portal-vue": "^2.1.5", "vue-functional-data-merge": "^3.1.0" diff --git a/gms-ui/package.json b/gms-ui/package.json index f963d0c13fedab73af4b738b028ce644e54d8418..2b6cd4b52fb816a543ea4bd377af3de87a79cadb 100644 --- a/gms-ui/package.json +++ b/gms-ui/package.json @@ -11,7 +11,7 @@ "@fortawesome/fontawesome-svg-core": "^1.2.19", "@fortawesome/free-solid-svg-icons": "^5.9.0", "@fortawesome/vue-fontawesome": "^0.1.6", - "bootstrap-vue": "^2.0.0-rc.19", + "bootstrap-vue": "^2.0.0-rc.27", "core-js": "^2.6.5", "vue": "^2.6.10", "vuex": "^3.1.1" diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue index c5635dbef5351ad33721a430cecb0fdec2c49c73..ed823404a633fc965f37c41d34c5a7d70b9994b2 100644 --- a/gms-ui/src/App.vue +++ b/gms-ui/src/App.vue @@ -11,6 +11,7 @@ import TopMenu from './components/TopMenu.vue'; import Main from './components/Main.vue'; import { mapState } from 'vuex'; +//import { ToastPlugin } from 'bootstrap-vue'; export default { name: 'app', @@ -23,6 +24,15 @@ export default { }), mounted: function() { this.$store.commit('fetchGroupsModel'); + + var self = this; + document.addEventListener('apiError', function (event) { + self.$bvToast.toast(event.message, { + title: "Error", + variant: 'danger', + solid: true + }); + }) } } </script> diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js index 1fc56867c964a2cb00b49a2da4ffb2ea37e995c4..00c8879d5034b84f0cad4c26675ed209efb57f49 100644 --- a/gms-ui/src/api/server/index.js +++ b/gms-ui/src/api/server/index.js @@ -1,9 +1,43 @@ const BASE_API_URL = "http://localhost:8081/" +function apiRequest(url, data) { + return new Promise((resolve, reject) => { + fetch(url, data) + .then(response => { + if([200, 201, 204, 400].includes(response.status)) { // valid status codes + resolve(response.json()); + } else { + response.json().then(jsonValue => dispatchApiErrorEvent(jsonValue)); + } + }) + .catch(error => { + dispatchApiErrorEvent(error); + }); + }); +} + +function dispatchApiErrorEvent(error) { + let message; + if(error.message) { + message = error.message; + } else { + message = 'Generic error'; + } + + let event = new CustomEvent('apiError'); + event.message = message; + document.dispatchEvent(event); +} + export default { fetchGroupsModel (input) { - let url = BASE_API_URL + 'groups?groupId=' + input.selectedGroupId + '&tab=' + input.selectedTab + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage; - return fetch(url, { + let url = BASE_API_URL + + 'groups?groupId=' + input.selectedGroupId + + '&tab=' + input.selectedTab + + '&paginatorPageSize=' + input.paginatorPageSize + + '&paginatorPage=' + input.paginatorPage + + '&page=' + input.page; + return apiRequest(url, { method: 'GET', cache: 'no-cache', credentials: 'include', @@ -11,6 +45,55 @@ export default { 'Content-Type': 'application/json', 'Accept': 'application/json', } - }).then(response => response.json()); + }); + }, + addGroup (newGroupName, input) { + let url = BASE_API_URL + 'group'; + return apiRequest(url, { + method: 'POST', + cache: 'no-cache', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify({ + newGroupName: newGroupName, + parentGroupId: input.selectedGroupId, + paginatorPageSize: input.paginatorPageSize, + paginatorPage: input.paginatorPage + }) + }); + }, + renameGroup (groupId, newGroupName, input) { + let url = BASE_API_URL + 'group/' + groupId; + return apiRequest(url, { + method: 'PUT', + cache: 'no-cache', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify({ + newGroupName: newGroupName, + paginatorPageSize: input.paginatorPageSize, + paginatorPage: input.paginatorPage + }) + }); + }, + deleteGroup (groupId, input) { + let url = BASE_API_URL + 'group/' + groupId + + '?paginatorPageSize=' + input.paginatorPageSize + + '&paginatorPage=' + input.paginatorPage; + return apiRequest(url, { + method: 'DELETE', + cache: 'no-cache', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + } + }); } }; diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue index 38fc4bcedb6eec49e1884f0a16a5e01c252ea6b9..bc37ed96aa7651c79370caafd5402b36cbec738b 100644 --- a/gms-ui/src/components/GroupsPanel.vue +++ b/gms-ui/src/components/GroupsPanel.vue @@ -8,12 +8,13 @@ <div id="groups-list"> <b-list-group v-for="group in model.groupsPanel.items"> <b-list-group-item href="#" v-on:click="openGroup(group)"> - <span class="float-left">{{group.name}}</span> + <span class="float-left">{{group.groupName}}</span> <span v-if="group.permissions.includes('ADMIN')" class="float-right"> - <a href="#" v-on:click="renameGroup(group)"> + <a href="#" v-on:click.stop="openRenameGroupModal(group)" title="Rename"> <font-awesome-icon icon="edit"></font-awesome-icon> </a> - <a href="#" v-on:click="deleteGroup(group)"> + + <a href="#" v-on:click.stop="openDeleteGroupModal(group)" class="text-danger" title="Delete"> <font-awesome-icon icon="trash"></font-awesome-icon> </a> </span> @@ -46,14 +47,22 @@ </div> <div class="text-center"> </div> + <RenameGroupModal ref="renameGroupModal" /> + <ConfirmDeleteGroupModal ref="confirmDeleteGroupModal" /> </b-tab> </template> <script> +import RenameGroupModal from './modals/RenameGroupModal.vue' +import ConfirmDeleteGroupModal from './modals/ConfirmDeleteGroupModal.vue' import { mapState, mapActions } from 'vuex' export default { name: 'GroupsPanel', + components: { + RenameGroupModal, + ConfirmDeleteGroupModal + }, computed: mapState({ model: state => state.model, input: state => state.input @@ -70,14 +79,14 @@ export default { }, methods: { openGroup: function(group) { - this.$store.state.input.selectedGroupId = group.id; + this.$store.state.input.selectedGroupId = group.groupId; this.$store.commit('fetchGroupsModel'); }, - renameGroup: function(group) { - console.log('rename ' + group.id); + openRenameGroupModal: function(group) { + this.$refs.renameGroupModal.openRenameGroupModal(group); }, - deleteGroup: function(group) { - console.log('del ' + group.id); + openDeleteGroupModal: function(group) { + this.$refs.confirmDeleteGroupModal.openDeleteGroupModal(group); }, setPage: function(page) { console.log('setPage ', page); diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue index 95aa9fb5532ed423159b01a2c419f2c421d783a3..87442ad1228f0d3d898baf363eea45048cb1888c 100644 --- a/gms-ui/src/components/Main.vue +++ b/gms-ui/src/components/Main.vue @@ -3,14 +3,14 @@ <GroupsBreadcrumb /> <div class=""> <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('ADMIN')">Add member</button> - <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('ADMIN')">Add group</button> - <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('ADMIN') || model.permissions.includes('MANAGE_MEMBERS')">Add collaborator</button> + <b-button variant="primary" class="float-right" v-if="model.permissions.includes('ADMIN')" v-b-modal.new-group-modal>Add group</b-button> + <button type="button" class="btn btn-primary float-right" v-if="model.permissions.includes('MANAGE_MEMBERS')">Add collaborator</button> </div> <b-tabs content-class="mt-3"> <GroupsPanel /> <MembersPanel /> - </b-tab> </b-tabs> + <NewGroupModal /> </div> </template> @@ -18,6 +18,7 @@ import GroupsBreadcrumb from './GroupsBreadcrumb.vue' import GroupsPanel from './GroupsPanel.vue' import MembersPanel from './MembersPanel.vue' +import NewGroupModal from './modals/NewGroupModal.vue' import { mapState } from 'vuex'; export default { @@ -25,16 +26,14 @@ export default { components: { GroupsBreadcrumb, GroupsPanel, - MembersPanel + MembersPanel, + NewGroupModal }, computed: mapState({ model: state => state.model }), methods: { - addGroup: function() { - - }, - addMemeber: function() { + addMember: function() { } } diff --git a/gms-ui/src/components/modals/ConfirmDeleteGroupModal.vue b/gms-ui/src/components/modals/ConfirmDeleteGroupModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..05d21834772896d0e468b3dddcbc0652f96994d9 --- /dev/null +++ b/gms-ui/src/components/modals/ConfirmDeleteGroupModal.vue @@ -0,0 +1,33 @@ +<template> + <b-modal id="confirm-delete-group-modal" title="Confirm action" ok-title="Delete" @ok="deleteGroup" ok-variant="danger"> + <b-form inline> + <p v-if="groupToDelete">Are you sure that you want to delete the group <strong>{{groupToDelete.groupName}}</strong>?</p> + </b-form> + </b-modal> +</template> + +<script> +import client from 'api-client'; + +export default { + name: 'ConfirmDeleteGroupModal', + data: function() { + return { + groupToDelete: null + } + }, + methods: { + openDeleteGroupModal: function(group) { + this.groupToDelete = group; + this.$bvModal.show('confirm-delete-group-modal'); + }, + deleteGroup: function() { + client.deleteGroup(this.groupToDelete.groupId, this.$store.state.input) + .then(res => { + this.$store.commit('updateGroupsPanel', res); + this.$bvModal.hide('confirm-delete-group-modal'); + }) + } + } +} +</script> diff --git a/gms-ui/src/components/modals/NewGroupModal.vue b/gms-ui/src/components/modals/NewGroupModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..e976b8c905986de3fc529b295615ab1e36e294fa --- /dev/null +++ b/gms-ui/src/components/modals/NewGroupModal.vue @@ -0,0 +1,65 @@ +<template> + <b-modal id="new-group-modal" title="Add group" @show="resetModal" ok-title="Add" @ok="addGroup"> + <b-form inline> + <label class="w-25" for="new-group-name-input">Group name:</label> + <b-form-input v-model="newGroupName" + id="new-group-name-input" + class="w-75" + aria-describedby="new-group-name-input-feedback" + :state="newGroupNameState" + v-on:input="resetError"> + </b-form-input> + <b-form-invalid-feedback id="new-group-name-input-feedback">{{newGroupNameError}}</b-form-invalid-feedback> + </b-form> + </b-modal> +</template> + +<script> +import client from 'api-client'; + +export default { + name: 'NewGroupModal', + computed: { + newGroupNameState() { + if(this.newGroupNameError) { + return 'invalid'; + } + } + }, + data: function() { + return { + newGroupName: null, + newGroupNameError: null + }; + }, + methods: { + resetModal: function() { + this.newGroupName = null; + this.resetError(); + }, + resetError: function() { + this.newGroupNameError = null; + }, + addGroup: function(event) { + // Prevent modal from closing + event.preventDefault(); + + if(!this.newGroupName) { + this.newGroupNameError = "Group name is required"; + return; + } + + let parent = this.$store.getters.selectedGroupId; + client.addGroup(this.newGroupName, this.$store.state.input) + .then(res => { + if(res.status === 400) { + this.newGroupNameError = res.message; + } else { + this.$store.commit('updateGroupsPanel', res); + this.$bvModal.hide('new-group-modal'); + } + }); + } + } +} +</script> diff --git a/gms-ui/src/components/modals/RenameGroupModal.vue b/gms-ui/src/components/modals/RenameGroupModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..42af1b98471b28b53ea4546354f8b9b0741af3ef --- /dev/null +++ b/gms-ui/src/components/modals/RenameGroupModal.vue @@ -0,0 +1,73 @@ +<template> + <b-modal id="rename-group-modal" title="Rename group" ok-title="Rename" @ok="renameGroup"> + <b-form inline> + <label class="w-25" for="new-group-name-input">Group name:</label> + <b-form-input v-model="newGroupName" + id="new-group-name-input" + class="w-75" + aria-describedby="new-group-name-input-feedback" + :state="newGroupNameState" + v-on:input="resetError"> + </b-form-input> + <b-form-invalid-feedback id="new-group-name-input-feedback">{{newGroupNameError}}</b-form-invalid-feedback> + </b-form> + </b-modal> +</template> + +<script> +import client from 'api-client'; + +export default { + name: 'RenameGroupModal', + computed: { + newGroupNameState() { + if(this.newGroupNameError) { + return 'invalid'; + } + } + }, + data: function() { + return { + groupId: null, + oldGroupName: null, + newGroupName: null, + newGroupNameError: null + }; + }, + methods: { + resetError: function() { + this.newGroupNameError = null; + }, + openRenameGroupModal: function(group) { + this.newGroupName = group.groupName; + this.groupId = group.groupId; + this.$bvModal.show('rename-group-modal'); + }, + renameGroup: function(event) { + // Prevent modal from closing + event.preventDefault(); + + if(!this.newGroupName) { + this.newGroupNameError = "Group name is required"; + return; + } + + if(this.oldGroupName === this.newGroupName) { + this.$bvModal.hide('rename-group-modal'); + return; + } + + let parent = this.$store.getters.selectedGroupId; + client.renameGroup(this.groupId, this.newGroupName, this.$store.state.input) + .then(res => { + if(res.status === 400) { + this.newGroupNameError = res.message; + } else { + this.$store.commit('updateGroupsPanel', res); + this.$bvModal.hide('rename-group-modal'); + } + }); + } + } +} +</script> diff --git a/gms-ui/src/plugins/bootstrap-vue.js b/gms-ui/src/plugins/bootstrap-vue.js index 7ad0b7c63b50dd355c0b04584f71b039cf5bdd25..c29a066a30dc2d0588c297632514a86edc1989cd 100644 --- a/gms-ui/src/plugins/bootstrap-vue.js +++ b/gms-ui/src/plugins/bootstrap-vue.js @@ -1,7 +1,8 @@ -import Vue from 'vue' +import Vue from 'vue'; -import BootstrapVue from 'bootstrap-vue' -import 'bootstrap/dist/css/bootstrap.min.css' -import 'bootstrap-vue/dist/bootstrap-vue.css' +import { BootstrapVue } from 'bootstrap-vue'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import 'bootstrap-vue/dist/bootstrap-vue.css'; -Vue.use(BootstrapVue) +Vue.use(BootstrapVue); +//Vue.use(ToastPlugin); diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js index dcf9906887a82740bc935afdcf71d9575664a89a..c6e95938d4ec09717f28576cf62fcf007f60a5f2 100644 --- a/gms-ui/src/store.js +++ b/gms-ui/src/store.js @@ -2,7 +2,7 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import client from 'api-client' +import client from 'api-client'; Vue.use(Vuex); @@ -15,7 +15,8 @@ export default new Vuex.Store({ selectedGroupId: 'ROOT', paginatorPageSize: 20, paginatorPage: 1, - selectedTab: 'groups' + selectedTab: 'groups', + page: 'main' } }, mutations: { @@ -24,6 +25,14 @@ export default new Vuex.Store({ .then(model => { this.state.model = model; }); + }, + updateGroupsPanel(state, groupsPanel) { + this.state.model.groupsPanel = groupsPanel; + } + }, + getters: { + selectedGroupId: state => { + return state.model.breadcrumbs[state.model.breadcrumbs.length - 1].groupId; } } }); diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java index 42fa7388f43e7a51de285fcec97ba8925fe88f24..c1b4c6a54f6cf8b42b984e0f92cef3b5059b235f 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java +++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SecurityConfig.java @@ -45,6 +45,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues(); + config.addAllowedMethod(HttpMethod.PUT); + config.addAllowedMethod(HttpMethod.DELETE); config.setAllowedOrigins(Arrays.asList(corsAllowedOrigin)); config.setAllowCredentials(true); source.registerCorsConfiguration("/**", config); diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java index 4ee7683fd7b167f1ba269aa677aa5f8bce0cda05..256cc9a0604f9db95131615db0333f740ce2a188 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java @@ -1,21 +1,32 @@ package it.inaf.ia2.gms.controller; import it.inaf.ia2.gms.authn.SessionData; +import it.inaf.ia2.gms.exception.BadRequestException; import it.inaf.ia2.gms.model.CreateGroupRequest; +import it.inaf.ia2.gms.model.GroupNode; import it.inaf.ia2.gms.model.GroupsModelRequest; import it.inaf.ia2.gms.model.GroupsModelResponse; +import it.inaf.ia2.gms.model.PaginatedData; +import it.inaf.ia2.gms.model.PaginatedModelRequest; +import it.inaf.ia2.gms.model.RenameGroupRequest; import it.inaf.ia2.gms.persistence.model.Group; import it.inaf.ia2.gms.persistence.model.User; import it.inaf.ia2.gms.service.GroupsModelService; import it.inaf.ia2.gms.service.GroupsService; import it.inaf.ia2.gms.service.UsersService; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -40,9 +51,44 @@ public class GroupsController { } @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity<String> createGroup(@Valid @RequestBody CreateGroupRequest createGroupRequest) { - User user = usersService.getUserById(session.getUserId()); - Group group = groupsService.addGroup(createGroupRequest.getParentGroupId(), createGroupRequest.getGroupName(), user); - return new ResponseEntity<>(group.getId(), HttpStatus.CREATED); + public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody CreateGroupRequest request) { + + User user = getUser(); + Group newGroup = groupsService.addGroup(request.getParentGroupId(), request.getNewGroupName(), user); + + PaginatedData<GroupNode> groupsPanel = getGroupsPanel(newGroup.getParentGroup(), request, user); + + return new ResponseEntity<>(groupsPanel, HttpStatus.CREATED); + } + + @PutMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity<PaginatedData<GroupNode>> renameGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) { + + User user = getUser(); + Group renamedGroup = groupsService.renameGroup(groupId, request.getNewGroupName(), user); + + PaginatedData<GroupNode> groupsPanel = getGroupsPanel(renamedGroup.getParentGroup(), request, user); + + return ResponseEntity.ok(groupsPanel); + } + + @DeleteMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity<?> deleteGroup(@PathVariable("groupId") String groupId, PaginatedModelRequest request) { + + User user = getUser(); + Group parent = groupsService.deleteGroup(groupId, user); + + PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request, user); + + return ResponseEntity.ok(groupsPanel); + } + + private User getUser() { + return usersService.getUserById(session.getUserId()); + } + + private PaginatedData<GroupNode> getGroupsPanel(Group parentGroup, PaginatedModelRequest paginatedRequest, User user) { + List<GroupNode> groupNodes = groupsService.getSubgroups(parentGroup, user); + return new PaginatedData<>(groupNodes, paginatedRequest.getPaginatorPage(), paginatedRequest.getPaginatorPageSize()); } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java index 039ecfe5de87742c49e5da4b04dbeb51342788ba..50f633f15518320125e89e2d79f58735b5c9195f 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java +++ b/gms/src/main/java/it/inaf/ia2/gms/exception/BadRequestException.java @@ -1,5 +1,9 @@ package it.inaf.ia2.gms.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.BAD_REQUEST) public class BadRequestException extends RuntimeException { public BadRequestException(String message) { diff --git a/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java index 2b07d8c30bb0f9c9f5df74611726acfc0f781dcc..e4f73db59e96d619a5cc6e8b8c0458347cd2ebb2 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java +++ b/gms/src/main/java/it/inaf/ia2/gms/exception/UnauthorizedException.java @@ -1,5 +1,9 @@ package it.inaf.ia2.gms.exception; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(value = HttpStatus.UNAUTHORIZED) public class UnauthorizedException extends RuntimeException { public UnauthorizedException(String message) { diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java deleted file mode 100644 index 1330e697373ab887171e3839102c2d9df31d2f72..0000000000000000000000000000000000000000 --- a/gms/src/main/java/it/inaf/ia2/gms/model/BaseModelRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package it.inaf.ia2.gms.model; - -public abstract class BaseModelRequest { - - private String page; - - public String getPage() { - return page; - } - - public void setPage(String page) { - this.page = page; - } -} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java index a62c852f9186227f7ebaa2a43abc3875eb3a8d2f..c7444a9667c480de21bd19dc7be602bb765060b7 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/CreateGroupRequest.java @@ -1,23 +1,23 @@ package it.inaf.ia2.gms.model; -public class CreateGroupRequest { +public class CreateGroupRequest extends PaginatedModelRequest { private String parentGroupId; - private String groupName; + private String newGroupName; - public String getParentGroupId() { - return parentGroupId; + public String getNewGroupName() { + return newGroupName; } - public void setParentGroupId(String parentGroupId) { - this.parentGroupId = parentGroupId; + public void setNewGroupName(String newGroupName) { + this.newGroupName = newGroupName; } - public String getGroupName() { - return groupName; + public String getParentGroupId() { + return parentGroupId; } - public void setGroupName(String groupName) { - this.groupName = groupName; + public void setParentGroupId(String parentGroupId) { + this.parentGroupId = parentGroupId; } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java index ccdaf286a6d995ac2ad29bba701abada20d95cee..6ca71a1e443eb6815c707eb81837e026d6b15e57 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java @@ -1,6 +1,5 @@ package it.inaf.ia2.gms.model; -import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -18,7 +17,6 @@ public class GroupNode { permissions = new HashSet<>(); } - @JsonProperty("id") public String getGroupId() { return groupId; } @@ -27,7 +25,6 @@ public class GroupNode { this.groupId = groupId; } - @JsonProperty("name") public String getGroupName() { return groupName; } diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java index 79e22f42680ad960288957a29d34b603c290b7d1..5c7a393ce19c8420069bc5b00f256b3f723bbb31 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelRequest.java @@ -1,9 +1,8 @@ package it.inaf.ia2.gms.model; -import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; -public class GroupsModelRequest extends BaseModelRequest { +public class GroupsModelRequest extends PaginatedModelRequest { @NotNull private String groupId; @@ -11,9 +10,8 @@ public class GroupsModelRequest extends BaseModelRequest { @NotNull private String tab; - @Min(1) - private int paginatorPage; - private int paginatorPageSize; + @NotNull + private String page; public String getGroupId() { return groupId; @@ -31,19 +29,11 @@ public class GroupsModelRequest extends BaseModelRequest { this.tab = tab; } - public int getPaginatorPage() { - return paginatorPage; - } - - public void setPaginatorPage(int paginatorPage) { - this.paginatorPage = paginatorPage; - } - - public int getPaginatorPageSize() { - return paginatorPageSize; + public String getPage() { + return page; } - public void setPaginatorPageSize(int paginatorPageSize) { - this.paginatorPageSize = paginatorPageSize; + public void setPage(String page) { + this.page = page; } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedModelRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedModelRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..371b90b548ab9794e7851f18689b7f37d4e56717 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/PaginatedModelRequest.java @@ -0,0 +1,26 @@ +package it.inaf.ia2.gms.model; + +import javax.validation.constraints.Min; + +public class PaginatedModelRequest { + + @Min(1) + private int paginatorPage; + private int paginatorPageSize; + + public int getPaginatorPage() { + return paginatorPage; + } + + public void setPaginatorPage(int paginatorPage) { + this.paginatorPage = paginatorPage; + } + + public int getPaginatorPageSize() { + return paginatorPageSize; + } + + public void setPaginatorPageSize(int paginatorPageSize) { + this.paginatorPageSize = paginatorPageSize; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/RenameGroupRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/RenameGroupRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..90feff7ea368b6932de9f903140d74a18f30bc17 --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/RenameGroupRequest.java @@ -0,0 +1,19 @@ +package it.inaf.ia2.gms.model; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +public class RenameGroupRequest extends PaginatedModelRequest { + + @NotNull + @Size(min = 1) + private String newGroupName; + + public String getNewGroupName() { + return newGroupName; + } + + public void setNewGroupName(String newGroupName) { + this.newGroupName = newGroupName; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java index 4be39d1f5dd4e8ebbd28aee5361cfc6b968684b3..c82324af529a173586df1a9a256acbf0cd842454 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java @@ -53,15 +53,15 @@ public class GroupsService { Group parent = getGroupById(parentId); + if (!getPermissions(parent, user).contains(Permission.ADMIN)) { + throw new UnauthorizedException("Missing admin privileges"); + } + if (parent.getChildrenGroups().stream() .anyMatch(g -> g.getName().equals(groupName))) { throw new BadRequestException("There is already a group named " + groupName); } - if (!getPermissions(parent, user).contains(Permission.ADMIN)) { - throw new UnauthorizedException("Missing admin privileges"); - } - Group group = new Group(); group.setId(UUID.randomUUID().toString()); group.setName(groupName); @@ -72,6 +72,42 @@ public class GroupsService { return group; } + public Group renameGroup(String groupId, String newGroupName, User user) { + + Group group = getGroupById(groupId); + + if (!getPermissions(group, user).contains(Permission.ADMIN)) { + throw new UnauthorizedException("Missing admin privileges"); + } + + if (group.getParentGroup().getChildrenGroups().stream() + .anyMatch(g -> g.getName().equals(newGroupName))) { + throw new BadRequestException("There is already a group named " + newGroupName); + } + + group.setName(newGroupName); + return groupsRepository.save(group); + } + + public Group deleteGroup(String groupId, User user) { + + Group group = getGroupById(groupId); + + if (!getPermissions(group, user).contains(Permission.ADMIN)) { + throw new UnauthorizedException("Missing admin privileges"); + } + + if (ROOT.equals(group.getId())) { + throw new UnauthorizedException("It is not possible to remove the ROOT"); + } + + Group parent = group.getParentGroup(); + + groupsRepository.delete(group); + + return parent; + } + @Transactional public List<GroupNode> getSubgroups(Group parent, User user) {