From f37e749bd4032ed49226a721be180be475395bd8 Mon Sep 17 00:00:00 2001 From: hoangnt2 Date: Tue, 26 Dec 2023 14:23:40 +0700 Subject: [PATCH] feat(User): Add new endpoints to get/update requesting user profile --- .../src/docs/asciidoc/users.adoc | 33 ++++- .../core/JacksonCustomizations.java | 1 - .../core/RestControllerHelper.java | 10 ++ .../resourceserver/user/Sw360UserService.java | 5 + .../resourceserver/user/UserController.java | 33 +++++ .../restdocs/ComponentSpecTest.java | 1 + .../resourceserver/restdocs/UserSpecTest.java | 114 ++++++++++++++++++ 7 files changed, 195 insertions(+), 2 deletions(-) diff --git a/rest/resource-server/src/docs/asciidoc/users.adoc b/rest/resource-server/src/docs/asciidoc/users.adoc index b3137a63be..84a4653cd6 100644 --- a/rest/resource-server/src/docs/asciidoc/users.adoc +++ b/rest/resource-server/src/docs/asciidoc/users.adoc @@ -68,4 +68,35 @@ include::{snippets}/should_document_create_user/response-fields.adoc[] include::{snippets}/should_document_create_user/curl-request.adoc[] ===== Example response -include::{snippets}/should_document_create_user/http-response.adoc[] \ No newline at end of file +include::{snippets}/should_document_create_user/http-response.adoc[] + +[[resources-user-get-profile]] +==== Get requesting user's profile + +A `GET` request will get requesting user's profile. + +===== Response structure +include::{snippets}/should_document_get_user_profile/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_get_user_profile/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_get_user_profile/http-response.adoc[] + +[[resources-user-update-profile]] +==== Update requesting user's profile + +A `PATCH` request will update requesting user's profile. + +===== Request structure +include::{snippets}/should_document_update_user_profile/request-fields.adoc[] + +===== Response structure +include::{snippets}/should_document_update_user_profile/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_update_user_profile/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_update_user_profile/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java index 9b2af40a30..87e994877a 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/JacksonCustomizations.java @@ -388,7 +388,6 @@ static abstract class EmbeddedProjectMixin extends ProjectMixin { "id", "revision", "setPassword", - "wantsMailNotification", "setWantsMailNotification", "setId", "setRevision", diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index a5913748ae..82815b3f94 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -622,6 +622,16 @@ public Package updatePackage(Package packageToUpdate, Package requestBodyPackage return packageToUpdate; } + public User updateUserProfile(User userToUpdate, Map requestBodyUser, ImmutableSet setOfUserProfileFields) { + for (User._Fields field : setOfUserProfileFields) { + Object fieldValue = requestBodyUser.get(field.getFieldName()); + if (fieldValue != null) { + userToUpdate.setFieldValue(field, fieldValue); + } + } + return userToUpdate; + } + public Component convertToComponent(ComponentDTO componentDTO) { Component component = new Component(); diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java index a7c38e6979..c5e8be9d7f 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/Sw360UserService.java @@ -114,4 +114,9 @@ private UserService.Iface getThriftUserClient() throws TTransportException { TProtocol protocol = new TCompactProtocol(thriftClient); return new UserService.Client(protocol); } + + public void updateUser(User sw360User) throws TException { + UserService.Iface sw360UserClient = getThriftUserClient(); + sw360UserClient.updateUser(sw360User); + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java index 926ed33c9c..49370626b0 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/user/UserController.java @@ -9,6 +9,7 @@ */ package org.eclipse.sw360.rest.resourceserver.user; +import com.google.common.collect.ImmutableSet; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.Operation; @@ -47,6 +48,7 @@ import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; +import java.util.Map; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; @@ -70,6 +72,10 @@ public class UserController implements RepresentationModelProcessor setOfUserProfileFields = ImmutableSet.builder() + .add(User._Fields.WANTS_MAIL_NOTIFICATION) + .add(User._Fields.NOTIFICATION_PREFERENCES).build(); + @Operation( summary = "List all of the service's users.", description = "List all of the service's users.", @@ -165,6 +171,33 @@ public ResponseEntity> createUser( return ResponseEntity.created(location).body(halResource); } + @Operation( + summary = "Get user's profile.", + description = "Get user's profile.", + tags = {"Users"} + ) + @GetMapping(value = USERS_URL + "/profile") + public ResponseEntity> getUserProfile() { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + HalResource halUserResource = new HalResource<>(sw360User); + return ResponseEntity.ok(halUserResource); + } + + @Operation( + summary = "Update user's profile.", + description = "Update user's profile.", + tags = {"Users"} + ) + @PatchMapping(value = USERS_URL + "/profile") + public ResponseEntity> updateUserProfile( + @RequestBody Map userProfile + ) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + sw360User = restControllerHelper.updateUserProfile(sw360User, userProfile, setOfUserProfileFields); + userService.updateUser(sw360User); + HalResource halUserResource = new HalResource<>(sw360User); + return ResponseEntity.ok(halUserResource); + } @Override public RepositoryLinksResource process(RepositoryLinksResource resource) { resource.add(linkTo(UserController.class).slash("api/users").withRel("users")); diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java index bee2e7af86..07b89b904f 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ComponentSpecTest.java @@ -640,6 +640,7 @@ public void should_document_get_components_with_all_details() throws Exception { subsectionWithPath("_embedded.sw360:components.[]homepage").description("The homepage url of the component").optional(), subsectionWithPath("_embedded.sw360:components.[]_embedded.createdBy.email").description("The email of user who created this Component").optional(), + subsectionWithPath("_embedded.sw360:components.[]_embedded.createdBy.wantsMailNotification").description("Does user want to be notified via mail?").optional(), subsectionWithPath("_embedded.sw360:components.[]_embedded.createdBy.deactivated").description("The user is activated or deactivated").optional(), subsectionWithPath("_embedded.sw360:components.[]_embedded.createdBy._links").description("Self <> to Component resource").optional(), subsectionWithPath("_embedded.sw360:components.[]_embedded.sw360:attachments.[]filename").description("Attached file name").optional(), diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java index 3cf0d575b9..25ef1bac27 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/UserSpecTest.java @@ -40,6 +40,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; @@ -71,6 +72,29 @@ public void before() throws TException { userGroups2.add(UserGroup.SECURITY_ADMIN); secondaryDepartmentsAndRoles.put("DEPARTMENT1", userGroups1); secondaryDepartmentsAndRoles.put("DEPARTMENT2", userGroups2); + + Map notificationPreferences = new HashMap<>(); + notificationPreferences.put("releaseCONTRIBUTORS", true); + notificationPreferences.put("componentCREATED_BY", true); + notificationPreferences.put("releaseCREATED_BY", true); + notificationPreferences.put("moderationREQUESTING_USER", true); + notificationPreferences.put("projectPROJECT_OWNER", true); + notificationPreferences.put("moderationMODERATORS", true); + notificationPreferences.put("releaseSUBSCRIBERS", true); + notificationPreferences.put("componentMODERATORS", true); + notificationPreferences.put("projectMODERATORS", true); + notificationPreferences.put("projectROLES", true); + notificationPreferences.put("releaseROLES", true); + notificationPreferences.put("componentROLES", true); + notificationPreferences.put("projectLEAD_ARCHITECT", true); + notificationPreferences.put("componentCOMPONENT_OWNER", true); + notificationPreferences.put("projectSECURITY_RESPONSIBLES", true); + notificationPreferences.put("clearingREQUESTING_USER", true); + notificationPreferences.put("projectCONTRIBUTORS", true); + notificationPreferences.put("componentSUBSCRIBERS", true); + notificationPreferences.put("projectPROJECT_RESPONSIBLE", true); + notificationPreferences.put("releaseMODERATORS", true); + user = new User(); user.setEmail("admin@sw360.org"); user.setId("4784587578e87989"); @@ -82,6 +106,7 @@ public void before() throws TException { user.setWantsMailNotification(true); user.setFormerEmailAddresses(Sets.newHashSet("admin_bachelor@sw360.org")); user.setSecondaryDepartmentsAndRoles(secondaryDepartmentsAndRoles); + user.setNotificationPreferences(notificationPreferences); userList.add(user); given(this.userServiceMock.getUserByEmail("admin@sw360.org")).willReturn(user); @@ -90,6 +115,7 @@ public void before() throws TException { when(this.userServiceMock.addUser(any())).then( invocation -> new User("test@sw360.org", "DEPARTMENT").setId("1234567890").setFullname("FTest lTest") .setGivenname("FTest").setLastname("lTest").setUserGroup(UserGroup.USER)); + given(this.userServiceMock.getUserByEmailOrExternalId(any())).willReturn(user); User user2 = new User(); user2.setEmail("jane@sw360.org"); @@ -104,6 +130,8 @@ public void before() throws TException { userList.add(user2); given(this.userServiceMock.getAllUsers()).willReturn(userList); + User userWithTokens = new User("admin@sw360.org", "sw360").setId("123456789"); + given(this.userServiceMock.getUserByEmailOrExternalId(any())).willReturn(user); } @Test @@ -176,6 +204,7 @@ public void should_document_create_user() throws Exception { subsectionWithPath("givenName").description("The given name of the user"), subsectionWithPath("lastName").description("The last name of the user"), subsectionWithPath("deactivated").description("Is user deactivated"), + fieldWithPath("wantsMailNotification").description("Does user want to be notified via mail?"), subsectionWithPath("_links").description("<> to user resource") ))); } @@ -201,6 +230,8 @@ public void should_document_get_user_by_id() throws Exception { subsectionWithPath("secondaryDepartmentsAndRoles").description("The user's secondary departments and roles"), fieldWithPath("formerEmailAddresses").description("The user's former email addresses"), fieldWithPath("deactivated").description("Is user deactivated"), + fieldWithPath("wantsMailNotification").description("Does user want to be notified via mail?"), + subsectionWithPath("notificationPreferences").description("User's notification preferences"), subsectionWithPath("_links").description("<> to other resources") ))); } @@ -226,6 +257,89 @@ public void should_document_get_user() throws Exception { subsectionWithPath("secondaryDepartmentsAndRoles").description("The user's secondary departments and roles"), fieldWithPath("formerEmailAddresses").description("The user's former email addresses"), fieldWithPath("deactivated").description("Is user deactivated"), + fieldWithPath("wantsMailNotification").description("Does user want to be notified via mail?"), + subsectionWithPath("notificationPreferences").description("User's notification preferences"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } + + @Test + public void should_document_get_user_profile() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + mockMvc.perform(get("/api/users/profile") + .header("Authorization", "Bearer " + accessToken) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + responseFields( + fieldWithPath("email").description("The user's email"), + fieldWithPath("userGroup").description("The user group, possible values are: " + Arrays.asList(UserGroup.values())), + fieldWithPath("fullName").description("The users's full name"), + fieldWithPath("givenName").description("The user's given name"), + fieldWithPath("lastName").description("The user's last name"), + fieldWithPath("department").description("The user's company department"), + subsectionWithPath("secondaryDepartmentsAndRoles").description("The user's secondary departments and roles"), + fieldWithPath("formerEmailAddresses").description("The user's former email addresses"), + fieldWithPath("deactivated").description("Is user deactivated"), + fieldWithPath("wantsMailNotification").description("Does user want to be notified via mail?"), + subsectionWithPath("notificationPreferences").description("User's notification preferences"), + subsectionWithPath("_links").description("<> to other resources") + ))); + } + + @Test + public void should_document_update_user_profile() throws Exception { + String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword); + + Map notificationPreferences = new HashMap<>(); + notificationPreferences.put("releaseCONTRIBUTORS", true); + notificationPreferences.put("componentCREATED_BY", false); + notificationPreferences.put("releaseCREATED_BY", false); + notificationPreferences.put("moderationREQUESTING_USER", false); + notificationPreferences.put("projectPROJECT_OWNER", true); + notificationPreferences.put("moderationMODERATORS", false); + notificationPreferences.put("releaseSUBSCRIBERS", true); + notificationPreferences.put("componentMODERATORS", true); + notificationPreferences.put("projectMODERATORS", false); + notificationPreferences.put("projectROLES", false); + notificationPreferences.put("releaseROLES", true); + notificationPreferences.put("componentROLES", true); + notificationPreferences.put("projectLEAD_ARCHITECT", false); + notificationPreferences.put("componentCOMPONENT_OWNER", true); + notificationPreferences.put("projectSECURITY_RESPONSIBLES", true); + notificationPreferences.put("clearingREQUESTING_USER", true); + notificationPreferences.put("projectCONTRIBUTORS", true); + notificationPreferences.put("componentSUBSCRIBERS", true); + notificationPreferences.put("projectPROJECT_RESPONSIBLE", false); + notificationPreferences.put("releaseMODERATORS", false); + + Map updatedProfile = new HashMap<>(); + updatedProfile.put("wantsMailNotification", true); + updatedProfile.put("notificationPreferences", notificationPreferences); + + mockMvc.perform(patch("/api/users/profile") + .header("Authorization", "Bearer " + accessToken) + .contentType(MediaTypes.HAL_JSON) + .content(this.objectMapper.writeValueAsString(updatedProfile)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andDo(this.documentationHandler.document( + requestFields( + fieldWithPath("wantsMailNotification").description("Does user want to be notified via mail?"), + subsectionWithPath("notificationPreferences").description("User's notification preferences") + ), + responseFields( + fieldWithPath("email").description("The user's email"), + fieldWithPath("userGroup").description("The user group, possible values are: " + Arrays.asList(UserGroup.values())), + fieldWithPath("fullName").description("The users's full name"), + fieldWithPath("givenName").description("The user's given name"), + fieldWithPath("lastName").description("The user's last name"), + fieldWithPath("department").description("The user's company department"), + fieldWithPath("deactivated").description("Is user deactivated"), + subsectionWithPath("secondaryDepartmentsAndRoles").description("The user's secondary departments and roles"), + fieldWithPath("formerEmailAddresses").description("The user's former email addresses"), + fieldWithPath("wantsMailNotification").description("Does user want to be notified via mail?"), + subsectionWithPath("notificationPreferences").description("User's notification preferences"), subsectionWithPath("_links").description("<> to other resources") ))); }