Commit 155d35e2 authored by Ashok Kumar K's avatar Ashok Kumar K

Added request validation, db sequence generator, swagger

parent f607ef4d
plugins {
id 'org.springframework.boot' version '2.3.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
id 'org.springframework.boot' version '2.3.0.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.nisum'
......@@ -9,39 +9,42 @@ version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
mavenCentral()
}
ext {
set('springBootAdminVersion', "2.2.3")
set('springBootAdminVersion', "2.2.3")
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'de.codecentric:spring-boot-admin-starter-client'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'de.codecentric:spring-boot-admin-starter-client'
compile 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
compile "io.springfox:springfox-swagger2:2.9.2"
compile "io.springfox:springfox-swagger-ui:2.9.2"
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
}
dependencyManagement {
imports {
mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
}
imports {
mavenBom "de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
}
}
test {
useJUnitPlatform()
useJUnitPlatform()
}
package com.nisum.ecomcustomer.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket apiDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
\ No newline at end of file
package com.nisum.ecomcustomer.config.mongodb.listeners;
import com.nisum.ecomcustomer.model.Customer;
import com.nisum.ecomcustomer.model.DbSequence;
import com.nisum.ecomcustomer.repository.DbSequenceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.stereotype.Component;
@Component
public class CustomerDbOpsEventListener extends AbstractMongoEventListener<Customer> {
@Autowired
DbSequenceRepository dbSequenceRepository;
@Override
public void onBeforeConvert(BeforeConvertEvent<Customer> event) {
super.onBeforeConvert(event);
Customer source = event.getSource();
if (source.getId() == null) {
DbSequence dbSequence = dbSequenceRepository.findBySequenceName(Customer.SEQUENCE_NAME).orElse(new DbSequence(Customer.SEQUENCE_NAME));
source.setId(dbSequence.incrementLastId());
dbSequenceRepository.save(dbSequence);
}
}
}
......@@ -3,27 +3,61 @@ package com.nisum.ecomcustomer.controller;
import com.nisum.ecomcustomer.model.Customer;
import com.nisum.ecomcustomer.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import java.util.List;
@RestController
@RequestMapping("/customer")
@Validated
public class CustomerController {
@Autowired
CustomerService customerService;
@PostMapping("/register")
public ResponseEntity<Customer> registerCustomer(@RequestBody @Valid @NotNull(message = "body can't be empty") Customer customer) {
return new ResponseEntity<>(customerService.register(customer), HttpStatus.CREATED);
}
@PostMapping("/")
public Customer register(@RequestBody Customer customer) {
return customerService.register(customer);
@PostMapping("/registerAll")
public ResponseEntity<List<Customer>> registerCustomers(@RequestBody @NotNull List<Customer> customer) {
return new ResponseEntity<>(customerService.registerAll(customer), HttpStatus.CREATED);
}
@GetMapping("/")
public List<Customer> getAllCustomers() {
return customerService.getAllCustomers();
public ResponseEntity<List<Customer>> getAllCustomers() {
return ResponseEntity.ok(customerService.getAllCustomers());
}
@GetMapping("/id/{id}")
public ResponseEntity<Customer> getCustomerById(@PathVariable Long id) {
return ResponseEntity.ok(customerService.getCustomerById(id));
}
@GetMapping("/email/{email}")
public ResponseEntity<Customer> getCustomerByEmail(@PathVariable @Email String email) {
return ResponseEntity.ok(customerService.getCustomerByEmail(email));
}
@GetMapping("/fname/{fname}")
public ResponseEntity<List<Customer>> getCustomersByFirstName(@PathVariable("fname") String fName) {
return ResponseEntity.ok(customerService.getCustomerByFirstName(fName));
}
@GetMapping("/lname/{lname}")
public ResponseEntity<List<Customer>> getCustomersByLastName(@PathVariable("lname") String lName) {
return ResponseEntity.ok(customerService.getCustomerByLastName(lName));
}
@DeleteMapping("/id/{id}")
public void deleteById(@PathVariable Long id) {
customerService.deleteById(id);
}
}
package com.nisum.ecomcustomer.exceptions;
public class CustomerNotFoundException extends RuntimeException {
public CustomerNotFoundException() {
super("customer not found");
}
public CustomerNotFoundException(Long id) {
super("customer not found with ID: "+ id);
}
public CustomerNotFoundException(String message) {
super(message);
}
public CustomerNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
package com.nisum.ecomcustomer.exceptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice(basePackages = {"com.nisum.ecomcustomer"})
public class CustomerServiceExceptionHandler {
@ExceptionHandler(CustomerNotFoundException.class)
public ResponseEntity<ErrorResponse> handleCustomerNotFoundException(CustomerNotFoundException ex, HttpServletRequest httpServletRequest) {
return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.NOT_FOUND, httpServletRequest);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest httpServletRequest) {
ErrorResponse errorResponse = buildErrorResponse("Invalid payload/request-params/path-params", HttpStatus.BAD_REQUEST, httpServletRequest);
List<SubError> subErrors = ex.getBindingResult().getFieldErrors()
.stream()
.map(fieldError -> new ValidationError(fieldError.getObjectName(), fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage()))
.collect(Collectors.toList());
errorResponse.setSubErrors(subErrors);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
// TODO - build default error object
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex, HttpServletRequest httpServletRequest) {
return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest);
}
private ResponseEntity<ErrorResponse> buildErrorResponseEntity(String message, HttpStatus httpstatus, HttpServletRequest httpServletRequest) {
ErrorResponse response = buildErrorResponse(message, httpstatus, httpServletRequest);
return new ResponseEntity<>(response, httpstatus);
}
private ErrorResponse buildErrorResponse(String message, HttpStatus httpstatus, HttpServletRequest servletRequest) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setStatus(httpstatus);
errorResponse.setTimestamp(LocalDateTime.now());
errorResponse.setMessage(message);
errorResponse.setPath(servletRequest.getRequestURI());
errorResponse.setSubErrors(Collections.emptyList());
return errorResponse;
}
}
\ No newline at end of file
package com.nisum.ecomcustomer.exceptions;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
import java.time.LocalDateTime;
import java.util.List;
@NoArgsConstructor
@Data
public class ErrorResponse {
HttpStatus status;
private LocalDateTime timestamp;
private String message;
private String path;
private List<SubError> subErrors;
}
\ No newline at end of file
package com.nisum.ecomcustomer.exceptions;
public class SubError {
}
package com.nisum.ecomcustomer.exceptions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Data
public class ValidationError extends SubError{
private String object;
private String field;
private Object rejectedValue;
private String message;
}
......@@ -2,11 +2,14 @@ package com.nisum.ecomcustomer.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.springframework.data.mongodb.core.mapping.Field;
import javax.validation.constraints.NotBlank;
@Data
public class Address {
// @Field("id")
// @Field("id")
// private Long addressId;
@Field("type")
private AddressType addressType;
......@@ -19,5 +22,7 @@ public class Address {
private String city;
private String state;
private String country;
@NotBlank(message = "zip code is mandatory")
@Length(max = 6, min = 5, message = "valid zip code is of 5 or 6 letters")
private String zipCode;
}
......@@ -3,26 +3,49 @@ package com.nisum.ecomcustomer.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import java.util.List;
//TODO - check the relevance of annotations
@NoArgsConstructor
@Data
@TypeAlias("customer")
@Document("customers")
public class Customer {
@Transient
public static final String SEQUENCE_NAME = "customers_sequence";
@Id
private String id;
private Long id;
@Field("fName")
@NotBlank(message = "first name can't be blank")
private String firstName;
@Field("lName")
private String lastName;
@Indexed(unique = true)
@NotBlank(message = "email is mandatory")
@Email
private String email;
@NotBlank(message = "dayPhone can't be blank")
private String dayPhone;
@Field("type")
private CustomerType customerType;
List<Address> addresses;
@Valid
private List<Address> addresses;
// TODO - add audit fields
// @CreatedDate
// private LocalDateTime createdDate;
//
// @LastModifiedDate
// private LocalDateTime modifiedDate;
}
package com.nisum.ecomcustomer.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
@Data
@Document("database_sequences")
public class DbSequence {
@Id
private String id;
@Field("seq_name")
@Indexed
private String sequenceName;
private Long lastId;
public DbSequence(String sequenceName) {
this.sequenceName = sequenceName;
this.lastId = 0L;
}
public Long incrementLastId() {
return ++lastId;
}
}
......@@ -2,6 +2,17 @@ package com.nisum.ecomcustomer.repository;
import com.nisum.ecomcustomer.model.Customer;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
import java.util.List;
public interface CustomerRepository extends MongoRepository<Customer, Long> {
Customer findByEmailIgnoreCase(String email);
@Query(value = "{'firstName': {$regex : ?0, $options: 'i'}}")
List<Customer> findByFirstName(String fName);
List<Customer> findByLastNameIgnoreCase(String lName);
public interface CustomerRepository extends MongoRepository<Customer, String> {
}
package com.nisum.ecomcustomer.repository;
import com.nisum.ecomcustomer.model.DbSequence;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.Optional;
public interface DbSequenceRepository extends MongoRepository<DbSequence, String> {
Optional<DbSequence> findBySequenceName(String sequenceName);
}
......@@ -7,6 +7,19 @@ import java.util.List;
@Service
public interface CustomerService {
public Customer register(Customer customer);
public List<Customer> getAllCustomers();
Customer register(Customer customer);
List<Customer> registerAll(List<Customer> customers);
List<Customer> getAllCustomers();
Customer getCustomerById(Long id);
void deleteById(Long id);
Customer getCustomerByEmail(String email);
List<Customer> getCustomerByFirstName(String fName);
List<Customer> getCustomerByLastName(String lName);
}
package com.nisum.ecomcustomer.service.impl;
import com.nisum.ecomcustomer.exceptions.CustomerNotFoundException;
import com.nisum.ecomcustomer.model.Customer;
import com.nisum.ecomcustomer.repository.CustomerRepository;
import com.nisum.ecomcustomer.service.CustomerService;
......@@ -19,8 +20,40 @@ public class CustomerServiceImpl implements CustomerService {
return customerRepository.save(customer);
}
@Override
public List<Customer> registerAll(List<Customer> customers) {
return customerRepository.saveAll(customers);
}
@Override
public List<Customer> getAllCustomers() {
return customerRepository.findAll();
}
@Override
public Customer getCustomerById(Long id) {
return customerRepository.findById(id).orElseThrow(CustomerNotFoundException::new);
}
@Override
public Customer getCustomerByEmail(String email) {
return customerRepository.findByEmailIgnoreCase(email);
}
@Override
public List<Customer> getCustomerByFirstName(String fName) {
return customerRepository.findByFirstName(fName);
}
@Override
public List<Customer> getCustomerByLastName(String lName) {
return customerRepository.findByLastNameIgnoreCase(lName);
}
@Override
public void deleteById(Long id) {
if (customerRepository.existsById(id)) customerRepository.deleteById(id);
else throw new CustomerNotFoundException(id);
}
}
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