Skip to content

Commit

Permalink
[SELC-5971] Feat: Added API to createUser by userID from onboarding (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
flaminiaScarciofolo authored Nov 12, 2024
1 parent 796b659 commit e84cfae
Show file tree
Hide file tree
Showing 8 changed files with 471 additions and 5 deletions.
45 changes: 45 additions & 0 deletions apps/user-ms/src/main/docs/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,51 @@
"SecurityScheme" : [ ]
} ]
}
},
"/users/{userId}/onboarding" : {
"post" : {
"tags" : [ "User" ],
"summary" : "Check if the user is manager or Update/create a user by userId with a new role",
"description" : "Checks if the user is already a manager for the specified product and, if not, creates or updates the user with a new role.",
"operationId" : "createUserByUserId",
"parameters" : [ {
"name" : "userId",
"in" : "path",
"required" : true,
"schema" : {
"type" : "string"
}
} ],
"requestBody" : {
"content" : {
"application/json" : {
"schema" : {
"$ref" : "#/components/schemas/AddUserRoleDto"
}
}
}
},
"responses" : {
"200" : {
"description" : "The user is already a manager for the specified product.",
"content" : {
"application/json" : { }
}
},
"201" : {
"description" : "The user has been created or updated with a new role."
},
"401" : {
"description" : "Not Authorized"
},
"403" : {
"description" : "Not Allowed"
}
},
"security" : [ {
"SecurityScheme" : [ ]
} ]
}
}
},
"components" : {
Expand Down
33 changes: 33 additions & 0 deletions apps/user-ms/src/main/docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,39 @@ paths:
description: Not Allowed
security:
- SecurityScheme: []
/users/{userId}/onboarding:
post:
tags:
- User
summary: Check if the user is manager or Update/create a user by userId with
a new role
description: "Checks if the user is already a manager for the specified product\
\ and, if not, creates or updates the user with a new role."
operationId: createUserByUserId
parameters:
- name: userId
in: path
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/AddUserRoleDto"
responses:
"200":
description: The user is already a manager for the specified product.
content:
application/json: {}
"201":
description: The user has been created or updated with a new role.
"401":
description: Not Authorized
"403":
description: Not Allowed
security:
- SecurityScheme: []
components:
schemas:
AddUserRoleDto:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import it.pagopa.selfcare.user.controller.response.*;
import it.pagopa.selfcare.user.controller.response.product.SearchUserDto;
import it.pagopa.selfcare.user.exception.InvalidRequestException;
import it.pagopa.selfcare.user.exception.UserRoleAlreadyPresentException;
import it.pagopa.selfcare.user.mapper.UserMapper;
import it.pagopa.selfcare.user.model.LoggedUser;
import it.pagopa.selfcare.user.model.UpdateUserRequest;
Expand Down Expand Up @@ -319,6 +320,29 @@ public Uni<Response> createOrUpdateByUserId(@PathParam("userId") String userId,

}

/**
*
* @param userId String
* @param userDto AddUserRoleDto
*/
@Operation(description = "Checks if the user is already a manager for the specified product and, if not, creates or updates the user with a new role.", summary = "Check if the user is manager or Update/create a user by userId with a new role")
@APIResponses({
@APIResponse(responseCode = "200", description = "The user is already a manager for the specified product."),
@APIResponse(responseCode = "201", description = "The user has been created or updated with a new role."),
})
@POST
@Path("/{userId}/onboarding")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<Response> createUserByUserId(@PathParam("userId") String userId,
@Valid AddUserRoleDto userDto,
@Context SecurityContext ctx) {
return readUserIdFromToken(ctx)
.onItem().transformToUni(loggedUser -> userService.createUserByUserId(userDto, userId, loggedUser))
.onItem().transform(ignore -> Response.status(HttpStatus.SC_CREATED).entity(userId).build())
.onFailure(UserRoleAlreadyPresentException.class).recoverWithUni(throwable -> Uni.createFrom().item(Response.status(HttpStatus.SC_OK).entity(userId).build()));
}

/**
* The createOrUpdateByFiscalCode function is used to create a new user or update an existing one.
* The function takes in a CreateUserDto object, which contains the following fields:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package it.pagopa.selfcare.user.exception;

public class UserRoleAlreadyPresentException extends RuntimeException{

public UserRoleAlreadyPresentException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ public interface UserService {

Uni<String> createOrUpdateUserByUserId(AddUserRoleDto userDto, String userId, LoggedUser loggedUser);

Uni<String> createUserByUserId(AddUserRoleDto userDto, String userId, LoggedUser loggedUser);

Multi<UserDataResponse> retrieveUsersData(String institutionId, String personId, List<String> roles, List<String> states, List<String> products, List<String> productRoles, String userId);

Uni<Void> updateInstitutionDescription(String institutionId, UpdateDescriptionDto descriptionDto);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import it.pagopa.selfcare.user.entity.filter.UserInstitutionFilter;
import it.pagopa.selfcare.user.exception.InvalidRequestException;
import it.pagopa.selfcare.user.exception.ResourceNotFoundException;
import it.pagopa.selfcare.user.exception.UserRoleAlreadyPresentException;
import it.pagopa.selfcare.user.mapper.OnboardedProductMapper;
import it.pagopa.selfcare.user.mapper.UserInstitutionMapper;
import it.pagopa.selfcare.user.mapper.UserMapper;
Expand Down Expand Up @@ -56,8 +57,7 @@
import static it.pagopa.selfcare.user.model.constants.EventsMetric.EVENTS_USER_INSTITUTION_FAILURE;
import static it.pagopa.selfcare.user.model.constants.EventsMetric.EVENTS_USER_INSTITUTION_SUCCESS;
import static it.pagopa.selfcare.user.model.constants.EventsName.EVENT_USER_MS_NAME;
import static it.pagopa.selfcare.user.model.constants.OnboardedProductState.ACTIVE;
import static it.pagopa.selfcare.user.model.constants.OnboardedProductState.PENDING;
import static it.pagopa.selfcare.user.model.constants.OnboardedProductState.*;
import static it.pagopa.selfcare.user.util.GeneralUtils.formatQueryParameterList;
import static it.pagopa.selfcare.user.util.UserUtils.VALID_USER_PRODUCT_STATES_FOR_NOTIFICATION;

Expand Down Expand Up @@ -453,6 +453,68 @@ public Uni<String> createOrUpdateUserByUserId(AddUserRoleDto userDto, String use
.onFailure().invoke(exception -> log.error("Error during retrieve user from userRegistry: {} ", exception.getMessage(), exception));
}

public Uni<String> createUserByUserId(AddUserRoleDto userDto, String userId, LoggedUser loggedUser) {
var userInstitutionFilters = UserInstitutionFilter.builder().userId(userId).institutionId(userDto.getInstitutionId()).build().constructMap();
var productFilters = OnboardedProductFilter.builder().productId(userDto.getProduct().getProductId()).status(List.of(ACTIVE)).build().constructMap();
Map<String, Object> queryParameter = userUtils.retrieveMapForFilter(userInstitutionFilters, productFilters);
return userInstitutionService.retrieveFirstFilteredUserInstitution(queryParameter)
.onItem().transformToUni(userInstitution -> {
if (Objects.nonNull(userInstitution)) {
log.info("User with userId: {} has already onboarded for product {}. Proceeding with check role", userId, userDto.getProduct().getProductId());
PartyRole roleOnProduct = retrieveUserRoleOnProduct(userInstitution, userDto.getProduct().getProductId());
return evaluateRoleAndCreateOrUpdateUserByUserId(userDto, userId, loggedUser, roleOnProduct);
} else {
log.info("User with userId: {} has not onboarded for product {}. Proceeding with create", userId, userDto.getProduct().getProductId());
return createOrUpdateUserByUserId(userDto, userId, loggedUser);
}
})
.onFailure().invoke(exception -> log.error("Error during createOrUpdateManagerByUserId for userId: {}, institutionId: {}: {}", userId, userDto.getInstitutionId(), exception.getMessage(), exception));
}

/**
* The evaluateRoleAndCreateOrUpdateUserByUserId method is designed to evaluate the role of a user for a specific product
* and either create or update the user's role based on certain conditions.
* First, the method checks if the roleOnProduct is null. If it is, we need to create new role for the user on selected product.
* Next, the method compares the role specified in userDto with the existing roleOnProduct.
* If the new role is lower than the existing role (es. new role DELEGATE, old role MANAGER), it then calls
* we have to delete the old role, and subsequently we create new role for the user on selected product.
* If the new role is equals or higher than the existing role, throw a UserRoleAlreadyPresentException to indicate that we need to keep the old role.
*/
private Uni<String> evaluateRoleAndCreateOrUpdateUserByUserId(AddUserRoleDto userDto, String userId, LoggedUser loggedUser, PartyRole roleOnProduct) {
PartyRole newRole;
try {
newRole = PartyRole.valueOf(userDto.getProduct().getRole());
} catch (IllegalArgumentException e) {
throw new InvalidRequestException("Invalid role: " + userDto.getProduct().getRole() + ". Allowed value are: " + Arrays.toString(PartyRole.values()));
}
if (Objects.isNull(roleOnProduct)) {
return createOrUpdateUserByUserId(userDto, userId, loggedUser);
} else if (newRole.compareTo(roleOnProduct) < 0) {
log.info("User {}, for product {}, has role {}, which is lower than {}. The old role will be deleted, and the new role will be created.", userId, userDto.getProduct().getProductId(), roleOnProduct, userDto.getProduct().getRole());
return userInstitutionService.updateUserStatusWithOptionalFilterByInstitutionAndProduct(userId, userDto.getInstitutionId(), userDto.getProduct().getProductId(), null, null, DELETED)
.onItem().transformToUni(longValue -> createOrUpdateUserByUserId(userDto, userId, loggedUser));
} else {
log.info("User {}, for product {}, has role {}, which is equals or biggest than {}. The old role is kept.", userId, userDto.getProduct().getProductId(), roleOnProduct, userDto.getProduct().getRole());
return Uni.createFrom().failure(new UserRoleAlreadyPresentException(String.format("User already has a role equals or bigger than %s for the product [%s] we cannot create %s role",
roleOnProduct, userDto.getProduct().getProductId(), userDto.getProduct().getRole())));
}
}

/**
* This method retrieve the user role on a specific product if exists (status = ACTIVE).
*/
private PartyRole retrieveUserRoleOnProduct(UserInstitution userInstitution, String productId) {
if (Objects.nonNull(userInstitution.getProducts()) && !userInstitution.getProducts().isEmpty()) {
return userInstitution.getProducts().stream()
.filter(onboardedProduct -> productId.equalsIgnoreCase(onboardedProduct.getProductId()))
.filter(onboardedProduct -> ACTIVE.equals(onboardedProduct.getStatus()))
.findFirst()
.map(OnboardedProduct::getRole)
.orElse(null);
}
return null;
}

/**
* Updates or creates a UserInstitution by userId and institutionId, persists the changes,
* and sends notifications if needed. If the productRole already exists return nullItem.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import it.pagopa.selfcare.user.entity.UserInstitutionRole;
import it.pagopa.selfcare.user.exception.InvalidRequestException;
import it.pagopa.selfcare.user.exception.ResourceNotFoundException;
import it.pagopa.selfcare.user.exception.UserRoleAlreadyPresentException;
import it.pagopa.selfcare.user.model.LoggedUser;
import it.pagopa.selfcare.user.model.UpdateUserRequest;
import it.pagopa.selfcare.user.model.UserNotificationToSend;
Expand Down Expand Up @@ -684,6 +685,49 @@ void testCreateOrUpdateUserWithInvalidBodyByUserId() {
.statusCode(400);
}

@Test
@TestSecurity(user = "userJwt")
void testCheckRoleOrCreateOrUpdateByUserIdWhenUserIsManager() {
AddUserRoleDto userDto = buildAddUserRoleDto();
when(userService.createUserByUserId(any(AddUserRoleDto.class), anyString(), any()))
.thenReturn(Uni.createFrom().failure(new UserRoleAlreadyPresentException("test")));
given()
.when()
.contentType(ContentType.JSON)
.body(userDto)
.post("/userId/onboarding")
.then()
.statusCode(200);
}

@Test
@TestSecurity(user = "userJwt")
void testCheckRoleOrCreateOrUpdateByUserIdWhenUserIsNotManager() {
AddUserRoleDto userDto = buildAddUserRoleDtoWithOtherRole();
when(userService.createUserByUserId(any(AddUserRoleDto.class), anyString(), any()))
.thenReturn(Uni.createFrom().nullItem());
given()
.when()
.contentType(ContentType.JSON)
.body(userDto)
.post("/userId/onboarding")
.then()
.statusCode(201);
}

@Test
@TestSecurity(user = "userJwt")
void testCheckRoleOrCreateOrUpdateByUserIdWithInvalidBody() {
AddUserRoleDto userDto = new AddUserRoleDto();
given()
.when()
.contentType(ContentType.JSON)
.body(userDto)
.post("/userId/onboarding")
.then()
.statusCode(400);
}

@Test
@TestSecurity(user = "userJwt")
void retrieveUsers() {
Expand Down Expand Up @@ -763,4 +807,25 @@ private AddUserRoleDto buildAddUserRoleDto() {
userDto.setProduct(product);
return userDto;
}

private AddUserRoleDto buildAddUserRoleDtoWithOtherRole() {
AddUserRoleDto userDto = new AddUserRoleDto();
userDto.setInstitutionId("institutionId");
userDto.setInstitutionDescription("institutionDescription");
userDto.setInstitutionRootName("institutionRootName");
CreateUserDto.User user = new CreateUserDto.User();
user.setBirthDate("birthDate");
user.setFamilyName("familyName");
user.setFiscalCode("fiscalCode");
user.setName("name");
user.setInstitutionEmail("institutionEmail");
AddUserRoleDto.Product product = new AddUserRoleDto.Product();
product.setProductId("productId");
product.setRole(PartyRole.DELEGATE.name());
product.setTokenId("tokenId");
product.setProductRoles(Collections.singletonList("productRole"));
product.setDelegationId("delegationId");
userDto.setProduct(product);
return userDto;
}
}
Loading

0 comments on commit e84cfae

Please sign in to comment.