From a89c5c103f8f07afb8cdd06fd81a3195e4b7fa8b Mon Sep 17 00:00:00 2001 From: Sonia Zorba <sonia.zorba@inaf.it> Date: Fri, 26 Jun 2020 23:00:52 +0200 Subject: [PATCH] Improved navigation using Vue Router; Improved permission editing; Bugfixes and refactoring --- gms-ui/src/App.vue | 21 +- gms-ui/src/api/mock/index.js | 3 + gms-ui/src/api/server/index.js | 200 +++++++++++------- gms-ui/src/assets/logo.png | Bin 6849 -> 0 bytes .../src/components/GenericSearchResults.vue | 21 +- gms-ui/src/components/GroupsBreadcrumb.vue | 8 +- gms-ui/src/components/GroupsPanel.vue | 9 +- gms-ui/src/components/Main.vue | 28 +-- gms-ui/src/components/MembersPanel.vue | 12 +- gms-ui/src/components/Paginator.vue | 4 - gms-ui/src/components/PermissionsPanel.vue | 33 ++- gms-ui/src/components/TopMenu.vue | 22 +- gms-ui/src/components/User.vue | 6 +- gms-ui/src/components/UserSearchResult.vue | 23 +- .../src/components/modals/AddGroupModal.vue | 11 +- .../src/components/modals/AddMemberModal.vue | 3 - .../components/modals/AddPermissionModal.vue | 35 ++- .../modals/ConfirmRemoveMemberModal.vue | 5 +- .../modals/ConfirmRemovePermissionModal.vue | 5 +- .../src/components/modals/EditGroupModal.vue | 11 +- gms-ui/src/components/modals/SearchUser.vue | 11 +- gms-ui/src/main.js | 8 +- gms-ui/src/router.js | 21 ++ gms-ui/src/store.js | 133 +++++++----- .../gms/controller/PermissionsController.java | 21 +- .../ia2/gms/controller/UsersController.java | 2 +- .../ia2/gms/manager/PermissionsManager.java | 5 + .../model/request/AddPermissionRequest.java | 10 + .../request/UpdatePermissionRequest.java | 39 ++++ .../ia2/gms/persistence/PermissionsDAO.java | 18 ++ .../ia2/gms/service/PermissionsService.java | 8 + 31 files changed, 465 insertions(+), 271 deletions(-) delete mode 100644 gms-ui/src/assets/logo.png create mode 100644 gms-ui/src/router.js create mode 100644 gms/src/main/java/it/inaf/ia2/gms/model/request/UpdatePermissionRequest.java diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue index cd5b2f3..aa5de32 100644 --- a/gms-ui/src/App.vue +++ b/gms-ui/src/App.vue @@ -2,9 +2,7 @@ <div id="app" v-if="model"> <TopMenu v-bind:user="model.user" /> <div class="container"> - <Main v-if="page === 'main'" /> - <GenericSearchResults v-if="page === 'search'" /> - <UserSearchResult v-if="page === 'userSearch'" /> + <router-view></router-view> </div> <div id="loading" v-if="loading"> <div id="spinner-wrapper"> @@ -16,21 +14,13 @@ <script> import TopMenu from './components/TopMenu.vue'; -import Main from './components/Main.vue'; -import GenericSearchResults from './components/GenericSearchResults.vue'; -import UserSearchResult from './components/UserSearchResult.vue'; -import { - mapState -} from 'vuex'; +import { mapState } from 'vuex'; import client from 'api-client'; export default { name: 'app', components: { - TopMenu, - Main, - GenericSearchResults, - UserSearchResult + TopMenu }, computed: mapState({ model: state => state.model, @@ -52,10 +42,7 @@ export default { }); // retrieve the initial model - client.fetchHomePageModel(this.input) - .then(model => { - this.$store.commit('updateHomePageModel', model); - }); + this.$store.dispatch('loadHomePageModel'); setInterval(client.keepAlive, 60000); } diff --git a/gms-ui/src/api/mock/index.js b/gms-ui/src/api/mock/index.js index 33bd82d..c1c8e00 100644 --- a/gms-ui/src/api/mock/index.js +++ b/gms-ui/src/api/mock/index.js @@ -47,6 +47,9 @@ export default { addPermission() { return fetch(permissionsPanel); }, + updatePermission() { + return fetch({"permission": "ADMIN"}); + }, getPermission() { return fetch(permission); }, diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js index 4006f18..f7079c8 100644 --- a/gms-ui/src/api/server/index.js +++ b/gms-ui/src/api/server/index.js @@ -1,35 +1,45 @@ const BASE_API_URL = process.env.VUE_APP_API_BASE_URL; -function apiRequest(url, options, showLoading = true) { +import axios from 'axios'; + +function apiRequest(options, showLoading = true, handleValidationErrors = false) { if (showLoading) { loading(true); } - return new Promise((resolve) => { - fetch(url, options) + return new Promise((resolve, reject) => { + axios(options) .then(response => { - loading(false); - if ([200, 201, 204, 400].includes(response.status)) { // valid status codes - if (response.status === 204) { - resolve({}); - } else { - resolve(response.json()); - } + if (response.status === 204) { + resolve({}); } else { - response.json().then(jsonValue => dispatchApiErrorEvent(jsonValue)); + resolve(response.data); } + loading(false); }) .catch(error => { + if(handleValidationErrors && error.response && error.response.status === 400) { + reject(error.response.data); + } else { + dispatchApiErrorEvent(error); + } loading(false); - dispatchApiErrorEvent(error); }); }); } function dispatchApiErrorEvent(error) { let event = new CustomEvent('apiError'); + let errorMessage; + if (error.response && error.response.data && error.response.data.message) { + errorMessage = error.response.data.message; + } else if (error.message) { + errorMessage = error.message; + } else { + errorMessage = 'Unknown error'; + } event.message = { title: error.error || 'Error', - body: error.message || 'Unknown error' + body: errorMessage }; document.dispatchEvent(event); } @@ -47,13 +57,14 @@ export default { 'home?groupId=' + input.selectedGroupId + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, @@ -66,13 +77,14 @@ export default { if (input.searchFilter !== null) { url += '&searchFilter=' + input.searchFilter; } - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, @@ -85,13 +97,14 @@ export default { if (input.searchFilter !== null) { url += '&searchFilter=' + input.searchFilter; } - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, @@ -100,13 +113,14 @@ export default { 'members?groupId=' + input.selectedGroupId + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, @@ -115,54 +129,57 @@ export default { 'permissions?groupId=' + input.selectedGroupId + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, addGroup(newGroupName, leaf, input) { let url = BASE_API_URL + 'group'; - return apiRequest(url, { + return apiRequest({ method: 'POST', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' }, - body: JSON.stringify({ + data: { newGroupName: newGroupName, parentGroupId: input.selectedGroupId, paginatorPageSize: input.paginatorPageSize, paginatorPage: input.paginatorPage, searchFilter: input.searchFilter, leaf: leaf - }) - }); + } + }, true, true); }, updateGroup(groupId, newGroupName, leaf, input) { let url = BASE_API_URL + 'group/' + groupId; - return apiRequest(url, { + return apiRequest({ method: 'PUT', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' }, - body: JSON.stringify({ + data: { newGroupName: newGroupName, leaf: leaf, paginatorPageSize: input.paginatorPageSize, paginatorPage: input.paginatorPage, searchFilter: input.searchFilter - }) - }); + } + }, true, true); }, removeGroup(groupId, input) { let url = BASE_API_URL + 'group/' + groupId + @@ -171,80 +188,104 @@ export default { if (input.searchFilter !== null) { url += '&searchFilter=' + input.searchFilter; } - return apiRequest(url, { + return apiRequest({ method: 'DELETE', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, searchUser(searchInput) { let url = BASE_API_URL + 'users?search=' + searchInput; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { - 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, - addPermission(userId, permission, input) { + addPermission(userId, permission, input, override) { let url = BASE_API_URL + 'permission'; - return apiRequest(url, { + return apiRequest({ method: 'POST', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' }, - body: JSON.stringify({ + data: { groupId: input.selectedGroupId, userId: userId, permission: permission, + override: override, paginatorPageSize: input.paginatorPageSize, paginatorPage: input.paginatorPage - }) + } + }); + }, + updatePermission(groupId, userId, permission) { + let url = BASE_API_URL + 'permission'; + + return apiRequest({ + method: 'PUT', + url: url, + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Cache-Control': 'no-cache' + }, + data: { + groupId: groupId, + userId: userId, + permission: permission + } }); }, getPermission(groupId, userId) { let url = BASE_API_URL + 'permission?groupId=' + groupId + '&userId=' + userId; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, addMember(userId, permission, input) { let url = BASE_API_URL + 'member'; - return apiRequest(url, { + return apiRequest({ method: 'POST', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' }, - body: JSON.stringify({ + data: { groupId: input.selectedGroupId, userId: userId, permission: permission, paginatorPageSize: input.paginatorPageSize, paginatorPage: input.paginatorPage - }) + } }); }, removeMember(userId, removeAlsoPermission, input) { @@ -254,13 +295,14 @@ export default { '&removeAlsoPermission=' + removeAlsoPermission + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage; - return apiRequest(url, { + return apiRequest({ method: 'DELETE', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, @@ -270,13 +312,14 @@ export default { '&userId=' + userId + '&paginatorPageSize=' + input.paginatorPageSize + '&paginatorPage=' + input.paginatorPage; - return apiRequest(url, { + return apiRequest({ method: 'DELETE', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, @@ -284,36 +327,41 @@ export default { let url = BASE_API_URL + 'search?query=' + input.genericSearch.filter + '&page=' + input.genericSearch.paginatorPage + '&pageSize=' + input.genericSearch.paginatorPageSize; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, openUserSearchResult(userId) { let url = BASE_API_URL + 'search/user/' + userId; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include', + url: url, + withCredentials: true, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', + 'Cache-Control': 'no-cache' } }); }, keepAlive() { let url = BASE_API_URL + 'keepAlive'; - return apiRequest(url, { + return apiRequest({ method: 'GET', - cache: 'no-cache', - credentials: 'include' + url: url, + withCredentials: true, + headers: { + 'Cache-Control': 'no-cache' + } }, false); } }; diff --git a/gms-ui/src/assets/logo.png b/gms-ui/src/assets/logo.png deleted file mode 100644 index f3d2503fc2a44b5053b0837ebea6e87a2d339a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6849 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 diff --git a/gms-ui/src/components/GenericSearchResults.vue b/gms-ui/src/components/GenericSearchResults.vue index ea3ae44..e0277a7 100644 --- a/gms-ui/src/components/GenericSearchResults.vue +++ b/gms-ui/src/components/GenericSearchResults.vue @@ -17,11 +17,8 @@ </template> <script> -import client from 'api-client'; import Paginator from './Paginator.vue'; -import { - mapState -} from 'vuex'; +import { mapState } from 'vuex'; export default { name: 'GenericSearchResults', @@ -32,22 +29,26 @@ export default { model: state => state.model, input: state => state.input }), + created () { + this.updateSearchResults(); + }, + watch: { + // call again the method if the route changes + '$route': 'updateSearchResults' + }, methods: { openSearchResult: function(result) { switch (result.type) { case 'GROUP': - this.$store.commit('openGroup', result.id); + this.$store.dispatch('openGroup', result.id); break; case 'USER': - this.$store.dispatch('openUserPage', result.id); + this.$router.push({ path: `/user/${result.id}` }, () => {}); break; } }, updateSearchResults: function() { - client.search(this.input) - .then(results => { - this.$store.commit('displaySearchResults', results); - }); + this.$store.dispatch('search', this.$route.query.q); } } } diff --git a/gms-ui/src/components/GroupsBreadcrumb.vue b/gms-ui/src/components/GroupsBreadcrumb.vue index 629059b..283c847 100644 --- a/gms-ui/src/components/GroupsBreadcrumb.vue +++ b/gms-ui/src/components/GroupsBreadcrumb.vue @@ -1,7 +1,7 @@ <template> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> - <li class="breadcrumb-item" v-for="group in groups" v-bind:class="{ active: group.active }"> + <li class="breadcrumb-item" v-for="group in groups" v-bind:class="{ active: group.active }" v-bind:key="group.groupId"> <a href="#" v-on:click="changeBreadcrumb(group.groupId)" v-if="!group.active">{{group.groupName}}</a> <span v-if="group.active">{{group.groupName}}</span> </li> @@ -10,9 +10,7 @@ </template> <script> -import { - mapState -} from 'vuex'; +import { mapState } from 'vuex'; import client from 'api-client'; function buildItems(values) { @@ -49,7 +47,7 @@ export default { this.$store.commit('updateGroups', model); }); } else { - this.$store.commit('setTabIndex', 0); + this.$store.dispatch('changeTab', 0); } } } diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue index dfdb7d7..d7dac81 100644 --- a/gms-ui/src/components/GroupsPanel.vue +++ b/gms-ui/src/components/GroupsPanel.vue @@ -6,7 +6,7 @@ </b-col> </b-row> <div id="groups-list" v-if="model.groupsPanel !== null"> - <b-list-group v-for="group in model.groupsPanel.items"> + <b-list-group v-for="group in model.groupsPanel.items" v-bind:key="group.groupId"> <b-list-group-item href="#" v-on:click="openGroup(group)"> <span class="float-left">{{group.groupName}}</span> <span v-if="group.permission === 'ADMIN'" class="float-right"> @@ -32,10 +32,7 @@ import EditGroupModal from './modals/EditGroupModal.vue'; import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue'; import Paginator from './Paginator.vue'; -import { - mapState, - mapActions -} from 'vuex'; +import { mapState } from 'vuex'; import client from 'api-client'; import debounce from 'debounce'; // for delaying the input event (search filter) @@ -52,7 +49,7 @@ export default { }), methods: { openGroup: function(group) { - this.$store.commit('openGroup', group.groupId); + this.$store.dispatch('openGroup', group.groupId); }, openEditGroupModal: function(group) { this.$refs.editGroupModal.openEditGroupModal(group); diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue index 055309c..7c7829f 100644 --- a/gms-ui/src/components/Main.vue +++ b/gms-ui/src/components/Main.vue @@ -28,10 +28,7 @@ import PermissionsPanel from './PermissionsPanel.vue' import AddGroupModal from './modals/AddGroupModal.vue' import AddMemberModal from './modals/AddMemberModal.vue' import AddPermissionModal from './modals/AddPermissionModal.vue' -import { - mapState -} from 'vuex'; -import client from 'api-client'; +import { mapState } from 'vuex'; export default { name: 'Main', @@ -54,28 +51,7 @@ export default { }), methods: { tabChanged: function(tabIndex) { - // reset paginator - this.input.paginatorPage = 1; - switch (tabIndex) { - case 0: - client.fetchGroupsTab(this.input) - .then(model => { - this.$store.commit('updateGroups', model); - }); - break; - case 1: - client.fetchMembersPanel(this.input) - .then(panel => { - this.$store.commit('updateMembersPanel', panel); - }); - break; - case 2: - client.fetchPermissionsPanel(this.input) - .then(panel => { - this.$store.commit('updatePermissionsPanel', panel); - }); - break; - } + this.$store.dispatch('changeTab', tabIndex); }, openAddGroupModal: function() { this.$bvModal.show('add-group-modal'); diff --git a/gms-ui/src/components/MembersPanel.vue b/gms-ui/src/components/MembersPanel.vue index 30ea021..34af9ce 100644 --- a/gms-ui/src/components/MembersPanel.vue +++ b/gms-ui/src/components/MembersPanel.vue @@ -1,8 +1,8 @@ <template> <b-tab title="Members" v-if="model.permission === 'ADMIN' || model.permission === 'MANAGE_MEMBERS' || model.permission === 'VIEW_MEMBERS'"> <div v-if="model.membersPanel !== null"> - <b-list-group v-for="member in model.membersPanel.items" id="members-list"> - <b-list-group-item href="#" @click="openUser(member)"> + <b-list-group v-for="member in model.membersPanel.items" id="members-list" v-bind:key="member.memberId"> + <b-list-group-item href="#" @click.prevent="openUser(member)"> <div class="float-left"> <User :user="member" :anchor="false" /> </div> @@ -25,9 +25,7 @@ import client from 'api-client'; import User from './User.vue'; import Paginator from './Paginator.vue'; import ConfirmRemoveMemberModal from './modals/ConfirmRemoveMemberModal.vue'; -import { - mapState -} from 'vuex'; +import { mapState } from 'vuex'; export default { name: 'MembersPanel', @@ -54,13 +52,13 @@ export default { }); }, openUser(member) { - this.$store.dispatch('openUserPage', member.id); + this.$router.push({ path: `/user/${member.id}` }, () => {}); } } } function getMemberPermission(self, member) { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (self.model.permission === 'ADMIN') { let groupId = self.$store.state.input.selectedGroupId; client.getPermission(groupId, member.id) diff --git a/gms-ui/src/components/Paginator.vue b/gms-ui/src/components/Paginator.vue index b6ee2a0..32c89da 100644 --- a/gms-ui/src/components/Paginator.vue +++ b/gms-ui/src/components/Paginator.vue @@ -18,10 +18,6 @@ </template> <script> -import { - mapState -} from 'vuex'; - export default { name: 'Paginator', props: { diff --git a/gms-ui/src/components/PermissionsPanel.vue b/gms-ui/src/components/PermissionsPanel.vue index a8537de..8a30a8c 100644 --- a/gms-ui/src/components/PermissionsPanel.vue +++ b/gms-ui/src/components/PermissionsPanel.vue @@ -10,11 +10,30 @@ </tr> </thead> <tbody> - <tr v-for="up in model.permissionsPanel.items"> + <tr v-for="(up, index) in model.permissionsPanel.items" v-bind:key="index"> <td> <User :user="up.user" :anchor="true" /> </td> - <td>{{up.permission}}</td> + <td v-if="!up.editable"> + {{up.permission}} + <a href="#" v-on:click.stop.prevent="openPermissionEditor(index)"> + <font-awesome-icon icon="edit"></font-awesome-icon> + </a> + </td> + <td v-if="up.editable" class="col-3"> + <b-input-group> + <b-form-select v-model="up.permission"> + <option value="ADMIN">ADMIN</option> + <option value="MANAGE_MEMBERS">MANAGE_MEMBERS</option> + <option value="VIEW_MEMBERS">VIEW_MEMBERS</option> + </b-form-select> + <b-input-group-append> + <b-button @click="savePermission(up, index)"> + <font-awesome-icon icon="save"></font-awesome-icon> + </b-button> + </b-input-group-append> + </b-input-group> + </td> <td> <a href="#" v-on:click.stop="openRemovePermissionModal(up.user)" class="text-danger" title="Remove permission"> <font-awesome-icon icon="trash"></font-awesome-icon> @@ -59,6 +78,16 @@ export default { .then(panel => { this.$store.commit('updatePermissionsPanel', panel); }); + }, + openPermissionEditor(index) { + this.$store.commit('togglePermissionEditable', index); + }, + savePermission(up, index) { + let permission = up.permission; + client.updatePermission(this.input.selectedGroupId, up.user.id, permission) + .then(() => { + this.$store.commit('togglePermissionEditable', index); + }); } } } diff --git a/gms-ui/src/components/TopMenu.vue b/gms-ui/src/components/TopMenu.vue index fc6e5bc..b71a95c 100644 --- a/gms-ui/src/components/TopMenu.vue +++ b/gms-ui/src/components/TopMenu.vue @@ -10,7 +10,7 @@ <!-- Right aligned nav items --> <b-navbar-nav class="ml-auto"> <b-nav-form> - <b-form-input size="sm" class="mr-sm-2" placeholder="Search" v-model="input.genericSearch.filter" @keydown.native.enter="genericSearch"></b-form-input> + <b-form-input size="sm" class="mr-sm-2" placeholder="Search" v-model="input.genericSearch.filter" @keydown.native.enter.prevent="genericSearch"></b-form-input> <b-button size="sm" class="my-2 my-sm-0" type="button" v-on:click="genericSearch()">Search</b-button> </b-nav-form> <b-nav-item-dropdown :text="user" right v-if="user"> @@ -23,10 +23,7 @@ </template> <script> -import client from 'api-client'; -import { - mapState -} from 'vuex'; +import { mapState } from 'vuex'; export default { name: 'TopMenu', @@ -38,20 +35,13 @@ export default { }), methods: { showMainPage() { - this.$store.commit('showMainPage'); + this.$router.push('/', () => {}); }, - genericSearch(event) { - // prevent the page reload if enter is pressed - if (event) { - event.preventDefault(); - } - + genericSearch() { this.input.genericSearch.page = 1; this.input.genericSearch.pageSize = 20; - client.search(this.input) - .then(results => { - this.$store.commit('displaySearchResults', results); - }); + this.$router.push({ path: '/search', query: { q: this.input.genericSearch.filter } }, () => {}); + this.$store.dispatch('search', this.input.genericSearch.filter); } } } diff --git a/gms-ui/src/components/User.vue b/gms-ui/src/components/User.vue index 2c6c16e..7c2af04 100644 --- a/gms-ui/src/components/User.vue +++ b/gms-ui/src/components/User.vue @@ -1,12 +1,12 @@ <template> <div :id="'user-name-' + user.id"> - <component :is="anchor ? 'a' : 'span'" :href="anchor ? '#' : false" @click="openUser">{{user.displayName}}</component> + <component :is="anchor ? 'a' : 'span'" :href="anchor ? '#' : false" @click.prevent="openUser">{{user.displayName}}</component> <b-tooltip ref="user-tooltip" :target="'user-name-' + user.id" placement="bottom"> <div class="text-left"> <p><strong>User id</strong>: {{user.id}}</p> <p><strong>Identities</strong>:</p> <ul> - <li v-for="identity in user.identities" v-bind:key="identity.typedId"> + <li v-for="(identity, index) in user.identities" v-bind:key="index"> {{identity.email}} ({{identity.type}}) </li> </ul> @@ -24,7 +24,7 @@ export default { }, methods: { openUser() { - this.$store.dispatch('openUserPage', this.user.id); + this.$router.push({ path: `/user/${this.user.id}` }, () => {}); } } } diff --git a/gms-ui/src/components/UserSearchResult.vue b/gms-ui/src/components/UserSearchResult.vue index c648aff..d5ccf14 100644 --- a/gms-ui/src/components/UserSearchResult.vue +++ b/gms-ui/src/components/UserSearchResult.vue @@ -1,5 +1,5 @@ <template> -<div class="mt-sm-3" v-if="user !== null"> +<div class="mt-sm-3" v-if="user"> <b-button variant="primary" class="float-right" v-on:click="back()">Back</b-button> <h5><strong>{{user.displayName}}</strong>:</h5> @@ -29,7 +29,7 @@ <b-row> <b-col lg="10"> <b-list-group> - <b-list-group-item v-for="identity in user.identities" v-bind:key="identity.typedId"> + <b-list-group-item v-for="(identity, index) in user.identities" v-bind:key="index"> <dl class="mb-0 ml-0 row"> <dt class="col-3">Type</dt><dd class="col-9">{{identity.type}}</dd> <dt class="col-3">Email</dt><dd class="col-9">{{identity.email}}</dd> @@ -64,14 +64,11 @@ </b-col> </b-row> </b-container> - </div> </template> <script> -import { - mapState -} from 'vuex'; +import { mapState } from 'vuex'; export default { name: 'UserSearchResult', @@ -80,12 +77,22 @@ export default { groups: state => state.model.userSearchResults.groups, permissions: state => state.model.userSearchResults.permissions }), + created() { + this.openUserPage(); + }, + watch: { + // call again the method if the route changes + '$route': 'openUserPage' + }, methods: { back() { - this.$store.commit('backFromUserPage'); + this.$router.go(-1); + }, + openUserPage() { + this.$store.dispatch('openUserPage', this.$route.params.id); }, openGroup(groupId) { - this.$store.commit('openGroup', groupId); + this.$store.dispatch('openGroup', groupId); } } } diff --git a/gms-ui/src/components/modals/AddGroupModal.vue b/gms-ui/src/components/modals/AddGroupModal.vue index 7f3bcd1..8850297 100644 --- a/gms-ui/src/components/modals/AddGroupModal.vue +++ b/gms-ui/src/components/modals/AddGroupModal.vue @@ -50,12 +50,11 @@ export default { } else { client.addGroup(this.newGroupName, this.leaf, this.$store.state.input) .then(res => { - if (res.status === 400) { - this.newGroupNameError = res.message; - } else { - this.$store.commit('updateGroupsPanel', res); - this.$bvModal.hide('add-group-modal'); - } + this.$store.commit('updateGroupsPanel', res); + this.$bvModal.hide('add-group-modal'); + }) + .catch(res => { + this.newGroupNameError = res.message; }); } } diff --git a/gms-ui/src/components/modals/AddMemberModal.vue b/gms-ui/src/components/modals/AddMemberModal.vue index b021b10..13e91d9 100644 --- a/gms-ui/src/components/modals/AddMemberModal.vue +++ b/gms-ui/src/components/modals/AddMemberModal.vue @@ -7,9 +7,6 @@ <script> import client from 'api-client'; import SearchUser from './SearchUser.vue' -import { - mapState -} from 'vuex'; export default { name: 'AddMemberModal', diff --git a/gms-ui/src/components/modals/AddPermissionModal.vue b/gms-ui/src/components/modals/AddPermissionModal.vue index 9fdb437..b210f47 100644 --- a/gms-ui/src/components/modals/AddPermissionModal.vue +++ b/gms-ui/src/components/modals/AddPermissionModal.vue @@ -1,6 +1,9 @@ <template> -<b-modal id="add-permission-modal" title="Add permission" @shown="afterShow" ok-title="Add" @ok="addPermission"> +<b-modal id="add-permission-modal" title="Add permission" @show="beforeShow" @shown="afterShow" :ok-title="okTitle" @ok="addPermission" :ok-variant="okBtnVariant"> <SearchUser ref="searchUser" @searchUserEnter="addPermission" /> + <b-alert :show="!!existingPermission" variant="warning" class="mt-3"> + <strong>Warning</strong>: the user has already a permission ({{existingPermission}}). Click confirm to override it. + </b-alert> </b-modal> </template> @@ -13,7 +16,23 @@ export default { components: { SearchUser }, + computed: { + okBtnVariant: function() { + return this.existingPermission ? 'danger' : 'primary'; + }, + okTitle: function() { + return this.existingPermission ? 'Confirm' : 'Add'; + } + }, + data() { + return { + existingPermission: null + }; + }, methods: { + beforeShow() { + this.existingPermission = null; + }, afterShow: function() { this.$refs.searchUser.$refs.userInput.focus(); }, @@ -25,15 +44,23 @@ export default { let userId = this.$refs.searchUser.selectedUser; let permission = this.$refs.searchUser.permission; + let input = this.$store.state.input; if (!userId || !permission) { return; } - client.addPermission(userId, permission, this.$store.state.input) + client.getPermission(input.selectedGroupId, userId) .then(res => { - this.$store.commit('updatePermissionsPanel', res); - this.$bvModal.hide('add-permission-modal'); + if (res.permission && res.permission !== permission && !this.existingPermission) { + this.existingPermission = res.permission; + } else { + client.addPermission(userId, permission, input, !!this.existingPermission) + .then(res => { + this.$store.commit('updatePermissionsPanel', res); + this.$bvModal.hide('add-permission-modal'); + }); + } }); } } diff --git a/gms-ui/src/components/modals/ConfirmRemoveMemberModal.vue b/gms-ui/src/components/modals/ConfirmRemoveMemberModal.vue index 0d85f65..5227c1e 100644 --- a/gms-ui/src/components/modals/ConfirmRemoveMemberModal.vue +++ b/gms-ui/src/components/modals/ConfirmRemoveMemberModal.vue @@ -9,10 +9,7 @@ <script> import client from 'api-client'; -import { - mapState, - mapActions -} from 'vuex'; +import { mapState } from 'vuex'; export default { name: 'ConfirmRemoveGroupModal', diff --git a/gms-ui/src/components/modals/ConfirmRemovePermissionModal.vue b/gms-ui/src/components/modals/ConfirmRemovePermissionModal.vue index 978a590..e3d8b98 100644 --- a/gms-ui/src/components/modals/ConfirmRemovePermissionModal.vue +++ b/gms-ui/src/components/modals/ConfirmRemovePermissionModal.vue @@ -6,10 +6,7 @@ <script> import client from 'api-client'; -import { - mapState, - mapActions -} from 'vuex'; +import { mapState } from 'vuex'; export default { name: 'ConfirmRemovePermissionModal', diff --git a/gms-ui/src/components/modals/EditGroupModal.vue b/gms-ui/src/components/modals/EditGroupModal.vue index d699c64..bfd117c 100644 --- a/gms-ui/src/components/modals/EditGroupModal.vue +++ b/gms-ui/src/components/modals/EditGroupModal.vue @@ -58,12 +58,11 @@ export default { client.updateGroup(this.groupId, this.newGroupName, this.leaf, this.$store.state.input) .then(res => { - if (res.status === 400) { - this.newGroupNameError = res.message; - } else { - this.$store.commit('updateGroupsPanel', res); - this.$bvModal.hide('edit-group-modal'); - } + this.$store.commit('updateGroupsPanel', res); + this.$bvModal.hide('edit-group-modal'); + }) + .catch(res => { + this.newGroupNameError = res.message; }); } } diff --git a/gms-ui/src/components/modals/SearchUser.vue b/gms-ui/src/components/modals/SearchUser.vue index bb55488..ebb0287 100644 --- a/gms-ui/src/components/modals/SearchUser.vue +++ b/gms-ui/src/components/modals/SearchUser.vue @@ -1,7 +1,7 @@ <template> <b-form inline> <label class="w-25" for="user-input">Search:</label> - <b-form-input v-model="searchInput" id="user-input" ref="userInput" class="w-75 mb-2" aria-describedby="user-input-feedback" v-on:input="searchUser" @keydown.native.enter="enterPressed" placeholder="User"> + <b-form-input v-model="searchInput" id="user-input" ref="userInput" class="w-75 mb-2" aria-describedby="user-input-feedback" v-on:input="searchUser" @keydown.native.enter.prevent="enterPressed" placeholder="User"> </b-form-input> <label class="w-25" for="user-input" v-if="users.length > 0">Selected user:</label> <b-form-select v-model="selectedUser" :options="users" class="w-75" v-if="users.length > 0"></b-form-select> @@ -18,18 +18,15 @@ <script> import client from 'api-client'; -import { - mapState -} from 'vuex'; export default { name: 'SearchUser', properties: { actionOnEnter: Function }, - computed: mapState({ - model: state => state.model, - }), + computed: { + model: function() { return this.$store.state.model } + }, data: function() { return { searchInput: null, diff --git a/gms-ui/src/main.js b/gms-ui/src/main.js index 58c7221..255ab43 100644 --- a/gms-ui/src/main.js +++ b/gms-ui/src/main.js @@ -5,13 +5,14 @@ import store from './store.js' import './plugins/bootstrap-vue' import App from './App.vue' import { library } from '@fortawesome/fontawesome-svg-core' -import { faTrash, faEdit, faSpinner, faFolder, faUser } from '@fortawesome/free-solid-svg-icons' +import { faTrash, faEdit, faSpinner, faFolder, faUser, faSave } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import VueRouter from 'vue-router' Vue.use(VueRouter) +import router from './router.js'; -library.add(faTrash, faEdit, faSpinner, faFolder, faUser); +library.add(faTrash, faEdit, faSpinner, faFolder, faUser, faSave); Vue.component('font-awesome-icon', FontAwesomeIcon); @@ -19,5 +20,6 @@ Vue.config.productionTip = false; new Vue({ render: h => h(App), - store + store, + router }).$mount('#app'); diff --git a/gms-ui/src/router.js b/gms-ui/src/router.js new file mode 100644 index 0000000..a250a1f --- /dev/null +++ b/gms-ui/src/router.js @@ -0,0 +1,21 @@ +import VueRouter from 'vue-router'; + +import Main from './components/Main.vue'; +import GenericSearchResults from './components/GenericSearchResults.vue'; +import UserSearchResult from './components/UserSearchResult.vue'; + +export default new VueRouter({ + routes: [{ + path: '/', + component: Main + }, + { + path: '/search', + component: GenericSearchResults + }, + { + path: '/user/:id', + component: UserSearchResult + } + ] +}); diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js index f175ec5..fe02ab5 100644 --- a/gms-ui/src/store.js +++ b/gms-ui/src/store.js @@ -3,6 +3,7 @@ import Vue from 'vue'; import Vuex from 'vuex'; import client from 'api-client'; +import router from './router.js'; Vue.use(Vuex); @@ -17,9 +18,9 @@ export default new Vuex.Store({ permission: null, leaf: false, user: null, - genericSearchResults: [], + genericSearchResults: {}, userSearchResults: { - userLabel: null, + user: null, groups: {}, permissions: {} } @@ -37,84 +38,112 @@ export default new Vuex.Store({ paginatorPageSize: 20 } }, - loading: false, - previousPage: null, - page: 'main' + loading: false }, mutations: { updateHomePageModel(state, model) { - this.state.model.breadcrumbs = model.breadcrumbs; - this.state.model.groupsPanel = model.groupsPanel; - this.state.model.permission = model.permission; - this.state.model.user = model.user; - }, - openGroup(state, groupId) { - let input = this.state.input; - input.selectedGroupId = groupId; - input.searchFilter = ''; - client.fetchGroupsTab(input) - .then(model => { - this.commit('updateGroups', model); - if (model.leaf) { - // If there are no subgroups show the members panel - this.commit('setTabIndex', 1); - } else { - this.commit('setTabIndex', 0); - } - this.commit('showMainPage'); - }); + state.model.breadcrumbs = model.breadcrumbs; + state.model.groupsPanel = model.groupsPanel; + state.model.permission = model.permission; + state.model.user = model.user; }, updateGroups(state, model) { - this.state.model.breadcrumbs = model.breadcrumbs; - this.state.model.groupsPanel = model.groupsPanel; - this.state.model.permission = model.permission; - this.state.model.leaf = model.leaf; + state.model.breadcrumbs = model.breadcrumbs; + state.model.groupsPanel = model.groupsPanel; + state.model.permission = model.permission; + state.model.leaf = model.leaf; }, updateGroupsPanel(state, groupsPanel) { - this.state.model.groupsPanel = groupsPanel; - this.state.input.paginatorPage = groupsPanel.currentPage; + state.model.groupsPanel = groupsPanel; + state.input.paginatorPage = groupsPanel.currentPage; }, updatePermissionsPanel(state, permissionsPanel) { - this.state.model.permissionsPanel = permissionsPanel; - this.state.input.paginatorPage = permissionsPanel.currentPage; + state.model.permissionsPanel = permissionsPanel; + for(let up of permissionsPanel.items) { + Vue.set(up, 'editable', false); + } + state.input.paginatorPage = permissionsPanel.currentPage; + }, + togglePermissionEditable(state, index) { + let up = state.model.permissionsPanel.items[index]; + up.editable = !up.editable; }, updateMembersPanel(state, membersPanel) { - this.state.model.membersPanel = membersPanel; - this.state.input.paginatorPage = membersPanel.currentPage; + state.model.membersPanel = membersPanel; + state.input.paginatorPage = membersPanel.currentPage; }, setTabIndex(state, tabIndex) { // this will trigger the tabChanged() method in Main.vue - this.state.input.tabIndex = tabIndex; + state.input.tabIndex = tabIndex; }, setLoading(state, loading) { - this.state.loading = loading; - }, - showMainPage(state) { - this.state.page = 'main'; + state.loading = loading; }, displaySearchResults(state, results) { - this.state.page = 'search'; if (results) { - this.state.model.genericSearchResults = results; + state.model.genericSearchResults = results; } }, updateSearchResults(state, results) { - this.state.model.genericSearchResults = results; + state.model.genericSearchResults = results; + }, + setUserSearchModel(state, model) { + state.model.userSearchResults.user = model.user; + state.model.userSearchResults.groups = model.groups; + state.model.userSearchResults.permissions = model.permissions; }, - backFromUserPage(state) { - state.page = state.previousPage; + setGenericSearchFilter(state, filter) { + state.input.genericSearch.filter = filter; } }, actions: { - openUserPage({ state }, userId) { - state.previousPage = state.page; - client.openUserSearchResult(userId) + loadHomePageModel({ commit, state }) { + client.fetchHomePageModel(state.input) + .then(model => commit('updateHomePageModel', model)); + }, + search({ commit, state }, filter) { + commit('setGenericSearchFilter', filter); + client.search(state.input) + .then(results => commit('displaySearchResults', results)); + }, + changeTab({ commit, state }, tabIndex) { + state.input.tabIndex = tabIndex; + // reset paginator + state.input.paginatorPage = 1; + switch (tabIndex) { + case 0: + client.fetchGroupsTab(state.input) + .then(model => commit('updateGroups', model)); + break; + case 1: + client.fetchMembersPanel(state.input) + .then(panel => commit('updateMembersPanel', panel)); + break; + case 2: + client.fetchPermissionsPanel(state.input) + .then(panel => commit('updatePermissionsPanel', panel)); + break; + } + }, + openGroup({ commit, dispatch, state }, groupId) { + let input = state.input; + input.selectedGroupId = groupId; + input.searchFilter = ''; + client.fetchGroupsTab(input) .then(model => { - state.page = 'userSearch'; - state.model.userSearchResults.user = model.user; - state.model.userSearchResults.groups = model.groups; - state.model.userSearchResults.permissions = model.permissions; + commit('updateGroups', model); + if (model.leaf) { + // If there are no subgroups show the members panel + dispatch('changeTab', 1); + } else { + dispatch('changeTab', 0); + } + router.push('/', () => {}); }); + }, + openUserPage({ commit }, userId) { + client.openUserSearchResult(userId) + .then(model => commit('setUserSearchModel', model)); } }, getters: { diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java index 5840978..9742396 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java @@ -9,7 +9,9 @@ import it.inaf.ia2.gms.model.request.PaginatedModelRequest; import it.inaf.ia2.gms.model.Permission; import it.inaf.ia2.gms.model.UserPermission; import it.inaf.ia2.gms.model.request.TabRequest; +import it.inaf.ia2.gms.model.request.UpdatePermissionRequest; import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.persistence.model.PermissionEntity; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -22,6 +24,7 @@ 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.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -63,11 +66,27 @@ public class PermissionsController { public ResponseEntity<PaginatedData<UserPermission>> addPermission(@Valid @RequestBody AddPermissionRequest request) { GroupEntity group = groupsManager.getGroupById(request.getGroupId()); - permissionsManager.addPermission(group, request.getUserId(), request.getPermission()); + if (request.isOverride()) { + permissionsManager.updatePermission(group, request.getUserId(), request.getPermission()); + } else { + permissionsManager.addPermission(group, request.getUserId(), request.getPermission()); + } return new ResponseEntity<>(getPermissionsPanel(group, request), HttpStatus.CREATED); } + @PutMapping(value = "/permission", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Map<String, String>> updatePermission(@Valid @RequestBody UpdatePermissionRequest request) { + + GroupEntity group = groupsManager.getGroupById(request.getGroupId()); + PermissionEntity updatedEntity = permissionsManager.updatePermission(group, request.getUserId(), request.getPermission()); + + Map<String, String> response = new HashMap<>(); + response.put("permission", updatedEntity.getPermission().toString()); + + return ResponseEntity.ok(response); + } + @DeleteMapping(value = "/permission", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<PaginatedData<UserPermission>> deletePermission(@Valid MemberRequest request) { diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/UsersController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/UsersController.java index 595b1e4..e5d908c 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/UsersController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/UsersController.java @@ -16,7 +16,7 @@ public class UsersController { @Autowired private RapClient rapClient; - @GetMapping(value = "users", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "users", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<List<RapUser>> searchUsers(@RequestParam("search") String searchText) { return ResponseEntity.ok(rapClient.searchUsers(searchText)); } diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java index a1503f0..238797c 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java +++ b/gms/src/main/java/it/inaf/ia2/gms/manager/PermissionsManager.java @@ -83,6 +83,11 @@ public class PermissionsManager extends UserAwareComponent { return permissionsService.addPermission(group, userId, permission); } + public PermissionEntity updatePermission(GroupEntity group, String userId, Permission permission) { + verifyUserCanManagePermissions(group); + return permissionsService.updatePermission(group, userId, permission); + } + public void removePermission(GroupEntity group, String userId) { verifyUserCanManagePermissions(group); permissionsService.removePermission(group, userId); diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionRequest.java index c10ae5f..c1d4fd0 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionRequest.java +++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/AddPermissionRequest.java @@ -13,6 +13,8 @@ public class AddPermissionRequest extends PaginatedModelRequest { @NotNull private Permission permission; + private boolean override; + public String getGroupId() { return groupId; } @@ -36,4 +38,12 @@ public class AddPermissionRequest extends PaginatedModelRequest { public void setPermission(Permission permission) { this.permission = permission; } + + public boolean isOverride() { + return override; + } + + public void setOverride(boolean override) { + this.override = override; + } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/request/UpdatePermissionRequest.java b/gms/src/main/java/it/inaf/ia2/gms/model/request/UpdatePermissionRequest.java new file mode 100644 index 0000000..2125f0d --- /dev/null +++ b/gms/src/main/java/it/inaf/ia2/gms/model/request/UpdatePermissionRequest.java @@ -0,0 +1,39 @@ +package it.inaf.ia2.gms.model.request; + +import it.inaf.ia2.gms.model.Permission; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +public class UpdatePermissionRequest { + + @NotEmpty + private String groupId; + @NotEmpty + private String userId; + @NotNull + private Permission permission; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public Permission getPermission() { + return permission; + } + + public void setPermission(Permission permission) { + this.permission = permission; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java index d8d417e..4ffd9b9 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java @@ -41,6 +41,24 @@ public class PermissionsDAO { return userPermission; } + public PermissionEntity updatePermission(PermissionEntity userPermission, Permission newPermission) { + + String sql = "UPDATE gms_permission SET permission = ? WHERE group_id = ? AND user_id = ? AND group_path = ?"; + + userPermission.setPermission(newPermission); + + jdbcTemplate.update(conn -> { + PreparedStatement ps = conn.prepareStatement(sql); + ps.setObject(1, userPermission.getPermission().toString(), Types.OTHER); + ps.setString(2, userPermission.getGroupId()); + ps.setString(3, userPermission.getUserId()); + ps.setObject(4, userPermission.getGroupPath(), Types.OTHER); + return ps; + }); + + return userPermission; + } + public List<PermissionEntity> findUserPermissions(String userId) { String sql = "SELECT group_id, permission, group_path FROM gms_permission WHERE user_id = ?"; diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java index af9a942..b0525e4 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java +++ b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java @@ -61,4 +61,12 @@ public class PermissionsService { return permissionEntity; } + + public PermissionEntity updatePermission(GroupEntity group, String userId, Permission permission) { + + PermissionEntity permissionEntity = permissionsDAO.findPermissionEntity(group.getId(), userId) + .orElseThrow(() -> new IllegalArgumentException("Specified permission not found")); + + return permissionsDAO.updatePermission(permissionEntity, permission); + } } -- GitLab