diff --git a/apps/onboarding-ms/src/main/docs/openapi.json b/apps/onboarding-ms/src/main/docs/openapi.json index 36c63c77d..fe10a7be6 100644 --- a/apps/onboarding-ms/src/main/docs/openapi.json +++ b/apps/onboarding-ms/src/main/docs/openapi.json @@ -502,9 +502,19 @@ "operationId" : "onboardingCompletion", "requestBody" : { "content" : { - "application/json" : { + "multipart/form-data" : { "schema" : { - "$ref" : "#/components/schemas/OnboardingDefaultRequest" + "required" : [ "contract", "onboardingRequest" ], + "type" : "object", + "properties" : { + "contract" : { + "format" : "binary", + "type" : "string" + }, + "onboardingRequest" : { + "type" : "string" + } + } } } } @@ -680,9 +690,19 @@ "operationId" : "onboardingPaCompletion", "requestBody" : { "content" : { - "application/json" : { + "multipart/form-data" : { "schema" : { - "$ref" : "#/components/schemas/OnboardingPaRequest" + "required" : [ "contract", "onboardingRequest" ], + "type" : "object", + "properties" : { + "contract" : { + "format" : "binary", + "type" : "string" + }, + "onboardingRequest" : { + "type" : "string" + } + } } } } @@ -832,9 +852,19 @@ "operationId" : "onboardingPspCompletion", "requestBody" : { "content" : { - "application/json" : { + "multipart/form-data" : { "schema" : { - "$ref" : "#/components/schemas/OnboardingPspRequest" + "required" : [ "contract", "onboardingRequest" ], + "type" : "object", + "properties" : { + "contract" : { + "format" : "binary", + "type" : "string" + }, + "onboardingRequest" : { + "type" : "string" + } + } } } } diff --git a/apps/onboarding-ms/src/main/docs/openapi.yaml b/apps/onboarding-ms/src/main/docs/openapi.yaml index 3237ae842..d3efcb77c 100644 --- a/apps/onboarding-ms/src/main/docs/openapi.yaml +++ b/apps/onboarding-ms/src/main/docs/openapi.yaml @@ -366,9 +366,18 @@ paths: operationId: onboardingCompletion requestBody: content: - application/json: + multipart/form-data: schema: - $ref: "#/components/schemas/OnboardingDefaultRequest" + required: + - contract + - onboardingRequest + type: object + properties: + contract: + format: binary + type: string + onboardingRequest: + type: string responses: "200": description: OK @@ -498,9 +507,18 @@ paths: operationId: onboardingPaCompletion requestBody: content: - application/json: + multipart/form-data: schema: - $ref: "#/components/schemas/OnboardingPaRequest" + required: + - contract + - onboardingRequest + type: object + properties: + contract: + format: binary + type: string + onboardingRequest: + type: string responses: "200": description: OK @@ -606,9 +624,18 @@ paths: operationId: onboardingPspCompletion requestBody: content: - application/json: + multipart/form-data: schema: - $ref: "#/components/schemas/OnboardingPspRequest" + required: + - contract + - onboardingRequest + type: object + properties: + contract: + format: binary + type: string + onboardingRequest: + type: string responses: "200": description: OK diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/OnboardingController.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/OnboardingController.java index 332df436a..2901e6387 100644 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/OnboardingController.java +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/OnboardingController.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Objects; +import static it.pagopa.selfcare.onboarding.util.Utils.parseOnboardingRequest; import static it.pagopa.selfcare.onboarding.util.Utils.retrieveContractFromFormData; @Authenticated @@ -169,26 +170,58 @@ public Uni onboardingPsp(@Valid OnboardingPspRequest onboard ) @Path("/completion") @POST - @Consumes(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) - public Uni onboardingCompletion(@Valid OnboardingDefaultRequest onboardingRequest, @Context SecurityContext ctx) { - return readUserIdFromToken(ctx) - .onItem().transformToUni(userId -> onboardingService - .onboardingCompletion(fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), onboardingRequest.getUsers())); + public Uni onboardingCompletion( + @NotNull @RestForm("contract") File file, + @NotNull @RestForm("onboardingRequest") String onboardingRequestJson, + @Context ResteasyReactiveRequestContext ctx, + @Context SecurityContext securityContext) { + + return readUserIdFromToken(securityContext) + .onItem() + .transformToUni( + userId -> { + // Parse JSON into OnboardingDefaultRequest + OnboardingDefaultRequest onboardingRequest = + parseOnboardingRequest(onboardingRequestJson, OnboardingDefaultRequest.class); + + // Call the onboarding service + return onboardingService.onboardingCompletion( + fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), + onboardingRequest.getUsers(), + retrieveContractFromFormData(ctx.getFormData(), file)); + }); } @Operation( summary = "Complete PA onboarding request and set status to COMPLETED.", - description = "Perform onboarding as /onboarding/pa but completing the onboarding request to COMPLETED phase." - ) + description = + "Perform onboarding as /onboarding/pa but completing the onboarding request to COMPLETED phase.") @POST @Path("/pa/completion") - @Consumes(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) - public Uni onboardingPaCompletion(@Valid OnboardingPaRequest onboardingRequest, @Context SecurityContext ctx) { - return readUserIdFromToken(ctx) - .onItem().transformToUni(userId -> onboardingService - .onboardingCompletion(fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), onboardingRequest.getUsers())); + public Uni onboardingPaCompletion( + @NotNull @RestForm("contract") File file, + @NotNull @RestForm("onboardingRequest") String onboardingRequestJson, + @Context ResteasyReactiveRequestContext ctx, + @Context SecurityContext securityContext) { + + return readUserIdFromToken(securityContext) + .onItem() + .transformToUni( + userId -> { + // Parse JSON into OnboardingPaRequest + OnboardingPaRequest onboardingRequest = + parseOnboardingRequest(onboardingRequestJson, OnboardingPaRequest.class); + + // Call the onboarding service + return onboardingService.onboardingCompletion( + fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), + onboardingRequest.getUsers(), + retrieveContractFromFormData(ctx.getFormData(), file)); + }); } @Operation( @@ -221,16 +254,32 @@ public Uni onboardingPspImport(@Valid OnboardingImportPspReq @Operation( summary = "Complete PSP onboarding request and set status to COMPLETED.", - description = "Perform onboarding as /onboarding/psp but completing the onboarding request to COMPLETED phase." - ) + description = + "Perform onboarding as /onboarding/psp but completing the onboarding request to COMPLETED phase.") @POST @Path("/psp/completion") - @Consumes(MediaType.APPLICATION_JSON) + @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.APPLICATION_JSON) - public Uni onboardingPspCompletion(@Valid OnboardingPspRequest onboardingRequest, @Context SecurityContext ctx) { - return readUserIdFromToken(ctx) - .onItem().transformToUni(userId -> onboardingService - .onboardingCompletion(fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), onboardingRequest.getUsers())); + public Uni onboardingPspCompletion( + @NotNull @RestForm("contract") File file, + @NotNull @RestForm("onboardingRequest") String onboardingRequestJson, + @Context ResteasyReactiveRequestContext ctx, + @Context SecurityContext securityContext) { + + return readUserIdFromToken(securityContext) + .onItem() + .transformToUni( + userId -> { + // Parse JSON into OnboardingPspRequest + OnboardingPspRequest onboardingRequest = + parseOnboardingRequest(onboardingRequestJson, OnboardingPspRequest.class); + + // Call the onboarding service + return onboardingService.onboardingCompletion( + fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), + onboardingRequest.getUsers(), + retrieveContractFromFormData(ctx.getFormData(), file)); + }); } @Operation( @@ -246,7 +295,7 @@ public Uni onboardingPspCompletion(@Valid OnboardingPspReque public Uni onboardingPgCompletion(@Valid OnboardingPgRequest onboardingRequest, @Context SecurityContext ctx) { return readUserIdFromToken(ctx) .onItem().transformToUni(userId -> onboardingService - .onboardingCompletion(fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), onboardingRequest.getUsers())); + .onboardingCompletion(fillUserId(onboardingMapper.toEntity(onboardingRequest), userId), onboardingRequest.getUsers(), null)); } @Operation( diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java index e83376b95..29fdd61e1 100644 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java @@ -20,15 +20,27 @@ public interface OnboardingService { - Uni onboarding(Onboarding onboarding, List userRequests, List aggregates); + Uni onboarding( + Onboarding onboarding, + List userRequests, + List aggregates); - Uni onboardingUsers(OnboardingUserRequest onboardingUserRequest, String userId, WorkflowType workflowType); + Uni onboardingUsers( + OnboardingUserRequest onboardingUserRequest, String userId, WorkflowType workflowType); - Uni onboardingImport(Onboarding onboarding, List userRequests, OnboardingImportContract contractImported, boolean forceImport); + Uni onboardingImport( + Onboarding onboarding, + List userRequests, + OnboardingImportContract contractImported, + boolean forceImport); - Uni onboardingCompletion(Onboarding onboarding, List userRequests); + Uni onboardingCompletion( + Onboarding onboarding, List userRequests, FormItem formItem); - Uni onboardingAggregationCompletion(Onboarding onboarding, List userRequests, List aggregates); + Uni onboardingAggregationCompletion( + Onboarding onboarding, + List userRequests, + List aggregates); Uni onboardingUserPg(Onboarding onboarding, List userRequests); @@ -46,9 +58,16 @@ public interface OnboardingService { Uni onboardingPending(String onboardingId); - Uni> institutionOnboardings(String taxCode, String subunitCode, String origin, String originId, OnboardingStatus status); + Uni> institutionOnboardings( + String taxCode, String subunitCode, String origin, String originId, OnboardingStatus status); - Uni> verifyOnboarding(String taxCode, String subunitCode, String origin, String originId, OnboardingStatus status, String productId); + Uni> verifyOnboarding( + String taxCode, + String subunitCode, + String origin, + String originId, + OnboardingStatus status, + String productId); Uni onboardingGet(String onboardingId); @@ -60,6 +79,8 @@ public interface OnboardingService { Uni checkRecipientCode(String recipientCode, String originId); - Uni onboardingIncrement(Onboarding onboarding, List userRequests, List aggregates); - + Uni onboardingIncrement( + Onboarding onboarding, + List userRequests, + List aggregates); } diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefault.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefault.java index b7ebdc0d6..035874fd8 100644 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefault.java +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefault.java @@ -1,11 +1,8 @@ package it.pagopa.selfcare.onboarding.service; -import static it.pagopa.selfcare.onboarding.common.ProductId.PROD_INTEROP; -import static it.pagopa.selfcare.onboarding.common.ProductId.PROD_PAGOPA; -import static it.pagopa.selfcare.onboarding.constants.CustomError.*; -import static it.pagopa.selfcare.onboarding.util.ErrorMessage.*; -import static it.pagopa.selfcare.product.utils.ProductUtils.validRoles; - +import eu.europa.esig.dss.enumerations.DigestAlgorithm; +import eu.europa.esig.dss.model.DSSDocument; +import eu.europa.esig.dss.model.FileDocument; import io.quarkus.logging.Log; import io.quarkus.mongodb.panache.common.reactive.Panache; import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; @@ -26,6 +23,7 @@ import it.pagopa.selfcare.onboarding.controller.response.OnboardingResponse; import it.pagopa.selfcare.onboarding.controller.response.UserResponse; import it.pagopa.selfcare.onboarding.entity.*; +import it.pagopa.selfcare.onboarding.entity.registry.RegistryManager; import it.pagopa.selfcare.onboarding.entity.registry.RegistryResourceFactory; import it.pagopa.selfcare.onboarding.exception.InvalidRequestException; import it.pagopa.selfcare.onboarding.exception.OnboardingNotAllowedException; @@ -49,14 +47,6 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.ws.rs.WebApplicationException; -import java.io.IOException; -import java.nio.file.Files; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.bson.Document; @@ -74,10 +64,29 @@ import org.openapi.quarkus.party_registry_proxy_json.api.InfocamereApi; import org.openapi.quarkus.party_registry_proxy_json.api.NationalRegistriesApi; import org.openapi.quarkus.party_registry_proxy_json.api.UoApi; -import org.openapi.quarkus.party_registry_proxy_json.model.*; +import org.openapi.quarkus.party_registry_proxy_json.model.BusinessesResource; +import org.openapi.quarkus.party_registry_proxy_json.model.GetInstitutionsByLegalDto; +import org.openapi.quarkus.party_registry_proxy_json.model.GetInstitutionsByLegalFilterDto; import org.openapi.quarkus.user_registry_json.api.UserApi; import org.openapi.quarkus.user_registry_json.model.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static it.pagopa.selfcare.onboarding.common.ProductId.PROD_INTEROP; +import static it.pagopa.selfcare.onboarding.common.ProductId.PROD_PAGOPA; +import static it.pagopa.selfcare.onboarding.constants.CustomError.*; +import static it.pagopa.selfcare.onboarding.util.ErrorMessage.*; +import static it.pagopa.selfcare.onboarding.util.Utils.CONTRACT_FILENAME_FUNC; +import static it.pagopa.selfcare.product.utils.ProductUtils.validRoles; + @ApplicationScoped public class OnboardingServiceDefault implements OnboardingService { @@ -100,6 +109,7 @@ public class OnboardingServiceDefault implements OnboardingService { private static final String ID_MAIL_PREFIX = "ID_MAIL#"; public static final String NOT_MANAGER_OF_THE_INSTITUTION_ON_THE_REGISTRY = "User is not manager of the institution on the registry"; + protected static final String PDF_FORMAT_FILENAME = "%s_accordo_adesione.pdf"; @RestClient @Inject @@ -204,7 +214,8 @@ public Uni onboardingIncrement( * As onboarding but it is specific for USERS workflow */ @Override - public Uni onboardingUsers(OnboardingUserRequest request, String userId, WorkflowType workflowType) { + public Uni onboardingUsers( + OnboardingUserRequest request, String userId, WorkflowType workflowType) { return getInstitutionFromUserRequest(request) .onItem() .transform(response -> institutionMapper.toEntity(response)) @@ -219,7 +230,7 @@ public Uni onboardingUsers(OnboardingUserRequest request, St return onboarding; }) .onItem() - .transformToUni(onboarding -> fillUsers(onboarding, request.getUsers(), null)); + .transformToUni(onboarding -> verifyExistingOnboarding(onboarding, request.getUsers(), null)); } /** @@ -229,12 +240,12 @@ public Uni onboardingUsers(OnboardingUserRequest request, St */ @Override public Uni onboardingCompletion( - Onboarding onboarding, List userRequests) { + Onboarding onboarding, List userRequests, FormItem formItem) { onboarding.setWorkflowType(WorkflowType.CONFIRMATION); onboarding.setStatus(OnboardingStatus.PENDING); - return fillUsersAndOnboarding( - onboarding, userRequests, null, TIMEOUT_ORCHESTRATION_RESPONSE, false); + return fillUsersAndOnboardingCompletion( + onboarding, userRequests, TIMEOUT_ORCHESTRATION_RESPONSE, formItem); } @Override @@ -273,52 +284,86 @@ private Uni fillUsersAndOnboarding( List aggregates, String timeout, boolean isAggregatesIncrement) { + + onboarding.setCreatedAt(LocalDateTime.now()); + + return verifyExistingOnboarding(onboarding, isAggregatesIncrement) + .onItem() + .transformToUni(product -> handleOnboarding(onboarding, userRequests, aggregates, timeout, product, null)); + } + + private Uni fillUsersAndOnboardingCompletion( + Onboarding onboarding, + List userRequests, + String timeout, + FormItem formItem) { + onboarding.setCreatedAt(LocalDateTime.now()); + return verifyExistingOnboarding(onboarding, false) + .onItem() + .transformToUni(product -> handleOnboarding(onboarding, userRequests, null, timeout, product, formItem)); + } + + private Uni verifyExistingOnboarding(Onboarding onboarding, boolean isAggregatesIncrement) { return getProductByOnboarding(onboarding) .onItem() .transformToUni( - product -> - verifyAlreadyOnboarding( - onboarding.getInstitution(), - product.getId(), - product.getParentId(), - isAggregatesIncrement) - .replaceWith(product)) + product -> verifyAlreadyOnboarding( + onboarding.getInstitution(), + product.getId(), + product.getParentId(), + isAggregatesIncrement) + .replaceWith(product)); + } + + private Uni handleOnboarding( + Onboarding onboarding, + List userRequests, + List aggregates, + String timeout, + Product product, + FormItem formItem) { + + return Uni.createFrom() + .item(registryResourceFactory.create(onboarding)) .onItem() - .transformToUni( - product -> - Uni.createFrom() - .item(registryResourceFactory.create(onboarding)) - .onItem() - .invoke( - registryManager -> - registryManager.setResource(registryManager.retrieveInstitution())) - .runSubscriptionOn(Infrastructure.getDefaultWorkerPool()) - .onItem() - .transformToUni( - registryManager -> - registryManager - .isValid() - .onItem() - .transformToUni( - ignored -> registryManager.customValidation(product))) - /* if product has some test environments, request must also onboard them (for ex. prod-interop-coll) */ - .onItem() - .invoke(() -> onboarding.setTestEnvProductIds(product.getTestEnvProductIds())) - .onItem() - .transformToUni( - current -> persistOnboarding(onboarding, userRequests, product, aggregates)) - /* Update onboarding data with users and start orchestration */ - .onItem() - .transformToUni( - currentOnboarding -> - persistAndStartOrchestrationOnboarding( - currentOnboarding, - orchestrationApi.apiStartOnboardingOrchestrationGet( - currentOnboarding.getId(), timeout))) - .onItem() - .transform(onboardingMapper::toResponse)); + .invoke(registryManager -> registryManager.setResource(registryManager.retrieveInstitution())) + .runSubscriptionOn(Infrastructure.getDefaultWorkerPool()) + .onItem() + .transformToUni(registryManager -> validateAndPersistOnboarding(registryManager, onboarding, userRequests, aggregates, product, formItem, timeout)); + } + + private Uni validateAndPersistOnboarding( + RegistryManager registryManager, + Onboarding onboarding, + List userRequests, + List aggregates, + Product product, + FormItem formItem, + String timeout) { + + return registryManager.isValid() + .onItem() + .transformToUni(ignored -> registryManager.customValidation(product)) + .onItem() + .invoke(() -> onboarding.setTestEnvProductIds(product.getTestEnvProductIds())) + .onItem() + .transformToUni(current -> persistOnboarding(onboarding, userRequests, product, aggregates)) + .onItem() + .transformToUni(persistedOnboarding -> handleSignedContractAndToken(persistedOnboarding, formItem)) + .onItem() + .transformToUni(currentOnboarding -> persistAndStartOrchestrationOnboarding(currentOnboarding, + orchestrationApi.apiStartOnboardingOrchestrationGet(currentOnboarding.getId(), timeout))) + .onItem() + .transform(onboardingMapper::toResponse); + } + + private Uni handleSignedContractAndToken(Onboarding persistedOnboarding, FormItem formItem) { + return Optional.ofNullable(formItem) + .map(item -> uploadSignedContractAndUpdateToken(persistedOnboarding, item) + .map(ignore -> persistedOnboarding)) + .orElse(Uni.createFrom().item(persistedOnboarding)); } /** @@ -345,7 +390,7 @@ private Uni verifyAlreadyOnboarding( * response is delivered synchronously. If is null the timeout is default 1 sec and the * response is delivered asynchronously */ - private Uni fillUsers( + private Uni verifyExistingOnboarding( Onboarding onboarding, List userRequests, String timeout) { onboarding.setCreatedAt(LocalDateTime.now()); @@ -653,7 +698,6 @@ private Uni validateAllowedMap(String taxCode, String subunitCode, Stri Log.infof( "Validating allowed map for: taxCode %s, subunitCode %s, product %s", taxCode, subunitCode, productId); - if (!onboardingValidationStrategy.validate(productId, taxCode)) { return Uni.createFrom() .failure( @@ -661,7 +705,6 @@ private Uni validateAllowedMap(String taxCode, String subunitCode, Stri String.format(ONBOARDING_NOT_ALLOWED_ERROR_MESSAGE_TEMPLATE, taxCode, productId), DEFAULT_ERROR.getCode())); } - return Uni.createFrom().item(Boolean.TRUE); } @@ -1157,8 +1200,7 @@ private Uni complete( .onItem() .transformToUni( onboarding -> - uploadSignedContractAndUpdateToken(onboardingId, formItem) - .map(ignore -> onboarding)) + uploadSignedContractAndUpdateToken(onboarding, formItem).map(ignore -> onboarding)) // Start async activity if onboardingOrchestrationEnabled is true .onItem() .transformToUni( @@ -1193,8 +1235,7 @@ private Uni completeOnboardingUsers( .onItem() .transformToUni( onboarding -> - uploadSignedContractAndUpdateToken(onboardingId, formItem) - .map(ignore -> onboarding)) + uploadSignedContractAndUpdateToken(onboarding, formItem).map(ignore -> onboarding)) // Start async activity if onboardingOrchestrationEnabled is true .onItem() .transformToUni( @@ -1206,47 +1247,91 @@ private Uni completeOnboardingUsers( : Uni.createFrom().item(onboarding)); } - private Uni uploadSignedContractAndUpdateToken(String onboardingId, FormItem formItem) { + private Uni uploadSignedContractAndUpdateToken(Onboarding onboarding, FormItem formItem) { + String onboardingId = onboarding.getId(); + return retrieveToken(onboardingId) + .onFailure() + .recoverWithUni(() -> createToken(onboarding, onboardingId, formItem)) .onItem() - .transformToUni( - token -> - Uni.createFrom() - .item( - Unchecked.supplier( - () -> { - final String path = - String.format("%s%s", pathContracts, onboardingId); - final String signedContractExtension = - getFileExtension(formItem.getFileName()); - final String persistedContractFileName = - Optional.ofNullable(token.getContractFilename()) - .orElse(onboardingId); - final String signedContractFileName = - replaceFileExtension( - persistedContractFileName, signedContractExtension); - final String filename = - String.format("signed_%s", signedContractFileName); - - try { - return azureBlobClient.uploadFile( - path, - filename, - Files.readAllBytes(formItem.getFile().toPath())); - } catch (IOException e) { - throw new OnboardingNotAllowedException( - GENERIC_ERROR.getCode(), - "Error on upload contract for onboarding with id " - + onboardingId); - } - })) - .runSubscriptionOn(Infrastructure.getDefaultWorkerPool()) - .onItem() - .transformToUni( - filepath -> - Token.update("contractSigned", filepath) - .where("_id", token.getId()) - .replaceWith(filepath))); + .call(this::persistToken) + .onItem() + .transformToUni(token -> processAndUploadFile(token, onboardingId, formItem)); + } + + private Uni createToken(Onboarding onboarding, String onboardingId, FormItem formItem) { + return Uni.createFrom() + .item(() -> createToken(onboarding, onboardingId, formItem.getFile())); + } + + private Uni persistToken(Token tokenPersisted) { + return Panache.withTransaction(() -> Token.persist(tokenPersisted)); + } + + private Uni processAndUploadFile(Token token, String onboardingId, FormItem formItem) { + return Uni.createFrom() + .item(Unchecked.supplier(() -> uploadFileToAzure(token, onboardingId, formItem))) + .runSubscriptionOn(Infrastructure.getDefaultWorkerPool()) + .onItem() + .transformToUni(filepath -> updateTokenWithFilePath(filepath, token)); + } + + private String uploadFileToAzure(Token token, String onboardingId, FormItem formItem) + throws OnboardingNotAllowedException { + final String path = String.format("%s%s", pathContracts, onboardingId); + final String signedContractExtension = getFileExtension(formItem.getFileName()); + final String persistedContractFileName = + Optional.ofNullable(token.getContractFilename()).orElse(onboardingId); + final String signedContractFileName = + replaceFileExtension(persistedContractFileName, signedContractExtension); + final String filename = String.format("signed_%s", signedContractFileName); + + try { + return azureBlobClient.uploadFile( + path, filename, Files.readAllBytes(formItem.getFile().toPath())); + } catch (IOException e) { + throw new OnboardingNotAllowedException( + GENERIC_ERROR.getCode(), + "Error on upload contract for onboarding with id " + onboardingId); + } + } + + private Uni updateTokenWithFilePath(String filepath, Token token) { + return Token.update("contractSigned", filepath) + .where("_id", token.getId()) + .replaceWith(filepath); + } + + private Token createToken(Onboarding onboarding, String onboardingId, File file) { + Product product = productService.getProduct(onboarding.getProductId()); + final String filename = CONTRACT_FILENAME_FUNC.apply(PDF_FORMAT_FILENAME, product.getTitle()); + DSSDocument document = new FileDocument(file); + String digest = document.getDigest(DigestAlgorithm.SHA256); + Token token = new Token(); + token.setCreatedAt(LocalDateTime.now()); + token.setOnboardingId(onboardingId); + token.setId(onboardingId); + token.setProductId(onboarding.getProductId()); + token.setUpdatedAt(LocalDateTime.now()); + token.setActivatedAt(LocalDateTime.now()); + token.setContractFilename(filename); + token.setContractTemplate(getContractTemplatePath(product, onboarding)); + token.setContractVersion(getContractTemplateVersion(product, onboarding)); + token.setChecksum(digest); + token.setType(TokenType.INSTITUTION); + return token; + } + + private String getContractTemplatePath(Product product, Onboarding onboarding) { + return product + .getInstitutionContractTemplate(InstitutionUtils.getCurrentInstitutionType(onboarding)) + .getContractTemplatePath(); + } + + private String getContractTemplateVersion(Product product, Onboarding onboarding) { + return product + .getInstitutionContractTemplate(InstitutionUtils.getCurrentInstitutionType(onboarding)) + .getContractTemplateVersion(); } private String getFileExtension(String name) { @@ -2045,4 +2130,4 @@ private Uni checkIfUserIsManagerOnADE(String userTaxCode, String businessT return Uni.createFrom().failure(ex); }); } -} \ No newline at end of file +} diff --git a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/Utils.java b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/Utils.java index a9dde5595..d2290f32e 100644 --- a/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/Utils.java +++ b/apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/Utils.java @@ -1,28 +1,44 @@ package it.pagopa.selfcare.onboarding.util; +import com.fasterxml.jackson.databind.ObjectMapper; import it.pagopa.selfcare.onboarding.constants.CustomError; import it.pagopa.selfcare.onboarding.exception.InvalidRequestException; import it.pagopa.selfcare.onboarding.model.FormItem; +import org.apache.commons.lang3.StringUtils; import org.jboss.resteasy.reactive.server.core.multipart.FormData; import org.jboss.resteasy.reactive.server.multipart.FormValue; import java.io.File; +import java.io.IOException; import java.util.Deque; +import java.util.function.BinaryOperator; public class Utils { private static final String DEFAULT_CONTRACT_FORM_DATA_NAME = "contract"; + private Utils() { } public static FormItem retrieveContractFromFormData(FormData formData, File file) { Deque deck = formData.get(DEFAULT_CONTRACT_FORM_DATA_NAME); - if(deck.size() > 1) { - throw new InvalidRequestException(CustomError.TOO_MANY_CONTRACTS.getMessage(), CustomError.TOO_MANY_CONTRACTS.getCode()); + if (deck.size() > 1) { + throw new InvalidRequestException( + CustomError.TOO_MANY_CONTRACTS.getMessage(), CustomError.TOO_MANY_CONTRACTS.getCode()); } - return FormItem.builder() - .file(file) - .fileName(deck.getFirst().getFileName()) - .build(); + return FormItem.builder().file(file).fileName(deck.getFirst().getFileName()).build(); + } + + public static final BinaryOperator CONTRACT_FILENAME_FUNC = + (filename, productName) -> + String.format(filename, StringUtils.stripAccents(productName.replaceAll("\\s+", "_"))); + + public static T parseOnboardingRequest(String onboardingRequestJson, Class targetType) { + try { + return new ObjectMapper().readValue(onboardingRequestJson, targetType); + } catch (IOException e) { + throw new InvalidRequestException("Invalid onboardingRequest JSON format for " + targetType.getSimpleName()); + } } + } diff --git a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/OnboardingControllerTest.java b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/OnboardingControllerTest.java index 269f06c70..0c25fbe96 100644 --- a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/OnboardingControllerTest.java +++ b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/OnboardingControllerTest.java @@ -59,6 +59,9 @@ class OnboardingControllerTest { static final OnboardingUserPgRequest onboardingUserPgValid; + final String onboardingRequestJson = + "{\"productId\":\"prod-pn\",\"users\":[{\"taxCode\":\"GRSGNT71A62C950S\",\"name\":\"Gianantonio\",\"surname\":\"Grassi\",\"email\":\"g.grassi@test.it\",\"role\":\"MANAGER\"}],\"institution\":{\"institutionType\":\"PA\",\"taxCode\":\"80415740580\",\"origin\":\"IPA\",\"originId\":\"m_ef\",\"city\":\"ROMA\",\"country\":\"IT\",\"county\":\"RM\",\"description\":\"Ministero dell'Economia e delle Finanze\",\"digitalAddress\":\"mef@pec.mef.gov.it\",\"address\":\"Via XX Settembre, 97\",\"zipCode\":\"00187\",\"geographicTaxonomies\":[{\"code\":\"ITA\",\"desc\":\"ITALIA\"}],\"imported\":true},\"billing\":{\"vatNumber\":\"80415740580\",\"recipientCode\":\"Y7NS8B\",\"publicServices\":true}}"; + @InjectMock OnboardingService onboardingService; @@ -576,61 +579,77 @@ void approve() { @Test @TestSecurity(user = "userJwt") - void onboardingComplete() { + void onboardingCompletionMultipart() { - Mockito.when(onboardingService.onboardingCompletion(any(), any())) + // Mock della risposta del servizio + Mockito.when(onboardingService.onboardingCompletion(any(), any(), any())) .thenReturn(Uni.createFrom().item(new OnboardingResponse())); + // Creazione di un file fittizio per il test + File testFile = new File("src/test/resources/application.properties"); + + // Esecuzione della richiesta simulata given() + .multiPart("contract", testFile) // Aggiunta del file + .multiPart("onboardingRequest", onboardingRequestJson) // Aggiunta del JSON come stringa + .contentType("multipart/form-data") .when() - .body(onboardingBaseValid) - .contentType(ContentType.JSON) .post("/completion") .then() - .statusCode(200); + .statusCode(200); // Verifica del codice di stato - Mockito.verify(onboardingService, times(1)) - .onboardingCompletion(any(), any()); + // Verifica che il servizio sia stato chiamato correttamente + Mockito.verify(onboardingService, times(1)).onboardingCompletion(any(), any(), any()); } @Test @TestSecurity(user = "userJwt") - void onboardingCompletePa() { - - OnboardingPaRequest onboardingPaValid = dummyOnboardingPa(); + void onboardingPaCompletionMultipart() { - Mockito.when(onboardingService.onboardingCompletion(any(), any())) + // Mock della risposta del servizio + Mockito.when(onboardingService.onboardingCompletion(any(), any(), any())) .thenReturn(Uni.createFrom().item(new OnboardingResponse())); + // Creazione di un file fittizio per il test + File testFile = new File("src/test/resources/application.properties"); + + // Esecuzione della richiesta simulata given() + .multiPart("contract", testFile) // Aggiunta del file + .multiPart("onboardingRequest", onboardingRequestJson) // Aggiunta del JSON come stringa + .contentType("multipart/form-data") .when() - .body(onboardingPaValid) - .contentType(ContentType.JSON) .post("/pa/completion") .then() - .statusCode(200); + .statusCode(200); // Verifica del codice di stato - Mockito.verify(onboardingService, times(1)) - .onboardingCompletion(any(), any()); + // Verifica che il servizio sia stato chiamato correttamente + Mockito.verify(onboardingService, times(1)).onboardingCompletion(any(), any(), any()); } @Test @TestSecurity(user = "userJwt") - void onboardingCompletePsp() { + void onboardingPspCompletionMultipart() { - Mockito.when(onboardingService.onboardingCompletion(any(), any())) + // Mock della risposta del servizio + Mockito.when(onboardingService.onboardingCompletion(any(), any(), any())) .thenReturn(Uni.createFrom().item(new OnboardingResponse())); + // Creazione di un file fittizio per il test + File testFile = new File("src/test/resources/application.properties"); + + // Esecuzione della richiesta simulata given() + .multiPart("contract", testFile) // Aggiunta del file + .multiPart("onboardingRequest", onboardingRequestJson) // Aggiunta del JSON come stringa + .contentType("multipart/form-data") .when() - .body(onboardingPspValid) - .contentType(ContentType.JSON) .post("/psp/completion") .then() - .statusCode(200); + .statusCode(200); // Verifica del codice di stato - Mockito.verify(onboardingService, times(1)) - .onboardingCompletion(any(), any()); + // Verifica che il servizio sia stato chiamato correttamente + Mockito.verify(onboardingService, times(1)).onboardingCompletion(any(), any(), any()); } @Test @@ -644,7 +663,7 @@ void onboardingCompletePg() { onboardingPgRequest.setDigitalAddress("digital@address.it"); onboardingPgRequest.setOrigin(Origin.INFOCAMERE); - Mockito.when(onboardingService.onboardingCompletion(any(), any())) + Mockito.when(onboardingService.onboardingCompletion(any(), any(), any())) .thenReturn(Uni.createFrom().item(new OnboardingResponse())); given() @@ -656,8 +675,7 @@ void onboardingCompletePg() { .statusCode(200); ArgumentCaptor captor = ArgumentCaptor.forClass(Onboarding.class); - Mockito.verify(onboardingService, times(1)) - .onboardingCompletion(captor.capture(), any()); + Mockito.verify(onboardingService, times(1)).onboardingCompletion(captor.capture(), any(), any()); assertEquals(InstitutionType.PG, captor.getValue().getInstitution().getInstitutionType()); } diff --git a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefaultTest.java b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefaultTest.java index ac24c55cb..fc0f2efb0 100644 --- a/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefaultTest.java +++ b/apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/service/OnboardingServiceDefaultTest.java @@ -930,7 +930,7 @@ void onboarding_Onboarding_Aggregator(UniAsserter asserter) { institutionResource.setDescription(DESCRIPTION_FIELD); when(institutionRegistryProxyApi.findInstitutionUsingGET(any(), any(), any())).thenReturn(Uni.createFrom().item(institutionResource)); - asserter.assertThat(() -> onboardingService.onboarding(request, users,null), Assertions::assertNotNull); + asserter.assertThat(() -> onboardingService.onboarding(request, users, null), Assertions::assertNotNull); asserter.execute(() -> { PanacheMock.verify(Onboarding.class).persist(any(Onboarding.class), any()); @@ -1067,6 +1067,7 @@ Product createDummyProduct(String productId, boolean hasParent) { productResource.setId(productId); productResource.setRoleMappings(Map.of(manager.getRole(), dummyProductRoleInfo(productRoleAdminCode))); productResource.setRoleMappingsByInstitutionType(Map.of(PSP.name(), roleMappingByInstitutionType)); + productResource.setTitle("title"); if (hasParent) { Product parent = new Product(); @@ -2035,6 +2036,50 @@ void onboarding_aggregationCompletion(UniAsserter asserter) { }); } + @Test + @RunOnVertxContext + void onboardingCompletion(UniAsserter asserter) { + Onboarding request = new Onboarding(); + Product product = createDummyProduct(PROD_IO.getValue(), false); + List users = List.of(manager); + request.setProductId(PROD_IO.getValue()); + Institution institutionBaseRequest = new Institution(); + institutionBaseRequest.setTaxCode("taxCode"); + institutionBaseRequest.setInstitutionType(InstitutionType.SA); + request.setInstitution(institutionBaseRequest); + + mockPersistOnboarding(asserter); + mockPersistToken(asserter); + + mockSimpleSearchPOSTAndPersist(asserter); + mockSimpleProductValidAssert(request.getProductId(), false, asserter); + mockVerifyOnboardingNotFound(); + mockVerifyAllowedMap(request.getInstitution().getTaxCode(), request.getProductId(), asserter); + + asserter.execute(() -> when(productService.getProduct(any())).thenReturn(product)); + + asserter.execute(() -> when(userRegistryApi.updateUsingPATCH(any(), any())) + .thenReturn(Uni.createFrom().item(Response.noContent().build()))); + + InstitutionResource institutionResource = new InstitutionResource(); + institutionResource.setCategory("L37"); + asserter.execute(() -> when(institutionRegistryProxyApi.findInstitutionUsingGET(institutionBaseRequest.getTaxCode(), null, null)) + .thenReturn(Uni.createFrom().item(institutionResource))); + + final String filepath = "upload-file-path"; + when(azureBlobClient.uploadFile(any(), any(), any())).thenReturn(filepath); + mockUpdateToken(asserter, filepath); + + asserter.assertThat(() -> onboardingService.onboardingCompletion(request, users, TEST_FORM_ITEM), Assertions::assertNotNull); + + asserter.execute(() -> { + PanacheMock.verify(Onboarding.class).persist(any(Onboarding.class), any()); + PanacheMock.verify(Onboarding.class).persistOrUpdate(any(List.class)); + PanacheMock.verify(Onboarding.class).find(any(Document.class)); + PanacheMock.verifyNoMoreInteractions(Onboarding.class); + }); + } + @Test @RunOnVertxContext void onboardingUsers(UniAsserter asserter) {