From a2b0d93c112fbce48f587e823184840a3211ec41 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Tue, 11 Dec 2018 23:32:03 -0200 Subject: [PATCH 01/17] =?UTF-8?q?Scaffolding=20do=20projeto=20com=20a=20im?= =?UTF-8?q?plementa=C3=A7=C3=A3o=20das=20primeiras=20funcionalidades?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 ++ pom.xml | 66 ++++++++++++++++ .../java/microservice/SpendMicroservice.java | 14 ++++ .../controllers/CategoryController.java | 37 +++++++++ .../controllers/SpendController.java | 79 +++++++++++++++++++ .../interceptor/JWTInterceptor.java | 51 ++++++++++++ .../java/microservice/models/Category.java | 18 +++++ src/main/java/microservice/models/Spend.java | 70 ++++++++++++++++ .../repositories/CategoryRepository.java | 14 ++++ .../repositories/SpendRepository.java | 16 ++++ .../services/CategoryService.java | 21 +++++ .../microservice/services/SpendService.java | 32 ++++++++ 12 files changed, 424 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/microservice/SpendMicroservice.java create mode 100644 src/main/java/microservice/controllers/CategoryController.java create mode 100644 src/main/java/microservice/controllers/SpendController.java create mode 100644 src/main/java/microservice/interceptor/JWTInterceptor.java create mode 100644 src/main/java/microservice/models/Category.java create mode 100644 src/main/java/microservice/models/Spend.java create mode 100644 src/main/java/microservice/repositories/CategoryRepository.java create mode 100644 src/main/java/microservice/repositories/SpendRepository.java create mode 100644 src/main/java/microservice/services/CategoryService.java create mode 100644 src/main/java/microservice/services/SpendService.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..17b5b0b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.settings/* +.project +.classpath +target/* +.DS_Store +.vscode/* \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..0cda05b9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + org.springframework + spend_microservice + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.0.5.RELEASE + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.springframework.boot + spring-boot-starter-web + + + + com.auth0 + java-jwt + 3.4.1 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.jayway.jsonpath + json-path + test + + + + + 1.8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-releases + https://repo.spring.io/libs-release + + + + \ No newline at end of file diff --git a/src/main/java/microservice/SpendMicroservice.java b/src/main/java/microservice/SpendMicroservice.java new file mode 100644 index 00000000..503e5d72 --- /dev/null +++ b/src/main/java/microservice/SpendMicroservice.java @@ -0,0 +1,14 @@ +package microservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class SpendMicroservice { + + public static void main(String[] args) { + SpringApplication.run(SpendMicroservice.class, args); + } + +} diff --git a/src/main/java/microservice/controllers/CategoryController.java b/src/main/java/microservice/controllers/CategoryController.java new file mode 100644 index 00000000..6aaf9bae --- /dev/null +++ b/src/main/java/microservice/controllers/CategoryController.java @@ -0,0 +1,37 @@ +package microservice.controllers; + +import java.util.List; +import microservice.models.Category; +import javax.validation.constraints.Size; +import org.springframework.http.ResponseEntity; +import microservice.services.CategoryService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.concurrent.Callable; + + +@RestController +@Validated +public class CategoryController { + + @Autowired + private CategoryService categoryService; + + + @RequestMapping(value = "/category/suggestions", method = RequestMethod.GET) + public Callable>> getSuggestedCategories( + @Size(min=2, message="partial_name should contain at least 2 characters") + @RequestParam(value="partial_name") + String partialCategoryName) { + + return () -> ResponseEntity.ok( + categoryService.listSimilarCategories(partialCategoryName) + ); + + } + +} diff --git a/src/main/java/microservice/controllers/SpendController.java b/src/main/java/microservice/controllers/SpendController.java new file mode 100644 index 00000000..d4ed40c1 --- /dev/null +++ b/src/main/java/microservice/controllers/SpendController.java @@ -0,0 +1,79 @@ +package microservice.controllers; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import javax.validation.Valid; +import microservice.models.Spend; +import java.util.concurrent.Callable; +import org.springframework.http.ResponseEntity; +import java.net.URI; +import org.springframework.web.util.UriComponentsBuilder; +import microservice.services.SpendService; + +// import java.text.SimpleDateFormat; +// import java.util.Date; +// import java.util.List; +// import java.util.Calendar; +// import java.util.TimeZone; +// import org.bson.types.ObjectId; +// import java.text.ParseException; +// import microservice.models.Category; +// import microservice.repositories.SpendRepository; +// import microservice.repositories.CategoryRepository; +// import org.springframework.web.bind.annotation.RequestParam; + + +@RestController +public class SpendController { + + // private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + @Autowired + private SpendService spendService; + + + @RequestMapping(value = "/spend", method = RequestMethod.POST) + public Callable> insertNewSpend( + UriComponentsBuilder builder, + @Valid @RequestBody Spend spend) { + + return () -> { + Spend storedSpend = spendService.insertNewSpend(spend); + return ResponseEntity + .created(new URI(builder.toUriString() + spend.get_id())) + .body(storedSpend); + }; + } + + + // @RequestMapping(value = "/spends", method = RequestMethod.GET) + // public Callable>> getSpendListByDate( + // @RequestParam(value="startDate", defaultValue="") String startDateString, + // @RequestParam(value="endDate", defaultValue="") String endDateString) { + + // return () -> { + // Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + // Date now = calendar.getTime(); + // calendar.add(Calendar.DATE, -1); + + // Date startDate = null; + // Date endDate = null; + // try { + // startDate = startDateString.trim().isEmpty() || startDateString.equals(null) ? + // calendar.getTime() : dateFormat.parse(startDateString); + // endDate = startDateString.trim().isEmpty() || startDateString.equals(null) ? + // now : dateFormat.parse(endDateString); + // } + // catch (ParseException e) { + // // Implement the exception handling strategy here... + // e.printStackTrace(); + // } + + // return ResponseEntity.ok(spendRepo.findByDate(startDate, endDate)); + // }; + // } + +} diff --git a/src/main/java/microservice/interceptor/JWTInterceptor.java b/src/main/java/microservice/interceptor/JWTInterceptor.java new file mode 100644 index 00000000..bfc50c71 --- /dev/null +++ b/src/main/java/microservice/interceptor/JWTInterceptor.java @@ -0,0 +1,51 @@ +package microservice.interceptor; + + +import com.auth0.jwt.JWTVerifier; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.exceptions.JWTVerificationException; + + +public class JWTInterceptor extends HandlerInterceptorAdapter { + + public static final String SECRET = "dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend"; + private static final String ISSUER = "http://api.santandertest.com.br"; + + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Max-Age", "6000"); + response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); + + String requestMethod = request.getMethod(); + + if (!requestMethod.equals("OPTIONS")) { + String token = request.getHeader("Authorization"); + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer(ISSUER) + .build(); + DecodedJWT jwt = verifier.verify(token); + } + catch (JWTVerificationException e) { + if (token == null) + response.sendError(HttpStatus.UNAUTHORIZED.value()); + else + response.sendError(HttpStatus.FORBIDDEN.value()); + + return false; + } + + } + return super.preHandle(request, response, handler); + } +} diff --git a/src/main/java/microservice/models/Category.java b/src/main/java/microservice/models/Category.java new file mode 100644 index 00000000..12b2ad45 --- /dev/null +++ b/src/main/java/microservice/models/Category.java @@ -0,0 +1,18 @@ +package microservice.models; + +import org.springframework.data.annotation.Id; + + +public class Category { + @Id + private String name; + + public Category() { } + + public Category(String name) { this.name = name; } + + public String getName() { return name; } + + public void setName(String name) { this.name = name; } + +} diff --git a/src/main/java/microservice/models/Spend.java b/src/main/java/microservice/models/Spend.java new file mode 100644 index 00000000..f57b145d --- /dev/null +++ b/src/main/java/microservice/models/Spend.java @@ -0,0 +1,70 @@ +package microservice.models; + +import java.util.Date; +import javax.validation.constraints.NotNull; +import org.springframework.data.annotation.Id; +import org.bson.types.ObjectId; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.math.BigDecimal; + + +public class Spend { + @Id + private ObjectId _id; + + private String description; + + @NotNull + private BigDecimal value; + + @NotNull + private Integer userCode; + + private String category; + + @NotNull + @JsonFormat(shape=JsonFormat.Shape.STRING, + pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", + timezone="UTC") + private Date date; + + public Spend() { } + + public Spend(ObjectId _id, + String description, + BigDecimal value, + int userCode, + String category, + Date date) { + this._id = _id; + this.description = description; + this.value = value; + this.userCode = userCode; + this.category = category; + this.date = date; + } + + public String get_id() { return _id.toHexString(); } + + public String getDescription() { return description; } + + public BigDecimal getValue() { return value; } + + public Integer getUserCode() { return userCode; } + + public String getCategory() { return category; } + + public Date getDate() { return date; } + + public void set_id(ObjectId _id) { this._id = _id; } + + public void setDescription(String description) { this.description = description; } + + public void setValue(BigDecimal value) { this.value = value.setScale(2, BigDecimal.ROUND_HALF_UP); } + + public void setUserCode(int userCode) { this.userCode = userCode; } + + public void setCategory(String category) { this.category = category; } + + public void setDate(Date date) { this.date = date; } +} \ No newline at end of file diff --git a/src/main/java/microservice/repositories/CategoryRepository.java b/src/main/java/microservice/repositories/CategoryRepository.java new file mode 100644 index 00000000..8fc20bc7 --- /dev/null +++ b/src/main/java/microservice/repositories/CategoryRepository.java @@ -0,0 +1,14 @@ +package microservice.repositories; + +import java.util.List; +import microservice.models.Category; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + + +public interface CategoryRepository extends MongoRepository { + + @Query("{ '_id': { '$regex': '^?0' } }") + public List findBySimilarName(String name); + +} diff --git a/src/main/java/microservice/repositories/SpendRepository.java b/src/main/java/microservice/repositories/SpendRepository.java new file mode 100644 index 00000000..fc423457 --- /dev/null +++ b/src/main/java/microservice/repositories/SpendRepository.java @@ -0,0 +1,16 @@ +package microservice.repositories; + +import java.util.Date; +import java.util.List; +import microservice.models.Spend; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +public interface SpendRepository extends MongoRepository { + + @Query("{ 'date': { '$gte': ?0, '$lte': ?1 } }") + public List findByDate(Date startDate, Date endDate); + + public List findByDescription(String description); + +} diff --git a/src/main/java/microservice/services/CategoryService.java b/src/main/java/microservice/services/CategoryService.java new file mode 100644 index 00000000..cf437c7b --- /dev/null +++ b/src/main/java/microservice/services/CategoryService.java @@ -0,0 +1,21 @@ +package microservice.services; + + +import microservice.repositories.CategoryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import microservice.models.Category; +import java.util.List; + + +@Service +public class CategoryService { + + @Autowired + private CategoryRepository categoryRepo; + + public List listSimilarCategories(String partialCategoryName) { + return categoryRepo.findBySimilarName(partialCategoryName); + } + +} diff --git a/src/main/java/microservice/services/SpendService.java b/src/main/java/microservice/services/SpendService.java new file mode 100644 index 00000000..98f634e7 --- /dev/null +++ b/src/main/java/microservice/services/SpendService.java @@ -0,0 +1,32 @@ +package microservice.services; + + +import microservice.repositories.SpendRepository; +import microservice.repositories.CategoryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import microservice.models.Spend; +import microservice.models.Category; +import org.bson.types.ObjectId; + + +@Service +public class SpendService { + + @Autowired + private SpendRepository spendRepo; + + @Autowired + private CategoryRepository categoryRepo; + + public Spend insertNewSpend(Spend spend) { + spend.set_id(ObjectId.get()); + if (spend.getCategory() != null) { + Category c = new Category(spend.getCategory()); + categoryRepo.save(c); + } + spendRepo.save(spend); + return spend; + } + +} From 0913d1f3589f87ead00d8bdb6f5317239c2b4d27 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Wed, 12 Dec 2018 03:18:44 -0200 Subject: [PATCH 02/17] add interceptor, jwt validation, custom thread mgmt + properties file --- .../java/microservice/SpendMicroservice.java | 18 +++++++- src/main/java/microservice/WebMvcConfig.java | 21 +++++++++ .../controllers/CategoryController.java | 20 +++++---- .../controllers/SpendController.java | 27 ++++++++---- .../interceptor/JWTInterceptor.java | 44 +++++++++++++++---- .../microservice/models/ErrorMessage.java | 23 ++++++++++ .../services/CategoryService.java | 7 ++- .../microservice/services/SpendService.java | 8 ++-- src/main/resources/application.properties | 2 + 9 files changed, 139 insertions(+), 31 deletions(-) create mode 100644 src/main/java/microservice/WebMvcConfig.java create mode 100644 src/main/java/microservice/models/ErrorMessage.java create mode 100644 src/main/resources/application.properties diff --git a/src/main/java/microservice/SpendMicroservice.java b/src/main/java/microservice/SpendMicroservice.java index 503e5d72..5cc125f1 100644 --- a/src/main/java/microservice/SpendMicroservice.java +++ b/src/main/java/microservice/SpendMicroservice.java @@ -2,13 +2,29 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.core.task.TaskExecutor; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +@EnableAsync @SpringBootApplication public class SpendMicroservice { + @Bean(name = "ThreadPoolExecutor") + public TaskExecutor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(7); + executor.setMaxPoolSize(42); + executor.setQueueCapacity(500); + executor.setThreadNamePrefix("ThreadPoolExecutor-"); + executor.initialize(); + return executor; + } + public static void main(String[] args) { SpringApplication.run(SpendMicroservice.class, args); } - + } diff --git a/src/main/java/microservice/WebMvcConfig.java b/src/main/java/microservice/WebMvcConfig.java new file mode 100644 index 00000000..9116343c --- /dev/null +++ b/src/main/java/microservice/WebMvcConfig.java @@ -0,0 +1,21 @@ +package microservice; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import microservice.interceptor.JWTInterceptor; + + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private JWTInterceptor interceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry){ + registry.addInterceptor(interceptor).addPathPatterns("/**"); + } + +} diff --git a/src/main/java/microservice/controllers/CategoryController.java b/src/main/java/microservice/controllers/CategoryController.java index 6aaf9bae..d11bd238 100644 --- a/src/main/java/microservice/controllers/CategoryController.java +++ b/src/main/java/microservice/controllers/CategoryController.java @@ -1,5 +1,6 @@ package microservice.controllers; + import java.util.List; import microservice.models.Category; import javax.validation.constraints.Size; @@ -11,7 +12,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.beans.factory.annotation.Autowired; -import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.springframework.http.MediaType; @RestController @@ -21,16 +24,17 @@ public class CategoryController { @Autowired private CategoryService categoryService; - - @RequestMapping(value = "/category/suggestions", method = RequestMethod.GET) - public Callable>> getSuggestedCategories( + + @RequestMapping(value = "/category/suggestions", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity> getSuggestedCategories( @Size(min=2, message="partial_name should contain at least 2 characters") @RequestParam(value="partial_name") - String partialCategoryName) { + String partialCategoryName) throws InterruptedException, ExecutionException { - return () -> ResponseEntity.ok( - categoryService.listSimilarCategories(partialCategoryName) - ); + CompletableFuture> categoryPromisse = categoryService.listSimilarCategories(partialCategoryName); + return ResponseEntity.ok(categoryPromisse.get()); } diff --git a/src/main/java/microservice/controllers/SpendController.java b/src/main/java/microservice/controllers/SpendController.java index d4ed40c1..0209a914 100644 --- a/src/main/java/microservice/controllers/SpendController.java +++ b/src/main/java/microservice/controllers/SpendController.java @@ -1,5 +1,6 @@ package microservice.controllers; + import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @@ -7,11 +8,15 @@ import org.springframework.web.bind.annotation.RequestBody; import javax.validation.Valid; import microservice.models.Spend; -import java.util.concurrent.Callable; import org.springframework.http.ResponseEntity; import java.net.URI; +import java.net.URISyntaxException; import org.springframework.web.util.UriComponentsBuilder; import microservice.services.SpendService; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.springframework.http.MediaType; + // import java.text.SimpleDateFormat; // import java.util.Date; @@ -34,18 +39,22 @@ public class SpendController { @Autowired private SpendService spendService; - - @RequestMapping(value = "/spend", method = RequestMethod.POST) - public Callable> insertNewSpend( - UriComponentsBuilder builder, - @Valid @RequestBody Spend spend) { - return () -> { - Spend storedSpend = spendService.insertNewSpend(spend); + @RequestMapping(value = "/spend", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity insertNewSpend(UriComponentsBuilder builder, + @Valid @RequestBody Spend spend) + throws URISyntaxException, + InterruptedException, + ExecutionException { + + CompletableFuture spendPromisse = spendService.insertNewSpend(spend); + Spend storedSpend = spendPromisse.get(); return ResponseEntity .created(new URI(builder.toUriString() + spend.get_id())) .body(storedSpend); - }; + } diff --git a/src/main/java/microservice/interceptor/JWTInterceptor.java b/src/main/java/microservice/interceptor/JWTInterceptor.java index bfc50c71..73971ee6 100644 --- a/src/main/java/microservice/interceptor/JWTInterceptor.java +++ b/src/main/java/microservice/interceptor/JWTInterceptor.java @@ -4,18 +4,27 @@ import com.auth0.jwt.JWTVerifier; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.exceptions.JWTVerificationException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import microservice.models.ErrorMessage; +import com.fasterxml.jackson.databind.ObjectMapper; +@Component public class JWTInterceptor extends HandlerInterceptorAdapter { - public static final String SECRET = "dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend"; - private static final String ISSUER = "http://api.santandertest.com.br"; + @Value("${jwt.issuer}") + private String ISSUER; + + @Value("${jwt.secret}") + private String SECRET; @Override @@ -26,7 +35,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); String requestMethod = request.getMethod(); - + if (!requestMethod.equals("OPTIONS")) { String token = request.getHeader("Authorization"); try { @@ -34,18 +43,37 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons JWTVerifier verifier = JWT.require(algorithm) .withIssuer(ISSUER) .build(); + DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException e) { - if (token == null) - response.sendError(HttpStatus.UNAUTHORIZED.value()); - else - response.sendError(HttpStatus.FORBIDDEN.value()); - + formatErrorResponse(response); + return false; + } + catch (NullPointerException e) { + formatErrorResponse(response); return false; } } return super.preHandle(request, response, handler); } + + + private void formatErrorResponse(HttpServletResponse response) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ErrorMessage errorMsg = new ErrorMessage(); + errorMsg.setContent("empty Authorization header"); + errorMsg.setStatus(false); + + //Object to JSON in String + String jsonInString = mapper.writeValueAsString(errorMsg); + + response.setContentType("application/json"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.getWriter().write(jsonInString); + response.getWriter().flush(); + response.getWriter().close(); + } + } diff --git a/src/main/java/microservice/models/ErrorMessage.java b/src/main/java/microservice/models/ErrorMessage.java new file mode 100644 index 00000000..3044590f --- /dev/null +++ b/src/main/java/microservice/models/ErrorMessage.java @@ -0,0 +1,23 @@ +package microservice.models; + +public class ErrorMessage { + + private String content; + private boolean status; + + public ErrorMessage() { } + + public ErrorMessage(String content, boolean status) { + this.content = content; + this.status = status; + } + + public String getContent() { return content; } + + public void setContent(String content) { this.content = content; } + + public String getStatus() { return status ? "success" : "failed"; } + + public void setStatus(boolean status) { this.status = status; } + +} diff --git a/src/main/java/microservice/services/CategoryService.java b/src/main/java/microservice/services/CategoryService.java index cf437c7b..ec8b8cad 100644 --- a/src/main/java/microservice/services/CategoryService.java +++ b/src/main/java/microservice/services/CategoryService.java @@ -3,9 +3,11 @@ import microservice.repositories.CategoryRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import microservice.models.Category; import java.util.List; +import java.util.concurrent.CompletableFuture; @Service @@ -14,8 +16,9 @@ public class CategoryService { @Autowired private CategoryRepository categoryRepo; - public List listSimilarCategories(String partialCategoryName) { - return categoryRepo.findBySimilarName(partialCategoryName); + @Async("ThreadPoolExecutor") + public CompletableFuture> listSimilarCategories(String partialCategoryName) { + return CompletableFuture.completedFuture(categoryRepo.findBySimilarName(partialCategoryName)); } } diff --git a/src/main/java/microservice/services/SpendService.java b/src/main/java/microservice/services/SpendService.java index 98f634e7..e9ad8047 100644 --- a/src/main/java/microservice/services/SpendService.java +++ b/src/main/java/microservice/services/SpendService.java @@ -4,10 +4,12 @@ import microservice.repositories.SpendRepository; import microservice.repositories.CategoryRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import microservice.models.Spend; import microservice.models.Category; import org.bson.types.ObjectId; +import java.util.concurrent.CompletableFuture; @Service @@ -19,14 +21,14 @@ public class SpendService { @Autowired private CategoryRepository categoryRepo; - public Spend insertNewSpend(Spend spend) { + @Async("ThreadPoolExecutor") + public CompletableFuture insertNewSpend(Spend spend) { spend.set_id(ObjectId.get()); if (spend.getCategory() != null) { Category c = new Category(spend.getCategory()); categoryRepo.save(c); } - spendRepo.save(spend); - return spend; + return CompletableFuture.completedFuture(spendRepo.save(spend)); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 00000000..c6f4dfac --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +jwt.issuer=dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend +jwt.secret=http://api.santandertest.com.br From bda5c2da3c132ab1f177db6ca2beaab5bc97dd1c Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Wed, 12 Dec 2018 03:26:18 -0200 Subject: [PATCH 03/17] =?UTF-8?q?add=20pasta=20de=20configs=20+=20corre?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20mgs=20de=20erro=20do=20interceptor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => configurations}/WebMvcConfig.java | 5 +++-- .../JWTInterceptor.java | 12 +++++------- 2 files changed, 8 insertions(+), 9 deletions(-) rename src/main/java/microservice/{ => configurations}/WebMvcConfig.java (86%) rename src/main/java/microservice/{interceptor => interceptors}/JWTInterceptor.java (88%) diff --git a/src/main/java/microservice/WebMvcConfig.java b/src/main/java/microservice/configurations/WebMvcConfig.java similarity index 86% rename from src/main/java/microservice/WebMvcConfig.java rename to src/main/java/microservice/configurations/WebMvcConfig.java index 9116343c..0efd4b02 100644 --- a/src/main/java/microservice/WebMvcConfig.java +++ b/src/main/java/microservice/configurations/WebMvcConfig.java @@ -1,10 +1,11 @@ -package microservice; +package microservice.configurations; + import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import microservice.interceptor.JWTInterceptor; +import microservice.interceptors.JWTInterceptor; @Configuration diff --git a/src/main/java/microservice/interceptor/JWTInterceptor.java b/src/main/java/microservice/interceptors/JWTInterceptor.java similarity index 88% rename from src/main/java/microservice/interceptor/JWTInterceptor.java rename to src/main/java/microservice/interceptors/JWTInterceptor.java index 73971ee6..66132004 100644 --- a/src/main/java/microservice/interceptor/JWTInterceptor.java +++ b/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -1,4 +1,4 @@ -package microservice.interceptor; +package microservice.interceptors; import com.auth0.jwt.JWTVerifier; @@ -47,11 +47,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons DecodedJWT jwt = verifier.verify(token); } catch (JWTVerificationException e) { - formatErrorResponse(response); + formatErrorResponse(response, "invalid token within the Authorization header"); return false; } catch (NullPointerException e) { - formatErrorResponse(response); + formatErrorResponse(response, "empty Authorization header"); return false; } @@ -60,11 +60,9 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } - private void formatErrorResponse(HttpServletResponse response) throws IOException { + private void formatErrorResponse(HttpServletResponse response, String messageContent) throws IOException { ObjectMapper mapper = new ObjectMapper(); - ErrorMessage errorMsg = new ErrorMessage(); - errorMsg.setContent("empty Authorization header"); - errorMsg.setStatus(false); + ErrorMessage errorMsg = new ErrorMessage(messageContent, false); //Object to JSON in String String jsonInString = mapper.writeValueAsString(errorMsg); From 9cbf751280b04b966a0785407322121f11a1abb5 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Thu, 13 Dec 2018 18:01:11 -0200 Subject: [PATCH 04/17] refatorando ErrorMessage para Message --- .../java/microservice/SpendMicroservice.java | 19 +++++++++++++++---- .../interceptors/JWTInterceptor.java | 15 +++++---------- .../{ErrorMessage.java => Message.java} | 16 +++++++++++++--- src/main/resources/application.properties | 3 +++ 4 files changed, 36 insertions(+), 17 deletions(-) rename src/main/java/microservice/models/{ErrorMessage.java => Message.java} (52%) diff --git a/src/main/java/microservice/SpendMicroservice.java b/src/main/java/microservice/SpendMicroservice.java index 5cc125f1..db208158 100644 --- a/src/main/java/microservice/SpendMicroservice.java +++ b/src/main/java/microservice/SpendMicroservice.java @@ -6,23 +6,34 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; - +import org.springframework.beans.factory.annotation.Value; @EnableAsync @SpringBootApplication public class SpendMicroservice { + @Value("${thread.pool.core.size}") + private int corePoolSize; + + @Value("${thread.pool.max.size}") + private int maxPoolSize; + + @Value("${thread.queue.capacity}") + private int queueCapacity; + + @Bean(name = "ThreadPoolExecutor") public TaskExecutor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(7); - executor.setMaxPoolSize(42); - executor.setQueueCapacity(500); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); executor.setThreadNamePrefix("ThreadPoolExecutor-"); executor.initialize(); return executor; } + public static void main(String[] args) { SpringApplication.run(SpendMicroservice.class, args); } diff --git a/src/main/java/microservice/interceptors/JWTInterceptor.java b/src/main/java/microservice/interceptors/JWTInterceptor.java index 66132004..f5f6e09d 100644 --- a/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -13,8 +13,7 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import microservice.models.ErrorMessage; -import com.fasterxml.jackson.databind.ObjectMapper; +import microservice.models.Message; @Component @@ -51,7 +50,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return false; } catch (NullPointerException e) { - formatErrorResponse(response, "empty Authorization header"); + formatErrorResponse(response, "no Authorization header"); return false; } @@ -61,15 +60,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons private void formatErrorResponse(HttpServletResponse response, String messageContent) throws IOException { - ObjectMapper mapper = new ObjectMapper(); - ErrorMessage errorMsg = new ErrorMessage(messageContent, false); - - //Object to JSON in String - String jsonInString = mapper.writeValueAsString(errorMsg); - + Message errorMsg = new Message(messageContent, false); response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write(jsonInString); + response.getWriter().write(errorMsg.toJSONString()); response.getWriter().flush(); response.getWriter().close(); } diff --git a/src/main/java/microservice/models/ErrorMessage.java b/src/main/java/microservice/models/Message.java similarity index 52% rename from src/main/java/microservice/models/ErrorMessage.java rename to src/main/java/microservice/models/Message.java index 3044590f..b0e4ae3b 100644 --- a/src/main/java/microservice/models/ErrorMessage.java +++ b/src/main/java/microservice/models/Message.java @@ -1,13 +1,20 @@ package microservice.models; -public class ErrorMessage { + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + + +public class Message { private String content; private boolean status; - public ErrorMessage() { } + private ObjectMapper mapper = new ObjectMapper(); - public ErrorMessage(String content, boolean status) { + public Message() { } + + public Message(String content, boolean status) { this.content = content; this.status = status; } @@ -20,4 +27,7 @@ public ErrorMessage(String content, boolean status) { public void setStatus(boolean status) { this.status = status; } + public String toJSONString() throws JsonProcessingException { + return mapper.writeValueAsString(this); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c6f4dfac..d3637467 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,5 @@ jwt.issuer=dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend jwt.secret=http://api.santandertest.com.br +thread.pool.core.size=7 +thread.pool.max.size=42 +thread.queue.capacity=11 \ No newline at end of file From 1d20d263a322f3f0867285c6a6d5b4ac93d91c79 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Fri, 14 Dec 2018 21:23:21 -0200 Subject: [PATCH 05/17] adding auth microservice --- .gitignore | 8 +- auth_microservice/pom.xml | 72 ++++++++++++ .../java/microservice/AuthMicroservice.java | 39 +++++++ .../controllers/SystemController.java | 73 ++++++++++++ .../controllers/UserController.java | 73 ++++++++++++ .../microservice/models/Authorization.java | 39 +++++++ .../java/microservice/models/Message.java | 24 ++++ .../main/java/microservice/models/System.java | 43 +++++++ .../main/java/microservice/models/User.java | 43 +++++++ .../repositories/SystemRepository.java | 12 ++ .../repositories/UserRepository.java | 12 ++ .../microservice/services/SystemService.java | 109 ++++++++++++++++++ .../microservice/services/UserService.java | 108 +++++++++++++++++ .../microservice/util/PasswordHandler.java | 29 +++++ .../src/main/resources/application.properties | 6 + pom.xml => spend_microservice/pom.xml | 0 .../java/microservice/SpendMicroservice.java | 0 .../configurations/WebMvcConfig.java | 0 .../controllers/CategoryController.java | 4 +- .../controllers/SpendController.java | 14 +-- .../interceptors/JWTInterceptor.java | 0 .../java/microservice/models/Category.java | 0 .../java/microservice/models/Message.java | 0 .../main/java/microservice/models/Spend.java | 0 .../repositories/CategoryRepository.java | 0 .../repositories/SpendRepository.java | 0 .../services/CategoryService.java | 0 .../microservice/services/SpendService.java | 0 .../main/resources/application.properties | 0 29 files changed, 694 insertions(+), 14 deletions(-) create mode 100644 auth_microservice/pom.xml create mode 100644 auth_microservice/src/main/java/microservice/AuthMicroservice.java create mode 100644 auth_microservice/src/main/java/microservice/controllers/SystemController.java create mode 100644 auth_microservice/src/main/java/microservice/controllers/UserController.java create mode 100644 auth_microservice/src/main/java/microservice/models/Authorization.java create mode 100644 auth_microservice/src/main/java/microservice/models/Message.java create mode 100644 auth_microservice/src/main/java/microservice/models/System.java create mode 100644 auth_microservice/src/main/java/microservice/models/User.java create mode 100644 auth_microservice/src/main/java/microservice/repositories/SystemRepository.java create mode 100644 auth_microservice/src/main/java/microservice/repositories/UserRepository.java create mode 100644 auth_microservice/src/main/java/microservice/services/SystemService.java create mode 100644 auth_microservice/src/main/java/microservice/services/UserService.java create mode 100644 auth_microservice/src/main/java/microservice/util/PasswordHandler.java create mode 100644 auth_microservice/src/main/resources/application.properties rename pom.xml => spend_microservice/pom.xml (100%) rename {src => spend_microservice/src}/main/java/microservice/SpendMicroservice.java (100%) rename {src => spend_microservice/src}/main/java/microservice/configurations/WebMvcConfig.java (100%) rename {src => spend_microservice/src}/main/java/microservice/controllers/CategoryController.java (87%) rename {src => spend_microservice/src}/main/java/microservice/controllers/SpendController.java (83%) rename {src => spend_microservice/src}/main/java/microservice/interceptors/JWTInterceptor.java (100%) rename {src => spend_microservice/src}/main/java/microservice/models/Category.java (100%) rename {src => spend_microservice/src}/main/java/microservice/models/Message.java (100%) rename {src => spend_microservice/src}/main/java/microservice/models/Spend.java (100%) rename {src => spend_microservice/src}/main/java/microservice/repositories/CategoryRepository.java (100%) rename {src => spend_microservice/src}/main/java/microservice/repositories/SpendRepository.java (100%) rename {src => spend_microservice/src}/main/java/microservice/services/CategoryService.java (100%) rename {src => spend_microservice/src}/main/java/microservice/services/SpendService.java (100%) rename {src => spend_microservice/src}/main/resources/application.properties (100%) diff --git a/.gitignore b/.gitignore index 17b5b0b6..3443465a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -.settings/* +.settings +.vscode +.DS_Store .project .classpath -target/* -.DS_Store -.vscode/* \ No newline at end of file +target \ No newline at end of file diff --git a/auth_microservice/pom.xml b/auth_microservice/pom.xml new file mode 100644 index 00000000..a88fc118 --- /dev/null +++ b/auth_microservice/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + org.springframework + authentication_microservice + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.0.5.RELEASE + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.springframework.boot + spring-boot-starter-web + + + + org.mindrot + jbcrypt + 0.4 + + + + com.auth0 + java-jwt + 3.4.1 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.jayway.jsonpath + json-path + test + + + + + 1.8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-releases + https://repo.spring.io/libs-release + + + + \ No newline at end of file diff --git a/auth_microservice/src/main/java/microservice/AuthMicroservice.java b/auth_microservice/src/main/java/microservice/AuthMicroservice.java new file mode 100644 index 00000000..a00b5aad --- /dev/null +++ b/auth_microservice/src/main/java/microservice/AuthMicroservice.java @@ -0,0 +1,39 @@ +package microservice; + + +import org.springframework.boot.SpringApplication; +import org.springframework.core.task.TaskExecutor; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.SpringBootApplication; + + +@SpringBootApplication +public class AuthMicroservice { + + @Value("${thread.pool.core.size}") + private int corePoolSize; + + @Value("${thread.pool.max.size}") + private int maxPoolSize; + + @Value("${thread.queue.capacity}") + private int queueCapacity; + + @Bean(name = "ThreadPoolExecutor") + public TaskExecutor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setThreadNamePrefix("ThreadPoolExecutor-"); + executor.initialize(); + return executor; + } + + public static void main(String[] args) { + SpringApplication.run(AuthMicroservice.class, args); + } + +} diff --git a/auth_microservice/src/main/java/microservice/controllers/SystemController.java b/auth_microservice/src/main/java/microservice/controllers/SystemController.java new file mode 100644 index 00000000..1e6fe531 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/controllers/SystemController.java @@ -0,0 +1,73 @@ +package microservice.controllers; + + +import org.springframework.web.bind.annotation.RestController; +import org.springframework.beans.factory.annotation.Autowired; +import microservice.services.SystemService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestBody; +import javax.validation.Valid; +import org.springframework.http.ResponseEntity; +import java.net.URI; +import java.net.URISyntaxException; +import org.springframework.web.util.UriComponentsBuilder; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.springframework.http.MediaType; +import microservice.models.Message; +import microservice.models.System; +import microservice.models.Authorization; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestHeader; + + +@RestController +public class SystemController { + + @Autowired + private SystemService systemService; + + @RequestMapping(value = "/system", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity inserNewSystem(UriComponentsBuilder builder, + @Valid @RequestBody System system) + throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture systemFuture = systemService.insertNewSystem(system); + System storedSystem = systemFuture.get(); + return ResponseEntity + .created(new URI(builder.toUriString() + storedSystem.get_id())) + .body(new Message("system " + system.getUsername() + " successfully registered", true)); + } + + @RequestMapping(value = "/system/authorize", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity authorizeSystem(@Valid @RequestBody System system) + throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture systemFuture = systemService.authorizeSystem(system); + Object result = systemFuture.get(); + if (result.getClass() == Authorization.class) + return ResponseEntity.ok(result); + else + return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); + } + + @RequestMapping(value = "/system/authenticate", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity authenticateSystem(@RequestHeader("Authorization") String accessToken) + throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture systemFuture = systemService.authenticateSystem(accessToken); + Message msg = systemFuture.get(); + if (msg.getStatus().equals("success")) + return ResponseEntity.ok(msg); + else + return new ResponseEntity<>(msg, HttpStatus.UNAUTHORIZED); + } + +} diff --git a/auth_microservice/src/main/java/microservice/controllers/UserController.java b/auth_microservice/src/main/java/microservice/controllers/UserController.java new file mode 100644 index 00000000..cd8436c7 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/controllers/UserController.java @@ -0,0 +1,73 @@ +package microservice.controllers; + + +import org.springframework.web.bind.annotation.RestController; +import org.springframework.beans.factory.annotation.Autowired; +import microservice.services.UserService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import javax.validation.Valid; +import org.springframework.http.ResponseEntity; +import java.net.URI; +import java.net.URISyntaxException; +import org.springframework.web.util.UriComponentsBuilder; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; +import microservice.models.Authorization; +import microservice.models.Message; +import microservice.models.User; + + +@RestController +public class UserController { + + @Autowired + private UserService userService; + + @RequestMapping(value = "/user", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity inserNewUser(UriComponentsBuilder builder, + @Valid @RequestBody User user) + throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture userFuture = userService.insertNewUser(user); + User storedUser = userFuture.get(); + return ResponseEntity + .created(new URI(builder.toUriString() + storedUser.get_id())) + .body(new Message("user " + user.getUsername() + " successfully registered", true)); + } + + @RequestMapping(value = "/user/authorize", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity authorizeUser(@Valid @RequestBody User user) + throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture userFuture = userService.authorizeUser(user); + Object result = userFuture.get(); + if (result.getClass() == Authorization.class) + return ResponseEntity.ok(result); + else + return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); + } + + @RequestMapping(value = "/user/authenticate", + method = RequestMethod.POST, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity authenticateUser(@RequestHeader("Authorization") String accessToken) + throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture userFuture = userService.authenticateUser(accessToken); + Message msg = userFuture.get(); + if (msg.getStatus().equals("success")) + return ResponseEntity.ok(msg); + else + return new ResponseEntity<>(msg, HttpStatus.UNAUTHORIZED); + } + +} diff --git a/auth_microservice/src/main/java/microservice/models/Authorization.java b/auth_microservice/src/main/java/microservice/models/Authorization.java new file mode 100644 index 00000000..5b98efc6 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/models/Authorization.java @@ -0,0 +1,39 @@ +package microservice.models; + + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + + +public class Authorization { + + private String accessToken; + private boolean status; + + @JsonFormat(shape=JsonFormat.Shape.STRING, + pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", + timezone="UTC") + private Date expiryDate; + + + public Authorization() { } + + public Authorization(String accessToken, Date expiryDate, boolean status) { + this.accessToken = accessToken; + this.status = status; + this.expiryDate = expiryDate; + } + + public String getAccessToken() { return accessToken; } + + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + + public Date getExpiryDate() { return expiryDate; } + + public void setExpiryDate(Date expiryDate) { this.expiryDate = expiryDate; } + + public String getStatus() { return status ? "success" : "failed"; } + + public void setStatus(boolean status) { this.status = status; } + +} diff --git a/auth_microservice/src/main/java/microservice/models/Message.java b/auth_microservice/src/main/java/microservice/models/Message.java new file mode 100644 index 00000000..82b9ae82 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/models/Message.java @@ -0,0 +1,24 @@ +package microservice.models; + + +public class Message { + + private String content; + private boolean status; + + public Message() { } + + public Message(String content, boolean status) { + this.content = content; + this.status = status; + } + + public String getContent() { return content; } + + public void setContent(String content) { this.content = content; } + + public String getStatus() { return status ? "success" : "failed"; } + + public void setStatus(boolean status) { this.status = status; } + +} diff --git a/auth_microservice/src/main/java/microservice/models/System.java b/auth_microservice/src/main/java/microservice/models/System.java new file mode 100644 index 00000000..32f4cc8b --- /dev/null +++ b/auth_microservice/src/main/java/microservice/models/System.java @@ -0,0 +1,43 @@ +package microservice.models; + + +import org.springframework.data.annotation.Id; +import javax.validation.constraints.NotNull; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + + +@Document(collection = "system") +public class System { + + @Id + private ObjectId _id; + + @NotNull + @Indexed(unique = true) + private String username; + + @NotNull + private String password; + + public System() { } + + public System(String username, String password) { + this.username = username; + this.password = password; + } + + public String get_id() { return _id.toHexString(); } + + public String getUsername() { return username; } + + public String getPassword() { return password; } + + public void set_id(ObjectId _id) { this._id = _id; } + + public void setUsername(String username) { this.username = username; } + + public void setPassword(String password) { this.password = password; } + +} diff --git a/auth_microservice/src/main/java/microservice/models/User.java b/auth_microservice/src/main/java/microservice/models/User.java new file mode 100644 index 00000000..318dbd4f --- /dev/null +++ b/auth_microservice/src/main/java/microservice/models/User.java @@ -0,0 +1,43 @@ +package microservice.models; + + +import org.springframework.data.annotation.Id; +import javax.validation.constraints.NotNull; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; + + +@Document(collection = "user") +public class User { + + @Id + private ObjectId _id; + + @NotNull + @Indexed(unique = true) + private String username; + + @NotNull + private String password; + + public User() { } + + public User(String username, String password) { + this.username = username; + this.password = password; + } + + public String get_id() { return _id.toHexString(); } + + public String getUsername() { return username; } + + public String getPassword() { return password; } + + public void set_id(ObjectId _id) { this._id = _id; } + + public void setUsername(String username) { this.username = username; } + + public void setPassword(String password) { this.password = password; } + +} diff --git a/auth_microservice/src/main/java/microservice/repositories/SystemRepository.java b/auth_microservice/src/main/java/microservice/repositories/SystemRepository.java new file mode 100644 index 00000000..4a46980b --- /dev/null +++ b/auth_microservice/src/main/java/microservice/repositories/SystemRepository.java @@ -0,0 +1,12 @@ +package microservice.repositories; + + +import microservice.models.System; +import org.springframework.data.mongodb.repository.MongoRepository; + + +public interface SystemRepository extends MongoRepository { + + public System findByUsername(String username); + +} diff --git a/auth_microservice/src/main/java/microservice/repositories/UserRepository.java b/auth_microservice/src/main/java/microservice/repositories/UserRepository.java new file mode 100644 index 00000000..fb0766a4 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/repositories/UserRepository.java @@ -0,0 +1,12 @@ +package microservice.repositories; + + +import microservice.models.User; +import org.springframework.data.mongodb.repository.MongoRepository; + + +public interface UserRepository extends MongoRepository { + + public User findByUsername(String username); + +} diff --git a/auth_microservice/src/main/java/microservice/services/SystemService.java b/auth_microservice/src/main/java/microservice/services/SystemService.java new file mode 100644 index 00000000..4250a46f --- /dev/null +++ b/auth_microservice/src/main/java/microservice/services/SystemService.java @@ -0,0 +1,109 @@ +package microservice.services; + + +import microservice.repositories.SystemRepository; +import microservice.util.PasswordHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import microservice.models.Authorization; +import microservice.models.Message; +import microservice.models.System; +import java.util.concurrent.CompletableFuture; +import org.springframework.scheduling.annotation.Async; +import org.bson.types.ObjectId; +import java.util.Calendar; +import com.auth0.jwt.algorithms.Algorithm; +import org.springframework.beans.factory.annotation.Value; +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.exceptions.JWTVerificationException; + + +@Service +public class SystemService { + + @Value("${jwt.issuer}") + private String ISSUER; + + @Value("${jwt.secret}") + private String SECRET; + + @Value("${jwt.duration.in.days}") + private int ACCESS_TOKEN_DURATION; + + @Autowired + private SystemRepository systemRepo; + + + @Async("ThreadPoolExecutor") + public CompletableFuture insertNewSystem(System system) { + system.set_id(ObjectId.get()); + system.setPassword(PasswordHandler.encryptPassword(system.getPassword())); + return CompletableFuture.completedFuture(systemRepo.save(system)); + } + + @Async("ThreadPoolExecutor") + public CompletableFuture authorizeSystem(System system) { + System storedSystem = systemRepo.findByUsername(system.getUsername()); + String msg = "invalid username or password"; + + if (storedSystem != null) { + boolean isValidPassword = PasswordHandler.checkPassword(system.getPassword(), + storedSystem.getPassword()); + + if (isValidPassword) { + Calendar issuedAt = Calendar.getInstance(); + Calendar expiresAt = Calendar.getInstance(); + expiresAt.add(Calendar.DATE, ACCESS_TOKEN_DURATION); + + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + String token = JWT.create() + .withIssuer(ISSUER) + .withIssuedAt(issuedAt.getTime()) + .withExpiresAt(expiresAt.getTime()) + .withClaim("systemId", storedSystem.get_id()) + .sign(algorithm); + + Authorization auth = new Authorization(token, expiresAt.getTime(), true); + return CompletableFuture.completedFuture(auth); + } + catch (JWTCreationException e) { + // invalid signing configuration / couldn't convert claims + msg = e.getMessage(); + } + } + } + + return CompletableFuture.completedFuture(new Message(msg, false)); + } + + @Async("ThreadPoolExecutor") + public CompletableFuture authenticateSystem(String token) { + String msg = "and error has occurred"; + boolean status = false; + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer(ISSUER) + .build(); + + DecodedJWT jwt = verifier.verify(token); + String systemId = jwt.getClaim("systemId").asString(); + msg = "valid token"; + status = true; + } + catch (JWTVerificationException e) { + msg = "invalid token"; + } + catch (NullPointerException e) { + msg = "no token provided"; + } + + return CompletableFuture.completedFuture(new Message(msg, status)); + } + + +} diff --git a/auth_microservice/src/main/java/microservice/services/UserService.java b/auth_microservice/src/main/java/microservice/services/UserService.java new file mode 100644 index 00000000..d6769252 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/services/UserService.java @@ -0,0 +1,108 @@ +package microservice.services; + + +import microservice.repositories.UserRepository; +import microservice.util.PasswordHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import microservice.models.Authorization; +import microservice.models.Message; +import microservice.models.User; +import java.util.concurrent.CompletableFuture; +import org.springframework.scheduling.annotation.Async; +import org.bson.types.ObjectId; +import java.util.Calendar; +import com.auth0.jwt.algorithms.Algorithm; +import org.springframework.beans.factory.annotation.Value; +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.exceptions.JWTVerificationException; + + +@Service +public class UserService { + + @Value("${jwt.issuer}") + private String ISSUER; + + @Value("${jwt.secret}") + private String SECRET; + + @Value("${jwt.duration.in.days}") + private int ACCESS_TOKEN_DURATION; + + @Autowired + private UserRepository userRepo; + + + @Async("ThreadPoolExecutor") + public CompletableFuture insertNewUser(User user) { + user.set_id(ObjectId.get()); + user.setPassword(PasswordHandler.encryptPassword(user.getPassword())); + return CompletableFuture.completedFuture(userRepo.save(user)); + } + + @Async("ThreadPoolExecutor") + public CompletableFuture authorizeUser(User user) { + User storedUser = userRepo.findByUsername(user.getUsername()); + String msg = "invalid username or password"; + + if (storedUser != null) { + boolean isValidPassword = PasswordHandler.checkPassword(user.getPassword(), + storedUser.getPassword()); + + if (isValidPassword) { + Calendar issuedAt = Calendar.getInstance(); + Calendar expiresAt = Calendar.getInstance(); + expiresAt.add(Calendar.DATE, ACCESS_TOKEN_DURATION); + + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + String token = JWT.create() + .withIssuer(ISSUER) + .withIssuedAt(issuedAt.getTime()) + .withExpiresAt(expiresAt.getTime()) + .withClaim("userId", storedUser.get_id()) + .sign(algorithm); + + Authorization auth = new Authorization(token, expiresAt.getTime(), true); + return CompletableFuture.completedFuture(auth); + } + catch (JWTCreationException e) { + // invalid signing configuration / couldn't convert claims + msg = e.getMessage(); + } + } + } + + return CompletableFuture.completedFuture(new Message(msg, false)); + } + + @Async("ThreadPoolExecutor") + public CompletableFuture authenticateUser(String token) { + String msg = "an error has occurred"; + boolean status = false; + try { + Algorithm algorithm = Algorithm.HMAC256(SECRET); + JWTVerifier verifier = JWT.require(algorithm) + .withIssuer(ISSUER) + .build(); + + DecodedJWT jwt = verifier.verify(token); + String userId = jwt.getClaim("userId").asString(); + msg = "valid token"; + status = true; + } + catch (JWTVerificationException e) { + msg = "invalid token"; + } + catch (NullPointerException e) { + msg = "no token provided"; + } + + return CompletableFuture.completedFuture(new Message(msg, status)); + } + +} diff --git a/auth_microservice/src/main/java/microservice/util/PasswordHandler.java b/auth_microservice/src/main/java/microservice/util/PasswordHandler.java new file mode 100644 index 00000000..26b2be4a --- /dev/null +++ b/auth_microservice/src/main/java/microservice/util/PasswordHandler.java @@ -0,0 +1,29 @@ +package microservice.util; + + +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.stereotype.Component; + + +@Component +public class PasswordHandler { + + private static int workload = 12; + + public static String encryptPassword(String plaintextPassword) { + String salt = BCrypt.gensalt(workload); + String hashedPassword = BCrypt.hashpw(plaintextPassword, salt); + return hashedPassword; + } + + public static boolean checkPassword(String plaintextPassword, String storedHash) { + boolean verifiedPassword = false; + + if (null == storedHash || !storedHash.startsWith("$2a$")) + throw new java.lang.IllegalArgumentException("invalid hash provided for comparison"); + + verifiedPassword = BCrypt.checkpw(plaintextPassword, storedHash); + + return verifiedPassword; + } +} \ No newline at end of file diff --git a/auth_microservice/src/main/resources/application.properties b/auth_microservice/src/main/resources/application.properties new file mode 100644 index 00000000..816b45b6 --- /dev/null +++ b/auth_microservice/src/main/resources/application.properties @@ -0,0 +1,6 @@ +jwt.issuer=dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend +jwt.secret=http://api.santandertest.com.br +thread.pool.core.size=7 +thread.pool.max.size=42 +thread.queue.capacity=11 +jwt.duration.in.days=14 \ No newline at end of file diff --git a/pom.xml b/spend_microservice/pom.xml similarity index 100% rename from pom.xml rename to spend_microservice/pom.xml diff --git a/src/main/java/microservice/SpendMicroservice.java b/spend_microservice/src/main/java/microservice/SpendMicroservice.java similarity index 100% rename from src/main/java/microservice/SpendMicroservice.java rename to spend_microservice/src/main/java/microservice/SpendMicroservice.java diff --git a/src/main/java/microservice/configurations/WebMvcConfig.java b/spend_microservice/src/main/java/microservice/configurations/WebMvcConfig.java similarity index 100% rename from src/main/java/microservice/configurations/WebMvcConfig.java rename to spend_microservice/src/main/java/microservice/configurations/WebMvcConfig.java diff --git a/src/main/java/microservice/controllers/CategoryController.java b/spend_microservice/src/main/java/microservice/controllers/CategoryController.java similarity index 87% rename from src/main/java/microservice/controllers/CategoryController.java rename to spend_microservice/src/main/java/microservice/controllers/CategoryController.java index d11bd238..039e9464 100644 --- a/src/main/java/microservice/controllers/CategoryController.java +++ b/spend_microservice/src/main/java/microservice/controllers/CategoryController.java @@ -33,8 +33,8 @@ public ResponseEntity> getSuggestedCategories( @RequestParam(value="partial_name") String partialCategoryName) throws InterruptedException, ExecutionException { - CompletableFuture> categoryPromisse = categoryService.listSimilarCategories(partialCategoryName); - return ResponseEntity.ok(categoryPromisse.get()); + CompletableFuture> categoryFuture = categoryService.listSimilarCategories(partialCategoryName); + return ResponseEntity.ok(categoryFuture.get()); } diff --git a/src/main/java/microservice/controllers/SpendController.java b/spend_microservice/src/main/java/microservice/controllers/SpendController.java similarity index 83% rename from src/main/java/microservice/controllers/SpendController.java rename to spend_microservice/src/main/java/microservice/controllers/SpendController.java index 0209a914..1b9bc7a6 100644 --- a/src/main/java/microservice/controllers/SpendController.java +++ b/spend_microservice/src/main/java/microservice/controllers/SpendController.java @@ -44,16 +44,14 @@ public class SpendController { method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity insertNewSpend(UriComponentsBuilder builder, - @Valid @RequestBody Spend spend) - throws URISyntaxException, - InterruptedException, - ExecutionException { + @Valid @RequestBody Spend spend) + throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture spendPromisse = spendService.insertNewSpend(spend); - Spend storedSpend = spendPromisse.get(); + CompletableFuture spendFuture = spendService.insertNewSpend(spend); + Spend storedSpend = spendFuture.get(); return ResponseEntity - .created(new URI(builder.toUriString() + spend.get_id())) - .body(storedSpend); + .created(new URI(builder.toUriString() + spend.get_id())) + .body(storedSpend); } diff --git a/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java similarity index 100% rename from src/main/java/microservice/interceptors/JWTInterceptor.java rename to spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java diff --git a/src/main/java/microservice/models/Category.java b/spend_microservice/src/main/java/microservice/models/Category.java similarity index 100% rename from src/main/java/microservice/models/Category.java rename to spend_microservice/src/main/java/microservice/models/Category.java diff --git a/src/main/java/microservice/models/Message.java b/spend_microservice/src/main/java/microservice/models/Message.java similarity index 100% rename from src/main/java/microservice/models/Message.java rename to spend_microservice/src/main/java/microservice/models/Message.java diff --git a/src/main/java/microservice/models/Spend.java b/spend_microservice/src/main/java/microservice/models/Spend.java similarity index 100% rename from src/main/java/microservice/models/Spend.java rename to spend_microservice/src/main/java/microservice/models/Spend.java diff --git a/src/main/java/microservice/repositories/CategoryRepository.java b/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java similarity index 100% rename from src/main/java/microservice/repositories/CategoryRepository.java rename to spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java diff --git a/src/main/java/microservice/repositories/SpendRepository.java b/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java similarity index 100% rename from src/main/java/microservice/repositories/SpendRepository.java rename to spend_microservice/src/main/java/microservice/repositories/SpendRepository.java diff --git a/src/main/java/microservice/services/CategoryService.java b/spend_microservice/src/main/java/microservice/services/CategoryService.java similarity index 100% rename from src/main/java/microservice/services/CategoryService.java rename to spend_microservice/src/main/java/microservice/services/CategoryService.java diff --git a/src/main/java/microservice/services/SpendService.java b/spend_microservice/src/main/java/microservice/services/SpendService.java similarity index 100% rename from src/main/java/microservice/services/SpendService.java rename to spend_microservice/src/main/java/microservice/services/SpendService.java diff --git a/src/main/resources/application.properties b/spend_microservice/src/main/resources/application.properties similarity index 100% rename from src/main/resources/application.properties rename to spend_microservice/src/main/resources/application.properties From 3f14eec77da02a92c5925747e497d3b7f3c6c438 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Fri, 14 Dec 2018 21:49:50 -0200 Subject: [PATCH 06/17] changing ports of both microservices --- .gitignore | 3 ++- auth_microservice/src/main/resources/application.properties | 3 ++- spend_microservice/src/main/resources/application.properties | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 3443465a..bf7ccc01 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .DS_Store .project .classpath -target \ No newline at end of file +target +bin \ No newline at end of file diff --git a/auth_microservice/src/main/resources/application.properties b/auth_microservice/src/main/resources/application.properties index 816b45b6..e8bc796a 100644 --- a/auth_microservice/src/main/resources/application.properties +++ b/auth_microservice/src/main/resources/application.properties @@ -3,4 +3,5 @@ jwt.secret=http://api.santandertest.com.br thread.pool.core.size=7 thread.pool.max.size=42 thread.queue.capacity=11 -jwt.duration.in.days=14 \ No newline at end of file +jwt.duration.in.days=14 +server.port=8080 \ No newline at end of file diff --git a/spend_microservice/src/main/resources/application.properties b/spend_microservice/src/main/resources/application.properties index d3637467..247493b1 100644 --- a/spend_microservice/src/main/resources/application.properties +++ b/spend_microservice/src/main/resources/application.properties @@ -2,4 +2,5 @@ jwt.issuer=dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend jwt.secret=http://api.santandertest.com.br thread.pool.core.size=7 thread.pool.max.size=42 -thread.queue.capacity=11 \ No newline at end of file +thread.queue.capacity=11 +server.port=8081 \ No newline at end of file From 30eba650cdf916ef4ed6babcd8015aa3969cb344 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Sun, 16 Dec 2018 02:12:14 -0200 Subject: [PATCH 07/17] connecting both microservices --- .../controllers/SystemController.java | 18 +++--- .../controllers/UserController.java | 20 +++---- .../microservice/services/SystemService.java | 8 +-- .../microservice/services/UserService.java | 8 +-- .../src/main/resources/application.properties | 5 +- spend_microservice/pom.xml | 6 -- .../interceptors/JWTInterceptor.java | 57 ++++++++++--------- .../java/microservice/models/Message.java | 16 ++++-- .../main/java/microservice/models/Spend.java | 8 +-- .../src/main/resources/application.properties | 6 +- 10 files changed, 77 insertions(+), 75 deletions(-) diff --git a/auth_microservice/src/main/java/microservice/controllers/SystemController.java b/auth_microservice/src/main/java/microservice/controllers/SystemController.java index 1e6fe531..fed1c0b2 100644 --- a/auth_microservice/src/main/java/microservice/controllers/SystemController.java +++ b/auth_microservice/src/main/java/microservice/controllers/SystemController.java @@ -35,20 +35,20 @@ public ResponseEntity inserNewSystem(UriComponentsBuilder builder, @Valid @RequestBody System system) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture systemFuture = systemService.insertNewSystem(system); + CompletableFuture systemFuture = systemService.register(system); System storedSystem = systemFuture.get(); return ResponseEntity - .created(new URI(builder.toUriString() + storedSystem.get_id())) + .created(new URI(builder.toUriString() + "/" + storedSystem.get_id())) .body(new Message("system " + system.getUsername() + " successfully registered", true)); } - @RequestMapping(value = "/system/authorize", + @RequestMapping(value = "/system/authentication", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity authorizeSystem(@Valid @RequestBody System system) + public ResponseEntity authenticateSystem(@Valid @RequestBody System system) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture systemFuture = systemService.authorizeSystem(system); + CompletableFuture systemFuture = systemService.authenticate(system); Object result = systemFuture.get(); if (result.getClass() == Authorization.class) return ResponseEntity.ok(result); @@ -56,13 +56,13 @@ public ResponseEntity authorizeSystem(@Valid @RequestBody System system) return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); } - @RequestMapping(value = "/system/authenticate", - method = RequestMethod.POST, + @RequestMapping(value = "/system/authorization", + method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity authenticateSystem(@RequestHeader("Authorization") String accessToken) + public ResponseEntity authorizeSystem(@RequestHeader("Authorization") String accessToken) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture systemFuture = systemService.authenticateSystem(accessToken); + CompletableFuture systemFuture = systemService.authorize(accessToken); Message msg = systemFuture.get(); if (msg.getStatus().equals("success")) return ResponseEntity.ok(msg); diff --git a/auth_microservice/src/main/java/microservice/controllers/UserController.java b/auth_microservice/src/main/java/microservice/controllers/UserController.java index cd8436c7..5d6fe896 100644 --- a/auth_microservice/src/main/java/microservice/controllers/UserController.java +++ b/auth_microservice/src/main/java/microservice/controllers/UserController.java @@ -31,24 +31,24 @@ public class UserController { @RequestMapping(value = "/user", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity inserNewUser(UriComponentsBuilder builder, + public ResponseEntity register(UriComponentsBuilder builder, @Valid @RequestBody User user) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture userFuture = userService.insertNewUser(user); + CompletableFuture userFuture = userService.register(user); User storedUser = userFuture.get(); return ResponseEntity - .created(new URI(builder.toUriString() + storedUser.get_id())) + .created(new URI(builder.toUriString() + "/" + storedUser.get_id())) .body(new Message("user " + user.getUsername() + " successfully registered", true)); } - @RequestMapping(value = "/user/authorize", + @RequestMapping(value = "/user/authentication", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity authorizeUser(@Valid @RequestBody User user) + public ResponseEntity authenticateUser(@Valid @RequestBody User user) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture userFuture = userService.authorizeUser(user); + CompletableFuture userFuture = userService.authenticate(user); Object result = userFuture.get(); if (result.getClass() == Authorization.class) return ResponseEntity.ok(result); @@ -56,13 +56,13 @@ public ResponseEntity authorizeUser(@Valid @RequestBody User user) return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); } - @RequestMapping(value = "/user/authenticate", - method = RequestMethod.POST, + @RequestMapping(value = "/user/authorization", + method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity authenticateUser(@RequestHeader("Authorization") String accessToken) + public ResponseEntity authorizeUser(@RequestHeader("Authorization") String accessToken) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture userFuture = userService.authenticateUser(accessToken); + CompletableFuture userFuture = userService.authorize(accessToken); Message msg = userFuture.get(); if (msg.getStatus().equals("success")) return ResponseEntity.ok(msg); diff --git a/auth_microservice/src/main/java/microservice/services/SystemService.java b/auth_microservice/src/main/java/microservice/services/SystemService.java index 4250a46f..98f0560c 100644 --- a/auth_microservice/src/main/java/microservice/services/SystemService.java +++ b/auth_microservice/src/main/java/microservice/services/SystemService.java @@ -27,7 +27,7 @@ public class SystemService { @Value("${jwt.issuer}") private String ISSUER; - @Value("${jwt.secret}") + @Value("${jwt.sys.secret}") private String SECRET; @Value("${jwt.duration.in.days}") @@ -38,14 +38,14 @@ public class SystemService { @Async("ThreadPoolExecutor") - public CompletableFuture insertNewSystem(System system) { + public CompletableFuture register(System system) { system.set_id(ObjectId.get()); system.setPassword(PasswordHandler.encryptPassword(system.getPassword())); return CompletableFuture.completedFuture(systemRepo.save(system)); } @Async("ThreadPoolExecutor") - public CompletableFuture authorizeSystem(System system) { + public CompletableFuture authenticate(System system) { System storedSystem = systemRepo.findByUsername(system.getUsername()); String msg = "invalid username or password"; @@ -81,7 +81,7 @@ public CompletableFuture authorizeSystem(System system) { } @Async("ThreadPoolExecutor") - public CompletableFuture authenticateSystem(String token) { + public CompletableFuture authorize(String token) { String msg = "and error has occurred"; boolean status = false; try { diff --git a/auth_microservice/src/main/java/microservice/services/UserService.java b/auth_microservice/src/main/java/microservice/services/UserService.java index d6769252..ed4c9852 100644 --- a/auth_microservice/src/main/java/microservice/services/UserService.java +++ b/auth_microservice/src/main/java/microservice/services/UserService.java @@ -27,7 +27,7 @@ public class UserService { @Value("${jwt.issuer}") private String ISSUER; - @Value("${jwt.secret}") + @Value("${jwt.usr.secret}") private String SECRET; @Value("${jwt.duration.in.days}") @@ -38,14 +38,14 @@ public class UserService { @Async("ThreadPoolExecutor") - public CompletableFuture insertNewUser(User user) { + public CompletableFuture register(User user) { user.set_id(ObjectId.get()); user.setPassword(PasswordHandler.encryptPassword(user.getPassword())); return CompletableFuture.completedFuture(userRepo.save(user)); } @Async("ThreadPoolExecutor") - public CompletableFuture authorizeUser(User user) { + public CompletableFuture authenticate(User user) { User storedUser = userRepo.findByUsername(user.getUsername()); String msg = "invalid username or password"; @@ -81,7 +81,7 @@ public CompletableFuture authorizeUser(User user) { } @Async("ThreadPoolExecutor") - public CompletableFuture authenticateUser(String token) { + public CompletableFuture authorize(String token) { String msg = "an error has occurred"; boolean status = false; try { diff --git a/auth_microservice/src/main/resources/application.properties b/auth_microservice/src/main/resources/application.properties index e8bc796a..9628d8ac 100644 --- a/auth_microservice/src/main/resources/application.properties +++ b/auth_microservice/src/main/resources/application.properties @@ -1,5 +1,6 @@ -jwt.issuer=dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend -jwt.secret=http://api.santandertest.com.br +jwt.usr.secret=dev@ibm#santanderTEST$11043512/Uz3r}Auth +jwt.sys.secret=dev@ibm#santanderTEST$11043512/SYZZ{Auth +jwt.issuer=http://api.santandertest.com.br thread.pool.core.size=7 thread.pool.max.size=42 thread.queue.capacity=11 diff --git a/spend_microservice/pom.xml b/spend_microservice/pom.xml index 0cda05b9..97915896 100644 --- a/spend_microservice/pom.xml +++ b/spend_microservice/pom.xml @@ -24,12 +24,6 @@ spring-boot-starter-web - - com.auth0 - java-jwt - 3.4.1 - - org.springframework.boot spring-boot-starter-test diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index f5f6e09d..21e0a158 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -1,30 +1,30 @@ package microservice.interceptors; -import com.auth0.jwt.JWTVerifier; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import com.auth0.jwt.algorithms.Algorithm; -import com.auth0.jwt.JWT; -import com.auth0.jwt.interfaces.DecodedJWT; -import com.auth0.jwt.exceptions.JWTVerificationException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import microservice.models.Message; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; @Component public class JWTInterceptor extends HandlerInterceptorAdapter { - @Value("${jwt.issuer}") - private String ISSUER; - - @Value("${jwt.secret}") - private String SECRET; + @Value("${usr.auth.endpoint}") + private String USR_AUTH_URL; + @Value("${sys.auth.endpoint}") + private String SYS_AUTH_URL; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { @@ -32,37 +32,40 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, PATCH, DELETE, OPTIONS"); response.setHeader("Access-Control-Max-Age", "6000"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); String requestMethod = request.getMethod(); - + String requestURI = request.getRequestURI(); + if (!requestMethod.equals("OPTIONS")) { String token = request.getHeader("Authorization"); + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", token); + HttpEntity entity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + String url = requestURI.contains("/spend") ? SYS_AUTH_URL : USR_AUTH_URL; + try { - Algorithm algorithm = Algorithm.HMAC256(SECRET); - JWTVerifier verifier = JWT.require(algorithm) - .withIssuer(ISSUER) - .build(); + ResponseEntity authResponse = restTemplate.exchange(url, HttpMethod.GET, entity, Message.class); + Message msg = authResponse.getBody(); - DecodedJWT jwt = verifier.verify(token); + if (!msg.getStatus().equals("success")) { + formatErrorResponse(response, msg); + return false; + } } - catch (JWTVerificationException e) { - formatErrorResponse(response, "invalid token within the Authorization header"); + catch (HttpClientErrorException e) { + Message errorMsg = new Message("invalid access token. " + e.getMessage(), "failed"); + formatErrorResponse(response, errorMsg); return false; } - catch (NullPointerException e) { - formatErrorResponse(response, "no Authorization header"); - return false; - } - } return super.preHandle(request, response, handler); } - private void formatErrorResponse(HttpServletResponse response, String messageContent) throws IOException { - Message errorMsg = new Message(messageContent, false); - response.setContentType("application/json"); - response.setCharacterEncoding("UTF-8"); + private void formatErrorResponse(HttpServletResponse response, Message errorMsg) throws IOException { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write(errorMsg.toJSONString()); response.getWriter().flush(); diff --git a/spend_microservice/src/main/java/microservice/models/Message.java b/spend_microservice/src/main/java/microservice/models/Message.java index b0e4ae3b..e8d4c0c9 100644 --- a/spend_microservice/src/main/java/microservice/models/Message.java +++ b/spend_microservice/src/main/java/microservice/models/Message.java @@ -8,26 +8,30 @@ public class Message { private String content; - private boolean status; + private String status; private ObjectMapper mapper = new ObjectMapper(); - public Message() { } + public Message() { + this.status = ""; + this.content = ""; + } - public Message(String content, boolean status) { + public Message(String content, String status) { this.content = content; - this.status = status; + this.status = status; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } - public String getStatus() { return status ? "success" : "failed"; } + public String getStatus() { return status; } - public void setStatus(boolean status) { this.status = status; } + public void setStatus(String status) { this.status = status; } public String toJSONString() throws JsonProcessingException { return mapper.writeValueAsString(this); } + } diff --git a/spend_microservice/src/main/java/microservice/models/Spend.java b/spend_microservice/src/main/java/microservice/models/Spend.java index f57b145d..e2f0ff61 100644 --- a/spend_microservice/src/main/java/microservice/models/Spend.java +++ b/spend_microservice/src/main/java/microservice/models/Spend.java @@ -18,7 +18,7 @@ public class Spend { private BigDecimal value; @NotNull - private Integer userCode; + private String userCode; private String category; @@ -33,7 +33,7 @@ public Spend() { } public Spend(ObjectId _id, String description, BigDecimal value, - int userCode, + String userCode, String category, Date date) { this._id = _id; @@ -50,7 +50,7 @@ public Spend(ObjectId _id, public BigDecimal getValue() { return value; } - public Integer getUserCode() { return userCode; } + public String getUserCode() { return userCode; } public String getCategory() { return category; } @@ -62,7 +62,7 @@ public Spend(ObjectId _id, public void setValue(BigDecimal value) { this.value = value.setScale(2, BigDecimal.ROUND_HALF_UP); } - public void setUserCode(int userCode) { this.userCode = userCode; } + public void setUserCode(String userCode) { this.userCode = userCode; } public void setCategory(String category) { this.category = category; } diff --git a/spend_microservice/src/main/resources/application.properties b/spend_microservice/src/main/resources/application.properties index 247493b1..4ef70e63 100644 --- a/spend_microservice/src/main/resources/application.properties +++ b/spend_microservice/src/main/resources/application.properties @@ -1,6 +1,6 @@ -jwt.issuer=dev@ibm#santanderTEST11043512WhereIsMyCategorizedSpend -jwt.secret=http://api.santandertest.com.br thread.pool.core.size=7 thread.pool.max.size=42 thread.queue.capacity=11 -server.port=8081 \ No newline at end of file +server.port=8081 +sys.auth.endpoint=http://localhost:8080/system/authorization +usr.auth.endpoint=http://localhost:8080/user/authorization \ No newline at end of file From 3ee354fcc8e1ec90f767aea4d54e421b9d6b76eb Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Sun, 16 Dec 2018 05:31:20 -0200 Subject: [PATCH 08/17] adding date filter to the GET List of Spends endpoint --- .../controllers/SystemController.java | 4 +- .../controllers/UserController.java | 4 +- .../microservice/models/Authorization.java | 8 +- .../microservice/services/SystemService.java | 2 +- .../microservice/services/UserService.java | 2 +- .../controllers/SpendController.java | 76 +++++++------------ .../interceptors/JWTInterceptor.java | 2 +- .../repositories/SpendRepository.java | 6 +- .../microservice/services/SpendService.java | 49 +++++++++++- 9 files changed, 93 insertions(+), 60 deletions(-) diff --git a/auth_microservice/src/main/java/microservice/controllers/SystemController.java b/auth_microservice/src/main/java/microservice/controllers/SystemController.java index fed1c0b2..dce97e01 100644 --- a/auth_microservice/src/main/java/microservice/controllers/SystemController.java +++ b/auth_microservice/src/main/java/microservice/controllers/SystemController.java @@ -46,7 +46,7 @@ public ResponseEntity inserNewSystem(UriComponentsBuilder builder, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authenticateSystem(@Valid @RequestBody System system) - throws URISyntaxException, InterruptedException, ExecutionException { + throws InterruptedException, ExecutionException { CompletableFuture systemFuture = systemService.authenticate(system); Object result = systemFuture.get(); @@ -60,7 +60,7 @@ public ResponseEntity authenticateSystem(@Valid @RequestBody System system) method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authorizeSystem(@RequestHeader("Authorization") String accessToken) - throws URISyntaxException, InterruptedException, ExecutionException { + throws InterruptedException, ExecutionException { CompletableFuture systemFuture = systemService.authorize(accessToken); Message msg = systemFuture.get(); diff --git a/auth_microservice/src/main/java/microservice/controllers/UserController.java b/auth_microservice/src/main/java/microservice/controllers/UserController.java index 5d6fe896..27547cbd 100644 --- a/auth_microservice/src/main/java/microservice/controllers/UserController.java +++ b/auth_microservice/src/main/java/microservice/controllers/UserController.java @@ -46,7 +46,7 @@ public ResponseEntity register(UriComponentsBuilder builder, method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authenticateUser(@Valid @RequestBody User user) - throws URISyntaxException, InterruptedException, ExecutionException { + throws InterruptedException, ExecutionException { CompletableFuture userFuture = userService.authenticate(user); Object result = userFuture.get(); @@ -60,7 +60,7 @@ public ResponseEntity authenticateUser(@Valid @RequestBody User user) method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authorizeUser(@RequestHeader("Authorization") String accessToken) - throws URISyntaxException, InterruptedException, ExecutionException { + throws InterruptedException, ExecutionException { CompletableFuture userFuture = userService.authorize(accessToken); Message msg = userFuture.get(); diff --git a/auth_microservice/src/main/java/microservice/models/Authorization.java b/auth_microservice/src/main/java/microservice/models/Authorization.java index 5b98efc6..61c53c90 100644 --- a/auth_microservice/src/main/java/microservice/models/Authorization.java +++ b/auth_microservice/src/main/java/microservice/models/Authorization.java @@ -9,6 +9,7 @@ public class Authorization { private String accessToken; private boolean status; + private String clientId; @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", @@ -18,10 +19,11 @@ public class Authorization { public Authorization() { } - public Authorization(String accessToken, Date expiryDate, boolean status) { + public Authorization(String accessToken, Date expiryDate, String clientId, boolean status) { this.accessToken = accessToken; this.status = status; this.expiryDate = expiryDate; + this.clientId = clientId; } public String getAccessToken() { return accessToken; } @@ -36,4 +38,8 @@ public Authorization(String accessToken, Date expiryDate, boolean status) { public void setStatus(boolean status) { this.status = status; } + public String getClientId() { return clientId; } + + public void setClientId(String clientId) { this.clientId = clientId; } + } diff --git a/auth_microservice/src/main/java/microservice/services/SystemService.java b/auth_microservice/src/main/java/microservice/services/SystemService.java index 98f0560c..dbb76946 100644 --- a/auth_microservice/src/main/java/microservice/services/SystemService.java +++ b/auth_microservice/src/main/java/microservice/services/SystemService.java @@ -67,7 +67,7 @@ public CompletableFuture authenticate(System system) { .withClaim("systemId", storedSystem.get_id()) .sign(algorithm); - Authorization auth = new Authorization(token, expiresAt.getTime(), true); + Authorization auth = new Authorization(token, expiresAt.getTime(), storedSystem.get_id(), true); return CompletableFuture.completedFuture(auth); } catch (JWTCreationException e) { diff --git a/auth_microservice/src/main/java/microservice/services/UserService.java b/auth_microservice/src/main/java/microservice/services/UserService.java index ed4c9852..92a252a4 100644 --- a/auth_microservice/src/main/java/microservice/services/UserService.java +++ b/auth_microservice/src/main/java/microservice/services/UserService.java @@ -67,7 +67,7 @@ public CompletableFuture authenticate(User user) { .withClaim("userId", storedUser.get_id()) .sign(algorithm); - Authorization auth = new Authorization(token, expiresAt.getTime(), true); + Authorization auth = new Authorization(token, expiresAt.getTime(), storedUser.get_id() ,true); return CompletableFuture.completedFuture(auth); } catch (JWTCreationException e) { diff --git a/spend_microservice/src/main/java/microservice/controllers/SpendController.java b/spend_microservice/src/main/java/microservice/controllers/SpendController.java index 1b9bc7a6..52ceb4fc 100644 --- a/spend_microservice/src/main/java/microservice/controllers/SpendController.java +++ b/spend_microservice/src/main/java/microservice/controllers/SpendController.java @@ -7,35 +7,27 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import javax.validation.Valid; +import javax.xml.bind.ValidationException; +import microservice.models.Message; import microservice.models.Spend; import org.springframework.http.ResponseEntity; import java.net.URI; import java.net.URISyntaxException; +import java.text.ParseException; import org.springframework.web.util.UriComponentsBuilder; import microservice.services.SpendService; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; - - -// import java.text.SimpleDateFormat; -// import java.util.Date; -// import java.util.List; -// import java.util.Calendar; -// import java.util.TimeZone; -// import org.bson.types.ObjectId; -// import java.text.ParseException; -// import microservice.models.Category; -// import microservice.repositories.SpendRepository; -// import microservice.repositories.CategoryRepository; -// import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; @RestController public class SpendController { - // private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - + @Autowired private SpendService spendService; @@ -43,44 +35,32 @@ public class SpendController { @RequestMapping(value = "/spend", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity insertNewSpend(UriComponentsBuilder builder, - @Valid @RequestBody Spend spend) - throws URISyntaxException, InterruptedException, ExecutionException { + public ResponseEntity register(UriComponentsBuilder builder, + @Valid @RequestBody Spend spend) throws URISyntaxException, InterruptedException, ExecutionException { - CompletableFuture spendFuture = spendService.insertNewSpend(spend); - Spend storedSpend = spendFuture.get(); - return ResponseEntity - .created(new URI(builder.toUriString() + spend.get_id())) - .body(storedSpend); + CompletableFuture spendFuture = spendService.insert(spend); + Spend storedSpend = spendFuture.get(); + return ResponseEntity + .created(new URI(builder.toUriString() + spend.get_id())) + .body(storedSpend); } - // @RequestMapping(value = "/spends", method = RequestMethod.GET) - // public Callable>> getSpendListByDate( - // @RequestParam(value="startDate", defaultValue="") String startDateString, - // @RequestParam(value="endDate", defaultValue="") String endDateString) { + @RequestMapping(value = "/user/{userId}/spend", + method = RequestMethod.GET, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity getUserSpends(@PathVariable("userId") String userId, + @RequestParam(value="startDate", defaultValue="") String startDateStr, + @RequestParam(value="endDate", defaultValue="") String endDateStr) + throws ValidationException, InterruptedException, ExecutionException, ParseException { - // return () -> { - // Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - // Date now = calendar.getTime(); - // calendar.add(Calendar.DATE, -1); - - // Date startDate = null; - // Date endDate = null; - // try { - // startDate = startDateString.trim().isEmpty() || startDateString.equals(null) ? - // calendar.getTime() : dateFormat.parse(startDateString); - // endDate = startDateString.trim().isEmpty() || startDateString.equals(null) ? - // now : dateFormat.parse(endDateString); - // } - // catch (ParseException e) { - // // Implement the exception handling strategy here... - // e.printStackTrace(); - // } - - // return ResponseEntity.ok(spendRepo.findByDate(startDate, endDate)); - // }; - // } + CompletableFuture spendFuture = spendService.filterBetweenDates(startDateStr, endDateStr, userId); + Object result = spendFuture.get(); + if (result.getClass() == Message.class) + return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); + else + return ResponseEntity.ok(result); + } } diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index 21e0a158..9cd8c25d 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -44,7 +44,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons headers.set("Authorization", token); HttpEntity entity = new HttpEntity<>(headers); RestTemplate restTemplate = new RestTemplate(); - String url = requestURI.contains("/spend") ? SYS_AUTH_URL : USR_AUTH_URL; + String url = requestURI.contains("/spend") && requestMethod.equals("POST") ? SYS_AUTH_URL : USR_AUTH_URL; try { ResponseEntity authResponse = restTemplate.exchange(url, HttpMethod.GET, entity, Message.class); diff --git a/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java b/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java index fc423457..a1d0733b 100644 --- a/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java +++ b/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java @@ -8,8 +8,10 @@ public interface SpendRepository extends MongoRepository { - @Query("{ 'date': { '$gte': ?0, '$lte': ?1 } }") - public List findByDate(Date startDate, Date endDate); + @Query("{ 'date': { '$gte': ?0, '$lte': ?1 }, 'userCode': ?2 }") + public List findByStartAndEndDate(Date startDate, Date endDate, String userCode); + + public List findByUserCode(String userCode); public List findByDescription(String description); diff --git a/spend_microservice/src/main/java/microservice/services/SpendService.java b/spend_microservice/src/main/java/microservice/services/SpendService.java index e9ad8047..d1e5715a 100644 --- a/spend_microservice/src/main/java/microservice/services/SpendService.java +++ b/spend_microservice/src/main/java/microservice/services/SpendService.java @@ -9,12 +9,22 @@ import microservice.models.Spend; import microservice.models.Category; import org.bson.types.ObjectId; +import microservice.models.Message; import java.util.concurrent.CompletableFuture; +import javax.xml.bind.ValidationException; +import java.util.List; +import java.util.Date; +import java.util.Calendar; +import java.text.SimpleDateFormat; +import java.util.TimeZone; +import java.text.ParseException; @Service public class SpendService { + private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + @Autowired private SpendRepository spendRepo; @@ -22,7 +32,7 @@ public class SpendService { private CategoryRepository categoryRepo; @Async("ThreadPoolExecutor") - public CompletableFuture insertNewSpend(Spend spend) { + public CompletableFuture insert(Spend spend) { spend.set_id(ObjectId.get()); if (spend.getCategory() != null) { Category c = new Category(spend.getCategory()); @@ -31,4 +41,39 @@ public CompletableFuture insertNewSpend(Spend spend) { return CompletableFuture.completedFuture(spendRepo.save(spend)); } -} + + @Async("ThreadPoolExecutor") + public CompletableFuture> getByUserId(String userId) { + return CompletableFuture.completedFuture(spendRepo.findByUserCode(userId)); + } + + @Async("ThreadPoolExecutor") + public CompletableFuture filterBetweenDates(String startDateStr, String endDateStr, String userId) + throws ParseException, ValidationException { + + startDateStr = startDateStr.replace("Z", "+0000").replace(" ", "+"); + endDateStr = endDateStr.replace("Z", "+0000").replace(" ", "+"); + + // getting the current Date as a UTC aware object + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Date now = calendar.getTime(); + + Date startDate = startDateStr.trim().isEmpty() ? addDays(now, -7) : dateFormat.parse(startDateStr); + Date endDate = startDateStr.trim().isEmpty() ? now : dateFormat.parse(endDateStr); + + if (startDate.after(endDate)) + return CompletableFuture.completedFuture(new Message("startDate cannot be after endDate", "failed")); + else if (addDays(startDate, 21).before(endDate)) + return CompletableFuture.completedFuture(new Message("the time range between startDate and endDate have to be smaller than 21 days", "failed")); + + return CompletableFuture.completedFuture(spendRepo.findByStartAndEndDate(startDate, endDate, userId)); + } + + + public static Date addDays(Date date, int days){ + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal.setTime(date); + cal.add(Calendar.DATE, days); //minus number would decrement the days + return cal.getTime(); + } +} \ No newline at end of file From dda56d79a60df0bf274c74082bc45cecb8707ca6 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Sun, 16 Dec 2018 06:06:54 -0200 Subject: [PATCH 09/17] refine services to require less parameters in the list spends endpoint --- .../java/microservice/controllers/SystemController.java | 2 +- .../java/microservice/controllers/UserController.java | 2 +- .../src/main/java/microservice/models/Message.java | 8 +++++++- .../main/java/microservice/services/SystemService.java | 7 ++++--- .../src/main/java/microservice/services/UserService.java | 9 +++++---- .../java/microservice/controllers/SpendController.java | 8 ++++---- .../java/microservice/interceptors/JWTInterceptor.java | 4 +++- .../src/main/java/microservice/models/Message.java | 8 +++++++- .../main/java/microservice/services/SpendService.java | 4 ++-- 9 files changed, 34 insertions(+), 18 deletions(-) diff --git a/auth_microservice/src/main/java/microservice/controllers/SystemController.java b/auth_microservice/src/main/java/microservice/controllers/SystemController.java index dce97e01..8ea9ba16 100644 --- a/auth_microservice/src/main/java/microservice/controllers/SystemController.java +++ b/auth_microservice/src/main/java/microservice/controllers/SystemController.java @@ -39,7 +39,7 @@ public ResponseEntity inserNewSystem(UriComponentsBuilder builder, System storedSystem = systemFuture.get(); return ResponseEntity .created(new URI(builder.toUriString() + "/" + storedSystem.get_id())) - .body(new Message("system " + system.getUsername() + " successfully registered", true)); + .body(new Message("system " + system.getUsername() + " successfully registered", storedSystem.get_id(), true)); } @RequestMapping(value = "/system/authentication", diff --git a/auth_microservice/src/main/java/microservice/controllers/UserController.java b/auth_microservice/src/main/java/microservice/controllers/UserController.java index 27547cbd..338aa4a8 100644 --- a/auth_microservice/src/main/java/microservice/controllers/UserController.java +++ b/auth_microservice/src/main/java/microservice/controllers/UserController.java @@ -39,7 +39,7 @@ public ResponseEntity register(UriComponentsBuilder builder, User storedUser = userFuture.get(); return ResponseEntity .created(new URI(builder.toUriString() + "/" + storedUser.get_id())) - .body(new Message("user " + user.getUsername() + " successfully registered", true)); + .body(new Message("user " + user.getUsername() + " successfully registered", storedUser.get_id(), true)); } @RequestMapping(value = "/user/authentication", diff --git a/auth_microservice/src/main/java/microservice/models/Message.java b/auth_microservice/src/main/java/microservice/models/Message.java index 82b9ae82..364b2790 100644 --- a/auth_microservice/src/main/java/microservice/models/Message.java +++ b/auth_microservice/src/main/java/microservice/models/Message.java @@ -5,11 +5,13 @@ public class Message { private String content; private boolean status; + private String clientId; public Message() { } - public Message(String content, boolean status) { + public Message(String content, String clientId, boolean status) { this.content = content; + this.clientId = clientId; this.status = status; } @@ -21,4 +23,8 @@ public Message(String content, boolean status) { public void setStatus(boolean status) { this.status = status; } + public String getClientId() { return clientId; } + + public void setClientId(String clientId) { this.clientId = clientId; } + } diff --git a/auth_microservice/src/main/java/microservice/services/SystemService.java b/auth_microservice/src/main/java/microservice/services/SystemService.java index dbb76946..37741222 100644 --- a/auth_microservice/src/main/java/microservice/services/SystemService.java +++ b/auth_microservice/src/main/java/microservice/services/SystemService.java @@ -77,13 +77,14 @@ public CompletableFuture authenticate(System system) { } } - return CompletableFuture.completedFuture(new Message(msg, false)); + return CompletableFuture.completedFuture(new Message(msg, storedSystem.get_id(), false)); } @Async("ThreadPoolExecutor") public CompletableFuture authorize(String token) { String msg = "and error has occurred"; boolean status = false; + String systemId = null; try { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) @@ -91,7 +92,7 @@ public CompletableFuture authorize(String token) { .build(); DecodedJWT jwt = verifier.verify(token); - String systemId = jwt.getClaim("systemId").asString(); + systemId = jwt.getClaim("systemId").asString(); msg = "valid token"; status = true; } @@ -102,7 +103,7 @@ public CompletableFuture authorize(String token) { msg = "no token provided"; } - return CompletableFuture.completedFuture(new Message(msg, status)); + return CompletableFuture.completedFuture(new Message(msg, systemId, status)); } diff --git a/auth_microservice/src/main/java/microservice/services/UserService.java b/auth_microservice/src/main/java/microservice/services/UserService.java index 92a252a4..b72a7a8a 100644 --- a/auth_microservice/src/main/java/microservice/services/UserService.java +++ b/auth_microservice/src/main/java/microservice/services/UserService.java @@ -67,7 +67,7 @@ public CompletableFuture authenticate(User user) { .withClaim("userId", storedUser.get_id()) .sign(algorithm); - Authorization auth = new Authorization(token, expiresAt.getTime(), storedUser.get_id() ,true); + Authorization auth = new Authorization(token, expiresAt.getTime(), storedUser.get_id(), true); return CompletableFuture.completedFuture(auth); } catch (JWTCreationException e) { @@ -77,13 +77,14 @@ public CompletableFuture authenticate(User user) { } } - return CompletableFuture.completedFuture(new Message(msg, false)); + return CompletableFuture.completedFuture(new Message(msg, storedUser.get_id(), false)); } @Async("ThreadPoolExecutor") public CompletableFuture authorize(String token) { String msg = "an error has occurred"; boolean status = false; + String userId = null; try { Algorithm algorithm = Algorithm.HMAC256(SECRET); JWTVerifier verifier = JWT.require(algorithm) @@ -91,7 +92,7 @@ public CompletableFuture authorize(String token) { .build(); DecodedJWT jwt = verifier.verify(token); - String userId = jwt.getClaim("userId").asString(); + userId = jwt.getClaim("userId").asString(); msg = "valid token"; status = true; } @@ -102,7 +103,7 @@ public CompletableFuture authorize(String token) { msg = "no token provided"; } - return CompletableFuture.completedFuture(new Message(msg, status)); + return CompletableFuture.completedFuture(new Message(msg, userId, status)); } } diff --git a/spend_microservice/src/main/java/microservice/controllers/SpendController.java b/spend_microservice/src/main/java/microservice/controllers/SpendController.java index 52ceb4fc..91021747 100644 --- a/spend_microservice/src/main/java/microservice/controllers/SpendController.java +++ b/spend_microservice/src/main/java/microservice/controllers/SpendController.java @@ -20,8 +20,8 @@ import java.util.concurrent.ExecutionException; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; +import javax.servlet.http.HttpServletRequest; @RestController @@ -47,15 +47,15 @@ public ResponseEntity register(UriComponentsBuilder builder, } - @RequestMapping(value = "/user/{userId}/spend", + @RequestMapping(value = "/user/spends", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity getUserSpends(@PathVariable("userId") String userId, + public ResponseEntity getUserSpends(HttpServletRequest request, @RequestParam(value="startDate", defaultValue="") String startDateStr, @RequestParam(value="endDate", defaultValue="") String endDateStr) throws ValidationException, InterruptedException, ExecutionException, ParseException { - CompletableFuture spendFuture = spendService.filterBetweenDates(startDateStr, endDateStr, userId); + CompletableFuture spendFuture = spendService.filterBetweenDates(startDateStr, endDateStr, request.getAttribute("userId").toString()); Object result = spendFuture.get(); if (result.getClass() == Message.class) return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST); diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index 9cd8c25d..8f4e0b70 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -54,9 +54,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons formatErrorResponse(response, msg); return false; } + + request.setAttribute("userId", msg.getClientId()); } catch (HttpClientErrorException e) { - Message errorMsg = new Message("invalid access token. " + e.getMessage(), "failed"); + Message errorMsg = new Message("invalid access token. " + e.getMessage(), null, "failed"); formatErrorResponse(response, errorMsg); return false; } diff --git a/spend_microservice/src/main/java/microservice/models/Message.java b/spend_microservice/src/main/java/microservice/models/Message.java index e8d4c0c9..08a1142a 100644 --- a/spend_microservice/src/main/java/microservice/models/Message.java +++ b/spend_microservice/src/main/java/microservice/models/Message.java @@ -9,6 +9,7 @@ public class Message { private String content; private String status; + private String clientId; private ObjectMapper mapper = new ObjectMapper(); @@ -17,8 +18,9 @@ public Message() { this.content = ""; } - public Message(String content, String status) { + public Message(String content, String clientId, String status) { this.content = content; + this.clientId = clientId; this.status = status; } @@ -30,6 +32,10 @@ public Message(String content, String status) { public void setStatus(String status) { this.status = status; } + public String getClientId() { return clientId; } + + public void setClientId(String clientId) { this.clientId = clientId; } + public String toJSONString() throws JsonProcessingException { return mapper.writeValueAsString(this); } diff --git a/spend_microservice/src/main/java/microservice/services/SpendService.java b/spend_microservice/src/main/java/microservice/services/SpendService.java index d1e5715a..fd616dec 100644 --- a/spend_microservice/src/main/java/microservice/services/SpendService.java +++ b/spend_microservice/src/main/java/microservice/services/SpendService.java @@ -62,9 +62,9 @@ public CompletableFuture filterBetweenDates(String startDateStr, String endDa Date endDate = startDateStr.trim().isEmpty() ? now : dateFormat.parse(endDateStr); if (startDate.after(endDate)) - return CompletableFuture.completedFuture(new Message("startDate cannot be after endDate", "failed")); + return CompletableFuture.completedFuture(new Message("startDate cannot be after endDate", userId, "failed")); else if (addDays(startDate, 21).before(endDate)) - return CompletableFuture.completedFuture(new Message("the time range between startDate and endDate have to be smaller than 21 days", "failed")); + return CompletableFuture.completedFuture(new Message("the time range between startDate and endDate have to be smaller than 21 days", userId, "failed")); return CompletableFuture.completedFuture(spendRepo.findByStartAndEndDate(startDate, endDate, userId)); } From 5e5549fa5db1259c10d1eef44a4aace1f74924b4 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Mon, 17 Dec 2018 01:31:34 -0200 Subject: [PATCH 10/17] adding automated category assignment & category assignment by user --- .../controllers/CategoryController.java | 5 +-- .../controllers/SpendController.java | 29 +++++++++++++-- .../java/microservice/models/Category.java | 21 ++++++++--- .../repositories/CategoryRepository.java | 4 +-- .../repositories/SpendRepository.java | 9 +++-- .../services/CategoryService.java | 3 +- .../microservice/services/SpendService.java | 36 ++++++++++++++++--- 7 files changed, 89 insertions(+), 18 deletions(-) diff --git a/spend_microservice/src/main/java/microservice/controllers/CategoryController.java b/spend_microservice/src/main/java/microservice/controllers/CategoryController.java index 039e9464..b38f853b 100644 --- a/spend_microservice/src/main/java/microservice/controllers/CategoryController.java +++ b/spend_microservice/src/main/java/microservice/controllers/CategoryController.java @@ -29,12 +29,13 @@ public class CategoryController { method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity> getSuggestedCategories( - @Size(min=2, message="partial_name should contain at least 2 characters") + @Size(min=3, message="partial_name should contain at least 3 characters") @RequestParam(value="partial_name") String partialCategoryName) throws InterruptedException, ExecutionException { CompletableFuture> categoryFuture = categoryService.listSimilarCategories(partialCategoryName); - return ResponseEntity.ok(categoryFuture.get()); + List categoryList = categoryFuture.get(); + return ResponseEntity.ok(categoryList); } diff --git a/spend_microservice/src/main/java/microservice/controllers/SpendController.java b/spend_microservice/src/main/java/microservice/controllers/SpendController.java index 91021747..d1f4635c 100644 --- a/spend_microservice/src/main/java/microservice/controllers/SpendController.java +++ b/spend_microservice/src/main/java/microservice/controllers/SpendController.java @@ -4,7 +4,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import org.bson.types.ObjectId; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import javax.validation.Valid; import javax.xml.bind.ValidationException; @@ -22,6 +24,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpServletRequest; +import microservice.models.Category; @RestController @@ -51,8 +54,8 @@ public ResponseEntity register(UriComponentsBuilder builder, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity getUserSpends(HttpServletRequest request, - @RequestParam(value="startDate", defaultValue="") String startDateStr, - @RequestParam(value="endDate", defaultValue="") String endDateStr) + @RequestParam(value="start_date", defaultValue="") String startDateStr, + @RequestParam(value="end_date", defaultValue="") String endDateStr) throws ValidationException, InterruptedException, ExecutionException, ParseException { CompletableFuture spendFuture = spendService.filterBetweenDates(startDateStr, endDateStr, request.getAttribute("userId").toString()); @@ -63,4 +66,26 @@ public ResponseEntity getUserSpends(HttpServletRequest request, return ResponseEntity.ok(result); } + + @RequestMapping(value = "/spend/{spendId}/category", + method = RequestMethod.PATCH, + produces = MediaType.APPLICATION_JSON_UTF8_VALUE) + public ResponseEntity updateCategory(UriComponentsBuilder builder, + HttpServletRequest request, + @Valid @RequestBody Category category, + @PathVariable ObjectId spendId) throws URISyntaxException, InterruptedException, ExecutionException { + + CompletableFuture spendFuture = spendService.updateCategory(spendId, request.getAttribute("userId").toString(), category); + Object result = spendFuture.get(); + if (result.getClass() == Message.class) { + Message msg = (Message) result; + return new ResponseEntity<>(msg, msg.getStatus().equals("forbidden") ? HttpStatus.FORBIDDEN : HttpStatus.NOT_FOUND); + } + else { + Spend spend = (Spend) result; + String location = (new URI(builder.toUriString() + spend.get_id())).toString(); + return ResponseEntity.ok().header("Location", location).body(spend); + } + } + } diff --git a/spend_microservice/src/main/java/microservice/models/Category.java b/spend_microservice/src/main/java/microservice/models/Category.java index 12b2ad45..890208e2 100644 --- a/spend_microservice/src/main/java/microservice/models/Category.java +++ b/spend_microservice/src/main/java/microservice/models/Category.java @@ -1,18 +1,29 @@ package microservice.models; + import org.springframework.data.annotation.Id; +import javax.validation.constraints.NotNull; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +@Document(collection = "category") public class Category { - @Id - private String name; + + @Id + private ObjectId _id; + + @NotNull + @Indexed(unique = true) + private String category; public Category() { } - public Category(String name) { this.name = name; } + public Category(String category) { this.category = category; } - public String getName() { return name; } + public String getCategory() { return category; } - public void setName(String name) { this.name = name; } + public void setCategory(String category) { this.category = category; } } diff --git a/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java b/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java index 8fc20bc7..d21b1c2f 100644 --- a/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java +++ b/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java @@ -8,7 +8,7 @@ public interface CategoryRepository extends MongoRepository { - @Query("{ '_id': { '$regex': '^?0' } }") - public List findBySimilarName(String name); + @Query("{ 'category': { $regex: '^?0', $options: 'i' } }") + public List findBySimilarName(String category); } diff --git a/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java b/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java index a1d0733b..d66c5792 100644 --- a/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java +++ b/spend_microservice/src/main/java/microservice/repositories/SpendRepository.java @@ -1,18 +1,23 @@ package microservice.repositories; + import java.util.Date; import java.util.List; import microservice.models.Spend; +import org.springframework.data.domain.Pageable; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; +import org.bson.types.ObjectId; +import org.springframework.data.domain.Page; + public interface SpendRepository extends MongoRepository { @Query("{ 'date': { '$gte': ?0, '$lte': ?1 }, 'userCode': ?2 }") public List findByStartAndEndDate(Date startDate, Date endDate, String userCode); - public List findByUserCode(String userCode); + public Spend findBy_id(ObjectId _id); - public List findByDescription(String description); + public Page findByDescriptionAndUserCodeAndCategoryNotNull(String description, String userCode, Pageable pageable); } diff --git a/spend_microservice/src/main/java/microservice/services/CategoryService.java b/spend_microservice/src/main/java/microservice/services/CategoryService.java index ec8b8cad..f414ec48 100644 --- a/spend_microservice/src/main/java/microservice/services/CategoryService.java +++ b/spend_microservice/src/main/java/microservice/services/CategoryService.java @@ -18,7 +18,8 @@ public class CategoryService { @Async("ThreadPoolExecutor") public CompletableFuture> listSimilarCategories(String partialCategoryName) { - return CompletableFuture.completedFuture(categoryRepo.findBySimilarName(partialCategoryName)); + List categories = categoryRepo.findBySimilarName(partialCategoryName); + return CompletableFuture.completedFuture(categories); } } diff --git a/spend_microservice/src/main/java/microservice/services/SpendService.java b/spend_microservice/src/main/java/microservice/services/SpendService.java index fd616dec..345163b6 100644 --- a/spend_microservice/src/main/java/microservice/services/SpendService.java +++ b/spend_microservice/src/main/java/microservice/services/SpendService.java @@ -12,12 +12,14 @@ import microservice.models.Message; import java.util.concurrent.CompletableFuture; import javax.xml.bind.ValidationException; -import java.util.List; import java.util.Date; import java.util.Calendar; import java.text.SimpleDateFormat; import java.util.TimeZone; import java.text.ParseException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; @Service @@ -36,15 +38,41 @@ public CompletableFuture insert(Spend spend) { spend.set_id(ObjectId.get()); if (spend.getCategory() != null) { Category c = new Category(spend.getCategory()); - categoryRepo.save(c); + try { categoryRepo.save(c); } + // do nothing if the category already exists + catch (DuplicateKeyException ignore) { } + } + + Page storedSpends = spendRepo.findByDescriptionAndUserCodeAndCategoryNotNull(spend.getDescription(), + spend.getUserCode(), + PageRequest.of(0, 1)); + if (storedSpends.getNumberOfElements() > 0) + spend.setCategory(storedSpends.getContent().get(0).getCategory()); return CompletableFuture.completedFuture(spendRepo.save(spend)); } @Async("ThreadPoolExecutor") - public CompletableFuture> getByUserId(String userId) { - return CompletableFuture.completedFuture(spendRepo.findByUserCode(userId)); + public CompletableFuture updateCategory(ObjectId spendId, String userId, Category newCategory) { + Spend spend = spendRepo.findBy_id(spendId); + if (spend != null) { + if (spend.getUserCode().equals(userId)) { + try { categoryRepo.save(newCategory); } + // do nothing if the category already exists + catch (DuplicateKeyException ignore) { } + + spend.setCategory(newCategory.getCategory()); + spend = spendRepo.save(spend); + return CompletableFuture.completedFuture(spend); + } + else { + return CompletableFuture.completedFuture(new Message("this spend does not belong to this user", userId, "forbidden")); + } + } + else { + return CompletableFuture.completedFuture(new Message("no spend with the provided spendId", userId, "failed")); + } } @Async("ThreadPoolExecutor") From 7a50b109da5c61e3e737d7927a743e1b39ba594d Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Mon, 17 Dec 2018 02:24:27 -0200 Subject: [PATCH 11/17] changing resource routes and collection names to their plural --- .../controllers/SystemController.java | 6 +++--- .../controllers/UserController.java | 6 +++--- .../main/java/microservice/models/System.java | 2 +- .../main/java/microservice/models/User.java | 2 +- .../microservice/util/PasswordHandler.java | 20 +++++++++---------- ...roservice.java => SpendsMicroservice.java} | 4 ++-- .../controllers/CategoryController.java | 5 ++--- .../controllers/SpendController.java | 6 +++--- .../interceptors/JWTInterceptor.java | 2 +- .../java/microservice/models/Category.java | 2 +- .../main/java/microservice/models/Spend.java | 2 ++ .../src/main/resources/application.properties | 4 ++-- 12 files changed, 31 insertions(+), 30 deletions(-) rename spend_microservice/src/main/java/microservice/{SpendMicroservice.java => SpendsMicroservice.java} (92%) diff --git a/auth_microservice/src/main/java/microservice/controllers/SystemController.java b/auth_microservice/src/main/java/microservice/controllers/SystemController.java index 8ea9ba16..7bb4910b 100644 --- a/auth_microservice/src/main/java/microservice/controllers/SystemController.java +++ b/auth_microservice/src/main/java/microservice/controllers/SystemController.java @@ -28,7 +28,7 @@ public class SystemController { @Autowired private SystemService systemService; - @RequestMapping(value = "/system", + @RequestMapping(value = "/systems", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity inserNewSystem(UriComponentsBuilder builder, @@ -42,7 +42,7 @@ public ResponseEntity inserNewSystem(UriComponentsBuilder builder, .body(new Message("system " + system.getUsername() + " successfully registered", storedSystem.get_id(), true)); } - @RequestMapping(value = "/system/authentication", + @RequestMapping(value = "/systems/authentication", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authenticateSystem(@Valid @RequestBody System system) @@ -56,7 +56,7 @@ public ResponseEntity authenticateSystem(@Valid @RequestBody System system) return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); } - @RequestMapping(value = "/system/authorization", + @RequestMapping(value = "/systems/authorization", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authorizeSystem(@RequestHeader("Authorization") String accessToken) diff --git a/auth_microservice/src/main/java/microservice/controllers/UserController.java b/auth_microservice/src/main/java/microservice/controllers/UserController.java index 338aa4a8..6fd71095 100644 --- a/auth_microservice/src/main/java/microservice/controllers/UserController.java +++ b/auth_microservice/src/main/java/microservice/controllers/UserController.java @@ -28,7 +28,7 @@ public class UserController { @Autowired private UserService userService; - @RequestMapping(value = "/user", + @RequestMapping(value = "/users", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity register(UriComponentsBuilder builder, @@ -42,7 +42,7 @@ public ResponseEntity register(UriComponentsBuilder builder, .body(new Message("user " + user.getUsername() + " successfully registered", storedUser.get_id(), true)); } - @RequestMapping(value = "/user/authentication", + @RequestMapping(value = "/users/authentication", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authenticateUser(@Valid @RequestBody User user) @@ -56,7 +56,7 @@ public ResponseEntity authenticateUser(@Valid @RequestBody User user) return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED); } - @RequestMapping(value = "/user/authorization", + @RequestMapping(value = "/users/authorization", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity authorizeUser(@RequestHeader("Authorization") String accessToken) diff --git a/auth_microservice/src/main/java/microservice/models/System.java b/auth_microservice/src/main/java/microservice/models/System.java index 32f4cc8b..35b545bd 100644 --- a/auth_microservice/src/main/java/microservice/models/System.java +++ b/auth_microservice/src/main/java/microservice/models/System.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.mapping.Document; -@Document(collection = "system") +@Document(collection = "systems") public class System { @Id diff --git a/auth_microservice/src/main/java/microservice/models/User.java b/auth_microservice/src/main/java/microservice/models/User.java index 318dbd4f..abdd6490 100644 --- a/auth_microservice/src/main/java/microservice/models/User.java +++ b/auth_microservice/src/main/java/microservice/models/User.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.mapping.Document; -@Document(collection = "user") +@Document(collection = "users") public class User { @Id diff --git a/auth_microservice/src/main/java/microservice/util/PasswordHandler.java b/auth_microservice/src/main/java/microservice/util/PasswordHandler.java index 26b2be4a..77bd1289 100644 --- a/auth_microservice/src/main/java/microservice/util/PasswordHandler.java +++ b/auth_microservice/src/main/java/microservice/util/PasswordHandler.java @@ -11,19 +11,19 @@ public class PasswordHandler { private static int workload = 12; public static String encryptPassword(String plaintextPassword) { - String salt = BCrypt.gensalt(workload); - String hashedPassword = BCrypt.hashpw(plaintextPassword, salt); - return hashedPassword; + String salt = BCrypt.gensalt(workload); + String hashedPassword = BCrypt.hashpw(plaintextPassword, salt); + return hashedPassword; } - + public static boolean checkPassword(String plaintextPassword, String storedHash) { - boolean verifiedPassword = false; + boolean verifiedPassword = false; - if (null == storedHash || !storedHash.startsWith("$2a$")) - throw new java.lang.IllegalArgumentException("invalid hash provided for comparison"); + if (null == storedHash || !storedHash.startsWith("$2a$")) + throw new java.lang.IllegalArgumentException("invalid hash provided for comparison"); - verifiedPassword = BCrypt.checkpw(plaintextPassword, storedHash); + verifiedPassword = BCrypt.checkpw(plaintextPassword, storedHash); - return verifiedPassword; - } + return verifiedPassword; + } } \ No newline at end of file diff --git a/spend_microservice/src/main/java/microservice/SpendMicroservice.java b/spend_microservice/src/main/java/microservice/SpendsMicroservice.java similarity index 92% rename from spend_microservice/src/main/java/microservice/SpendMicroservice.java rename to spend_microservice/src/main/java/microservice/SpendsMicroservice.java index db208158..e2f1774b 100644 --- a/spend_microservice/src/main/java/microservice/SpendMicroservice.java +++ b/spend_microservice/src/main/java/microservice/SpendsMicroservice.java @@ -10,7 +10,7 @@ @EnableAsync @SpringBootApplication -public class SpendMicroservice { +public class SpendsMicroservice { @Value("${thread.pool.core.size}") private int corePoolSize; @@ -35,7 +35,7 @@ public TaskExecutor getAsyncExecutor() { public static void main(String[] args) { - SpringApplication.run(SpendMicroservice.class, args); + SpringApplication.run(SpendsMicroservice.class, args); } } diff --git a/spend_microservice/src/main/java/microservice/controllers/CategoryController.java b/spend_microservice/src/main/java/microservice/controllers/CategoryController.java index b38f853b..a3c93c7b 100644 --- a/spend_microservice/src/main/java/microservice/controllers/CategoryController.java +++ b/spend_microservice/src/main/java/microservice/controllers/CategoryController.java @@ -25,13 +25,12 @@ public class CategoryController { private CategoryService categoryService; - @RequestMapping(value = "/category/suggestions", + @RequestMapping(value = "/categories/suggestions", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity> getSuggestedCategories( @Size(min=3, message="partial_name should contain at least 3 characters") - @RequestParam(value="partial_name") - String partialCategoryName) throws InterruptedException, ExecutionException { + @RequestParam(value="partial_name") String partialCategoryName) throws InterruptedException, ExecutionException { CompletableFuture> categoryFuture = categoryService.listSimilarCategories(partialCategoryName); List categoryList = categoryFuture.get(); diff --git a/spend_microservice/src/main/java/microservice/controllers/SpendController.java b/spend_microservice/src/main/java/microservice/controllers/SpendController.java index d1f4635c..679ffa5e 100644 --- a/spend_microservice/src/main/java/microservice/controllers/SpendController.java +++ b/spend_microservice/src/main/java/microservice/controllers/SpendController.java @@ -35,7 +35,7 @@ public class SpendController { private SpendService spendService; - @RequestMapping(value = "/spend", + @RequestMapping(value = "/spends", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity register(UriComponentsBuilder builder, @@ -50,7 +50,7 @@ public ResponseEntity register(UriComponentsBuilder builder, } - @RequestMapping(value = "/user/spends", + @RequestMapping(value = "/users/spends", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity getUserSpends(HttpServletRequest request, @@ -67,7 +67,7 @@ public ResponseEntity getUserSpends(HttpServletRequest request, } - @RequestMapping(value = "/spend/{spendId}/category", + @RequestMapping(value = "/spends/{spendId}/categories", method = RequestMethod.PATCH, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity updateCategory(UriComponentsBuilder builder, diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index 8f4e0b70..9d958401 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -44,7 +44,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons headers.set("Authorization", token); HttpEntity entity = new HttpEntity<>(headers); RestTemplate restTemplate = new RestTemplate(); - String url = requestURI.contains("/spend") && requestMethod.equals("POST") ? SYS_AUTH_URL : USR_AUTH_URL; + String url = requestURI.contains("/spends") && requestMethod.equals("POST") ? SYS_AUTH_URL : USR_AUTH_URL; try { ResponseEntity authResponse = restTemplate.exchange(url, HttpMethod.GET, entity, Message.class); diff --git a/spend_microservice/src/main/java/microservice/models/Category.java b/spend_microservice/src/main/java/microservice/models/Category.java index 890208e2..929530f0 100644 --- a/spend_microservice/src/main/java/microservice/models/Category.java +++ b/spend_microservice/src/main/java/microservice/models/Category.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.mapping.Document; -@Document(collection = "category") +@Document(collection = "categories") public class Category { @Id diff --git a/spend_microservice/src/main/java/microservice/models/Spend.java b/spend_microservice/src/main/java/microservice/models/Spend.java index e2f0ff61..e08626ac 100644 --- a/spend_microservice/src/main/java/microservice/models/Spend.java +++ b/spend_microservice/src/main/java/microservice/models/Spend.java @@ -6,8 +6,10 @@ import org.bson.types.ObjectId; import com.fasterxml.jackson.annotation.JsonFormat; import java.math.BigDecimal; +import org.springframework.data.mongodb.core.mapping.Document; +@Document(collection = "spends") public class Spend { @Id private ObjectId _id; diff --git a/spend_microservice/src/main/resources/application.properties b/spend_microservice/src/main/resources/application.properties index 4ef70e63..fa58a40a 100644 --- a/spend_microservice/src/main/resources/application.properties +++ b/spend_microservice/src/main/resources/application.properties @@ -2,5 +2,5 @@ thread.pool.core.size=7 thread.pool.max.size=42 thread.queue.capacity=11 server.port=8081 -sys.auth.endpoint=http://localhost:8080/system/authorization -usr.auth.endpoint=http://localhost:8080/user/authorization \ No newline at end of file +sys.auth.endpoint=http://localhost:8080/systems/authorization +usr.auth.endpoint=http://localhost:8080/users/authorization \ No newline at end of file From ed3278aa8f4e81cc29ca3062787570281efe4887 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Mon, 17 Dec 2018 03:32:41 -0200 Subject: [PATCH 12/17] logging incoming requests --- .../configurations/WebMvcConfig.java | 22 ++++++++++++ .../interceptors/JWTInterceptor.java | 34 +++++++++++++++++++ .../interceptors/JWTInterceptor.java | 9 ++++- 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 auth_microservice/src/main/java/microservice/configurations/WebMvcConfig.java create mode 100644 auth_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java diff --git a/auth_microservice/src/main/java/microservice/configurations/WebMvcConfig.java b/auth_microservice/src/main/java/microservice/configurations/WebMvcConfig.java new file mode 100644 index 00000000..0efd4b02 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/configurations/WebMvcConfig.java @@ -0,0 +1,22 @@ +package microservice.configurations; + + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import microservice.interceptors.JWTInterceptor; + + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private JWTInterceptor interceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry){ + registry.addInterceptor(interceptor).addPathPatterns("/**"); + } + +} diff --git a/auth_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/auth_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java new file mode 100644 index 00000000..1e1d8cb0 --- /dev/null +++ b/auth_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -0,0 +1,34 @@ +package microservice.interceptors; + + +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@Component +public class JWTInterceptor extends HandlerInterceptorAdapter { + + private static final Logger LOGGER = LoggerFactory.getLogger(JWTInterceptor.class); + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, PATCH, DELETE, OPTIONS"); + response.setHeader("Access-Control-Max-Age", "6000"); + response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With"); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + + String requestMethod = request.getMethod(); + String requestURI = request.getRequestURI(); + + LOGGER.info("[" + requestMethod + "] " + requestURI); + + return super.preHandle(request, response, handler); + } + +} diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index 9d958401..ed6586a5 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -15,7 +15,8 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Component public class JWTInterceptor extends HandlerInterceptorAdapter { @@ -26,6 +27,9 @@ public class JWTInterceptor extends HandlerInterceptorAdapter { @Value("${sys.auth.endpoint}") private String SYS_AUTH_URL; + private static final Logger LOGGER = LoggerFactory.getLogger(JWTInterceptor.class); + + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { response.setHeader("Access-Control-Allow-Origin", "*"); @@ -52,14 +56,17 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (!msg.getStatus().equals("success")) { formatErrorResponse(response, msg); + LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + msg.getContent()); return false; } + LOGGER.info("[" + requestMethod + "] " + requestURI); request.setAttribute("userId", msg.getClientId()); } catch (HttpClientErrorException e) { Message errorMsg = new Message("invalid access token. " + e.getMessage(), null, "failed"); formatErrorResponse(response, errorMsg); + LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + errorMsg.getContent()); return false; } } From bce5b172b9c118220bc3545b5a3d857f7f4921ea Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Tue, 18 Dec 2018 22:11:16 -0200 Subject: [PATCH 13/17] changing groupId and checking if auth is available at each request --- .gitignore | 4 +++- auth_microservice/pom.xml | 2 +- spend_microservice/pom.xml | 2 +- .../java/microservice/interceptors/JWTInterceptor.java | 7 +++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index bf7ccc01..4267c4b1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ .project .classpath target -bin \ No newline at end of file +bin +.idea +*.iml \ No newline at end of file diff --git a/auth_microservice/pom.xml b/auth_microservice/pom.xml index a88fc118..020e76b9 100644 --- a/auth_microservice/pom.xml +++ b/auth_microservice/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework + br.com.testesantander.api authentication_microservice 0.1.0 diff --git a/spend_microservice/pom.xml b/spend_microservice/pom.xml index 97915896..bfa9d905 100644 --- a/spend_microservice/pom.xml +++ b/spend_microservice/pom.xml @@ -3,7 +3,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - org.springframework + br.com.testesantander.api spend_microservice 0.1.0 diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index ed6586a5..21b716bd 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -4,6 +4,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import java.io.IOException; +import org.springframework.web.client.ResourceAccessException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Value; @@ -69,6 +70,12 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + errorMsg.getContent()); return false; } + catch (ResourceAccessException e) { + Message errorMsg = new Message("authorization microservice is not available", null, "failed"); + formatErrorResponse(response, errorMsg); + LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + errorMsg.getContent()); + return false; + } } return super.preHandle(request, response, handler); } From 45ced78af4ab826677128a2d614ae7a02b54eada Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Wed, 19 Dec 2018 04:41:33 -0200 Subject: [PATCH 14/17] First unit test: json response of category suggestions endpoints --- .../configurations/WebMvcConfig.java | 10 +-- .../CategoryControllerTest.java | 63 +++++++++++++++++++ .../microservice/models/Authorization.java | 44 +++++++++++++ .../test/java/microservice/models/Client.java | 25 ++++++++ .../java/microservice/util/AuthRequester.java | 20 ++++++ 5 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java create mode 100644 spend_microservice/src/test/java/microservice/models/Authorization.java create mode 100644 spend_microservice/src/test/java/microservice/models/Client.java create mode 100644 spend_microservice/src/test/java/microservice/util/AuthRequester.java diff --git a/spend_microservice/src/main/java/microservice/configurations/WebMvcConfig.java b/spend_microservice/src/main/java/microservice/configurations/WebMvcConfig.java index 0efd4b02..d76fab25 100644 --- a/spend_microservice/src/main/java/microservice/configurations/WebMvcConfig.java +++ b/spend_microservice/src/main/java/microservice/configurations/WebMvcConfig.java @@ -3,20 +3,22 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import microservice.interceptors.JWTInterceptor; +import org.springframework.context.annotation.Bean; @Configuration public class WebMvcConfig implements WebMvcConfigurer { - @Autowired - private JWTInterceptor interceptor; + @Bean + public JWTInterceptor jwtInterceptor() { + return new JWTInterceptor(); + } @Override public void addInterceptors(InterceptorRegistry registry){ - registry.addInterceptor(interceptor).addPathPatterns("/**"); + registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**"); } } diff --git a/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java b/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java new file mode 100644 index 00000000..465f5f0e --- /dev/null +++ b/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java @@ -0,0 +1,63 @@ +package microservice.controller_tests; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import microservice.controllers.CategoryController; +import microservice.models.Authorization; +import microservice.models.Category; +import microservice.util.AuthRequester; + + +@RunWith(SpringRunner.class) +@WebMvcTest(value = Category.class, secure = false) +public class CategoryControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private CategoryController controller; + + @Test + public void testJSONResponse() throws Exception { + when(controller.getSuggestedCategories(anyString())).thenReturn( + ResponseEntity.ok(Arrays.asList(new Category("newCategory1"), + new Category("newCategory2"), + new Category("newCategory3"))) + ); + + Authorization authorization = AuthRequester.authenticate( + "http://localhost:8080/users/authentication", + "zanferrari", + "zan12345"); + + String expected = "[{\"category\":\"newCategory1\"},{\"category\":\"newCategory2\"},{\"category\":\"newCategory3\"}]"; + + this.mockMvc.perform( + get("/categories/suggestions") + .param("partial_name", "cat") + .header("Authorization", authorization.getAccessToken())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().json(expected) + ); + } + + + + + +} diff --git a/spend_microservice/src/test/java/microservice/models/Authorization.java b/spend_microservice/src/test/java/microservice/models/Authorization.java new file mode 100644 index 00000000..ebf0e567 --- /dev/null +++ b/spend_microservice/src/test/java/microservice/models/Authorization.java @@ -0,0 +1,44 @@ +package microservice.models; + + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + + +public class Authorization { + + private String accessToken; + private String status; + private String clientId; + + @JsonFormat(shape=JsonFormat.Shape.STRING, + pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", + timezone="UTC") + private Date expiryDate; + + public Authorization() { } + + public Authorization(String accessToken, Date expiryDate, String clientId, String status) { + this.accessToken = accessToken; + this.status = status; + this.expiryDate = expiryDate; + this.clientId = clientId; + } + + public String getAccessToken() { return accessToken; } + + public void setAccessToken(String accessToken) { this.accessToken = accessToken; } + + public Date getExpiryDate() { return expiryDate; } + + public void setExpiryDate(Date expiryDate) { this.expiryDate = expiryDate; } + + public String getStatus() { return status; } + + public void setStatus(String status) { this.status = status; } + + public String getClientId() { return clientId; } + + public void setClientId(String clientId) { this.clientId = clientId; } + +} diff --git a/spend_microservice/src/test/java/microservice/models/Client.java b/spend_microservice/src/test/java/microservice/models/Client.java new file mode 100644 index 00000000..43ab9ade --- /dev/null +++ b/spend_microservice/src/test/java/microservice/models/Client.java @@ -0,0 +1,25 @@ +package microservice.models; + + +public class Client { + + private String username; + + private String password; + + public Client() { } + + public Client(String username, String password) { + this.username = username; + this.password = password; + } + + public String getUsername() { return username; } + + public String getPassword() { return password; } + + public void setUsername(String username) { this.username = username; } + + public void setPassword(String password) { this.password = password; } + +} diff --git a/spend_microservice/src/test/java/microservice/util/AuthRequester.java b/spend_microservice/src/test/java/microservice/util/AuthRequester.java new file mode 100644 index 00000000..e012f672 --- /dev/null +++ b/spend_microservice/src/test/java/microservice/util/AuthRequester.java @@ -0,0 +1,20 @@ +package microservice.util; + +import microservice.models.Authorization; +import microservice.models.Client; +import org.springframework.http.HttpEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.ResponseEntity; + + +public final class AuthRequester { + + public static Authorization authenticate(String authenticationURL, String username, String password) { + HttpEntity request = new HttpEntity<>(new Client(username, password)); + RestTemplate restTemplate = new RestTemplate(); + + ResponseEntity authResponse = restTemplate.postForEntity(authenticationURL, request, Authorization.class); + return authResponse.getBody(); + } + +} \ No newline at end of file From 2d85feab93a7ba8ee253726d8e65a3020e1eadb5 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Thu, 20 Dec 2018 02:29:46 -0200 Subject: [PATCH 15/17] correcting location headers and adding tests for main controller routes --- .../controllers/SystemController.java | 2 +- .../controllers/UserController.java | 2 +- .../controllers/SpendController.java | 8 +- .../interceptors/JWTInterceptor.java | 1 + .../java/microservice/models/Category.java | 14 +- .../main/java/microservice/models/Spend.java | 18 ++- .../microservice/services/SpendService.java | 4 +- .../CategoryControllerTest.java | 33 +++-- .../controller_tests/SpendControllerTest.java | 121 ++++++++++++++++++ 9 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 spend_microservice/src/test/java/microservice/controller_tests/SpendControllerTest.java diff --git a/auth_microservice/src/main/java/microservice/controllers/SystemController.java b/auth_microservice/src/main/java/microservice/controllers/SystemController.java index 7bb4910b..747560c2 100644 --- a/auth_microservice/src/main/java/microservice/controllers/SystemController.java +++ b/auth_microservice/src/main/java/microservice/controllers/SystemController.java @@ -38,7 +38,7 @@ public ResponseEntity inserNewSystem(UriComponentsBuilder builder, CompletableFuture systemFuture = systemService.register(system); System storedSystem = systemFuture.get(); return ResponseEntity - .created(new URI(builder.toUriString() + "/" + storedSystem.get_id())) + .created(new URI(builder.toUriString() + "/systems/" + storedSystem.get_id())) .body(new Message("system " + system.getUsername() + " successfully registered", storedSystem.get_id(), true)); } diff --git a/auth_microservice/src/main/java/microservice/controllers/UserController.java b/auth_microservice/src/main/java/microservice/controllers/UserController.java index 6fd71095..4bbf7f4b 100644 --- a/auth_microservice/src/main/java/microservice/controllers/UserController.java +++ b/auth_microservice/src/main/java/microservice/controllers/UserController.java @@ -38,7 +38,7 @@ public ResponseEntity register(UriComponentsBuilder builder, CompletableFuture userFuture = userService.register(user); User storedUser = userFuture.get(); return ResponseEntity - .created(new URI(builder.toUriString() + "/" + storedUser.get_id())) + .created(new URI(builder.toUriString() + "/users/" + storedUser.get_id())) .body(new Message("user " + user.getUsername() + " successfully registered", storedUser.get_id(), true)); } diff --git a/spend_microservice/src/main/java/microservice/controllers/SpendController.java b/spend_microservice/src/main/java/microservice/controllers/SpendController.java index 679ffa5e..3e5e4c74 100644 --- a/spend_microservice/src/main/java/microservice/controllers/SpendController.java +++ b/spend_microservice/src/main/java/microservice/controllers/SpendController.java @@ -44,7 +44,7 @@ public ResponseEntity register(UriComponentsBuilder builder, CompletableFuture spendFuture = spendService.insert(spend); Spend storedSpend = spendFuture.get(); return ResponseEntity - .created(new URI(builder.toUriString() + spend.get_id())) + .created(new URI(builder.toUriString() + "/spends/" + spend.get_id())) .body(storedSpend); } @@ -53,7 +53,7 @@ public ResponseEntity register(UriComponentsBuilder builder, @RequestMapping(value = "/users/spends", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity getUserSpends(HttpServletRequest request, + public ResponseEntity getUserSpends(HttpServletRequest request, @RequestParam(value="start_date", defaultValue="") String startDateStr, @RequestParam(value="end_date", defaultValue="") String endDateStr) throws ValidationException, InterruptedException, ExecutionException, ParseException { @@ -70,7 +70,7 @@ public ResponseEntity getUserSpends(HttpServletRequest request, @RequestMapping(value = "/spends/{spendId}/categories", method = RequestMethod.PATCH, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) - public ResponseEntity updateCategory(UriComponentsBuilder builder, + public ResponseEntity updateCategory(UriComponentsBuilder builder, HttpServletRequest request, @Valid @RequestBody Category category, @PathVariable ObjectId spendId) throws URISyntaxException, InterruptedException, ExecutionException { @@ -83,7 +83,7 @@ public ResponseEntity updateCategory(UriComponentsBuilder builder, } else { Spend spend = (Spend) result; - String location = (new URI(builder.toUriString() + spend.get_id())).toString(); + String location = (new URI(builder.toUriString() + "/spends/" + spend.get_id())).toString(); return ResponseEntity.ok().header("Location", location).body(spend); } } diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index 21b716bd..80c76e6d 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -19,6 +19,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + @Component public class JWTInterceptor extends HandlerInterceptorAdapter { diff --git a/spend_microservice/src/main/java/microservice/models/Category.java b/spend_microservice/src/main/java/microservice/models/Category.java index 929530f0..6eebe096 100644 --- a/spend_microservice/src/main/java/microservice/models/Category.java +++ b/spend_microservice/src/main/java/microservice/models/Category.java @@ -3,7 +3,6 @@ import org.springframework.data.annotation.Id; import javax.validation.constraints.NotNull; -import org.bson.types.ObjectId; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,7 +11,7 @@ public class Category { @Id - private ObjectId _id; + private String _id; @NotNull @Indexed(unique = true) @@ -22,8 +21,19 @@ public Category() { } public Category(String category) { this.category = category; } + public Category(String _id, String category) { + this._id = _id; + this.category = category; + } + public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } + public String get_id() { return _id; } + + public void set_id(String _id) { this._id = _id; } + + + } diff --git a/spend_microservice/src/main/java/microservice/models/Spend.java b/spend_microservice/src/main/java/microservice/models/Spend.java index e08626ac..5a16f525 100644 --- a/spend_microservice/src/main/java/microservice/models/Spend.java +++ b/spend_microservice/src/main/java/microservice/models/Spend.java @@ -3,7 +3,6 @@ import java.util.Date; import javax.validation.constraints.NotNull; import org.springframework.data.annotation.Id; -import org.bson.types.ObjectId; import com.fasterxml.jackson.annotation.JsonFormat; import java.math.BigDecimal; import org.springframework.data.mongodb.core.mapping.Document; @@ -12,7 +11,7 @@ @Document(collection = "spends") public class Spend { @Id - private ObjectId _id; + private String _id; private String description; @@ -32,7 +31,15 @@ public class Spend { public Spend() { } - public Spend(ObjectId _id, + public Spend(Spend otherSpend) { + this.description = otherSpend.getDescription(); + this.value = otherSpend.getValue(); + this.userCode = otherSpend.getUserCode(); + this.category = otherSpend.getCategory(); + this.date = otherSpend.getDate(); + } + + public Spend(String _id, String description, BigDecimal value, String userCode, @@ -46,7 +53,7 @@ public Spend(ObjectId _id, this.date = date; } - public String get_id() { return _id.toHexString(); } + public String get_id() { return _id; } public String getDescription() { return description; } @@ -58,7 +65,7 @@ public Spend(ObjectId _id, public Date getDate() { return date; } - public void set_id(ObjectId _id) { this._id = _id; } + public void set_id(String _id) { this._id = _id; } public void setDescription(String description) { this.description = description; } @@ -69,4 +76,5 @@ public Spend(ObjectId _id, public void setCategory(String category) { this.category = category; } public void setDate(Date date) { this.date = date; } + } \ No newline at end of file diff --git a/spend_microservice/src/main/java/microservice/services/SpendService.java b/spend_microservice/src/main/java/microservice/services/SpendService.java index 345163b6..8a80754b 100644 --- a/spend_microservice/src/main/java/microservice/services/SpendService.java +++ b/spend_microservice/src/main/java/microservice/services/SpendService.java @@ -24,8 +24,6 @@ @Service public class SpendService { - - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); @Autowired private SpendRepository spendRepo; @@ -35,7 +33,6 @@ public class SpendService { @Async("ThreadPoolExecutor") public CompletableFuture insert(Spend spend) { - spend.set_id(ObjectId.get()); if (spend.getCategory() != null) { Category c = new Category(spend.getCategory()); try { categoryRepo.save(c); } @@ -86,6 +83,7 @@ public CompletableFuture filterBetweenDates(String startDateStr, String endDa Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); Date now = calendar.getTime(); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); Date startDate = startDateStr.trim().isEmpty() ? addDays(now, -7) : dateFormat.parse(startDateStr); Date endDate = startDateStr.trim().isEmpty() ? now : dateFormat.parse(endDateStr); diff --git a/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java b/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java index 465f5f0e..f831cf66 100644 --- a/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java +++ b/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java @@ -7,6 +7,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Arrays; +import java.util.List; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -20,9 +22,15 @@ import microservice.models.Category; import microservice.util.AuthRequester; +// import static org.junit.Assert.*; +// import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +// import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +// import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; + @RunWith(SpringRunner.class) -@WebMvcTest(value = Category.class, secure = false) +@WebMvcTest(value = CategoryController.class, secure = false) public class CategoryControllerTest { @Autowired @@ -31,20 +39,25 @@ public class CategoryControllerTest { @MockBean private CategoryController controller; + private final ObjectMapper mapper = new ObjectMapper(); + @Test public void testJSONResponse() throws Exception { + List mockCategories = Arrays.asList(new Category("5c1afa52dd3b7e2268264e9d", "dummyCategory1"), + new Category("5c1afa82dd3b7e2268264e9f", "dummyCategory2"), + new Category("5c1aff16dd3b7e2698e06a1e", "dummyCategory3")); + when(controller.getSuggestedCategories(anyString())).thenReturn( - ResponseEntity.ok(Arrays.asList(new Category("newCategory1"), - new Category("newCategory2"), - new Category("newCategory3"))) + ResponseEntity.ok(mockCategories) ); Authorization authorization = AuthRequester.authenticate( "http://localhost:8080/users/authentication", "zanferrari", "zan12345"); - - String expected = "[{\"category\":\"newCategory1\"},{\"category\":\"newCategory2\"},{\"category\":\"newCategory3\"}]"; + + ObjectMapper mapper = new ObjectMapper(); + String expected = mapper.writeValueAsString(mockCategories); this.mockMvc.perform( get("/categories/suggestions") @@ -52,12 +65,10 @@ public void testJSONResponse() throws Exception { .header("Authorization", authorization.getAccessToken())) .andDo(print()) .andExpect(status().isOk()) - .andExpect(content().json(expected) - ); + .andExpect(content().json(expected)) + .andExpect(content().string(expected)); } - - - + } diff --git a/spend_microservice/src/test/java/microservice/controller_tests/SpendControllerTest.java b/spend_microservice/src/test/java/microservice/controller_tests/SpendControllerTest.java new file mode 100644 index 00000000..365c7d23 --- /dev/null +++ b/spend_microservice/src/test/java/microservice/controller_tests/SpendControllerTest.java @@ -0,0 +1,121 @@ +package microservice.controller_tests; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import javax.servlet.http.HttpServletRequest; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.net.URI; +import java.text.SimpleDateFormat; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import microservice.controllers.SpendController; +import microservice.models.Authorization; +import microservice.models.Spend; +import microservice.util.AuthRequester; +import org.springframework.web.util.UriComponentsBuilder; +import java.math.BigDecimal; + + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SpendController.class, secure = false) +public class SpendControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SpendController controller; + + private final ObjectMapper mapper = new ObjectMapper(); + private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + @Test + public void testInsertSpendJSONResponse() throws Exception { + String mockDateStr = "2018-12-20T02:32:13.743+0000"; + Date mockDate = dateFormat.parse(mockDateStr); + + Spend mockSpend = new Spend(); + mockSpend.set_id("5c1af62cdd3b7e2014b8b5a4"); + mockSpend.setDescription("dummyDescription"); + mockSpend.setUserCode("5c171d4ba917193e00cf68c4"); + mockSpend.setValue(new BigDecimal(123.456)); + mockSpend.setCategory("dummyCategory"); + mockSpend.setDate(mockDate); + + ResponseEntity mockCreatedResponse = ResponseEntity.created(new URI("http://localhost:8081/spend/5c17215ea917193e0c3c84ab")).body(mockSpend); + + when(controller.register(any(UriComponentsBuilder.class), any(Spend.class))).thenReturn(mockCreatedResponse); + + Authorization authorization = AuthRequester.authenticate( + "http://localhost:8080/systems/authentication", + "mySystem", + "54321naz"); + + String expected = mapper.writeValueAsString(mockSpend); + + String content = "{\"description\":\"dummyDescription\",\"value\":123.456,\"userCode\":\"5c171d4ba917193e00cf68c4\",\"category\":\"dummyCategory\",\"date\":\"2018-12-20T02:32:13.743+0000\"}"; + + this.mockMvc.perform( + post("/spends") + .header("Authorization", authorization.getAccessToken()) + .content(content) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isCreated()) + .andExpect(content().json(expected)) + .andExpect(content().string(expected)); + + } + + @Test + public void testListUserSpendsJSONResponse() throws Exception { + String mockDateStr = "2018-12-18T02:48:26.163+0000"; + String startDateStr = "2018-12-15T02:48:26.163+0000"; + String endDateStr = "2018-12-20T21:51:33.775+0000"; + + Date mockDate = dateFormat.parse(mockDateStr); + + Spend firstSpend = new Spend("5c1afa52dd3b7e2268264e9d", "dummyDescription1", new BigDecimal(123.465), "5c17214ea917193e0c3c84aa", "dummyCategory1", mockDate); + Spend secondSpend = new Spend("5c1afbb5dd3b7e2268264ead", "dummyDescription2", new BigDecimal(879.1011), "5c1aff16dd3b7e2698e06a1e", "dummyCategory2", mockDate); + Spend thirdSpend = new Spend("5c1aff16dd3b7e2698e06a1e", "dummyDescription3", new BigDecimal(1213.1415), "5c1aff42dd3b7e2698e06a22", "dummyCategory3", mockDate); + + List mockSpends = Arrays.asList(firstSpend, secondSpend, thirdSpend); + + when(controller.getUserSpends(any(HttpServletRequest.class), anyString(), anyString())) + .thenReturn(ResponseEntity.ok(mockSpends)); + + Authorization authorization = AuthRequester.authenticate( + "http://localhost:8080/users/authentication", + "zanferrari", + "zan12345"); + + String expected = mapper.writeValueAsString(mockSpends); + + this.mockMvc.perform( + get("/users/spends") + .param("start_date", startDateStr) + .param("end_date", endDateStr) + .header("Authorization", authorization.getAccessToken())) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(content().json(expected)) + .andExpect(content().string(expected)); + + } +} From 7cb2cb38fb7346087abf8461e9c86c68df24f0d1 Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Thu, 20 Dec 2018 03:40:00 -0200 Subject: [PATCH 16/17] final touches: status corrections, validation of null attr, functional testbench --- .../microservice/services/SystemService.java | 2 +- .../microservice/services/UserService.java | 2 +- .../IBM-Santander.postman_collection.json | 543 ++++++++++++++++++ .../IBM-Santander.postman_environment.json | 119 ++++ .../interceptors/JWTInterceptor.java | 22 +- .../main/java/microservice/models/Spend.java | 3 +- .../repositories/CategoryRepository.java | 2 +- .../CategoryControllerTest.java | 7 - 8 files changed, 682 insertions(+), 18 deletions(-) create mode 100644 postman_testbench/IBM-Santander.postman_collection.json create mode 100644 postman_testbench/IBM-Santander.postman_environment.json diff --git a/auth_microservice/src/main/java/microservice/services/SystemService.java b/auth_microservice/src/main/java/microservice/services/SystemService.java index 37741222..05ba2834 100644 --- a/auth_microservice/src/main/java/microservice/services/SystemService.java +++ b/auth_microservice/src/main/java/microservice/services/SystemService.java @@ -77,7 +77,7 @@ public CompletableFuture authenticate(System system) { } } - return CompletableFuture.completedFuture(new Message(msg, storedSystem.get_id(), false)); + return CompletableFuture.completedFuture(new Message(msg, null, false)); } @Async("ThreadPoolExecutor") diff --git a/auth_microservice/src/main/java/microservice/services/UserService.java b/auth_microservice/src/main/java/microservice/services/UserService.java index b72a7a8a..5166b24c 100644 --- a/auth_microservice/src/main/java/microservice/services/UserService.java +++ b/auth_microservice/src/main/java/microservice/services/UserService.java @@ -77,7 +77,7 @@ public CompletableFuture authenticate(User user) { } } - return CompletableFuture.completedFuture(new Message(msg, storedUser.get_id(), false)); + return CompletableFuture.completedFuture(new Message(msg, null, false)); } @Async("ThreadPoolExecutor") diff --git a/postman_testbench/IBM-Santander.postman_collection.json b/postman_testbench/IBM-Santander.postman_collection.json new file mode 100644 index 00000000..14171723 --- /dev/null +++ b/postman_testbench/IBM-Santander.postman_collection.json @@ -0,0 +1,543 @@ +{ + "info": { + "_postman_id": "a0d948d5-d8b6-4712-9527-17a8717d6bfc", + "name": "IBM-Santander", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "auth microservice", + "item": [ + { + "name": "register new user", + "event": [ + { + "listen": "test", + "script": { + "id": "10762c41-aadb-42f9-abe5-2fd62e7727a9", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " if (pm.response.to.have.header(\"Location\")) {", + " const id = postman.getResponseHeader('Location').replace(pm.variables.get(\"auth_url\"), '');", + " pm.environment.set(\"usr_id\", id);", + " }", + "});", + "", + "", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"username\": \"{{usr}}\",\n\t\"password\": \"{{usr_pwd}}\"\n}" + }, + "url": { + "raw": "{{auth_url}}users", + "host": [ + "{{auth_url}}users" + ] + } + }, + "response": [] + }, + { + "name": "register new system", + "event": [ + { + "listen": "test", + "script": { + "id": "10762c41-aadb-42f9-abe5-2fd62e7727a9", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " if (pm.response.to.have.header(\"Location\")) {", + " const id = postman.getResponseHeader('Location').replace(pm.variables.get(\"auth_url\"), '');", + " pm.environment.set(\"sys_id\", id);", + " }", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"username\": \"{{sys}}\",\n\t\"password\": \"{{sys_pwd}}\"\n}" + }, + "url": { + "raw": "{{auth_url}}systems", + "host": [ + "{{auth_url}}systems" + ] + } + }, + "response": [] + }, + { + "name": "user login", + "event": [ + { + "listen": "test", + "script": { + "id": "1117d661-8bb3-44af-9ca3-2cec7cd1347c", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.environment.set(\"token\", pm.response.json().accessToken);", + " pm.environment.set(\"usr_id\", pm.response.json().clientId);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "cab6fe3e-4c98-43bc-96a1-4640849ac2b9", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"username\": \"{{usr}}\",\n\t\"password\": \"{{usr_pwd}}\"\n}" + }, + "url": { + "raw": "{{auth_url}}users/authentication", + "host": [ + "{{auth_url}}users" + ], + "path": [ + "authentication" + ] + } + }, + "response": [] + }, + { + "name": "system login", + "event": [ + { + "listen": "test", + "script": { + "id": "1117d661-8bb3-44af-9ca3-2cec7cd1347c", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.environment.set(\"token\", pm.response.json().accessToken);", + " pm.environment.set(\"sys_id\", pm.response.json().clientId);", + "});", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"username\": \"{{sys}}\",\n\t\"password\": \"{{sys_pwd}}\"\n}" + }, + "url": { + "raw": "{{auth_url}}systems/authentication", + "host": [ + "{{auth_url}}systems" + ], + "path": [ + "authentication" + ] + } + }, + "response": [] + }, + { + "name": "validate user token", + "event": [ + { + "listen": "test", + "script": { + "id": "1117d661-8bb3-44af-9ca3-2cec7cd1347c", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{auth_url}}users/authorization", + "host": [ + "{{auth_url}}users" + ], + "path": [ + "authorization" + ] + } + }, + "response": [] + }, + { + "name": "validate system token", + "event": [ + { + "listen": "test", + "script": { + "id": "1117d661-8bb3-44af-9ca3-2cec7cd1347c", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{token}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{auth_url}}systems/authorization", + "host": [ + "{{auth_url}}systems" + ], + "path": [ + "authorization" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "spends microservice", + "item": [ + { + "name": "register new spend", + "event": [ + { + "listen": "test", + "script": { + "id": "7075ca05-67af-45ac-943f-e4ad20c8d6b7", + "exec": [ + "pm.test(\"Status code is 201\", function () {", + " pm.response.to.have.status(201);", + " pm.environment.set(\"spend_id\", pm.response.json()._id);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "2164e194-f49f-4265-8f7d-57724ef44607", + "exec": [ + "pm.environment.set(\"spend_date\", (new Date()).toISOString().replace('Z', '+0000'));" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "disabled": false, + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "{{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "{ \n\t\"description\": \"testSpend1\", \n\t\"value\": 99.98123456, \n\t\"userCode\": \"{{usr_id}}\",\n\t\"date\": \"{{spend_date}}\"\n}" + }, + "url": { + "raw": "{{spend_url}}spends", + "host": [ + "{{spend_url}}spends" + ] + } + }, + "response": [] + }, + { + "name": "retrieve user spends", + "event": [ + { + "listen": "test", + "script": { + "id": "7075ca05-67af-45ac-943f-e4ad20c8d6b7", + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "2164e194-f49f-4265-8f7d-57724ef44607", + "exec": [ + "const now = new Date();", + "pm.environment.set(\"endDate\", now.toISOString().replace('Z', '+0000'));", + "", + "let past = new Date();", + "past.setDate(now.getDate() - 21);", + "pm.environment.set(\"startDate\", past.toISOString().replace('Z', '+0000'));" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "disabled": false, + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "type": "text", + "value": "{{token}}" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{spend_url}}users/spends?start_date={{startDate}}&end_date={{endDate}}", + "host": [ + "{{spend_url}}users" + ], + "path": [ + "spends" + ], + "query": [ + { + "key": "start_date", + "value": "{{startDate}}" + }, + { + "key": "end_date", + "value": "{{endDate}}" + } + ] + } + }, + "response": [] + }, + { + "name": "update category", + "request": { + "method": "PATCH", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "{{token}}", + "equals": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"category\": \"newCategory\"\n}" + }, + "url": { + "raw": "{{spend_url}}spends/{{spend_id}}/categories", + "host": [ + "{{spend_url}}spends" + ], + "path": [ + "{{spend_id}}", + "categories" + ] + } + }, + "response": [] + }, + { + "name": "category suggestions", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "{{token}}", + "type": "text" + } + ], + "body": {}, + "url": { + "raw": "{{spend_url}}categories/suggestions?partial_name=cat", + "host": [ + "{{spend_url}}categories" + ], + "path": [ + "suggestions" + ], + "query": [ + { + "key": "partial_name", + "value": "cat" + } + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "55867996-6115-4d11-84bf-f549f04450af", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "1f5beed3-6e7b-44c1-b9a7-2dfa0104c40a", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "id": "3f578014-977b-4640-a289-ffd79438b179", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "751bf649-adb9-4061-b73d-511aae36d620", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] +} \ No newline at end of file diff --git a/postman_testbench/IBM-Santander.postman_environment.json b/postman_testbench/IBM-Santander.postman_environment.json new file mode 100644 index 00000000..4f59167a --- /dev/null +++ b/postman_testbench/IBM-Santander.postman_environment.json @@ -0,0 +1,119 @@ +{ + "id": "e4e4105e-af64-4847-a690-7dbaa64883eb", + "name": "IBM-Santander", + "values": [ + { + "key": "token", + "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzeXN0ZW1JZCI6IjVjMWIyOGVlZGQzYjdlM2VlZGZkNjQ4NCIsImlzcyI6Imh0dHA6Ly9hcGkuc2FudGFuZGVydGVzdC5jb20uYnIiLCJleHAiOjE1NDY0OTM0MzUsImlhdCI6MTU0NTI4MzgzNX0.aqKw1Aodhjf2rYLJ6XeS7Y6bDzMnAwi80ekj85UPIyg", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "usr_id", + "value": "5c1b1cc9dd3b7e3eedfd6482", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "usr", + "value": "testUser", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "usr_pwd", + "value": "testUser12345", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "sys_id", + "value": "5c1b28eedd3b7e3eedfd6484", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "sys", + "value": "testSystem", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "sys_pwd", + "value": "testSystem12345", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "auth_url", + "value": "http://localhost:8080/", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "spend_url", + "value": "http://localhost:8081/", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + }, + { + "key": "spend_date", + "value": "2018-12-20T05:31:04.054+0000", + "enabled": true + }, + { + "key": "endDate", + "value": "2018-12-20T05:25:06.567+0000", + "enabled": true + }, + { + "key": "startDate", + "value": "2018-11-29T05:25:06.567+0000", + "enabled": true + }, + { + "key": "now", + "value": "2018-12-16T07:54:32.744+0000", + "enabled": true + }, + { + "key": "spend_id", + "value": "5c1b2918dd3b7e47dfcc6ee3", + "description": { + "content": "", + "type": "text/plain" + }, + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2018-12-20T05:32:42.129Z", + "_postman_exported_using": "Postman/6.6.1" +} \ No newline at end of file diff --git a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java index 80c76e6d..018e6b1a 100644 --- a/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java +++ b/spend_microservice/src/main/java/microservice/interceptors/JWTInterceptor.java @@ -51,13 +51,12 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons HttpEntity entity = new HttpEntity<>(headers); RestTemplate restTemplate = new RestTemplate(); String url = requestURI.contains("/spends") && requestMethod.equals("POST") ? SYS_AUTH_URL : USR_AUTH_URL; - try { ResponseEntity authResponse = restTemplate.exchange(url, HttpMethod.GET, entity, Message.class); Message msg = authResponse.getBody(); if (!msg.getStatus().equals("success")) { - formatErrorResponse(response, msg); + formatErrorResponse(response, msg, HttpStatus.UNAUTHORIZED.value()); LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + msg.getContent()); return false; } @@ -66,14 +65,23 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons request.setAttribute("userId", msg.getClientId()); } catch (HttpClientErrorException e) { - Message errorMsg = new Message("invalid access token. " + e.getMessage(), null, "failed"); - formatErrorResponse(response, errorMsg); + Message errorMsg; + int status; + if (requestURI.contains("/error")) { + errorMsg = new Message("invalid request body. " + e.getMessage(), null, "failed"); + status = HttpStatus.BAD_REQUEST.value(); + } + else { + errorMsg = new Message("invalid access token. " + e.getMessage(), null, "failed"); + status = HttpStatus.UNAUTHORIZED.value(); + } + formatErrorResponse(response, errorMsg, status); LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + errorMsg.getContent()); return false; } catch (ResourceAccessException e) { Message errorMsg = new Message("authorization microservice is not available", null, "failed"); - formatErrorResponse(response, errorMsg); + formatErrorResponse(response, errorMsg, HttpStatus.UNAUTHORIZED.value()); LOGGER.error("[" + requestMethod + "] " + requestURI + " - " + errorMsg.getContent()); return false; } @@ -82,8 +90,8 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons } - private void formatErrorResponse(HttpServletResponse response, Message errorMsg) throws IOException { - response.setStatus(HttpStatus.UNAUTHORIZED.value()); + private void formatErrorResponse(HttpServletResponse response, Message errorMsg, int status) throws IOException { + response.setStatus(status); response.getWriter().write(errorMsg.toJSONString()); response.getWriter().flush(); response.getWriter().close(); diff --git a/spend_microservice/src/main/java/microservice/models/Spend.java b/spend_microservice/src/main/java/microservice/models/Spend.java index 5a16f525..fb6eb929 100644 --- a/spend_microservice/src/main/java/microservice/models/Spend.java +++ b/spend_microservice/src/main/java/microservice/models/Spend.java @@ -12,7 +12,8 @@ public class Spend { @Id private String _id; - + + @NotNull private String description; @NotNull diff --git a/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java b/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java index d21b1c2f..c47d926b 100644 --- a/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java +++ b/spend_microservice/src/main/java/microservice/repositories/CategoryRepository.java @@ -8,7 +8,7 @@ public interface CategoryRepository extends MongoRepository { - @Query("{ 'category': { $regex: '^?0', $options: 'i' } }") + @Query("{ 'category': { $regex: '?0', $options: 'i' } }") public List findBySimilarName(String category); } diff --git a/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java b/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java index f831cf66..63527399 100644 --- a/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java +++ b/spend_microservice/src/test/java/microservice/controller_tests/CategoryControllerTest.java @@ -22,12 +22,6 @@ import microservice.models.Category; import microservice.util.AuthRequester; -// import static org.junit.Assert.*; -// import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -// import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; -// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -// import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*; - @RunWith(SpringRunner.class) @WebMvcTest(value = CategoryController.class, secure = false) @@ -56,7 +50,6 @@ public void testJSONResponse() throws Exception { "zanferrari", "zan12345"); - ObjectMapper mapper = new ObjectMapper(); String expected = mapper.writeValueAsString(mockCategories); this.mockMvc.perform( From d36aa58be04f233d0c1253e97e1c31b2a346e05e Mon Sep 17 00:00:00 2001 From: Lucas Zanferrari Date: Thu, 20 Dec 2018 04:43:22 -0200 Subject: [PATCH 17/17] instructions on how to run the project --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/README.md b/README.md index 15d8f685..64f1fc00 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,46 @@ +# Desafio de desenvolvimento back end - Santander + +Passos para executar a solução: + +1) garanta que o computador possui o JDK versão 1.8, Maven versão 3.6.0, MongoDB versão 4.0.4 e git (qualquer versão recente) instalados. Para isso, abra uma janela de seu terminal e execute os comandos + +``` +java -version +mvn -version +mongod -version +git --version +``` + +As versões de cada um devem ser impressas na saída padrão. + +2) clone este repositório para o seu computador +3) inicie o MongoDB executando o comando `mongod`. Isso impedirá você de utilizar esta janela do terminal, portanto abra uma nova e passe a usá-la, mas deixe a anterior aberta +3) utilizando a nova janela do terminal, navegue até o diretório local onde você clonou este repositório +4) entre no o diretório do microserviço de autenticação, ou `./auth_microservice` +5) execute o comando `mvn spring-boot:run` +6) abra uma terceira janela de seu terminal e, novamente, navegue até o diretório local onde se encontra este repositório +7) entre no diretório do microserviço de gastos, ou `./spend_microservice`, e finalmente, execute o comando `mvn spring-boot:run` +8) desfrute da aplicação! +  +  +#### Observações +- Os microserviços estão configurados para ocuparem as portas 8080 e 8081, respectivamente. +- Para executar os testes unitários do microserviço de gastos, é preciso que o microserviço de autenticação esteja online +- Este repositório conta com uma collection e um environment do Postman, uma ferramenta muito útil para testar as funcionalidades de APIs. Para utilizá-los, faça o download e instale o Postman em seu computador (caso ainda não o possua intalado) e importe tanto collection quanto environment para o seu ambiente. +  +  +#### Possíveis otimizações (próximos passos) +1) Para melhorar a velocidade de resposta da rota de inserção de gastos, seria interessante refatorá-la para que ela inserisse os documentos a serem persistidos primeiramente em um sistema de mensageria, ou no próprio Redis (devido a sua velocidade de inserção e consulta). Após isso, workers assíncronos subscritos ao canal de mensagens seriam responsáveis por persistir os dados no banco de dados de fato +2) Outra possível melhoria seria tornar assíncronas as execuções dos métodos dos controllers (no momento, apenas os métodos da camada de serviço estão sendo processados de forma assíncrona). Isto não foi feito pois o uso da classe `HandlerInterceptorAdapter` faz com que duas chamadas de seu método `preHandle()` ocorram a cada requisição assíncrona recebida, e como a lógica de validação de tokens se encontra dentro deste método, o resultado seria que o número de requisições que o microserviço de autentição precisaria responder para o sistema funcionasse normalmente dobraria +3) Containerizar cada um dos microserviços tornaria muito mais simples a tarefa de movê-los de um infraestrutura para outra, escalá-los e administrá-los no geral +  + +  +Por fim, estou me candidando pela **IBM**. +  + +  +#### O texto original do desafio se encontra abaixo # Show me the code ### # DESAFIO: