Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a2b0d93
Scaffolding do projeto com a implementação das primeiras funcionalidades
lucaszanferrari Dec 12, 2018
0913d1f
add interceptor, jwt validation, custom thread mgmt + properties file
lucaszanferrari Dec 12, 2018
bda5c2d
add pasta de configs + correção de mgs de erro do interceptor
lucaszanferrari Dec 12, 2018
9cbf751
refatorando ErrorMessage para Message
lucaszanferrari Dec 13, 2018
1d20d26
adding auth microservice
lucaszanferrari Dec 14, 2018
3f14eec
changing ports of both microservices
lucaszanferrari Dec 14, 2018
30eba65
connecting both microservices
lucaszanferrari Dec 16, 2018
3ee354f
adding date filter to the GET List of Spends endpoint
lucaszanferrari Dec 16, 2018
dda56d7
refine services to require less parameters in the list spends endpoint
lucaszanferrari Dec 16, 2018
5e5549f
adding automated category assignment & category assignment by user
lucaszanferrari Dec 17, 2018
7a50b10
changing resource routes and collection names to their plural
lucaszanferrari Dec 17, 2018
ed3278a
logging incoming requests
lucaszanferrari Dec 17, 2018
bce5b17
changing groupId and checking if auth is available at each request
lucaszanferrari Dec 19, 2018
45ced78
First unit test: json response of category suggestions endpoints
lucaszanferrari Dec 19, 2018
2d85fea
correcting location headers and adding tests for main controller routes
lucaszanferrari Dec 20, 2018
7cb2cb3
final touches: status corrections, validation of null attr, functiona…
lucaszanferrari Dec 20, 2018
d36aa58
instructions on how to run the project
lucaszanferrari Dec 20, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.settings
.vscode
.DS_Store
.project
.classpath
target
bin
.idea
*.iml
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
72 changes: 72 additions & 0 deletions auth_microservice/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>br.com.testesantander.api</groupId>
<artifactId>authentication_microservice</artifactId>
<version>0.1.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.4</version>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<properties>
<java.version>1.8</java.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>

</project>
39 changes: 39 additions & 0 deletions auth_microservice/src/main/java/microservice/AuthMicroservice.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
@@ -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("/**");
}

}
Original file line number Diff line number Diff line change
@@ -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 = "/systems",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Message> inserNewSystem(UriComponentsBuilder builder,
@Valid @RequestBody System system)
throws URISyntaxException, InterruptedException, ExecutionException {

CompletableFuture<System> systemFuture = systemService.register(system);
System storedSystem = systemFuture.get();
return ResponseEntity
.created(new URI(builder.toUriString() + "/systems/" + storedSystem.get_id()))
.body(new Message("system " + system.getUsername() + " successfully registered", storedSystem.get_id(), true));
}

@RequestMapping(value = "/systems/authentication",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> authenticateSystem(@Valid @RequestBody System system)
throws InterruptedException, ExecutionException {

CompletableFuture<?> systemFuture = systemService.authenticate(system);
Object result = systemFuture.get();
if (result.getClass() == Authorization.class)
return ResponseEntity.ok(result);
else
return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED);
}

@RequestMapping(value = "/systems/authorization",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Message> authorizeSystem(@RequestHeader("Authorization") String accessToken)
throws InterruptedException, ExecutionException {

CompletableFuture<Message> systemFuture = systemService.authorize(accessToken);
Message msg = systemFuture.get();
if (msg.getStatus().equals("success"))
return ResponseEntity.ok(msg);
else
return new ResponseEntity<>(msg, HttpStatus.UNAUTHORIZED);
}

}
Original file line number Diff line number Diff line change
@@ -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 = "/users",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Message> register(UriComponentsBuilder builder,
@Valid @RequestBody User user)
throws URISyntaxException, InterruptedException, ExecutionException {

CompletableFuture<User> userFuture = userService.register(user);
User storedUser = userFuture.get();
return ResponseEntity
.created(new URI(builder.toUriString() + "/users/" + storedUser.get_id()))
.body(new Message("user " + user.getUsername() + " successfully registered", storedUser.get_id(), true));
}

@RequestMapping(value = "/users/authentication",
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> authenticateUser(@Valid @RequestBody User user)
throws InterruptedException, ExecutionException {

CompletableFuture<?> userFuture = userService.authenticate(user);
Object result = userFuture.get();
if (result.getClass() == Authorization.class)
return ResponseEntity.ok(result);
else
return new ResponseEntity<>(result, HttpStatus.UNAUTHORIZED);
}

@RequestMapping(value = "/users/authorization",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Message> authorizeUser(@RequestHeader("Authorization") String accessToken)
throws InterruptedException, ExecutionException {

CompletableFuture<Message> userFuture = userService.authorize(accessToken);
Message msg = userFuture.get();
if (msg.getStatus().equals("success"))
return ResponseEntity.ok(msg);
else
return new ResponseEntity<>(msg, HttpStatus.UNAUTHORIZED);
}

}
Original file line number Diff line number Diff line change
@@ -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);
}

}
Loading