Skip to content

Commit

Permalink
Merge pull request #11 from ShopSmartSG/feature/cor_design_pattern
Browse files Browse the repository at this point in the history
Chain of Responsibility design pattern implementation
  • Loading branch information
Arkmati authored Nov 20, 2024
2 parents d5de241 + 5ec2652 commit 3ec98e3
Show file tree
Hide file tree
Showing 23 changed files with 1,315 additions and 598 deletions.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
<properties>
<java.version>21</java.version>
<sonar.coverage.exclusions>**/sg/edu/nus/iss/shopsmart_backend/ShopsmartBackendApplication.java,
**/sg/edu/nus/iss/shopsmart_backend/chains/**,
**/sg/edu/nus/iss/shopsmart_backend/config/**,
**/sg/edu/nus/iss/shopsmart_backend/exception/**,
**/sg/edu/nus/iss/shopsmart_backend/handlers/Handler.java,
**/sg/edu/nus/iss/shopsmart_backend/model/**,
**/sg/edu/nus/iss/shopsmart_backend/service/CommonService.java,
**/sg/edu/nus/iss/shopsmart_backend/utils/**</sonar.coverage.exclusions>
Expand Down Expand Up @@ -134,8 +136,10 @@
<exclude>sg/edu/nus/iss/shopsmart_backend/HomeController.class</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/ShopsmartBackendApplication.class</exclude>
<!-- Exclude all classes in the model package from code coverage -->
<exclude>sg/edu/nus/iss/shopsmart_backend/chains/**</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/config/**</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/exception/**</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/handlers/Handler.class</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/model/**</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/service/CommonService.class</exclude>
<exclude>sg/edu/nus/iss/shopsmart_backend/utils/**</exclude>
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/sg/edu/nus/iss/shopsmart_backend/chains/Chain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package sg.edu.nus.iss.shopsmart_backend.chains;

import sg.edu.nus.iss.shopsmart_backend.model.ApiRequestResolver;
import sg.edu.nus.iss.shopsmart_backend.model.ApiResponseResolver;

import java.util.concurrent.CompletableFuture;

public interface Chain {
CompletableFuture<ApiResponseResolver> handleRequest(ApiRequestResolver request, String profileType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package sg.edu.nus.iss.shopsmart_backend.chains;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.core.util.Json;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import sg.edu.nus.iss.shopsmart_backend.handlers.CheckProfileExistsHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.GenerateOtpHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.Handler;
import sg.edu.nus.iss.shopsmart_backend.model.ApiRequestResolver;
import sg.edu.nus.iss.shopsmart_backend.model.ApiResponseResolver;
import sg.edu.nus.iss.shopsmart_backend.service.ProfileService;
import sg.edu.nus.iss.shopsmart_backend.utils.Constants;

import java.util.concurrent.CompletableFuture;

@Service
public class GenerateOtpForLoginChain extends Constants implements Chain{
private static final Logger log = LoggerFactory.getLogger(GenerateOtpForLoginChain.class);
private final ObjectMapper mapper = Json.mapper();

@Getter
private final Handler handlerChain;
private final ProfileService profileService;

@Autowired
public GenerateOtpForLoginChain(ProfileService profileService) {
this.profileService = profileService;
Handler checkProfileExistsHandler = new CheckProfileExistsHandler(profileService);
Handler generateOtpHandler = new GenerateOtpHandler(profileService);

generateOtpHandler.setNext(null);
checkProfileExistsHandler.setNext(generateOtpHandler);

this.handlerChain = checkProfileExistsHandler;
}

@Override
public CompletableFuture<ApiResponseResolver> handleRequest(ApiRequestResolver apiRequestResolver, String profileType) {
log.info("Starting GenerateOtpForLoginChain, for apiRequestResolver : {}, and profileType {}", apiRequestResolver, profileType);
log.info("{} Generating OTP for profile login with requestObt : {}", apiRequestResolver.getLoggerString(), apiRequestResolver.getRequestBody());
ApiResponseResolver apiResponseResolver = new ApiResponseResolver();
ObjectNode data = mapper.createObjectNode();
JsonNode payload = apiRequestResolver.getRequestBody();
if(payload.isNull() || payload.isEmpty()){
log.info("{} No request body found for opt generation for login", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "No request body found for otp generation for email");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
if(!payload.hasNonNull(EMAIL)){
log.info("{} No Email provided for generating OTP for login", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "Email is required for generating OTP for login");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
String email = payload.get(EMAIL).asText();
if(ADMIN.equalsIgnoreCase(profileType) && !profileService.checkIfValidAdminEmailId(email)){
log.info("{} Email {} is not valid for admin, not authorized", apiRequestResolver.getLoggerString(), email);
apiResponseResolver.setStatusCode(HttpStatus.UNAUTHORIZED); // 401 unauthorized
data.put(MESSAGE, "Email is not authorized for admin OTP generation");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
apiRequestResolver.addParamValue(EMAIL, email);
apiRequestResolver.addParamValue(NEEDS_USER_PROF, "true");
return handlerChain.handle(apiRequestResolver, profileType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package sg.edu.nus.iss.shopsmart_backend.chains;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.core.util.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import sg.edu.nus.iss.shopsmart_backend.handlers.CheckProfileExistsHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.GenerateOtpHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.Handler;
import sg.edu.nus.iss.shopsmart_backend.model.ApiRequestResolver;
import sg.edu.nus.iss.shopsmart_backend.model.ApiResponseResolver;
import sg.edu.nus.iss.shopsmart_backend.service.ProfileService;
import sg.edu.nus.iss.shopsmart_backend.utils.Constants;

import java.util.concurrent.CompletableFuture;

@Service
public class GenerateOtpForRegisterChain extends Constants implements Chain {
private static final Logger log = LoggerFactory.getLogger(GenerateOtpForRegisterChain.class);
private final ObjectMapper mapper = Json.mapper();

private final Handler handlerChain;
private final ProfileService profileService;

@Autowired
public GenerateOtpForRegisterChain(ProfileService profileService) {
this.profileService = profileService;
Handler checkProfileExistsHandler = new CheckProfileExistsHandler(profileService);
Handler generateOtpHandler = new GenerateOtpHandler(profileService);

generateOtpHandler.setNext(null);
checkProfileExistsHandler.setNext(generateOtpHandler);

this.handlerChain = checkProfileExistsHandler;
}

@Override
public CompletableFuture<ApiResponseResolver> handleRequest(ApiRequestResolver apiRequestResolver, String profileType) {
log.info("Starting GenerateOtpForRegisterChain, for apiRequestResolver : {}, and profileType {}", apiRequestResolver, profileType);
log.info("{} Generating OTP for profile registration with requestObt : {}", apiRequestResolver.getLoggerString(), apiRequestResolver.getRequestBody());
ApiResponseResolver apiResponseResolver = new ApiResponseResolver();
ObjectNode data = mapper.createObjectNode();
JsonNode payload = apiRequestResolver.getRequestBody();
if(payload.isNull() || payload.isEmpty()){
log.info("{} No request body found for fetching user id for email", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "No request body found for otp generation for email");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
if(!payload.hasNonNull(EMAIL)){
log.info("{} No Email provided for generating OTP for registration", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "Email is required for generating OTP for registration");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
String email = payload.get(EMAIL).asText();
if(ADMIN.equalsIgnoreCase(profileType) && !profileService.checkIfValidAdminEmailId(email)){
log.info("{} Email {} is not valid for admin, not authorized", apiRequestResolver.getLoggerString(), email);
apiResponseResolver.setStatusCode(HttpStatus.UNAUTHORIZED); // 401 unauthorized
data.put(MESSAGE, "Email is not authorized for admin OTP generation");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
apiRequestResolver.addParamValue(EMAIL, email);
apiRequestResolver.addParamValue(NEEDS_USER_PROF, "false");
return handlerChain.handle(apiRequestResolver, profileType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package sg.edu.nus.iss.shopsmart_backend.chains;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.core.util.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import sg.edu.nus.iss.shopsmart_backend.handlers.FetchUserIdForEmailHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.Handler;
import sg.edu.nus.iss.shopsmart_backend.handlers.ValidateOtpHandler;
import sg.edu.nus.iss.shopsmart_backend.model.ApiRequestResolver;
import sg.edu.nus.iss.shopsmart_backend.model.ApiResponseResolver;
import sg.edu.nus.iss.shopsmart_backend.service.ProfileService;
import sg.edu.nus.iss.shopsmart_backend.utils.Constants;

import java.util.concurrent.CompletableFuture;

@Service
public class ValidateOtpAndLoginChain extends Constants implements Chain{
private static final Logger log = LoggerFactory.getLogger(ValidateOtpAndLoginChain.class);
private final ObjectMapper mapper = Json.mapper();

private final Handler handlerChain;
private final ProfileService profileService;

@Autowired
public ValidateOtpAndLoginChain(ProfileService profileService) {
this.profileService = profileService;
Handler validateOtpHandler = new ValidateOtpHandler(profileService);
Handler fetchUserIdForEmailHandler = new FetchUserIdForEmailHandler(profileService);

validateOtpHandler.setNext(fetchUserIdForEmailHandler);
fetchUserIdForEmailHandler.setNext(null);

this.handlerChain = validateOtpHandler;
}

@Override
public CompletableFuture<ApiResponseResolver> handleRequest(ApiRequestResolver apiRequestResolver, String profileType){
log.info("Starting ValidateOtpAndLoginChain, for apiRequestResolver : {}, and profileType {}", apiRequestResolver, profileType);
log.info("{} validating OTP for profile login with requestObt : {}", apiRequestResolver.getLoggerString(), apiRequestResolver.getRequestBody());
ApiResponseResolver apiResponseResolver = new ApiResponseResolver();
ObjectNode data = mapper.createObjectNode();
JsonNode payload = apiRequestResolver.getRequestBody();
if(payload.isNull() || payload.isEmpty()){
log.info("{} No request body found for OTP validation for login", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "No request body found for otp validation of email");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
if(!payload.hasNonNull(EMAIL) || !payload.hasNonNull(OTP)){
log.info("{} No Email or OTP provided for validating OTP for login", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "Email and OTP are required for validating OTP for login");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
String email = payload.get(EMAIL).asText();
String otp = payload.get(OTP).asText();
apiRequestResolver.addParamValue(EMAIL, email);
apiRequestResolver.addParamValue(OTP, otp);
return handlerChain.handle(apiRequestResolver, profileType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sg.edu.nus.iss.shopsmart_backend.chains;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.core.util.Json;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import sg.edu.nus.iss.shopsmart_backend.handlers.CreateProfileHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.FetchUserIdForEmailHandler;
import sg.edu.nus.iss.shopsmart_backend.handlers.Handler;
import sg.edu.nus.iss.shopsmart_backend.handlers.ValidateOtpHandler;
import sg.edu.nus.iss.shopsmart_backend.model.ApiRequestResolver;
import sg.edu.nus.iss.shopsmart_backend.model.ApiResponseResolver;
import sg.edu.nus.iss.shopsmart_backend.service.ProfileService;
import sg.edu.nus.iss.shopsmart_backend.utils.Constants;

import java.util.concurrent.CompletableFuture;

@Service
public class ValidateOtpAndRegisterChain extends Constants implements Chain{
private static final Logger log = LoggerFactory.getLogger(ValidateOtpAndRegisterChain.class);
private final ObjectMapper mapper = Json.mapper();

private final Handler handlerChain;
private final ProfileService profileService;

@Autowired
public ValidateOtpAndRegisterChain(ProfileService profileService) {
this.profileService = profileService;
Handler validateOtpHandler = new ValidateOtpHandler(profileService);
Handler createProfileHandler = new CreateProfileHandler(profileService);
Handler fetchUserIdForEmailHandler = new FetchUserIdForEmailHandler(profileService);

validateOtpHandler.setNext(createProfileHandler);
createProfileHandler.setNext(fetchUserIdForEmailHandler);
fetchUserIdForEmailHandler.setNext(null);

this.handlerChain = validateOtpHandler;
}

@Override
public CompletableFuture<ApiResponseResolver> handleRequest(ApiRequestResolver apiRequestResolver, String profileType){
log.info("Starting ValidateOtpAndRegisterChain, for apiRequestResolver : {}, and profileType {}", apiRequestResolver, profileType);
log.info("{} validating OTP for profile register with requestObt : {}", apiRequestResolver.getLoggerString(), apiRequestResolver.getRequestBody());
ApiResponseResolver apiResponseResolver = new ApiResponseResolver();
ObjectNode data = mapper.createObjectNode();
JsonNode payload = apiRequestResolver.getRequestBody();
if(payload.isNull() || payload.isEmpty()){
log.info("{} No request body found for OTP validation for registration", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "No request body found for otp validation of email");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
if(!payload.hasNonNull(EMAIL) || !payload.hasNonNull(OTP)){
log.info("{} No Email or OTP provided for validating OTP for registration", apiRequestResolver.getLoggerString());
apiResponseResolver.setStatusCode(HttpStatus.BAD_REQUEST); // 400 bad request
data.put(MESSAGE, "Email and OTP are required for validating OTP for registration");
apiResponseResolver.setRespData(data);
return CompletableFuture.completedFuture(apiResponseResolver);
}
String email = payload.get(EMAIL).asText();
String otp = payload.get(OTP).asText();
apiRequestResolver.addParamValue(EMAIL, email);
apiRequestResolver.addParamValue(OTP, otp);
return handlerChain.handle(apiRequestResolver, profileType);
}
}
Loading

0 comments on commit 3ec98e3

Please sign in to comment.