Commit bf9d99cb authored by Shanelle Valencia's avatar Shanelle Valencia

Merge branch 'dev' into 'new-fetch-status-warehouse'

# Conflicts:
#   src/main/java/com/afp/ordermanagement/model/Order.java
#   src/main/java/com/afp/ordermanagement/service/OrderService.java
parents 1daa4d53 26bec4c7
Pipeline #1727 failed with stage
in 38 seconds
No preview for this file type
......@@ -13,7 +13,7 @@ target/
.springBeans
.sts4-cache
*.properties
application.properties
*.yml
### IntelliJ IDEA ###
......@@ -34,3 +34,6 @@ build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
apiVersion: apps/v1
kind: Deployment
metadata:
name: afp-order-management-backend-deployment
spec:
replicas: 2
selector:
matchLabels:
app: afp-order-management-backend
template:
metadata:
labels:
app: afp-order-management-backend
spec:
containers:
- name: afp-order-management-backend-container
image: nexus.mynisum.com/afp-order-management-backend:7
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
imagePullSecrets:
- name: registry-creds
---
apiVersion: v1
kind: Service
metadata:
name: afp-order-management-backend-service
spec:
type: LoadBalancer
ports:
- port: 8080
targetPort: 8080
selector:
app: afp-order-management-backend
\ No newline at end of file
......@@ -50,13 +50,6 @@
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<!--<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>-->
<!-- https://mvnrepository.com/artifact/com.github.javafaker/javafaker -->
<dependency>
<groupId>com.github.javafaker</groupId>
......@@ -92,10 +85,21 @@
<artifactId>velocity</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
<dependency>
<groupId>io.projectreactor.kafka</groupId>
<artifactId>reactor-kafka</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.api-client/google-api-client -->
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.31.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
......
package com.afp.ordermanagement.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessToken {
}
package com.afp.ordermanagement.annotation;
import com.afp.ordermanagement.service.ManagerTokenVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class AccessTokenResolver implements HandlerMethodArgumentResolver {
@Autowired
ManagerTokenVerifier managerTokenVerifier;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
boolean hasClassAnnotation = methodParameter.getDeclaringClass().isAnnotationPresent(AuthManagerController.class);
boolean hasMethodAnnotation = methodParameter.hasMethodAnnotation(AuthManagerResponse.class);
boolean hasParameterAnnotation = methodParameter.hasParameterAnnotation(AccessToken.class);
return (hasClassAnnotation || hasMethodAnnotation) && hasParameterAnnotation;
}
@Override
public Mono<Object> resolveArgument(MethodParameter methodParameter, BindingContext bindingContext, ServerWebExchange serverWebExchange) {
String accessToken = managerTokenVerifier.getTokenHeader(serverWebExchange);
return Mono.just(accessToken);
}
}
package com.afp.ordermanagement.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthManagerController {
}
package com.afp.ordermanagement.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthManagerResponse {
}
package com.afp.ordermanagement.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ManagerPayload {
String value() default "";
String pluck() default "";
}
package com.afp.ordermanagement.annotation;
import com.afp.ordermanagement.model.Manager;
import com.afp.ordermanagement.service.ManagerTokenVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
public class ManagerPayloadResolver implements HandlerMethodArgumentResolver {
@Autowired
ManagerTokenVerifier managerTokenVerifier;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
boolean hasClassAnnotation = methodParameter.getDeclaringClass().isAnnotationPresent(AuthManagerController.class);
boolean hasMethodAnnotation = methodParameter.hasMethodAnnotation(AuthManagerResponse.class);
boolean hasParameterAnnotation = methodParameter.hasParameterAnnotation(ManagerPayload.class);
return (hasClassAnnotation || hasMethodAnnotation) && hasParameterAnnotation;
}
@Override
public Mono<Object> resolveArgument(MethodParameter methodParameter, BindingContext bindingContext, ServerWebExchange serverWebExchange) {
String accessToken = managerTokenVerifier.getTokenHeader(serverWebExchange);
ManagerPayload annotation = methodParameter.getParameterAnnotation(ManagerPayload.class);
String value = annotation.value(), pluck = annotation.pluck(), selectedField;
List<Field> declaredFields = Arrays.asList(Manager.class.getDeclaredFields());
Manager manager = managerTokenVerifier.createManagerFromToken(accessToken);
if (value.isEmpty() && pluck.isEmpty()) {
return Mono.just(manager);
} else {
selectedField = value.isEmpty() ? pluck : value;
if (!declaredFields.contains(selectedField))
return Mono.just(manager);
try {
Field field = Manager.class.getDeclaredField(selectedField);
field.setAccessible(true);
return Mono.just(field.get(manager));
} catch (Exception ignore) {
return Mono.just(manager);
}
}
}
}
\ No newline at end of file
package com.afp.ordermanagement.config;
import com.afp.ordermanagement.annotation.AccessTokenResolver;
import com.afp.ordermanagement.annotation.ManagerPayloadResolver;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
@Configuration
//@ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
//@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
//@EnableWebFlux
public class AnnotationConfig implements WebFluxConfigurer {
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(accessTokenResolver());
configurer.addCustomResolver(managerPayloadResolver());
}
@Bean
public AccessTokenResolver accessTokenResolver() {
return new AccessTokenResolver();
}
@Bean
public ManagerPayloadResolver managerPayloadResolver() {
return new ManagerPayloadResolver();
}
}
package com.afp.ordermanagement.config;
import com.afp.ordermanagement.exception.BadAccessTokenException;
import com.afp.ordermanagement.service.ManagerTokenVerifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Component
public class AuthWebFilter implements WebFilter {
@Autowired
ManagerTokenVerifier managerTokenVerifier;
@Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
// String origin = serverWebExchange.getRequest().getHeaders().getOrigin();
// if (managerTokenVerifier.hasTokenHeader(serverWebExchange)) {
// String token = managerTokenVerifier.getTokenHeader(serverWebExchange);
// if (managerTokenVerifier.isTokenValid(token))
return webFilterChain.filter(serverWebExchange);
// }
// return Mono.error(new BadAccessTokenException());
}
}
package com.afp.ordermanagement.config;
import com.afp.ordermanagement.model.Manager;
import com.github.javafaker.Faker;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.util.Objects.isNull;
@Configuration
public class BeanConfig {
@Bean
public static Faker faker() {
return new Faker();
}
@Bean
public static FieldCombiner fieldCombiner() { return new FieldCombiner(); }
public static class FieldCombiner {
public Object combine(Object target, Object source, String[] omitFields) {
try {
for (Field field: Manager.class.getDeclaredFields()) {
String fieldName = field.getName();
field.setAccessible(true);
Object sourceValue = field.get(source);
if (!isNull(sourceValue) && Arrays.binarySearch(omitFields, fieldName) <= 0)
field.set(target, sourceValue);
}
} catch (Exception ignore) { }
return target;
}
public Object combine(Object target, Object source) {
return this.combine(target, source, new String[0]);
}
}
}
package com.afp.ordermanagement.config;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
@ComponentScan(basePackages = { "com.afp.ordermanagement.mail" })
@PropertySource(value={"classpath:application.properties"})
public class MailConfig {
@Value("${spring.mail.host}")
private String mailServerHost;
@Value("${spring.mail.port}")
private Integer mailServerPort;
@Value("${spring.mail.username}")
private String mailServerUsername;
@Value("${spring.mail.password}")
private String mailServerPassword;
@Value("${spring.mail.properties.mail.smtp.auth}")
private String mailServerAuth;
@Value("${spring.mail.properties.mail.smtp.starttls.enable}")
private String mailServerStartTls;
@Bean
public JavaMailSender getJavaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(mailServerHost);
mailSender.setPort(mailServerPort);
mailSender.setUsername(mailServerUsername);
mailSender.setPassword(mailServerPassword);
Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", mailServerAuth);
props.put("mail.smtp.starttls.enable", mailServerStartTls);
props.put("mail.debug", "true");
return mailSender;
}
@Bean
public SimpleMailMessage templateSimpleMessage() {
SimpleMailMessage message = new SimpleMailMessage();
message.setText("This is the test email template for your email:\n%s\n");
return message;
}
}
package com.afp.ordermanagement.controller;
import com.afp.ordermanagement.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmailController {
@Autowired
private EmailService emailService;
@GetMapping(value = "/email")
public String sendmail() {
emailService.sendMail(
"kkaminski@nisum.com",
"Email sent successfully!",
"Emails seem to be working properly, yay!"
);
return "sent";
}
}
\ No newline at end of file
package com.afp.ordermanagement.controller;
import com.afp.ordermanagement.annotation.AuthManagerController;
import com.afp.ordermanagement.annotation.ManagerPayload;
import com.afp.ordermanagement.exception.ResourceNotFoundException;
import com.afp.ordermanagement.model.Manager;
import com.afp.ordermanagement.repository.ManagerRepository;
import com.afp.ordermanagement.service.ManagerService;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import javax.validation.Valid;
@Validated
@RestController
@RequestMapping("/api")
@AuthManagerController
@RequestMapping("/api/managers/")
public class ManagerController {
@Autowired
ManagerRepository managerRepository;
ManagerService managerService;
@GetMapping("/manager")
public Flux<Manager> getAllManagers() {
System.out.println("here");
Flux<Manager> managerFlux = managerRepository.findAll();
return managerFlux;
@PostMapping("/auth")
public ResponseEntity<Mono<Manager>> signUpOrLogInManager(@ManagerPayload Manager managerPayload) {
Mono<Manager> manager = managerService.getByEmail(managerPayload.getEmail())
.switchIfEmpty(managerService.create(managerPayload));
return ResponseEntity.ok(manager);
}
@GetMapping("/account")
public ResponseEntity<Mono<Manager>> getManagerDetails(@ManagerPayload Manager managerPayload) {
Mono<Manager> existingManager = managerService.getByEmail(managerPayload.getEmail())
.switchIfEmpty(Mono.error(new ResourceNotFoundException()));
return ResponseEntity.ok(existingManager);
}
@PatchMapping("/account")
public ResponseEntity<Mono<Manager>> updateManagerDetails(@ManagerPayload Manager managerPayload, @Valid @RequestBody Manager managerBody) {
return ResponseEntity.ok(managerService.updateByEmail(managerPayload.getEmail(), managerBody));
}
@DeleteMapping("/account")
public ResponseEntity deleteManagerDetails(@ManagerPayload Manager managerPayload) {
managerService.getByEmail(managerPayload.getEmail())
.switchIfEmpty(Mono.error(new ResourceNotFoundException()))
.flatMap(managerService::delete);
return ResponseEntity.noContent().build();
}
}
......@@ -33,7 +33,7 @@ public class OrderController {
* @param orderObject
* @return
*/
@PostMapping("/ordersFromEcom")
@PostMapping("/orders")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Order> getOrderFromEcom(@RequestBody Order orderObject) {
sender.sendOrderToWarehouse(orderObject);
......@@ -54,7 +54,7 @@ public class OrderController {
return orderService.getOrderById(orderId);
}
@GetMapping("/orders/byCustomer/{customerId}")
@GetMapping("/orders/customer/{customerId}")
public Flux<Order> getAllOrdersByCustomerId(@PathVariable("customerId") String customerId) {
return orderService.getAllOrdersByCustomerId(customerId);
}
......@@ -66,6 +66,10 @@ public class OrderController {
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@DeleteMapping("/orders/{orderId}")
public void deleteOrderbyId(@PathVariable(value = "orderId") String orderId) {
orderService.deleteOrderById(orderId);
}
}
package com.afp.ordermanagement.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class BadAccessTokenException extends RuntimeException {
public BadAccessTokenException() {
super("Invalid access token");
}
}
package com.afp.ordermanagement.exception;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.server.ServerWebExchange;
@ControllerAdvice
@RequiredArgsConstructor
public class ControllerExceptionAdvice {
@ExceptionHandler(BadAccessTokenException.class)
public ResponseEntity<ErrorResponse> handleBadAccessTokenException(RuntimeException exc, ServerWebExchange exchange) {
final HttpStatus status = HttpStatus.UNAUTHORIZED;
final ServerHttpRequest request = exchange.getRequest();
return new ResponseEntity<>(new ErrorResponse(
status.value(),
request.getPath().value(),
status.getReasonPhrase(),
exc.getMessage()
), status);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleEntityNotFoundException(RuntimeException exc, ServerWebExchange exchange) {
final HttpStatus status = HttpStatus.NOT_FOUND;
final ServerHttpRequest request = exchange.getRequest();
return new ResponseEntity<>(new ErrorResponse(
status.value(),
request.getPath().value(),
status.getReasonPhrase(),
exc.getMessage()
), status);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException exc, ServerWebExchange exchange) {
final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
final ServerHttpRequest request = exchange.getRequest();
return new ResponseEntity<>(new ErrorResponse(
status.value(),
request.getPath().value(),
status.getReasonPhrase(),
exc.getMessage()
), status);
}
@ExceptionHandler(WebExchangeBindException.class)
public ResponseEntity<ErrorResponse> webExchangeBindException(WebExchangeBindException exc, ServerWebExchange exchange) {
final HttpStatus status = exc.getStatus();
final ServerHttpRequest request = exchange.getRequest();
if (InvalidEntityResponse.isEntityValid(exc.getTarget())) {
return new ResponseEntity<>(new ErrorResponse(
status.value(),
request.getPath().value(),
status.getReasonPhrase(),
exc.getMessage()
), status);
}
return new ResponseEntity<>(new InvalidEntityResponse(
status.value(),
request.getPath().value(),
status.getReasonPhrase(),
"Validation failed",
exc.getTarget()
), status);
}
}
package com.afp.ordermanagement.exception;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import java.time.Instant;
@Data
@RequiredArgsConstructor
public class ErrorResponse {
public final Integer status;
public final String path, error, message;
public String timestamp = Instant.now().toString();
}
package com.afp.ordermanagement.exception;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class InvalidEntityResponse extends ErrorResponse {
public List<Map<String, String>> fieldErrors;
public InvalidEntityResponse(Integer status, String path, String type, String message, Object entity) {
super(status, path, type, message);
this.fieldErrors = InvalidEntityResponse.parseViolations(entity);
}
public static boolean isEntityValid(Object entity) {
return validator.validate(entity).isEmpty();
}
static private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
static private List<Map<String, String>> parseViolations(Object entity) {
List<Map<String, String>> fieldErrors = new ArrayList<>();
for (ConstraintViolation<Object> cv : validator.validate(entity)) {
Map<String, String> errorMap = new HashMap<String, String>() {{
put("field", cv.getPropertyPath().toString());
put("message", cv.getMessage());
}};
fieldErrors.add(errorMap);
}
return fieldErrors;
}
}
package com.afp.ordermanagement.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException() {
super("Resource not found");
}
public ResourceNotFoundException(String message) {
super(message);
}
}
......@@ -2,35 +2,7 @@ package com.afp.ordermanagement.model;
import lombok.Data;
import java.util.Objects;
@Data
public class CustomerAddress {
private String street;
private String city;
private String state;
private String zip;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CustomerAddress)) return false;
CustomerAddress that = (CustomerAddress) o;
return getStreet().equals(that.getStreet()) && getCity().equals(that.getCity()) && getState().equals(that.getState()) && getZip().equals(that.getZip());
}
@Override
public int hashCode() {
return Objects.hash(getStreet(), getCity(), getState(), getZip());
}
@Override
public String toString() {
return "CustomerAddress{" +
"street='" + street + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", zip='" + zip + '\'' +
'}';
}
private String street, city, state, zip;
}
......@@ -10,11 +10,7 @@ import java.util.Objects;
@Getter
@Setter
public class Item {
private String itemId;
private String itemName;
private String itemSku;
private String itemId, itemName, itemSku;
private int itemQuantity;
private double itemPrice;
}
......@@ -4,12 +4,10 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.URL;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.*;
import java.lang.reflect.Field;
import static java.util.Objects.isNull;
@Data
@NoArgsConstructor
......@@ -18,30 +16,18 @@ public class Manager {
@Id
private String id;
@Size(min = 1, max = 250, message = "'firstName' must be between 1 & 250 characters")
@NotNull
@Size(min = 1, max = 250)
@NotNull()
private String firstName, lastName;
@Indexed(unique = true)
@Email
private String email;
@NotNull
@Size(min = 21, max = 21, message = "'googleId' is invalid")
@Size(min = 21, max = 21)
@NotNull()
private String googleId;
@URL(message = "'imageUrl' must be a valid URL")
@URL
private String imageUrl;
static public Manager combine(Manager target, Manager source) {
try {
for (Field field: Manager.class.getDeclaredFields()) {
String fieldName = field.getName();
Object sourceValue = field.get(source);
if (!isNull(sourceValue) && !fieldName.equals("id"))
field.set(target, sourceValue);
}
} catch (Exception ignore) { }
return target;
}
}
package com.afp.ordermanagement.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
import java.util.Objects;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "orders")
public class Order {
public Order(OrderStatus status) {
this.orderStatus = status;
}
@Id
......@@ -24,18 +25,13 @@ public class Order {
private String orderTrackingCode;
private OrderStatus orderStatus;
private long orderCreatedAt;
private long orderUpdatedAt;
private String customerId;
private String customerEmailAddress;
private long orderCreatedAt, orderUpdatedAt;
private List<Item> orderItems;
private CustomerAddress customerAddress;
public Order(){
}
private String customerId, customerEmailAddress;
private List<Item> orderItems;
private CustomerAddress customerAddress;
}
......@@ -2,8 +2,10 @@ package com.afp.ordermanagement.repository;
import com.afp.ordermanagement.model.Manager;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
//import org.springframework.stereotype.Repository;
//
//@Repository
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
@Repository
public interface ManagerRepository extends ReactiveMongoRepository<Manager, String> {
Mono<Manager> findByEmail(String email);
}
......@@ -6,38 +6,57 @@ import com.github.javafaker.Faker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Component;
import java.util.stream.IntStream;
@Component
public class ManagerSeeder {
// Toggle
static private final boolean RUN_SEEDER = false; // FIXME: <- Set to `true` to run seeder on app start-up (in development)
@Autowired
ManagerRepository managerRepository;
static private final Faker FAKER = new Faker();
@Autowired
Faker faker;
@Autowired
Environment env;
// Verify that the current environment is NOT production or test
boolean isEnvDevelopment() {
return env.acceptsProfiles(Profiles.of("default", "dev", "development", "local"));
}
static private final int SEED_COUNT = 1;
// Number of seed documents generated
static private final int SEED_COUNT = 10;
// Create manager seeds as soon as app is up and running
@EventListener
public void seedManager(ContextRefreshedEvent event) {
managerRepository
.deleteAll()
.subscribe();
IntStream.range(0, SEED_COUNT).forEach(n -> {
public void seedManagers(ContextRefreshedEvent event) {
// Only generate seeds if in development environment
if (isEnvDevelopment() && RUN_SEEDER && SEED_COUNT > 0) {
managerRepository
.insert(generateManager())
.deleteAll()
.subscribe();
});
IntStream.range(0, SEED_COUNT + 1).forEach(n -> {
managerRepository
.insert(generateManager())
.subscribe();
});
}
}
private Manager generateManager(){
Manager manager = new Manager();
manager.setFirstName(FAKER.name().firstName());
manager.setLastName(FAKER.name().lastName());
manager.setEmail(FAKER.internet().emailAddress());
manager.setGoogleId(FAKER.number().digits(21));
manager.setImageUrl(FAKER.internet().url());
manager.setFirstName(faker.name().firstName());
manager.setLastName(faker.name().lastName());
manager.setEmail(faker.internet().emailAddress());
manager.setGoogleId(faker.number().digits(21));
manager.setImageUrl("https://picsum.photos/200/200");
return manager;
}
}
package com.afp.ordermanagement.seeder;
import com.afp.ordermanagement.model.CustomerAddress;
import com.afp.ordermanagement.model.Item;
import com.afp.ordermanagement.model.Order;
import com.afp.ordermanagement.model.OrderStatus;
import com.afp.ordermanagement.repository.OrderRepository;
import com.github.javafaker.Faker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@Component
public class OrderSeeder {
// Toggle
static private final boolean RUN_SEEDER = false; // FIXME: <- Set to `true` to run seeder on app start-up (in development)
@Autowired
OrderRepository orderRepository;
@Autowired
Faker faker;
@Autowired
Environment env;
// Verify that the current environment is NOT production or test
boolean isEnvDevelopment() {
return env.acceptsProfiles(Profiles.of("default", "dev", "development", "local"));
}
static private Stream<String> CUSTOMER_USERNAMES = Stream.of(
"nrobinson", "rsara", "agannamaneni", "rsayannagari", // Superiors
"ccottier", "kmuldoon", "nmoosapet", "spangburn", "xlu", // ECom Team
"asegers", "dbhuller", "earndt", "kkaminski", "svalencia", "vivaddadhi" // OMan Team
);
// Create order seeds as soon as app is up and running
@EventListener
public void seedOrders(ContextRefreshedEvent event) {
// Only generate seeds if in development environment
if (isEnvDevelopment() && RUN_SEEDER) {
orderRepository
.deleteAll()
.subscribe();
CUSTOMER_USERNAMES.forEach(username -> {
String email = String.format("%s@nisum.com", username);
orderRepository
.insert(generateOrder(email))
.subscribe();
});
}
}
private Order generateOrder(String email){
OrderStatus randStatus = OrderStatus.values()[faker.number().numberBetween(0, 2)]; // exclusive range
Order order = new Order(randStatus);
order.setOrderTrackingCode(faker.number().digits(27));
long unixTime = System.currentTimeMillis();
order.setOrderCreatedAt(unixTime);
order.setOrderUpdatedAt(unixTime);
order.setCustomerId(faker.number().digits(12));
order.setCustomerEmailAddress(email);
// Order items
int randOrderCount = faker.number().numberBetween(1, 10);
List<Item> orderItems = IntStream
.range(0, randOrderCount)
.mapToObj(n -> generateItem())
.collect(Collectors.toList());
order.setOrderItems(orderItems);
// Order address
order.setCustomerAddress(generateOrderAddress());
return order;
}
private Item generateItem(){
Item item = new Item();
item.setItemId(faker.number().digits(12));
item.setItemName(faker.commerce().productName());
item.setItemSku(faker.random().hex(12));
item.setItemQuantity(faker.number().numberBetween(1, 30));
item.setItemPrice(faker.number().randomDouble(2, 5, 1000));
return item;
}
private CustomerAddress generateOrderAddress(){
CustomerAddress address = new CustomerAddress();
address.setStreet(faker.address().streetAddress());
address.setCity(faker.address().city());
String stateAbbr = faker.address().stateAbbr();
address.setState(stateAbbr);
address.setZip(faker.address().zipCode());
return address;
}
}
package com.afp.ordermanagement.service;
import com.afp.ordermanagement.model.Order;
import com.afp.ordermanagement.model.OrderStatus;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
private JavaMailSender javaMailSender;
public EmailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
public void sendMail(String toEmail, String subject, String message) {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(toEmail);
mailMessage.setSubject(subject);
mailMessage.setText(message);
mailMessage.setFrom("NOREPLY@nisum.com");
javaMailSender.send(mailMessage);
}
public String messageCreator(Order order) {
OrderStatus status = order.getOrderStatus();
String message1 = "message ", message2 = "build failed.";
switch (status) {
case RECEIVED:
message1 = "Hello, you order #" + order.getId() + " has been received!";
message2 = "We hope to have your order fulfilled soon.";
break;
case CANCELLED:
message1 = "I'm sorry, your order #" + order.getId() + " has been canceled.";
message2 = "For more information, contact {NULL}.";
break;
case FULFILLED:
message1 = "Good news everyone! Your order #" + order.getId() + " has been fulfilled.";
message2 = "Your tracking number is: " + order.getOrderTrackingCode();
break;
}
return message1 + message2;
}
public String subjectCreator(Order order) {
String status = "NULL";
switch (order.getOrderStatus()) {
case RECEIVED:
status = "RECEIVED";
break;
case CANCELLED:
status = "CANCELLED";
break;
case FULFILLED:
status = "FULFILLED";
break;
}
return "Your order #" + order.getId() + " has been" + status;
}
public String toCreator(Order order) {
return order.getCustomerEmailAddress();
}
public String emailCreator(Order order) {
String message = messageCreator(order);
String subject = subjectCreator(order);
String to = toCreator(order);
sendMail(to, subject, message);
return "Email sent to customer!";
}
}
package com.afp.ordermanagement.service;
import com.afp.ordermanagement.model.Manager;
import com.afp.ordermanagement.repository.ManagerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import static com.afp.ordermanagement.config.BeanConfig.FieldCombiner;
@Service
public class ManagerService {
@Autowired
FieldCombiner fieldCombiner;
@Autowired
ManagerRepository managerRepository;
public Mono<Manager> getByEmail(String email) {
return managerRepository.findByEmail(email);
}
public Mono<Manager> create(Manager newManager) {
return managerRepository.save(newManager);
}
public Mono<Manager> updateByEmail(String email, Manager managerUpdates) {
return this.getByEmail(email)
.map(existingManager -> (Manager) fieldCombiner.combine(existingManager, managerUpdates, new String[] { "id", "email"}))
.flatMap(managerRepository::save);
}
public Mono<Void> delete(Manager manager) {
return managerRepository.delete(manager);
}
}
package com.afp.ordermanagement.service;
import com.afp.ordermanagement.model.Manager;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ServerWebExchange;
import java.util.Collections;
import static java.util.Objects.isNull;
@Service
public class ManagerTokenVerifier {
private final String CLIENT_ID = "925243198137-hhe2e3ejlethf321hh7tbm7ontc19cpj.apps.googleusercontent.com";
private final JacksonFactory jsonFactory = new JacksonFactory();
private final HttpTransport transport = new NetHttpTransport();
private final GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(CLIENT_ID))
.build();
public boolean isTokenValid(String idTokenString) {
try {
verifier.verify(idTokenString);
return true;
} catch (Exception ignore) { }
return false;
}
private GoogleIdToken.Payload createPayloadFromToken(String idTokenString) {
GoogleIdToken idToken = null;
try {
idToken = verifier.verify(idTokenString);
} catch (Exception ignore) { }
assert idToken != null;
return idToken.getPayload();
}
public Manager createManagerFromToken(String idTokenString) {
GoogleIdToken.Payload payload = this.createPayloadFromToken(idTokenString);
Manager newManager = new Manager();
newManager.setFirstName((String) payload.get("given_name"));
newManager.setLastName((String) payload.get("family_name"));
newManager.setEmail(payload.getEmail());
newManager.setGoogleId(payload.getSubject());
newManager.setImageUrl((String) payload.get("picture"));
return newManager;
}
public boolean hasTokenHeader(ServerWebExchange serverWebExchange) {
HttpHeaders headers = serverWebExchange
.getRequest()
.getHeaders();
String accessToken = headers
.getFirst("Authorization");
return !isNull(accessToken);
}
public String getTokenHeader(ServerWebExchange serverWebExchange) {
HttpHeaders headers = serverWebExchange
.getRequest()
.getHeaders();
String accessToken = headers
.getFirst("Authorization")
.replace("Bearer ", "");
return accessToken;
}
}
......@@ -58,10 +58,7 @@ public class OrderService {
});
}
public Mono<Void> deleteOrderById(String orderId) {
return orderRepository.deleteById(orderId);
public void deleteOrderById(String orderId) {
orderRepository.deleteById(orderId);
}
}
kafka.producer.bootstrap-servers=localhost:9092
kafka.producer.acks=all
kafka.consumer.bootstrap-servers=localhost:9092
kafka.consumer.group-id=group_id
kafka.topic.input=orders
# Config for MailTrap SMTP Mail testing service
spring.mail.protocol=smtp
spring.mail.host=smtp.mailtrap.io
spring.mail.port=2525
spring.mail.username=945a9d376253be
spring.mail.password=96d65f623868cc
spring.mail.properties.mail.smtp.auth = true
spring.mail.properties.mail.smtp.starttls.enable = true
\ No newline at end of file
package com.afp.ordermanagement.UNIT_TESTS.helper;
import com.github.javafaker.Faker;
//import com.sun.tools.javac.util.List;
import org.junit.jupiter.api.Assertions;
import static org.junit.jupiter.api.Assertions.*;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.ParameterizedType;
import java.util.List;
public abstract class ValidatableEntity<E> {
private final Class<E> childClass;
static private final Faker FAKER = new Faker();
@SuppressWarnings("unchecked")
public ValidatableEntity() {
this.childClass = (
(Class<E>) (
(ParameterizedType) this.getClass()
.getGenericSuperclass()
)
.getActualTypeArguments()[0]
);
}
private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// assertFieldValidation(String fieldName, Object input, boolean isValidInput)
public ConstraintViolation<E> createViolation(String fieldName, Object fieldValue) {
return validator
.validateValue(this.childClass, fieldName, fieldValue)
.stream()
.findAny()
.orElse(null);
}
// assertFieldValidation(String fieldName, Object input, boolean isValidInput)
public void assertFieldValidation(String fieldName, Object input, boolean expected) {
boolean isValid = validator.validateValue(this.childClass, fieldName, input).isEmpty();
String v = expected ? "valid" : "invalid";
String errMsg = String.format("Expected '%s' to be a %s value for field '%s'", input, v, fieldName);
if (isValid != expected) fail(errMsg);
}
}
package com.afp.ordermanagement.UNIT_TESTS.model;
import com.afp.ordermanagement.UNIT_TESTS.helper.ValidatableEntity;
import com.afp.ordermanagement.model.Manager;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
public class ManagerTests extends ValidatableEntity<Manager> {
@ParameterizedTest
@CsvSource(value = {"null:false", "\"\":false", "Jerry:true"}, delimiter = ':')
public void shouldValidateNameFields(String input, boolean expected) {
assertFieldValidation("firstName", input, expected);
assertFieldValidation("lastName", input, expected);
}
@ParameterizedTest
@CsvSource(value = {"null:false", "\"\":false", "poop:false", "jerry@hotmail.com:true"}, delimiter = ':')
public void shouldValidateEmailField(String input, boolean expected) {
assertFieldValidation("email", input, expected);
}
@ParameterizedTest
@CsvSource(value = {"null:false", "\"\":false"}, delimiter = ':')
public void shouldValidateGoogleIdField(String input, boolean expected) {
assertFieldValidation("googleId", input, expected);
}
@ParameterizedTest
@CsvSource(value = {"\"\",false", "https://picsum.photos/200,true", "htps: picsum.photos/200,false"})
public void shouldValidateImageUrl(String input, boolean expected) {
assertFieldValidation("imageUrl", input, expected);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment