Skip to content

Commit

Permalink
Merge pull request #292 from bcgov/feature/onboarding-saga
Browse files Browse the repository at this point in the history
Add onboarding saga and endpoint
  • Loading branch information
arcshiftsolutions authored Nov 22, 2023
2 parents 985fce9 + 4dcaaa9 commit 512e64b
Show file tree
Hide file tree
Showing 27 changed files with 6,138 additions and 17 deletions.
5 changes: 5 additions & 0 deletions api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<version>1.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.7.1</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum EventOutcome {
*/
PERSONAL_ACTIVATION_CODE_CREATED,
SCHOOL_PRIMARY_CODE_CREATED,
DISTRICT_PRIMARY_CODE_CREATED,
/**
* User activation email sent outcome
*/
Expand Down Expand Up @@ -53,6 +54,7 @@ public enum EventOutcome {
// Create/move school Events Outcome
SCHOOL_CREATED,
SCHOOL_UPDATED,
SCHOOL_FOUND,
SCHOOL_MOVED,
USERS_TO_NEW_SCHOOL_COPIED,
NO_INITIAL_USER_FOUND,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum EventType {
REMOVE_USER_DISTRICT_ACCESS,
CREATE_PERSONAL_ACTIVATION_CODE,
CREATE_SCHOOL_PRIMARY_CODE,
CREATE_DISTRICT_PRIMARY_CODE,
/**
* Send edx user activation email event type.
*/
Expand Down Expand Up @@ -65,6 +66,7 @@ public enum EventType {
// Create/move School Events
CREATE_SCHOOL,
UPDATE_SCHOOL,
FIND_SCHOOL,
COPY_USERS_TO_NEW_SCHOOL,
GET_PAGINATED_SCHOOLS,
MOVE_SCHOOL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public enum SagaEnum {

MOVE_SCHOOL_SAGA,

CREATE_NEW_SCHOOL_SAGA
CREATE_NEW_SCHOOL_SAGA,
ONBOARD_SCHOOL_USER_SAGA,
ONBOARD_DISTRICT_USER_SAGA

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public enum TopicsEnum {
EDX_DISTRICT_USER_ACTIVATION_INVITE_TOPIC,
EDX_MOVE_SCHOOL_TOPIC,
EDX_CREATE_SCHOOL_TOPIC,
EDX_ONBOARD_SCHOOL_USER_TOPIC,
EDX_ONBOARD_DISTRICT_USER_TOPIC,
EDX_EVENT_TOPIC,
INSTITUTE_API_TOPIC

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import ca.bc.gov.educ.api.edx.mappers.v1.SagaDataMapper;
import ca.bc.gov.educ.api.edx.model.v1.SagaEntity;
import ca.bc.gov.educ.api.edx.orchestrator.base.Orchestrator;
import ca.bc.gov.educ.api.edx.service.v1.EdxFileOnboardingService;
import ca.bc.gov.educ.api.edx.service.v1.SagaService;
import ca.bc.gov.educ.api.edx.struct.v1.*;
import ca.bc.gov.educ.api.edx.utils.RequestUtil;
Expand All @@ -24,10 +25,7 @@
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Supplier;

import static ca.bc.gov.educ.api.edx.constants.SagaEnum.*;
Expand All @@ -53,6 +51,9 @@ public class EdxSagaController implements EdxSagaEndpoint {
@Getter(AccessLevel.PRIVATE)
private final CreateSchoolSagaPayloadValidator createSchoolSagaPayloadValidator;

@Getter(AccessLevel.PRIVATE)
private final EdxFileOnboardingService edxFileOnboardingService;

private final CreateSecureExchangeSagaPayloadValidator createSecureExchangeSagaPayloadValidator;

@Getter(PRIVATE)
Expand All @@ -63,14 +64,15 @@ public class EdxSagaController implements EdxSagaEndpoint {

private static final SagaDataMapper SAGA_DATA_MAPPER = SagaDataMapper.mapper;

public EdxSagaController(EdxActivationCodeSagaDataPayloadValidator edxActivationCodeSagaDataPayLoadValidator, SagaService sagaService, List<Orchestrator> orchestrators, SecureExchangePayloadValidator secureExchangePayloadValidator, SecureExchangeCommentSagaValidator secureExchangeCommentSagaValidator, CreateSecureExchangeSagaPayloadValidator createSecureExchangeSagaPayloadValidator, EdxUserPayloadValidator edxUserPayLoadValidator, CreateSchoolSagaPayloadValidator createSchoolSagaPayloadValidator) {
public EdxSagaController(EdxActivationCodeSagaDataPayloadValidator edxActivationCodeSagaDataPayLoadValidator, SagaService sagaService, List<Orchestrator> orchestrators, SecureExchangePayloadValidator secureExchangePayloadValidator, SecureExchangeCommentSagaValidator secureExchangeCommentSagaValidator, CreateSecureExchangeSagaPayloadValidator createSecureExchangeSagaPayloadValidator, EdxUserPayloadValidator edxUserPayLoadValidator, CreateSchoolSagaPayloadValidator createSchoolSagaPayloadValidator, EdxFileOnboardingService edxFileOnboardingService) {
this.edxActivationCodeSagaDataPayLoadValidator = edxActivationCodeSagaDataPayLoadValidator;
this.sagaService = sagaService;
this.secureExchangePayloadValidator = secureExchangePayloadValidator;
this.secureExchangeCommentSagaValidator = secureExchangeCommentSagaValidator;
this.createSecureExchangeSagaPayloadValidator = createSecureExchangeSagaPayloadValidator;
this.edxUserPayLoadValidator = edxUserPayLoadValidator;
this.createSchoolSagaPayloadValidator = createSchoolSagaPayloadValidator;
this.edxFileOnboardingService = edxFileOnboardingService;
orchestrators.forEach(orchestrator -> this.orchestratorMap.put(orchestrator.getSagaName(), orchestrator));
log.info("'{}' Saga Orchestrators are loaded.", String.join(",", this.orchestratorMap.keySet()));
}
Expand Down Expand Up @@ -133,6 +135,15 @@ public ResponseEntity<String> moveSchool(MoveSchoolData moveSchoolData) {
return this.processMoveSchoolSaga(MOVE_SCHOOL_SAGA, moveSchoolData);
}

@Override
public OnboardingFileProcessResponse processOnboardingFile(OnboardingFileUpload fileUpload) {
List<SagaEntity> sagaEntities = this.edxFileOnboardingService.processOnboardingFile(Base64.getDecoder().decode(fileUpload.getFileContents()), fileUpload.getCreateUser());
sagaEntities.forEach(sagaEntity -> processServicesSaga(sagaEntity.getSagaName().equals(ONBOARD_SCHOOL_USER_SAGA.toString()) ? ONBOARD_SCHOOL_USER_SAGA : ONBOARD_DISTRICT_USER_SAGA, sagaEntity));
OnboardingFileProcessResponse response = new OnboardingFileProcessResponse();
response.setProcessedCount(Integer.toString(sagaEntities.size()));
return response;
}

private ResponseEntity<String> processNewSchoolSaga(SagaEnum sagaName, CreateSchoolSagaData newSchoolSagaData) {
try {
RequestUtil.setAuditColumnsForCreate(newSchoolSagaData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import ca.bc.gov.educ.api.edx.struct.v1.*;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -102,4 +105,12 @@ public interface EdxSagaEndpoint {
@ApiResponses(value = {@ApiResponse(responseCode = "202", description = "ACCEPTED"), @ApiResponse(responseCode = "400", description = "BAD REQUEST."), @ApiResponse(responseCode = "409", description = "CONFLICT.")})
@ResponseStatus(ACCEPTED)
ResponseEntity<String> moveSchool(@Validated @RequestBody MoveSchoolData moveSchoolData);

@PreAuthorize("hasAuthority('SCOPE_WRITE_ACTIVATION_CODE')")
@PostMapping("onboarding-file")
@ApiResponses(value = {@ApiResponse(responseCode = "201", description = "CREATED"), @ApiResponse(responseCode = "400", description = "BAD REQUEST")})
@Transactional
@Tag(name = "Endpoint to Upload an excel file and convert to json structure.", description = "Endpoint to upload an onboarding CSV file")
@Schema(name = "OnboardingFileUpload", implementation = OnboardingFileUpload.class)
OnboardingFileProcessResponse processOnboardingFile(@Validated @RequestBody OnboardingFileUpload fileUpload);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ca.bc.gov.educ.api.edx.exception;

public class EdxRuntimeException extends RuntimeException {

private static final long serialVersionUID = 5241655513745148898L;

public EdxRuntimeException(String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ public interface SagaDataMapper {
@Mapping(target = "districtID", ignore = true)
SagaEntity toModel(String sagaName, SecureExchangeCreateSagaData sagaData) throws JsonProcessingException;

@Mapping(target = "status", ignore = true)
@Mapping(target = "secureExchangeId", ignore = true)
@Mapping(target = "sagaState", ignore = true)
@Mapping(target = "sagaId", ignore = true)
@Mapping(target = "sagaCompensated", ignore = true)
@Mapping(target = "retryCount", ignore = true)
@Mapping(target = "payload", expression = "java(ca.bc.gov.educ.api.edx.utils.JsonUtil.getJsonStringFromObject(sagaData))")
@Mapping(target = "emailId", source = "sagaData.email")
@Mapping(target = "edxUserId", ignore = true)
@Mapping(target = "schoolID", ignore = true)
@Mapping(target = "districtID", ignore = true)
SagaEntity toModel(String sagaName, OnboardSchoolUserSagaData sagaData) throws JsonProcessingException;

@Mapping(target = "status", ignore = true)
@Mapping(target = "secureExchangeId", ignore = true)
@Mapping(target = "sagaState", ignore = true)
@Mapping(target = "sagaId", ignore = true)
@Mapping(target = "sagaCompensated", ignore = true)
@Mapping(target = "retryCount", ignore = true)
@Mapping(target = "payload", expression = "java(ca.bc.gov.educ.api.edx.utils.JsonUtil.getJsonStringFromObject(sagaData))")
@Mapping(target = "emailId", source = "sagaData.email")
@Mapping(target = "edxUserId", ignore = true)
@Mapping(target = "schoolID", ignore = true)
@Mapping(target = "districtID", ignore = true)
SagaEntity toModel(String sagaName, OnboardDistrictUserSagaData sagaData) throws JsonProcessingException;

@Mapping(target = "status", ignore = true)
@Mapping(target = "secureExchangeId", ignore = true)
@Mapping(target = "sagaState", ignore = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package ca.bc.gov.educ.api.edx.orchestrator;

import ca.bc.gov.educ.api.edx.messaging.MessagePublisher;
import ca.bc.gov.educ.api.edx.messaging.jetstream.Publisher;
import ca.bc.gov.educ.api.edx.model.v1.SagaEntity;
import ca.bc.gov.educ.api.edx.model.v1.SagaEventStatesEntity;
import ca.bc.gov.educ.api.edx.service.v1.EdxDistrictUserActivationInviteOrchestratorService;
import ca.bc.gov.educ.api.edx.service.v1.EdxUsersService;
import ca.bc.gov.educ.api.edx.service.v1.OnboardUserOrchestratorService;
import ca.bc.gov.educ.api.edx.service.v1.SagaService;
import ca.bc.gov.educ.api.edx.struct.v1.Event;
import ca.bc.gov.educ.api.edx.struct.v1.OnboardDistrictUserSagaData;
import ca.bc.gov.educ.api.edx.utils.JsonUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import static ca.bc.gov.educ.api.edx.constants.EventOutcome.*;
import static ca.bc.gov.educ.api.edx.constants.EventType.*;
import static ca.bc.gov.educ.api.edx.constants.SagaEnum.ONBOARD_DISTRICT_USER_SAGA;
import static ca.bc.gov.educ.api.edx.constants.SagaStatusEnum.IN_PROGRESS;
import static ca.bc.gov.educ.api.edx.constants.TopicsEnum.*;
import static lombok.AccessLevel.PRIVATE;

@Component
@Slf4j
public class OnboardDistrictUserOrchestrator extends DistrictUserActivationBaseOrchestrator<OnboardDistrictUserSagaData> {

@Getter(PRIVATE)
private final Publisher publisher;

@Getter(PRIVATE)
private final EdxUsersService edxUsersService;

@Getter(PRIVATE)
private final OnboardUserOrchestratorService orchestratorService;
@Getter(PRIVATE)
private final EdxDistrictUserActivationInviteOrchestratorService edxSchoolUserActivationInviteOrchestratorService;

protected OnboardDistrictUserOrchestrator(
SagaService sagaService,
MessagePublisher messagePublisher,
OnboardUserOrchestratorService orchestratorService,
Publisher publisher,
EdxUsersService edxUsersService,
EdxDistrictUserActivationInviteOrchestratorService edxDistrictUserActivationInviteOrchestratorService
) {
super(
sagaService,
messagePublisher,
OnboardDistrictUserSagaData.class,
ONBOARD_DISTRICT_USER_SAGA.toString(),
EDX_ONBOARD_DISTRICT_USER_TOPIC.toString(),
edxDistrictUserActivationInviteOrchestratorService
);
this.publisher = publisher;
this.edxUsersService = edxUsersService;
this.orchestratorService = orchestratorService;
this.edxSchoolUserActivationInviteOrchestratorService = edxDistrictUserActivationInviteOrchestratorService;
}

@Override
public void populateStepsToExecuteMap() {
this.stepBuilder()
.begin(CREATE_DISTRICT_PRIMARY_CODE, this::createPrimaryCode)
.step(CREATE_DISTRICT_PRIMARY_CODE, DISTRICT_PRIMARY_CODE_CREATED, SEND_PRIMARY_ACTIVATION_CODE, this::sendPrimaryCode)
.step(SEND_PRIMARY_ACTIVATION_CODE, PRIMARY_ACTIVATION_CODE_SENT, CREATE_PERSONAL_ACTIVATION_CODE, this::createPersonalActivationCode)
.step(CREATE_PERSONAL_ACTIVATION_CODE, PERSONAL_ACTIVATION_CODE_CREATED, SEND_EDX_DISTRICT_USER_ACTIVATION_EMAIL, this::sendEdxUserActivationEmail)
.end(SEND_EDX_DISTRICT_USER_ACTIVATION_EMAIL, EDX_DISTRICT_USER_ACTIVATION_EMAIL_SENT);
}

public void createPrimaryCode(Event event, SagaEntity saga, OnboardDistrictUserSagaData sagaData) throws JsonProcessingException {
final SagaEventStatesEntity eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload());
saga.setSagaState(CREATE_SCHOOL_PRIMARY_CODE.toString());
saga.setStatus(IN_PROGRESS.toString());
this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates);

this.orchestratorService.createPrimaryActivationCode(sagaData);

final Event nextEvent = Event.builder().sagaId(saga.getSagaId())
.eventType(CREATE_DISTRICT_PRIMARY_CODE)
.eventOutcome(DISTRICT_PRIMARY_CODE_CREATED)
.replyTo(getTopicToSubscribe())
.eventPayload(JsonUtil.getJsonStringFromObject(sagaData))
.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
log.info("message sent to EDX_ONBOARD_DISTRICT_USER_TOPIC for CREATE_SCHOOL_PRIMARY_CODE Event. :: {}", saga.getSagaId());
}

public void sendPrimaryCode(Event event, SagaEntity saga, OnboardDistrictUserSagaData sagaData) throws JsonProcessingException {
final SagaEventStatesEntity eventStates = this.createEventState(saga, event.getEventType(), event.getEventOutcome(), event.getEventPayload());
saga.setSagaState(SEND_PRIMARY_ACTIVATION_CODE.toString());
this.getSagaService().updateAttachedSagaWithEvents(saga, eventStates);

this.orchestratorService.sendPrimaryActivationCodeNotification(sagaData);

final Event nextEvent = Event.builder().sagaId(saga.getSagaId())
.eventType(SEND_PRIMARY_ACTIVATION_CODE).eventOutcome(PRIMARY_ACTIVATION_CODE_SENT)
.eventPayload(JsonUtil.getJsonStringFromObject(sagaData))
.build();
this.postMessageToTopic(this.getTopicToSubscribe(), nextEvent);
log.info("message sent to EDX_ONBOARD_DISTRICT_USER_TOPIC for SEND_PRIMARY_ACTIVATION_CODE Event. :: {}", saga.getSagaId());
}
}
Loading

0 comments on commit 512e64b

Please sign in to comment.