Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(User): Add new endpoints to get/update requesting user profile #2258

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion rest/resource-server/src/docs/asciidoc/users.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
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[]
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,6 @@ static abstract class EmbeddedProjectMixin extends ProjectMixin {
"id",
"revision",
"setPassword",
"wantsMailNotification",
"setWantsMailNotification",
"setId",
"setRevision",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,32 @@ public Package updatePackage(Package packageToUpdate, Package requestBodyPackage
return packageToUpdate;
}

public User updateUserProfile(User userToUpdate, Map<String, Object> requestBodyUser, ImmutableSet<User._Fields> setOfUserProfileFields) {
for (User._Fields field : setOfUserProfileFields) {
Object fieldValue = requestBodyUser.get(field.getFieldName());
if (fieldValue != null) {
switch (field) {
case NOTIFICATION_PREFERENCES:
Object wantNotification = requestBodyUser.get(User._Fields.WANTS_MAIL_NOTIFICATION.getFieldName());
if (wantNotification == null) {
if (userToUpdate.isWantsMailNotification()) {
userToUpdate.setFieldValue(field, fieldValue);
}
} else {
if (Boolean.TRUE.equals(wantNotification)) {
userToUpdate.setFieldValue(field, fieldValue);
}
}
break;
default:
userToUpdate.setFieldValue(field, fieldValue);
break;
}
}
}
return userToUpdate;
}

public Component convertToComponent(ComponentDTO componentDTO) {
Component component = new Component();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -70,6 +72,10 @@ public class UserController implements RepresentationModelProcessor<RepositoryLi
@NonNull
private final RestControllerHelper restControllerHelper;

private static final ImmutableSet<User._Fields> setOfUserProfileFields = ImmutableSet.<User._Fields>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.",
Expand Down Expand Up @@ -165,6 +171,33 @@ public ResponseEntity<EntityModel<User>> 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<HalResource<User>> getUserProfile() {
User sw360User = restControllerHelper.getSw360UserFromAuthentication();
HalResource<User> 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<HalResource<User>> updateUserProfile(
@RequestBody Map<String, Object> userProfile
) throws TException {
User sw360User = restControllerHelper.getSw360UserFromAuthentication();
sw360User = restControllerHelper.updateUserProfile(sw360User, userProfile, setOfUserProfileFields);
userService.updateUser(sw360User);
HalResource<User> halUserResource = new HalResource<>(sw360User);
return ResponseEntity.ok(halUserResource);
}
@Override
public RepositoryLinksResource process(RepositoryLinksResource resource) {
resource.add(linkTo(UserController.class).slash("api/users").withRel("users"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<resources-index-links,Links>> to Component resource").optional(),
subsectionWithPath("_embedded.sw360:components.[]_embedded.sw360:attachments.[]filename").description("Attached file name").optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,6 +72,29 @@ public void before() throws TException {
userGroups2.add(UserGroup.SECURITY_ADMIN);
secondaryDepartmentsAndRoles.put("DEPARTMENT1", userGroups1);
secondaryDepartmentsAndRoles.put("DEPARTMENT2", userGroups2);

Map<String, Boolean> 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");
Expand All @@ -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);
Expand All @@ -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");
Expand All @@ -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
Expand Down Expand Up @@ -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("<<resources-user-get,User>> to user resource")
)));
}
Expand All @@ -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("<<resources-index-links,Links>> to other resources")
)));
}
Expand All @@ -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("<<resources-index-links,Links>> 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("<<resources-index-links,Links>> to other resources")
)));
}

@Test
public void should_document_update_user_profile() throws Exception {
String accessToken = TestHelper.getAccessToken(mockMvc, testUserId, testUserPassword);

Map<String, Boolean> 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<String, Object> 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("<<resources-index-links,Links>> to other resources")
)));
}
Expand Down