Skip to content

Commit

Permalink
Merge pull request #26 from pagopa/feature/SELC-3916
Browse files Browse the repository at this point in the history
[SELC-3916] feat: added API to retrieve user institutions with optional filters
  • Loading branch information
pierpaolodidato89 authored Jan 26, 2024
2 parents c087f8e + 42e2dde commit e59873b
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Multi;
import it.pagopa.selfcare.onboarding.common.PartyRole;
import it.pagopa.selfcare.user.constant.OnboardedProductState;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.controller.response.UserProductResponse;
import it.pagopa.selfcare.user.service.UserService;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import lombok.AllArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.Operation;

import java.util.List;

@Authenticated
@Path("/institutions")
@AllArgsConstructor
Expand All @@ -26,4 +29,17 @@ public class InstitutionController {
public Multi<UserProductResponse> getInstitutionUsers(@PathParam(value = "institutionId") String institutionId) {
return userService.getUserProductsByInstitution(institutionId);
}

@Operation(summary = "The API retrieves users with optional filters in input as query params")
@GET
@Path(value = "/{institutionId}/user-institutions")
@Produces(MediaType.APPLICATION_JSON)
public Multi<UserInstitutionResponse> retrieveUsers(@PathParam(value = "institutionId") String institutionId,
@QueryParam(value = "userId") String userId,
@QueryParam(value = "roles") List<String> roles,
@QueryParam(value = "states") List<String> states,
@QueryParam(value = "products") List<String> products,
@QueryParam(value = "productRoles") List<String> productRoles) {
return userService.findAllUserInstitutions(institutionId, userId, roles, states, products, productRoles);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,17 @@
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.user.controller.response.UserResponse;
import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.user.mapper.UserMapper;
import it.pagopa.selfcare.user.service.UserEventService;
import it.pagopa.selfcare.user.service.UserService;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.openapi.annotations.Operation;

import java.util.List;
import org.eclipse.microprofile.openapi.annotations.Operation;

@Authenticated
@Path("/users")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ public Map<String, Object> constructMap() {
map.put(CREATED_AT.getChild(), createdAt);
map.put(UPDATED_AT.getChild(), updatedAt);

map.values().removeIf(Objects::isNull);
map.entrySet().removeIf(e -> Objects.isNull(e.getValue()) ||
(e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty()));

return map;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public Map<String, Object> constructMap() {
map.put(INSTITUTION_ID.getDescription(), institutionId);
map.put(INSTITUTION_DESCRIPTION.getDescription(), institutionDescription);

map.values().removeIf(Objects::isNull);
map.entrySet().removeIf(e -> Objects.isNull(e.getValue()) ||
(e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty()));

return map;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class UserInstitutionServiceDefault implements UserInstitutionService {
private static final String CURRENT = ".$.";
private static final String CURRENT_ANY = ".$[].";


private final UserInstitutionMapper userInstitutionMapper;
private final QueryUtils queryUtils;

Expand Down Expand Up @@ -68,6 +67,7 @@ public Uni<UserInstitution> retrieveFirstFilteredUserInstitution(Map<String, Obj
@Override
public Multi<UserInstitution> findAllWithFilter(Map<String, Object> queryParameter) {
Document query = queryUtils.buildQueryDocument(queryParameter, USER_INSTITUTION_COLLECTION);
log.debug("Query: {}", query);
return runUserInstitutionFindQuery(query, null).stream();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.onboarding.common.PartyRole;
import it.pagopa.selfcare.user.constant.OnboardedProductState;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.controller.response.UserProductResponse;
import org.openapi.quarkus.user_registry_json.model.UserResource;

Expand All @@ -13,5 +16,10 @@ public interface UserService {
Uni<List<String>> getUsersEmails(String institutionId, String productId);
Multi<UserProductResponse> getUserProductsByInstitution(String institutionId);
Uni<UserResource> retrievePerson(String userId, String productId, String institutionId);

Multi<UserInstitutionResponse> findAllUserInstitutions(String institutionId,
String userId,
List<String> roles,
List<String> states,
List<String> products,
List<String> productRoles);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.controller.response.UserProductResponse;
import it.pagopa.selfcare.user.entity.UserInstitution;
import it.pagopa.selfcare.user.entity.filter.OnboardedProductFilter;
import it.pagopa.selfcare.user.entity.filter.UserInstitutionFilter;
import it.pagopa.selfcare.user.exception.ResourceNotFoundException;
import it.pagopa.selfcare.user.mapper.OnboardedProductMapper;
import it.pagopa.selfcare.user.util.QueryUtils;
import it.pagopa.selfcare.user.mapper.UserInstitutionMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.RequiredArgsConstructor;
Expand All @@ -21,11 +22,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.*;

import static it.pagopa.selfcare.user.constant.CustomError.USER_NOT_FOUND_ERROR;

import java.util.*;
import static it.pagopa.selfcare.user.util.GeneralUtils.formatQueryParameterList;

@RequiredArgsConstructor
@ApplicationScoped
Expand All @@ -35,20 +35,19 @@ public class UserServiceImpl implements UserService {
@Inject
private UserApi userRegistryApi;
private final OnboardedProductMapper onboardedProductMapper;
private final UserInstitutionMapper userInstitutionMapper;
private final UserInstitutionService userInstitutionService;
private static final String USERS_WORKS_FIELD_LIST = "fiscalCode,familyName,email,name,workContacts";
private static final String WORK_CONTACTS = "workContacts";
private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Inject
private QueryUtils queryUtils;


@Override
public Uni<List<String>> getUsersEmails(String institutionId, String productId) {
var userInstitutionFilters = constructUserInstitutionFilterMap(institutionId);
var productFilters = constructOnboardedProductFilterMap(productId);
Multi<UserInstitution> userInstitutions = userInstitutionService.findAllWithFilter(retrieveMapForFilter(userInstitutionFilters, productFilters));
return userInstitutions.onItem()
.transformToUni(obj -> userRegistryApi.findByIdUsingGET("workContacts", obj.getUserId()))
.transformToUni(obj -> userRegistryApi.findByIdUsingGET(WORK_CONTACTS, obj.getUserId()))
.merge()
.filter(userResource -> Objects.nonNull(userResource.getWorkContacts())
&& userResource.getWorkContacts().containsKey(institutionId))
Expand All @@ -73,27 +72,6 @@ public Multi<UserProductResponse> getUserProductsByInstitution(String institutio
.merge();
}

private Map<String, Object> constructUserInstitutionFilterMap(String institutionId) {
return UserInstitutionFilter
.builder()
.institutionId(institutionId)
.build()
.constructMap();
}

private Map<String, Object> constructOnboardedProductFilterMap(String productId) {
return OnboardedProductFilter.builder()
.productId(productId)
.build()
.constructMap();
}

private Map<String, Object> retrieveMapForFilter(Map<String, Object> ... maps) {
Map<String, Object> map = new HashMap<>();
Arrays.stream(maps).forEach(map::putAll);
return map;
}

@Override
public Uni<UserResource> retrievePerson(String userId, String productId, String institutionId) {
Map<String, Object> queryParameter = buildQueryParams(userId, productId, institutionId);
Expand All @@ -106,6 +84,19 @@ public Uni<UserResource> retrievePerson(String userId, String productId, String
.onFailure(this::checkIfNotFoundException).transform(t -> new ResourceNotFoundException(String.format(USER_NOT_FOUND_ERROR.getMessage(), userId), USER_NOT_FOUND_ERROR.getCode()));
}

@Override
public Multi<UserInstitutionResponse> findAllUserInstitutions(String institutionId,
String userId,
List<String> roles,
List<String> states,
List<String> products,
List<String> productRoles) {
var userInstitutionFilters = constructUserInstitutionFilterMap(institutionId, userId);
var productFilters = constructOnboardedProductFilterMap(products, states, roles, productRoles);
Multi<UserInstitution> userInstitutions = userInstitutionService.findAllWithFilter(retrieveMapForFilter(userInstitutionFilters, productFilters));
return userInstitutions.onItem().transform(userInstitutionMapper::toResponse);
}

private Map<String, Object> buildQueryParams(String userId, String productId, String institutionId) {
OnboardedProductFilter onboardedProductFilter = OnboardedProductFilter.builder()
.productId(productId)
Expand All @@ -121,6 +112,49 @@ private Map<String, Object> buildQueryParams(String userId, String productId, St
return filterMap;
}

private Map<String, Object> constructUserInstitutionFilterMap(String institutionId, String userId) {
return UserInstitutionFilter
.builder()
.institutionId(institutionId)
.userId(userId)
.build()
.constructMap();
}

private Map<String, Object> constructOnboardedProductFilterMap(List<String> products,
List<String> states,
List<String> roles,
List<String> productRoles) {
return OnboardedProductFilter.builder()
.productId(formatQueryParameterList(products))
.role(formatQueryParameterList(roles))
.status(formatQueryParameterList(states))
.productRole(formatQueryParameterList(productRoles))
.build()
.constructMap();
}

private Map<String, Object> constructUserInstitutionFilterMap(String institutionId) {
return UserInstitutionFilter
.builder()
.institutionId(institutionId)
.build()
.constructMap();
}

private Map<String, Object> constructOnboardedProductFilterMap(String productId) {
return OnboardedProductFilter.builder()
.productId(productId)
.build()
.constructMap();
}

private Map<String, Object> retrieveMapForFilter(Map<String, Object> ... maps) {
Map<String, Object> map = new HashMap<>();
Arrays.stream(maps).forEach(map::putAll);
return map;
}

private boolean checkIfNotFoundException(Throwable throwable) {
if(throwable instanceof WebClientApplicationException wex) {
return wex.getResponse().getStatus() == HttpStatus.SC_NOT_FOUND;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package it.pagopa.selfcare.user.util;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;

public class GeneralUtils {

private static final String DELIMITER = ",";

public static List<String> formatQueryParameterList(List<String> list) {
if(Objects.nonNull(list) && list.size() == 1 && list.get(0).contains(DELIMITER)) {
return Arrays.asList(list.get(0).split(DELIMITER));
}
return list;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private void addElemMatchOperator(Map<String, Object> mapForElemMatch, String co
private List<Bson> addEqAndInFilters(Map<String, Object> parameters) {
return parameters.entrySet().stream()
.map(entry -> {
if (entry.getValue() instanceof ArrayList<?>) {
if (entry.getValue() instanceof List<?>) {
return Filters.in(entry.getKey(), (Iterable<?>) entry.getValue());
}
return Filters.eq(entry.getKey(), entry.getValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.mongodb.MongoTestResource;
import io.smallrye.common.constraint.Assert;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.helpers.test.AssertSubscriber;
import io.smallrye.mutiny.helpers.test.UniAssertSubscriber;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.controller.response.UserProductResponse;
import it.pagopa.selfcare.user.entity.OnboardedProduct;
import it.pagopa.selfcare.user.entity.UserInstitution;
Expand Down Expand Up @@ -161,4 +163,18 @@ void testRetrievePersonFailsWhenPdvFails() {

subscriber.assertFailedWith(ResourceNotFoundException.class);
}

@Test
void retrieveUsersTest() {
when(userInstitutionService.findAllWithFilter(any())).thenReturn(Multi.createFrom().item(userInstitution));
AssertSubscriber<UserInstitutionResponse> subscriber = userService
.findAllUserInstitutions("institutionId", "userId", null, null, null, null)
.subscribe()
.withSubscriber(AssertSubscriber.create(10));

List<UserInstitutionResponse> actual = subscriber.assertCompleted().getItems();
Assert.assertNotNull(actual);
assertEquals(1, actual.size());
assertEquals(userInstitution.getUserId(), actual.get(0).getUserId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@
import io.quarkus.test.security.TestSecurity;
import io.restassured.http.ContentType;
import io.smallrye.mutiny.Multi;
import it.pagopa.selfcare.user.controller.response.UserInstitutionResponse;
import it.pagopa.selfcare.user.controller.response.UserProductResponse;
import it.pagopa.selfcare.user.service.UserService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.List;

import static io.restassured.RestAssured.given;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;

@QuarkusTest
Expand Down Expand Up @@ -58,4 +62,38 @@ void getInstitutionUsers() {
.then()
.statusCode(200);
}

/**
* Method under test: {@link InstitutionController#retrieveUsers(String, String, List, List, List, List))}
*/
@Test
void testGetUserInstitutionsNotAuthorized() {
var institutionId = "institutionId";
given()
.when()
.contentType(ContentType.JSON)
.pathParam("institutionId", institutionId)
.get("/{institutionId}/user-institutions")
.then()
.statusCode(401);
}

/**
* Method under test: {@link InstitutionController#retrieveUsers(String, String, List, List, List, List))}
*/
@Test
@TestSecurity(user = "userJwt")
void testGetUserInstitutions() {
var institutionId = "institutionId";
Mockito.when(userService.findAllUserInstitutions(any(), any(), any(), any(), any(), any()))
.thenReturn(Multi.createFrom().items(new UserInstitutionResponse()));
given()
.when()
.contentType(ContentType.JSON)
.pathParam("institutionId", institutionId)
.get("/{institutionId}/user-institutions?products=1,2")
.then()
.statusCode(200);
}

}

0 comments on commit e59873b

Please sign in to comment.