diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..82eca336 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4e8a6ffd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: java + + +sudo: required + + +script: + + + - java -version + + + - echo $JAVA_HOME + + +jdk: + + + - oraclejdk8 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8a50b558 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Andrews Lima + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 15d8f685..ac7211c0 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,132 @@ -# Show me the code - -### # DESAFIO: - -API REST para Gestão de Gastos! - -``` -Funcionalidade: Integração de gastos por cartão - Apenas sistemas credenciados poderão incluir novos gastos - É esperado um volume de 100.000 inclusões por segundo - Os gastos, serão informados atraves do protoloco JSON, seguindo padrão: - { "descricao": "alfanumerico", "valor": double americano, "codigousuario": numerico, "data": Data dem formato UTC } -``` -``` -Funcionalidade: Listagem de gastos* - Dado que acesso como um cliente autenticado que pode visualizar os gastos do cartão - Quando acesso a interface de listagem de gastos - Então gostaria de ver meus gastos mais atuais. +[![Build Status](https://travis-ci.org/adslima/TestBackJava.svg?branch=master)](https://travis-ci.org/adslima/TestBackJava) + + +# Micros serviços com spring boot, eureka, zuul, oauth, RabbitMq, solr e Axon Framework. + + +## Microservices architecture + +Contém 5 componentes, todos eles são aplicativos implantáveis ​​independentemente. + + +### Service Registry (registro de serviços): + + Este serviço mantém o registro de todos os microservices que foram implantados. +Foi feito uso do netflix eureka neste projeto. + +### Service Gateway (roteamento de serviços): + + Pensando na questão do client realizar suas chamadas, diretamente para um único ponto de entrada + que encaminharia a solicitação para o serviço de back-end apropriado. +Com base nessa questão, usei o netflix zuul, o configurando para rotear as solicitações especificando rotas. + +### Auth Service(serviço de autenticação): + + Para acessar qualquer recurso de autenticação é necessário, + em vez de usar as credenciais do proprietário do recurso para acessar recursos protegidos, + o cliente obtém um token de acesso. + + +### Expense Management Command & Expense Management Query + + Com base no que o CQRS propõe, para que separemos a aplicação em modelos diferentes para atualização e exibição, + que de acordo com o padrão do CQRS estariamos falando dos Comandos e Consultas. + No momento que o usuário realiza um compra com cartão, isso é roteado para o modelo de comando + que por sua vez, irá realizar operações necessárias e informará ao modelo de consulta + para que as novas informações sejam exibidas para o usuário. + Ainda com o uso do Axon Framework foi possivel a implementação do padrão arquitetural CQRS. + + Para comunicação de nossas aplicações. ocorrerá através de barramento de eventos fazendo uso do RabbitMq. + Para a persistência dos gastos com cartões, será feito uso do Mysql. + E para indexação das categotorias, será feito uso do SOLR. + + +# Pré requisito + + - Maven 3 + - Java 8 + - Git + - Docker/Docker Compose + +## No Docker serão criados os serviços listados abaixo: + + - Mysql + - Solr + - RabbitMq + + - Service Registry + - Auth Service + - Service Gateway + + - Expense Management Command + - Expense Management Query + +# Preparando ambiente + + Em cada serviço (registry, auth, gateway, command e query) + +### Executar o seguinte commando: + - mvn clean compile package -DskipTests + +# Executando + +### Após esse procedimento, dentro da pasta raiz do projeto executar + - docker-compose up --no-start + + + Ao termino desse processo, executar os seguintes comandos: + + - docker start mysql solr rabbit + - docker start eureka oauth + - docker start command query + - docker start zuul + + *Obs. Devemos considerar um intervalo entre entre os start's dos serviços. + + +## Com tudo rodando, devemos realizar a solicitação do token para acesso dos serviços; +para isso faremos o seguinte request: + + POST + http://localhost:8765/auth-api/oauth/token?grant_type=password&username=demo&password=password + + Authorization: Basic dHJ1c3RlZC1hcHA6cGFzc3dvcmQ= + + Content-Type: application/json + +com o token em 'mãos',vamos realizar um cadastro de gastos com cartão. + +POST + http://localhost:8765/commands/api-command + + { + "userCode": 12345, + "description": "RecargaCel", + "date": "2019-03-01T01:45:55.031", + "value": 15.00 + } + -*Para esta funcionalidade é esperado 2.000 acessos por segundo. -*O cliente espera ver gastos realizados a 5 segundos atrás. -``` -``` -Funcionalidade: Filtro de gastos - Dado que acesso como um cliente autenticado - E acessei a interface de listagem de gastos - E configure o filtro de data igual a 27/03/1992 - Então gostaria de ver meus gastos apenas deste dia. -``` -``` -Funcionalidade: Categorização de gastos - Dado que acesso como um cliente autenticado - Quando acesso o detalhe de um gasto - E este não possui uma categoria - Então devo conseguir incluir uma categoria para este -``` -``` -Funcionalidade: Sugestão de categoria - Dado que acesso como um cliente autenticado - Quando acesso o detalhe do gasto que não possui categoria - E começo a digitar a categoria que desejo - Então uma lista de sugestões de categoria deve ser exibida, estas baseadas em categorias já informadas por outro usuários. -``` -``` -Funcionalidade: Categorização automatica de gasto - No processo de integração de gastos, a categoria deve ser incluida automaticamente - caso a descrição de um gasto seja igual a descrição de qualquer outro gasto já categorizado pelo cliente - o mesmo deve receber esta categoria no momento da inclusão do mesmo -``` -### # Avaliação - -Você será avaliado pela usabilidade, por respeitar o design e pela arquitetura da API. -É esperado que você consiga explicar as decisões que tomou durante o desenvolvimento através de commits. - -* Springboot - Java - Maven (preferêncialmente) ([https://projects.spring.io/spring-boot/](https://projects.spring.io/spring-boot/)) -* RESTFul ([https://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/](https://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/)) -* DDD ([https://airbrake.io/blog/software-design/domain-driven-design](https://airbrake.io/blog/software-design/domain-driven-design)) -* Microservices ([https://martinfowler.com/microservices/](https://martinfowler.com/microservices/)) -* Testes unitários, teste o que achar importante (De preferência JUnit + Mockito). Mas pode usar o que você tem mais experiência, só nos explique o que ele tem de bom. -* SOAPUI para testes de carga ([https://www.soapui.org/load-testing/concept.html](https://www.soapui.org/load-testing/concept.html)) -* Uso de diferentes formas de armazenamento de dados (REDIS, Cassandra, Solr/Lucene) -* Uso do git -* Diferencial: Criptografia de comunicação, com troca de chaves. ([http://noiseprotocol.org/](http://noiseprotocol.org/)) -* Diferencial: CQRS ([https://martinfowler.com/bliki/CQRS.html](https://martinfowler.com/bliki/CQRS.html)) -* Diferencial: Docker File + Docker Compose (com dbs) para rodar seus jars. - -### # Observações gerais - -Adicione um arquivo [README.md](http://README.md) com os procedimentos para executar o projeto. -Pedimos que trabalhe sozinho e não divulgue o resultado na internet. - -Faça um fork desse desse repositório em seu Github e nos envie um Pull Request com o resultado, por favor informe por qual empresa você esta se candidatando. - -### # Importante: não há prazo de entrega, faça com qualidade! - -# BOA SORTE! +## Para edição de categorias + +PUT + http://localhost:8765/commands/api-command/{id}/categories + + {"category": "Serviços"} + +## Para Listarmos os gastos: + +GET + http://localhost:8765/queries/api-queries/12345/expense-menagement + +## Para a listagem por data; + +GET + http://localhost:8765/queries/api-queries/expense-menagement?userCode=12345&date=2019-03-01T00:00:00 + + +## Para sugestão de categotorias: + +GET + http://localhost:8765/queries/api-queries/{Texto_a_Pesquisar}/categories + diff --git a/auth-service/Dockerfile b/auth-service/Dockerfile new file mode 100644 index 00000000..1012364a --- /dev/null +++ b/auth-service/Dockerfile @@ -0,0 +1,9 @@ +FROM java:8-jre-alpine + +RUN mkdir -p /auth-service +ADD target/auth-service-0.0.1-SNAPSHOT.jar /auth-service +ADD target/classes/application.properties /auth-service + +WORKDIR /auth-service + +ENTRYPOINT ["java", "-jar", "auth-service-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/auth-service/pom.xml b/auth-service/pom.xml new file mode 100644 index 00000000..0b9dff1f --- /dev/null +++ b/auth-service/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + br.com.adslima + auth-service + 0.0.1-SNAPSHOT + jar + + auth-service + Oauth Server + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Dalston.SR2 + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-eureka + + + + + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/auth-service/src/main/java/br/com/adslima/ExpenseManagementAuthServerApplication.java b/auth-service/src/main/java/br/com/adslima/ExpenseManagementAuthServerApplication.java new file mode 100644 index 00000000..e54de5cd --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/ExpenseManagementAuthServerApplication.java @@ -0,0 +1,20 @@ +package br.com.adslima; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@EnableDiscoveryClient +@ComponentScan("br.com.adslima") +@EnableJpaRepositories(basePackages = { "br.com.adslima.repository" }) +@EntityScan(basePackages = { "br.com.adslima.entity" }) +public class ExpenseManagementAuthServerApplication { + + public static void main(String[] args) { + SpringApplication.run(ExpenseManagementAuthServerApplication.class, args); + } +} diff --git a/auth-service/src/main/java/br/com/adslima/config/AuthorizationServerConfiguration.java b/auth-service/src/main/java/br/com/adslima/config/AuthorizationServerConfiguration.java new file mode 100644 index 00000000..00ca1df4 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/config/AuthorizationServerConfiguration.java @@ -0,0 +1,90 @@ +package br.com.adslima.config; + +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; +import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.security.oauth2.provider.token.DefaultTokenServices; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenStore; + +import br.com.adslima.service.impl.UserAuthenticationServiceImpl; + + + +@Configuration +@EnableAuthorizationServer +public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter { + + private static String REALM = "MICRO_SERVICE_OAUTH"; + + @Autowired + private TokenStore tokenStore; + + @Autowired + private AuthorizationCodeServices authorizationCodeServices; + + + @Autowired + private UserAuthenticationServiceImpl userAuthenticationServiceImpl; + + @Autowired + @Qualifier("authenticationManagerBean") + private AuthenticationManager authenticationManager; + + @Autowired + private DataSource dataSource; + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @Autowired + private TokenEnhancer tokenEnhancer; + + final static Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class); + + + @Override + public void configure(ClientDetailsServiceConfigurer clients) throws Exception { + clients.jdbc(dataSource).passwordEncoder(passwordEncoder); + + } + + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { + + endpoints.tokenStore(tokenStore).authorizationCodeServices(authorizationCodeServices) + .userDetailsService(userAuthenticationServiceImpl) + .authenticationManager(authenticationManager).tokenEnhancer(tokenEnhancer) + .approvalStoreDisabled(); + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { + security.realm(REALM + "/client").passwordEncoder(passwordEncoder); + } + + @Bean + @Primary + public AuthorizationServerTokenServices tokenServices() { + DefaultTokenServices tokenServices = new DefaultTokenServices(); + tokenServices.setTokenStore(tokenStore); + tokenServices.setSupportRefreshToken(true); + tokenServices.setTokenEnhancer(tokenEnhancer); + return tokenServices; + } +} diff --git a/auth-service/src/main/java/br/com/adslima/config/OAuth2SecurityConfiguration.java b/auth-service/src/main/java/br/com/adslima/config/OAuth2SecurityConfiguration.java new file mode 100644 index 00000000..ef80fcb9 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/config/OAuth2SecurityConfiguration.java @@ -0,0 +1,71 @@ +package br.com.adslima.config; + + import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices; +import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices; +import org.springframework.security.oauth2.provider.token.TokenStore; +import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; + +import br.com.adslima.service.impl.UserAuthenticationServiceImpl; + + + + @Configuration + @EnableWebSecurity + public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + private UserAuthenticationServiceImpl userAuthenticationServiceImpl; + + @Autowired + private DataSource dataSource; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable().anonymous().disable().authorizeRequests().antMatchers("/oauth/token/**") + .permitAll(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Bean + public TokenStore tokenStore() { + return new JdbcTokenStore(dataSource); + } + + @Bean + public AuthorizationCodeServices authorizationCodeServices() { + return new JdbcAuthorizationCodeServices(dataSource); + } + + @Bean + public DaoAuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); + authenticationProvider.setUserDetailsService(userAuthenticationServiceImpl); + authenticationProvider.setPasswordEncoder(passwordEncoder()); + return authenticationProvider; + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(8); + } + public static void main(String[] args) { + System.out.println(new BCryptPasswordEncoder(8).encode("password")); + } + } + diff --git a/auth-service/src/main/java/br/com/adslima/config/OauthTokenEnhancer.java b/auth-service/src/main/java/br/com/adslima/config/OauthTokenEnhancer.java new file mode 100644 index 00000000..ef2fe6ce --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/config/OauthTokenEnhancer.java @@ -0,0 +1,32 @@ +package br.com.adslima.config; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.token.TokenEnhancer; +import org.springframework.stereotype.Component; + +import br.com.adslima.entity.Users; +import br.com.adslima.service.impl.UserServiceImpl; + +@Component +public class OauthTokenEnhancer implements TokenEnhancer { + + @Autowired + private UserServiceImpl userServiceImpl; + + @Override + public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { + String userName = authentication.getName(); + Users user = userServiceImpl.findByUsername(userName); + final Map additionalInfo = new HashMap<>(); + additionalInfo.put("name", user.getFirstName()); + ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo); + return accessToken; + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/config/ResourceServerConfiguration.java b/auth-service/src/main/java/br/com/adslima/config/ResourceServerConfiguration.java new file mode 100644 index 00000000..5a23940c --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/config/ResourceServerConfiguration.java @@ -0,0 +1,23 @@ +package br.com.adslima.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; + +@Configuration +@EnableResourceServer +public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter { + private static final String RESOURCE_ID = "auth-service"; + + @Override + public void configure(ResourceServerSecurityConfigurer resources) throws Exception { + resources.resourceId(RESOURCE_ID).stateless(false); + } + + @Override + public void configure(HttpSecurity http) throws Exception { + http.anonymous().disable().authorizeRequests().anyRequest().authenticated(); + } +} diff --git a/auth-service/src/main/java/br/com/adslima/controller/UserController.java b/auth-service/src/main/java/br/com/adslima/controller/UserController.java new file mode 100644 index 00000000..f7743116 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/controller/UserController.java @@ -0,0 +1,32 @@ +package br.com.adslima.controller; + +import java.security.Principal; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import br.com.adslima.entity.Users; +import br.com.adslima.service.impl.UserServiceImpl; + + +@RestController +@RequestMapping(value = "/users") +public class UserController { + + @Autowired + private UserServiceImpl userServiceImpl; + + @GetMapping("/authenticate") + public ResponseEntity user(Principal user) { + return ResponseEntity.ok(user); + } + + @GetMapping + public ResponseEntity getUserByUsername(Principal principal){ + Users user = userServiceImpl.findByUsername(principal.getName()); + return ResponseEntity.ok(user); + } +} diff --git a/auth-service/src/main/java/br/com/adslima/entity/Roles.java b/auth-service/src/main/java/br/com/adslima/entity/Roles.java new file mode 100644 index 00000000..3647862f --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/entity/Roles.java @@ -0,0 +1,58 @@ +package br.com.adslima.entity; + +import java.io.Serializable; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToMany; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +@Table(name = "roles", + uniqueConstraints = @UniqueConstraint(columnNames = {"role_id", "role_name"})) +public class Roles implements Serializable { + + private static final long serialVersionUID = -3090658385114905175L; + + @Id + @Column(name = "role_id", nullable = false, unique = true, columnDefinition = "smallint unsigned") + private Integer roleId; + + @Column(name = "role_name", nullable = false, unique = true) + private String roleName; + + @JsonIgnore + @ManyToMany(cascade = {CascadeType.MERGE}, mappedBy = "roles") + private Set users; + + public Integer getRoleId() { + return roleId; + } + + public void setRoleId(Integer roleId) { + this.roleId = roleId; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public Set getUsers() { + return users; + } + + public void setUsers(Set users) { + this.users = users; + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/entity/Users.java b/auth-service/src/main/java/br/com/adslima/entity/Users.java new file mode 100644 index 00000000..0e64b309 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/entity/Users.java @@ -0,0 +1,117 @@ +package br.com.adslima.entity; + +import java.io.Serializable; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.Table; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +@Entity +@Table(name = "users") +public class Users implements Serializable { + + private static final long serialVersionUID = -3788391832239645648L; + + @Id + @Column(name = "user_id", nullable = false, unique = true, columnDefinition = "int unsigned") + private Integer userId; + + @Column(name = "user_name", nullable = false, unique = true, length = 45) + private String userName; + + @JsonIgnore + @Column(name = "password", nullable = false) + private String password; + + @Column(name = "first_name", length = 45) + private String firstName; + + @Column(name = "last_name", length = 45) + private String lastName; + + @Column(name = "email", length = 45, unique =true) + private String email; + + @Column(name = "mobile", length = 15, unique=true) + private String mobile; + + @JsonIgnore + @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.REFRESH}) + @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) + private Set roles; + + public Integer getUserId() { + return userId; + } + + public void setUserId(Integer userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public Set getRoles() { + return roles; + } + + public void setRoles(Set roles) { + this.roles = roles; + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/exception/BaseException.java b/auth-service/src/main/java/br/com/adslima/exception/BaseException.java new file mode 100644 index 00000000..5a603e43 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/exception/BaseException.java @@ -0,0 +1,26 @@ +package br.com.adslima.exception; + +/** + * + * @author andrews.silva + * + */ +public class BaseException extends RuntimeException { + + private static final long serialVersionUID = 2827308907740826575L; + + private String code; + + public BaseException(String message) { + super(message); + } + + public BaseException(String message, String code) { + super(message); + this.code = code; + } + + public String getCode() { + return code; + } +} diff --git a/auth-service/src/main/java/br/com/adslima/exception/NotFoundException.java b/auth-service/src/main/java/br/com/adslima/exception/NotFoundException.java new file mode 100644 index 00000000..94473ca2 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/exception/NotFoundException.java @@ -0,0 +1,15 @@ +package br.com.adslima.exception; + +public class NotFoundException extends BaseException { + + private static final long serialVersionUID = 7156525748893002528L; + + public NotFoundException(String message) { + super(message); + } + + public NotFoundException(String message, String code) { + super(message, code); + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/exception/handler/GlobalExceptionHandler.java b/auth-service/src/main/java/br/com/adslima/exception/handler/GlobalExceptionHandler.java new file mode 100644 index 00000000..0ae702e7 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/exception/handler/GlobalExceptionHandler.java @@ -0,0 +1,82 @@ +package br.com.adslima.exception.handler; + +import java.io.IOException; +import java.sql.SQLException; + +import javax.persistence.EntityNotFoundException; +import javax.validation.ConstraintViolationException; + +import org.hibernate.HibernateException; +import org.hibernate.exception.DataException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import br.com.adslima.exception.NotFoundException; +import br.com.adslima.model.ErrorMessage; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + public static String getMessage(Exception e) { + logger.error("Exception", e); + return e.getCause() == null ? e.getMessage() : e.getCause().getMessage(); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(value = { NoHandlerFoundException.class, NotFoundException.class }) + public ErrorMessage resourceNotFoundExceptionHandler(Exception e) { + return new ErrorMessage(getMessage(e), "404"); + } + + @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public ErrorMessage methodNotSupportedException(Exception e) { + return new ErrorMessage("Method Not Supported :" + getMessage(e), "405"); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(value = { HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class, + ServletRequestBindingException.class }) + public ErrorMessage badRequestExceptionHandler(Exception e) { + return new ErrorMessage(getMessage(e), "400"); + } + + @ResponseStatus(HttpStatus.CONFLICT) + @ExceptionHandler(value = { DataIntegrityViolationException.class, ConstraintViolationException.class }) + public ErrorMessage dataIntegrityExceptionHandler(Exception e) { + return new ErrorMessage("Constraint voilation : " + getMessage(e), "409"); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(value = { EmptyResultDataAccessException.class, EntityNotFoundException.class, + HttpMessageNotWritableException.class }) + public ErrorMessage emptyResultDataAccessExceptionHandler(Exception e) { + return new ErrorMessage("Entity doesnot exist : " + getMessage(e), "400"); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(value = { DataException.class, SQLException.class, HibernateException.class, + JpaSystemException.class, JsonMappingException.class, JsonParseException.class, IOException.class }) + public ErrorMessage systemException(Exception e) { + return new ErrorMessage("System error : " + getMessage(e), "500"); + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/model/ErrorMessage.java b/auth-service/src/main/java/br/com/adslima/model/ErrorMessage.java new file mode 100644 index 00000000..89b56aa9 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/model/ErrorMessage.java @@ -0,0 +1,37 @@ +package br.com.adslima.model; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ErrorMessage implements Serializable{ + + private static final long serialVersionUID = -1325725054820828328L; + + @JsonProperty(value = "error") + private String error; + @JsonProperty(value = "error_description") + private String errorDescription; + + public ErrorMessage(String error, String errorDescription) { + this.error = error; + this.errorDescription = errorDescription; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getErrorDescription() { + return errorDescription; + } + + public void setErrorDescription(String errorDescription) { + this.errorDescription = errorDescription; + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/repository/BaseRepository.java b/auth-service/src/main/java/br/com/adslima/repository/BaseRepository.java new file mode 100644 index 00000000..92c046c3 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/repository/BaseRepository.java @@ -0,0 +1,11 @@ +package br.com.adslima.repository; + +import java.io.Serializable; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +@NoRepositoryBean +public interface BaseRepository extends JpaRepository { + +} diff --git a/auth-service/src/main/java/br/com/adslima/repository/RolesRepository.java b/auth-service/src/main/java/br/com/adslima/repository/RolesRepository.java new file mode 100644 index 00000000..98209ecf --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/repository/RolesRepository.java @@ -0,0 +1,7 @@ +package br.com.adslima.repository; + +import br.com.adslima.entity.Roles; + +public interface RolesRepository extends BaseRepository { + +} diff --git a/auth-service/src/main/java/br/com/adslima/repository/UsersRepository.java b/auth-service/src/main/java/br/com/adslima/repository/UsersRepository.java new file mode 100644 index 00000000..24ab1164 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/repository/UsersRepository.java @@ -0,0 +1,7 @@ +package br.com.adslima.repository; + +import br.com.adslima.entity.Users; + +public interface UsersRepository extends BaseRepository { + Users findByUserName(String username); +} diff --git a/auth-service/src/main/java/br/com/adslima/service/UserAuthenticationService.java b/auth-service/src/main/java/br/com/adslima/service/UserAuthenticationService.java new file mode 100644 index 00000000..1c757528 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/service/UserAuthenticationService.java @@ -0,0 +1,7 @@ +package br.com.adslima.service; + +import org.springframework.security.core.userdetails.UserDetailsService; + +public interface UserAuthenticationService extends UserDetailsService { + +} diff --git a/auth-service/src/main/java/br/com/adslima/service/UserService.java b/auth-service/src/main/java/br/com/adslima/service/UserService.java new file mode 100644 index 00000000..7f2ac037 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/service/UserService.java @@ -0,0 +1,9 @@ +package br.com.adslima.service; + +import br.com.adslima.entity.Users; + +public interface UserService { + Users findByUsername(String username); + + Users findByUserId(Integer userId); +} diff --git a/auth-service/src/main/java/br/com/adslima/service/impl/UserAuthenticationServiceImpl.java b/auth-service/src/main/java/br/com/adslima/service/impl/UserAuthenticationServiceImpl.java new file mode 100644 index 00000000..177f0535 --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/service/impl/UserAuthenticationServiceImpl.java @@ -0,0 +1,50 @@ +package br.com.adslima.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import br.com.adslima.entity.Roles; +import br.com.adslima.entity.Users; +import br.com.adslima.service.UserAuthenticationService; +import br.com.adslima.service.UserService; + + +@Service +@Transactional +public class UserAuthenticationServiceImpl implements UserAuthenticationService { + + private static Logger logger = LoggerFactory.getLogger(UserAuthenticationServiceImpl.class); + + @Autowired + private UserService userService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + Users user = userService.findByUsername(username); + if (user == null) { + throw new UsernameNotFoundException("User not found"); + } + String password = user.getPassword(); + Set roles = user.getRoles(); + List userGrants = new ArrayList<>(); + for (Roles role : roles) { + GrantedAuthority userGrant = new SimpleGrantedAuthority(role.getRoleName()); + userGrants.add(userGrant); + } + logger.info("user roles : {}", roles); + return new User(username, password, userGrants); + } + +} diff --git a/auth-service/src/main/java/br/com/adslima/service/impl/UserServiceImpl.java b/auth-service/src/main/java/br/com/adslima/service/impl/UserServiceImpl.java new file mode 100644 index 00000000..6f48ddaa --- /dev/null +++ b/auth-service/src/main/java/br/com/adslima/service/impl/UserServiceImpl.java @@ -0,0 +1,35 @@ +package br.com.adslima.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import br.com.adslima.entity.Users; +import br.com.adslima.exception.NotFoundException; +import br.com.adslima.repository.UsersRepository; +import br.com.adslima.service.UserService; + + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UsersRepository userRepository; + + @Override + public Users findByUsername(String username) { + Users user = userRepository.findByUserName(username); + if (user == null) { + throw new NotFoundException("User not found : " + username); + } + return user; + } + + @Override + public Users findByUserId(Integer userId) { + Users user = userRepository.findOne(userId); + if (user == null) { + throw new NotFoundException("User not found : " + userId); + } + return user; + } +} diff --git a/auth-service/src/main/resources/application.properties b/auth-service/src/main/resources/application.properties new file mode 100644 index 00000000..b54f4d8e --- /dev/null +++ b/auth-service/src/main/resources/application.properties @@ -0,0 +1,30 @@ +server.port=8787 +server.context-path=/auth-api +spring.application.name=auth-service + +################ DB Connection ################# +spring.datasource.url=jdbc:h2:mem:auth_service +spring.datasource.username= +spring.datasource.password= +spring.datasource.dataSourceClassName=org.h2.jdbcx.JdbcDataSource +spring.datasource.schema=classpath:/schema.sql +spring.datasource.data=classpath:/data.sql + +# JPA properties +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.database=H2 +spring.jpa.openInView=false +spring.jpa.show_sql=true +spring.jpa.generate-ddl=false +spring.jpa.hibernate.ddl-auto=none + +# Discovery Server Access +eureka.client.serviceUrl.defaultZone=http://${EUREKA_IP:localhost}:8761/eureka/ +eureka.instance.preferIpAddress=true +eureka.instance.leaseRenewalIntervalInSeconds: 1 +eureka.instance.leaseExpirationDurationInSeconds: 2 + +security.basic.enabled=false + +#To maintain oauth filter ordering +security.oauth2.resource.filter-order=3 diff --git a/auth-service/src/main/resources/data.sql b/auth-service/src/main/resources/data.sql new file mode 100644 index 00000000..4fc93c9a --- /dev/null +++ b/auth-service/src/main/resources/data.sql @@ -0,0 +1,11 @@ +INSERT INTO oauth_client_details(client_id, client_secret, scope, authorized_grant_types, authorities, access_token_validity, refresh_token_validity) +VALUES ('trusted-app', '$2a$08$V0jF.bqEvkO9EhpSEfXi5OZ2JAygrAyz/8X3BLCKHgc2pPyBi4Spy', 'read,write,trust', 'client_credentials,authorization_code,implicit,password,refresh_token','ROLE_CLIENT,ROLE_TRUSTED_CLIENT','45000', '45000'); + +INSERT INTO `users` (`user_id`, `email`, `first_name`, `last_name`, `mobile`, `password`, `user_name`) +VALUES(123456,'demo@example.com', 'Demo', 'Example', '1234578632', '$2a$08$V0jF.bqEvkO9EhpSEfXi5OZ2JAygrAyz/8X3BLCKHgc2pPyBi4Spy', 'demo'); + +INSERT INTO `roles` (`role_id`, `role_name`) +VALUES(1234, 'ROLE_DEMO'); + +INSERT INTO `user_roles` (`role_id`, `user_id`) +VALUES(1234, 123456); \ No newline at end of file diff --git a/auth-service/src/main/resources/schema.sql b/auth-service/src/main/resources/schema.sql new file mode 100644 index 00000000..f856f289 --- /dev/null +++ b/auth-service/src/main/resources/schema.sql @@ -0,0 +1,69 @@ +CREATE TABLE oauth_client_details ( + client_id VARCHAR(255) PRIMARY KEY, + resource_ids VARCHAR(255), + client_secret VARCHAR(255), + scope VARCHAR(255), + authorized_grant_types VARCHAR(255), + web_server_redirect_uri VARCHAR(255), + authorities VARCHAR(255), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additional_information VARCHAR(255), + autoapprove VARCHAR(255) + ); + +CREATE TABLE oauth_client_token ( + token_id VARCHAR(255), + token BLOB, + authentication_id VARCHAR(255), + user_name VARCHAR(255), + client_id VARCHAR(255) +); + +CREATE TABLE oauth_access_token ( + token_id VARCHAR(255), + token BLOB, + authentication_id VARCHAR(255), + user_name VARCHAR(255), + client_id VARCHAR(255), + authentication BLOB, + refresh_token VARCHAR(255) +); +CREATE TABLE oauth_refresh_token ( + token_id VARCHAR(255), + token BLOB, + authentication BLOB +); +CREATE TABLE oauth_code ( + code VARCHAR(255), authentication BLOB +); + +CREATE TABLE `users` ( + `user_id` int(10) unsigned NOT NULL, + `email` varchar(45) DEFAULT NULL, + `first_name` varchar(45) DEFAULT NULL, + `last_name` varchar(45) DEFAULT NULL, + `mobile` varchar(15) DEFAULT NULL, + `password` varchar(255) NOT NULL, + `user_name` varchar(45) NOT NULL, + PRIMARY KEY (`user_id`), + UNIQUE KEY `UK_k8d0f2n7n88w1a16yhua64onx` (`user_name`), + UNIQUE KEY `UK_6dotkott2kjsp8vw4d0m25fb7` (`email`), + UNIQUE KEY `UK_63cf888pmqtt5tipcne79xsbm` (`mobile`) +); + +CREATE TABLE `roles` ( + `role_id` smallint(5) unsigned NOT NULL, + `role_name` varchar(45) NOT NULL, + PRIMARY KEY (`role_id`), + UNIQUE KEY `UK3cyq9kgtpbol1tlouij82oufa` (`role_id`,`role_name`), + UNIQUE KEY `UK_716hgxp60ym1lifrdgp67xt5k` (`role_name`) +); + +CREATE TABLE `user_roles` ( + `user_id` int(10) unsigned NOT NULL, + `role_id` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`user_id`,`role_id`), + CONSTRAINT `FKh8ciramu9cc9q3qcqiv4ue8a6` FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`), + CONSTRAINT `FKhfh9dx7w3ubf1co1vdev94g3f` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) +); diff --git a/auth-service/src/main/resources/sql/data.sql b/auth-service/src/main/resources/sql/data.sql new file mode 100644 index 00000000..d725ce2f --- /dev/null +++ b/auth-service/src/main/resources/sql/data.sql @@ -0,0 +1,32 @@ + +DELETE FROM oauth_client_details; + +INSERT INTO user (username, email, password, activated) +SELECT * FROM (SELECT 'admin', 'admin@admin.com', '$2a$10$r0RFDmpneBVryx.ihHK9gu6FFJQi4nTxQUqzdSTvrPpaKZMxigqpy', true) AS tmp +WHERE NOT EXISTS ( + SELECT username FROM user WHERE username = 'admin' +) LIMIT 1; + +INSERT INTO authority (name) +SELECT * FROM (SELECT 'ROLE_USER') AS tmp +WHERE NOT EXISTS ( + SELECT name FROM authority WHERE name = 'ROLE_USER' +) LIMIT 1; + +INSERT INTO authority (name) +SELECT * FROM (SELECT 'ROLE_ADMIN') AS tmp +WHERE NOT EXISTS ( + SELECT name FROM authority WHERE name = 'ROLE_ADMIN' +) LIMIT 1; + +INSERT INTO user_authority (username, authority) +SELECT * FROM (SELECT 'admin', 'ROLE_USER') AS tmp +WHERE NOT EXISTS ( + SELECT username, authority FROM user_authority WHERE username = 'admin' and authority = 'ROLE_USER' +) LIMIT 1; + +INSERT INTO user_authority (username, authority) +SELECT * FROM (SELECT 'admin', 'ROLE_ADMIN') AS tmp +WHERE NOT EXISTS ( + SELECT username, authority FROM user_authority WHERE username = 'admin' and authority = 'ROLE_ADMIN' +) LIMIT 1; \ No newline at end of file diff --git a/auth-service/src/main/resources/sql/schema.sql b/auth-service/src/main/resources/sql/schema.sql new file mode 100644 index 00000000..ac56e40f --- /dev/null +++ b/auth-service/src/main/resources/sql/schema.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS user ( + username VARCHAR(50) NOT NULL PRIMARY KEY, + email VARCHAR(50), + password VARCHAR(500), + activated BOOLEAN DEFAULT FALSE, + activationkey VARCHAR(50) DEFAULT NULL, + resetpasswordkey VARCHAR(50) DEFAULT NULL +); + +CREATE TABLE IF NOT EXISTS authority ( + name VARCHAR(50) NOT NULL PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS user_authority ( + username VARCHAR(50) NOT NULL, + authority VARCHAR(50) NOT NULL, + FOREIGN KEY (username) REFERENCES user (username), + FOREIGN KEY (authority) REFERENCES authority (name), + UNIQUE INDEX user_authority_idx_1 (username, authority) +); + +CREATE TABLE IF NOT EXISTS oauth_access_token ( + token_id VARCHAR(256) DEFAULT NULL, + token BLOB, + authentication_id VARCHAR(256) DEFAULT NULL, + user_name VARCHAR(256) DEFAULT NULL, + client_id VARCHAR(256) DEFAULT NULL, + authentication BLOB, + refresh_token VARCHAR(256) DEFAULT NULL +); + +CREATE TABLE IF NOT EXISTS oauth_refresh_token ( + token_id VARCHAR(256) DEFAULT NULL, + token BLOB, + authentication BLOB +); + +CREATE TABLE IF NOT EXISTS oauth_client_details ( + client_id VARCHAR(255) PRIMARY KEY, + resource_ids VARCHAR(255), + client_secret VARCHAR(255), + scope VARCHAR(255), + authorized_grant_types VARCHAR(255), + web_server_redirect_uri VARCHAR(255), + authorities VARCHAR(255), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additional_information VARCHAR(4096), + autoapprove VARCHAR(255) +); \ No newline at end of file diff --git a/auth-service/src/test/java/br/com/adslima/ExpenseManagementAuthServerApplicationTests.java b/auth-service/src/test/java/br/com/adslima/ExpenseManagementAuthServerApplicationTests.java new file mode 100644 index 00000000..a94db150 --- /dev/null +++ b/auth-service/src/test/java/br/com/adslima/ExpenseManagementAuthServerApplicationTests.java @@ -0,0 +1,16 @@ +package br.com.adslima; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ExpenseManagementAuthServerApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/auth-service/target/classes/META-INF/MANIFEST.MF b/auth-service/target/classes/META-INF/MANIFEST.MF new file mode 100644 index 00000000..e6b1f5b8 --- /dev/null +++ b/auth-service/target/classes/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Implementation-Title: auth-service +Implementation-Version: 0.0.1-SNAPSHOT +Built-By: andrews.silva +Implementation-Vendor-Id: br.com.adslima +Build-Jdk: 1.8.0_201 +Implementation-URL: http://projects.spring.io/spring-boot/auth-service + / +Created-By: Maven Integration for Eclipse +Implementation-Vendor: Pivotal Software, Inc. + diff --git a/auth-service/target/classes/META-INF/maven/br.com.adslima/auth-service/pom.properties b/auth-service/target/classes/META-INF/maven/br.com.adslima/auth-service/pom.properties new file mode 100644 index 00000000..009ca2f4 --- /dev/null +++ b/auth-service/target/classes/META-INF/maven/br.com.adslima/auth-service/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Thu Mar 14 19:46:13 GMT-03:00 2019 +version=0.0.1-SNAPSHOT +groupId=br.com.adslima +m2e.projectName=auth-service +m2e.projectLocation=C\:\\Users\\andrews.silva\\Documents\\santander_bkp\\TestBackJava\\auth-service +artifactId=auth-service diff --git a/auth-service/target/classes/META-INF/maven/br.com.adslima/auth-service/pom.xml b/auth-service/target/classes/META-INF/maven/br.com.adslima/auth-service/pom.xml new file mode 100644 index 00000000..0b9dff1f --- /dev/null +++ b/auth-service/target/classes/META-INF/maven/br.com.adslima/auth-service/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + br.com.adslima + auth-service + 0.0.1-SNAPSHOT + jar + + auth-service + Oauth Server + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Dalston.SR2 + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-eureka + + + + + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/auth-service/target/classes/application.properties b/auth-service/target/classes/application.properties new file mode 100644 index 00000000..b54f4d8e --- /dev/null +++ b/auth-service/target/classes/application.properties @@ -0,0 +1,30 @@ +server.port=8787 +server.context-path=/auth-api +spring.application.name=auth-service + +################ DB Connection ################# +spring.datasource.url=jdbc:h2:mem:auth_service +spring.datasource.username= +spring.datasource.password= +spring.datasource.dataSourceClassName=org.h2.jdbcx.JdbcDataSource +spring.datasource.schema=classpath:/schema.sql +spring.datasource.data=classpath:/data.sql + +# JPA properties +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.database=H2 +spring.jpa.openInView=false +spring.jpa.show_sql=true +spring.jpa.generate-ddl=false +spring.jpa.hibernate.ddl-auto=none + +# Discovery Server Access +eureka.client.serviceUrl.defaultZone=http://${EUREKA_IP:localhost}:8761/eureka/ +eureka.instance.preferIpAddress=true +eureka.instance.leaseRenewalIntervalInSeconds: 1 +eureka.instance.leaseExpirationDurationInSeconds: 2 + +security.basic.enabled=false + +#To maintain oauth filter ordering +security.oauth2.resource.filter-order=3 diff --git a/auth-service/target/classes/data.sql b/auth-service/target/classes/data.sql new file mode 100644 index 00000000..4fc93c9a --- /dev/null +++ b/auth-service/target/classes/data.sql @@ -0,0 +1,11 @@ +INSERT INTO oauth_client_details(client_id, client_secret, scope, authorized_grant_types, authorities, access_token_validity, refresh_token_validity) +VALUES ('trusted-app', '$2a$08$V0jF.bqEvkO9EhpSEfXi5OZ2JAygrAyz/8X3BLCKHgc2pPyBi4Spy', 'read,write,trust', 'client_credentials,authorization_code,implicit,password,refresh_token','ROLE_CLIENT,ROLE_TRUSTED_CLIENT','45000', '45000'); + +INSERT INTO `users` (`user_id`, `email`, `first_name`, `last_name`, `mobile`, `password`, `user_name`) +VALUES(123456,'demo@example.com', 'Demo', 'Example', '1234578632', '$2a$08$V0jF.bqEvkO9EhpSEfXi5OZ2JAygrAyz/8X3BLCKHgc2pPyBi4Spy', 'demo'); + +INSERT INTO `roles` (`role_id`, `role_name`) +VALUES(1234, 'ROLE_DEMO'); + +INSERT INTO `user_roles` (`role_id`, `user_id`) +VALUES(1234, 123456); \ No newline at end of file diff --git a/auth-service/target/classes/schema.sql b/auth-service/target/classes/schema.sql new file mode 100644 index 00000000..f856f289 --- /dev/null +++ b/auth-service/target/classes/schema.sql @@ -0,0 +1,69 @@ +CREATE TABLE oauth_client_details ( + client_id VARCHAR(255) PRIMARY KEY, + resource_ids VARCHAR(255), + client_secret VARCHAR(255), + scope VARCHAR(255), + authorized_grant_types VARCHAR(255), + web_server_redirect_uri VARCHAR(255), + authorities VARCHAR(255), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additional_information VARCHAR(255), + autoapprove VARCHAR(255) + ); + +CREATE TABLE oauth_client_token ( + token_id VARCHAR(255), + token BLOB, + authentication_id VARCHAR(255), + user_name VARCHAR(255), + client_id VARCHAR(255) +); + +CREATE TABLE oauth_access_token ( + token_id VARCHAR(255), + token BLOB, + authentication_id VARCHAR(255), + user_name VARCHAR(255), + client_id VARCHAR(255), + authentication BLOB, + refresh_token VARCHAR(255) +); +CREATE TABLE oauth_refresh_token ( + token_id VARCHAR(255), + token BLOB, + authentication BLOB +); +CREATE TABLE oauth_code ( + code VARCHAR(255), authentication BLOB +); + +CREATE TABLE `users` ( + `user_id` int(10) unsigned NOT NULL, + `email` varchar(45) DEFAULT NULL, + `first_name` varchar(45) DEFAULT NULL, + `last_name` varchar(45) DEFAULT NULL, + `mobile` varchar(15) DEFAULT NULL, + `password` varchar(255) NOT NULL, + `user_name` varchar(45) NOT NULL, + PRIMARY KEY (`user_id`), + UNIQUE KEY `UK_k8d0f2n7n88w1a16yhua64onx` (`user_name`), + UNIQUE KEY `UK_6dotkott2kjsp8vw4d0m25fb7` (`email`), + UNIQUE KEY `UK_63cf888pmqtt5tipcne79xsbm` (`mobile`) +); + +CREATE TABLE `roles` ( + `role_id` smallint(5) unsigned NOT NULL, + `role_name` varchar(45) NOT NULL, + PRIMARY KEY (`role_id`), + UNIQUE KEY `UK3cyq9kgtpbol1tlouij82oufa` (`role_id`,`role_name`), + UNIQUE KEY `UK_716hgxp60ym1lifrdgp67xt5k` (`role_name`) +); + +CREATE TABLE `user_roles` ( + `user_id` int(10) unsigned NOT NULL, + `role_id` smallint(5) unsigned NOT NULL, + PRIMARY KEY (`user_id`,`role_id`), + CONSTRAINT `FKh8ciramu9cc9q3qcqiv4ue8a6` FOREIGN KEY (`role_id`) REFERENCES `roles` (`role_id`), + CONSTRAINT `FKhfh9dx7w3ubf1co1vdev94g3f` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) +); diff --git a/auth-service/target/classes/sql/data.sql b/auth-service/target/classes/sql/data.sql new file mode 100644 index 00000000..d725ce2f --- /dev/null +++ b/auth-service/target/classes/sql/data.sql @@ -0,0 +1,32 @@ + +DELETE FROM oauth_client_details; + +INSERT INTO user (username, email, password, activated) +SELECT * FROM (SELECT 'admin', 'admin@admin.com', '$2a$10$r0RFDmpneBVryx.ihHK9gu6FFJQi4nTxQUqzdSTvrPpaKZMxigqpy', true) AS tmp +WHERE NOT EXISTS ( + SELECT username FROM user WHERE username = 'admin' +) LIMIT 1; + +INSERT INTO authority (name) +SELECT * FROM (SELECT 'ROLE_USER') AS tmp +WHERE NOT EXISTS ( + SELECT name FROM authority WHERE name = 'ROLE_USER' +) LIMIT 1; + +INSERT INTO authority (name) +SELECT * FROM (SELECT 'ROLE_ADMIN') AS tmp +WHERE NOT EXISTS ( + SELECT name FROM authority WHERE name = 'ROLE_ADMIN' +) LIMIT 1; + +INSERT INTO user_authority (username, authority) +SELECT * FROM (SELECT 'admin', 'ROLE_USER') AS tmp +WHERE NOT EXISTS ( + SELECT username, authority FROM user_authority WHERE username = 'admin' and authority = 'ROLE_USER' +) LIMIT 1; + +INSERT INTO user_authority (username, authority) +SELECT * FROM (SELECT 'admin', 'ROLE_ADMIN') AS tmp +WHERE NOT EXISTS ( + SELECT username, authority FROM user_authority WHERE username = 'admin' and authority = 'ROLE_ADMIN' +) LIMIT 1; \ No newline at end of file diff --git a/auth-service/target/classes/sql/schema.sql b/auth-service/target/classes/sql/schema.sql new file mode 100644 index 00000000..ac56e40f --- /dev/null +++ b/auth-service/target/classes/sql/schema.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS user ( + username VARCHAR(50) NOT NULL PRIMARY KEY, + email VARCHAR(50), + password VARCHAR(500), + activated BOOLEAN DEFAULT FALSE, + activationkey VARCHAR(50) DEFAULT NULL, + resetpasswordkey VARCHAR(50) DEFAULT NULL +); + +CREATE TABLE IF NOT EXISTS authority ( + name VARCHAR(50) NOT NULL PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS user_authority ( + username VARCHAR(50) NOT NULL, + authority VARCHAR(50) NOT NULL, + FOREIGN KEY (username) REFERENCES user (username), + FOREIGN KEY (authority) REFERENCES authority (name), + UNIQUE INDEX user_authority_idx_1 (username, authority) +); + +CREATE TABLE IF NOT EXISTS oauth_access_token ( + token_id VARCHAR(256) DEFAULT NULL, + token BLOB, + authentication_id VARCHAR(256) DEFAULT NULL, + user_name VARCHAR(256) DEFAULT NULL, + client_id VARCHAR(256) DEFAULT NULL, + authentication BLOB, + refresh_token VARCHAR(256) DEFAULT NULL +); + +CREATE TABLE IF NOT EXISTS oauth_refresh_token ( + token_id VARCHAR(256) DEFAULT NULL, + token BLOB, + authentication BLOB +); + +CREATE TABLE IF NOT EXISTS oauth_client_details ( + client_id VARCHAR(255) PRIMARY KEY, + resource_ids VARCHAR(255), + client_secret VARCHAR(255), + scope VARCHAR(255), + authorized_grant_types VARCHAR(255), + web_server_redirect_uri VARCHAR(255), + authorities VARCHAR(255), + access_token_validity INTEGER, + refresh_token_validity INTEGER, + additional_information VARCHAR(4096), + autoapprove VARCHAR(255) +); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..77895966 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,105 @@ +version: '3' +services: + + mysql-expenseManagement: + image: mysql:5.7 + container_name: mysql + expose: + - 3306 + ports: + - 3306:3306 + environment: + - MYSQL_ROOT_PASSWORD=root + volumes: + - /data/mysql + + solr: + image: adslima/solr:1.1 + container_name: solr + expose: + - '8983' + entrypoint: + - docker-entrypoint.sh + - solr-precreate + - categories-core + ports: + - 8983:8983 + + rabbitmq: + image: rabbitmq:management + container_name: rabbit + expose: + - '15672' + ports: + - 5672:5672 + - 15672:15672 + + eureka: + build: ./service-registry + container_name: eureka + ports: + - 8761:8761 + + zuul: + build: ./service-gateway + container_name: zuul + ports: + - 8765:8765 + expose: + - 8765 + environment: + EUREKA_IP: eureka + depends_on: + - eureka + + oauth: + build: ./auth-service + container_name: oauth + ports: + - 8787:8787 + expose: + - '8787' + environment: + EUREKA_IP: eureka + depends_on: + - eureka + + expense-management-command: + build: ./expense-management-command + container_name: command + ports: + - 8081:8081 + expose: + - '8081' + environment: + MYSQL_IP: mysql-expenseManagement + EUREKA_IP: eureka + OAUTH_IP: oauth + SOLR_IP: solr + RABBITMQ_IP: rabbitmq + depends_on: + - eureka + - mysql-expenseManagement + - solr + - rabbitmq + - oauth + + expense-management-query: + build: ./expense-management-query + container_name: query + ports: + - 8082:8082 + expose: + - '8082' + environment: + MYSQL_IP: mysql-expenseManagement + EUREKA_IP: eureka + OAUTH_IP: oauth + SOLR_IP: solr + RABBITMQ_IP: rabbitmq + depends_on: + - eureka + - mysql-expenseManagement + - solr + - rabbitmq + - oauth \ No newline at end of file diff --git a/expense-management-command/.gitignore b/expense-management-command/.gitignore new file mode 100644 index 00000000..82eca336 --- /dev/null +++ b/expense-management-command/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/expense-management-command/Dockerfile b/expense-management-command/Dockerfile new file mode 100644 index 00000000..2767dfcc --- /dev/null +++ b/expense-management-command/Dockerfile @@ -0,0 +1,9 @@ +FROM java:8-jre-alpine + +RUN mkdir -p /expense-management-command +ADD target/expense-management-command-0.0.1-SNAPSHOT.jar /expense-management-command +ADD target/classes/bootstrap.yml /expense-management-command + +WORKDIR /expense-management-command + +ENTRYPOINT ["java", "-jar", "expense-management-command-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/expense-management-command/pom.xml b/expense-management-command/pom.xml new file mode 100644 index 00000000..3a129d52 --- /dev/null +++ b/expense-management-command/pom.xml @@ -0,0 +1,177 @@ + + + 4.0.0 + + br.com.adslima + expense-management-command + 0.0.1-SNAPSHOT + jar + + expense-management-command + Expense-Management-Command + + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + + + + + UTF-8 + UTF-8 + 1.8 + 3.3.4 + 1.18.2 + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + 2.0.0.RELEASE + + + + org.springframework.cloud + spring-cloud-commons + 2.0.0.RELEASE + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-jetty + + + + + + com.h2database + h2 + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.axonframework + axon-spring-boot-starter + ${axon.version} + + + org.axonframework + axon-amqp + ${axon.version} + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test + test + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + 2.0.0.RELEASE + + + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + 2.0.0.RELEASE + + + + + com.google.guava + guava + 19.0 + + + + + + + + + + org.springframework.retry + spring-retry + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + + org.axonframework + axon-test + ${axon.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + diff --git a/expense-management-command/src/main/java/br/com/adslima/ExpenseManagementCommandApplication.java b/expense-management-command/src/main/java/br/com/adslima/ExpenseManagementCommandApplication.java new file mode 100644 index 00000000..12f597e2 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/ExpenseManagementCommandApplication.java @@ -0,0 +1,60 @@ +package br.com.adslima; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author andrews.silva + * + */ +@EnableDiscoveryClient +@SpringBootApplication +@EnableResourceServer +@ComponentScan("br.com.adslima") +public class ExpenseManagementCommandApplication { + + public static void main(String[] args) { + SpringApplication.run(ExpenseManagementCommandApplication.class, args); + } + + @RestController + class ServiceInstanceRestController { + + @Autowired + private DiscoveryClient discoveryClient; + + @Value("${spring.application.name}") + private String appName; + + @RequestMapping("/instances") + public List serviceInstancesByApplicationName() { + return this.discoveryClient.getInstances(appName); + } + } + + @RefreshScope + @RestController + class MessageRestController { + + @Value("${message}") + private String message; + + @RequestMapping("/message") + String getMessage() { + return this.message; + } + } +} diff --git a/expense-management-command/src/main/java/br/com/adslima/aggregate/ExpenseManagementAggregate.java b/expense-management-command/src/main/java/br/com/adslima/aggregate/ExpenseManagementAggregate.java new file mode 100644 index 00000000..3ed58282 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/aggregate/ExpenseManagementAggregate.java @@ -0,0 +1,91 @@ +package br.com.adslima.aggregate; + +import static org.axonframework.commandhandling.model.AggregateLifecycle.apply; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import org.axonframework.commandhandling.CommandHandler; +import org.axonframework.commandhandling.model.AggregateIdentifier; +import org.axonframework.eventsourcing.EventSourcingHandler; +import org.axonframework.spring.stereotype.Aggregate; +import org.springframework.util.Assert; + +import br.com.adslima.command.AddExpenseManagementCommand; +import br.com.adslima.command.UpdateCategoryExpenseManagementCommand; +import br.com.adslima.events.ExpenseManagementAddedEvent; +import br.com.adslima.events.ExpenseManagementCategoryUpdatedEvent; +import br.com.adslima.model.ExpenseCategory; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author andrews.silva + * + */ +@Slf4j +@Getter +@Aggregate +@NoArgsConstructor +public class ExpenseManagementAggregate { + + @AggregateIdentifier + private String id; + private Integer userCode; + private String description; + private LocalDateTime date; + private BigDecimal value; + private ExpenseCategory category; + + @CommandHandler + public ExpenseManagementAggregate(AddExpenseManagementCommand cmd) { + log.info("Handling {} command: {}", cmd.getClass().getSimpleName(), cmd); + + Assert.hasLength(cmd.getId(), "Id não deve estar vazio ou nulo."); + Assert.notNull(cmd.getUserCode(), "UserCode não deve estar vazio ou nulo."); + Assert.hasLength(cmd.getDescription(), "Description não deve estar vazio ou nulo."); + Assert.notNull(cmd.getDate(), "Date não deve estar vazio ou nulo."); + Assert.notNull(cmd.getValue(), "Value não deve estar vazio ou nulo."); + + apply(new ExpenseManagementAddedEvent(cmd.getId(), cmd.getUserCode(), cmd.getDescription(), cmd.getDate(), + cmd.getValue(), cmd.getCategory())); + + log.info("Done handling {} command: {}", cmd.getClass().getSimpleName(), cmd); + } + + @CommandHandler + public void handle(UpdateCategoryExpenseManagementCommand cmd) { + log.info("Handling {} command: {}", cmd.getClass().getSimpleName(), cmd); + + Assert.hasLength(cmd.getCardExpenseId(), "ExpenseCardId não deve estar vazio ou nulo."); + Assert.notNull(cmd.getCategory(), "Category não deve estar vazio ou nulo."); + + apply(new ExpenseManagementCategoryUpdatedEvent(cmd.getCardExpenseId(), cmd.getCategory())); + + log.info("Done handling {} command: {}", cmd.getClass().getSimpleName(), cmd); + } + + @EventSourcingHandler + public void on(ExpenseManagementAddedEvent event) { + log.info("Handling {} event: {}", event.getClass().getSimpleName(), event); + + this.id = event.getId(); + this.userCode = event.getUserCode(); + this.description = event.getDescription(); + this.date = event.getDate(); + this.value = event.getValue(); + + log.info("Done handling {} event: {}", event.getClass().getSimpleName(), event); + } + + @EventSourcingHandler + public void on(ExpenseManagementCategoryUpdatedEvent event) { + + log.info("Handling {} event: {}", event.getClass().getSimpleName(), event); + this.category = event.getCategory(); + log.info("Done handling {} event: {}", event.getClass().getSimpleName(), event); + } + +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/command/AddExpenseManagementCommand.java b/expense-management-command/src/main/java/br/com/adslima/command/AddExpenseManagementCommand.java new file mode 100644 index 00000000..7fc50a65 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/command/AddExpenseManagementCommand.java @@ -0,0 +1,30 @@ +package br.com.adslima.command; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import org.axonframework.commandhandling.TargetAggregateIdentifier; + +import br.com.adslima.model.ExpenseCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * + * @author andrews.silva + * + */ +@Getter +@ToString +@AllArgsConstructor +public class AddExpenseManagementCommand { + + @TargetAggregateIdentifier + private String id; + private Integer userCode; + private String description; + private LocalDateTime date; + private BigDecimal value; + private ExpenseCategory category; +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/command/UpdateCategoryExpenseManagementCommand.java b/expense-management-command/src/main/java/br/com/adslima/command/UpdateCategoryExpenseManagementCommand.java new file mode 100644 index 00000000..0916b686 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/command/UpdateCategoryExpenseManagementCommand.java @@ -0,0 +1,23 @@ +package br.com.adslima.command; + +import org.axonframework.commandhandling.TargetAggregateIdentifier; + +import br.com.adslima.model.ExpenseCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * + * @author andrews.silva + * + */ +@Getter +@ToString +@AllArgsConstructor +public class UpdateCategoryExpenseManagementCommand { + + @TargetAggregateIdentifier + private String cardExpenseId; + private ExpenseCategory category; +} diff --git a/expense-management-command/src/main/java/br/com/adslima/configuration/AmqpEventPublicationConfiguration.java b/expense-management-command/src/main/java/br/com/adslima/configuration/AmqpEventPublicationConfiguration.java new file mode 100644 index 00000000..0998dc93 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/configuration/AmqpEventPublicationConfiguration.java @@ -0,0 +1,47 @@ +package br.com.adslima.configuration; + +import org.springframework.amqp.core.AmqpAdmin; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.Exchange; +import org.springframework.amqp.core.ExchangeBuilder; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.QueueBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + * @author andrews.silva + * + */ +@Configuration +public class AmqpEventPublicationConfiguration { + + @Value("${axon.amqp.exchange:expense-management.events}") + String exchangeName; + + @Bean + public Exchange exchange() { + return ExchangeBuilder.fanoutExchange(exchangeName).build(); + } + + @Bean + public Queue queue() { + return QueueBuilder.durable(exchangeName).build(); + } + + @Bean + public Binding binding(Queue queue, Exchange exchange) { + return BindingBuilder.bind(queue).to(exchange).with("*").noargs(); + } + + @Autowired + public void configure(AmqpAdmin amqpAdmin, Exchange exchange, Queue queue, Binding binding) { + amqpAdmin.declareExchange(exchange); + amqpAdmin.declareQueue(queue); + amqpAdmin.declareBinding(binding); + } +} diff --git a/expense-management-command/src/main/java/br/com/adslima/controller/EventController.java b/expense-management-command/src/main/java/br/com/adslima/controller/EventController.java new file mode 100644 index 00000000..7227379b --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/controller/EventController.java @@ -0,0 +1,33 @@ +package br.com.adslima.controller; + +import java.util.List; +import java.util.stream.Collectors; + +import org.axonframework.eventsourcing.eventstore.EventStore; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.AllArgsConstructor; + +/** + * + * @author andrews.silva + * + */ +@RestController +@AllArgsConstructor +@RequestMapping("/events") +public class EventController { + + private EventStore eventStore; + + @GetMapping + @RequestMapping("/{aggregateId}") + @Transactional(readOnly = true) + public List listEvents(@PathVariable String aggregateId) { + return eventStore.readEvents(aggregateId).asStream().collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/controller/ExpenseManagementController.java b/expense-management-command/src/main/java/br/com/adslima/controller/ExpenseManagementController.java new file mode 100644 index 00000000..3dccc65b --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/controller/ExpenseManagementController.java @@ -0,0 +1,89 @@ +package br.com.adslima.controller; + +import java.util.UUID; + +import org.axonframework.commandhandling.gateway.CommandGateway; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import br.com.adslima.command.AddExpenseManagementCommand; +import br.com.adslima.command.UpdateCategoryExpenseManagementCommand; +import br.com.adslima.dto.ExpenseManagementDTO; +import br.com.adslima.response.Response; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author andrews.silva + * + */ +@Slf4j +@RestController +@RequestMapping("/api-command") +@AllArgsConstructor +public class ExpenseManagementController { + + @Autowired + private CommandGateway commandGateway; + + /** + * + * @param dto + * @param result + * @return + */ + @PostMapping + public ResponseEntity> add(@RequestBody final ExpenseManagementDTO dto, + final BindingResult result) { + + AddExpenseManagementCommand command = new AddExpenseManagementCommand(UUID.randomUUID().toString(), + dto.getUserCode(), dto.getDescription(), dto.getDate(), dto.getValue(), dto.getCategory()); + + log.info("Executing command: {}", command.toString()); + + final Response response = new Response(); + + if (result.hasErrors()) { + + log.error("Erro ao adicionar um gasto com cartão: {}", result.getAllErrors()); + + result.getAllErrors().forEach(error -> response.getErrors().add(error.getDefaultMessage())); + return ResponseEntity.badRequest().body(response); + } + this.commandGateway.send(command); + response.setData(command); + + return ResponseEntity.ok().body(response); + } + + @PutMapping("/{id}/categories") + public ResponseEntity> updateCategory( + @PathVariable final String id, @RequestBody final ExpenseManagementDTO dto, final BindingResult result) { + + UpdateCategoryExpenseManagementCommand command = new UpdateCategoryExpenseManagementCommand(id, + dto.getCategory()); + + log.info("Executing command: {}", command.toString()); + + final Response response = new Response(); + + if (result.hasErrors()) { + log.error("erro ao atualizar uma categoria: {}", result.getAllErrors()); + + result.getAllErrors().forEach(error -> response.getErrors().add(error.getDefaultMessage())); + return ResponseEntity.badRequest().body(response); + } + commandGateway.send(command); + response.setData(command); + + return ResponseEntity.ok().body(response); + } +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/dto/ExpenseManagementDTO.java b/expense-management-command/src/main/java/br/com/adslima/dto/ExpenseManagementDTO.java new file mode 100644 index 00000000..f4c2dfee --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/dto/ExpenseManagementDTO.java @@ -0,0 +1,50 @@ +/** + * + */ +package br.com.adslima.dto; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import org.springframework.format.annotation.DateTimeFormat; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; + +import br.com.adslima.model.ExpenseCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author andrews.silva + * + */ +@Setter +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +@JsonAutoDetect(fieldVisibility = Visibility.ANY) +public class ExpenseManagementDTO implements Serializable{ + + /** + * + */ + private static final long serialVersionUID = -7315276288826601575L; + /** + * + */ + + private String id; + private Integer userCode; + private String description; + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime date; + private BigDecimal value; + private ExpenseCategory category; + +} diff --git a/expense-management-command/src/main/java/br/com/adslima/events/AbstractCommonsEvent.java b/expense-management-command/src/main/java/br/com/adslima/events/AbstractCommonsEvent.java new file mode 100644 index 00000000..560f5499 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/events/AbstractCommonsEvent.java @@ -0,0 +1,31 @@ +/** + * + */ +package br.com.adslima.events; + +import java.io.Serializable; + +/** + * @author andrews.silva + * + */ +public abstract class AbstractCommonsEvent implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private String id; + + public AbstractCommonsEvent() { + } + + public AbstractCommonsEvent(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/events/ExpenseManagementAddedEvent.java b/expense-management-command/src/main/java/br/com/adslima/events/ExpenseManagementAddedEvent.java new file mode 100644 index 00000000..14b1d0aa --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/events/ExpenseManagementAddedEvent.java @@ -0,0 +1,34 @@ +/** + * + */ +package br.com.adslima.events; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import br.com.adslima.model.ExpenseCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author andrews.silva + * + */ +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class ExpenseManagementAddedEvent { + + private String id; + private Integer userCode; + private String description; + private LocalDateTime date; + private BigDecimal value; + private ExpenseCategory category; + +} diff --git a/expense-management-command/src/main/java/br/com/adslima/events/ExpenseManagementCategoryUpdatedEvent.java b/expense-management-command/src/main/java/br/com/adslima/events/ExpenseManagementCategoryUpdatedEvent.java new file mode 100644 index 00000000..f64624c7 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/events/ExpenseManagementCategoryUpdatedEvent.java @@ -0,0 +1,26 @@ +/** + * + */ +package br.com.adslima.events; + +import br.com.adslima.model.ExpenseCategory; +/** + * @author andrews.silva + * + */ +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * @author andrews.silva + * + */ +@Getter +@ToString +@AllArgsConstructor +public class ExpenseManagementCategoryUpdatedEvent { + + private String cardExpenseId; + private ExpenseCategory category; +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/exceptions/CustomizedResponseExceptionHandler.java b/expense-management-command/src/main/java/br/com/adslima/exceptions/CustomizedResponseExceptionHandler.java new file mode 100644 index 00000000..3c6bc8e3 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/exceptions/CustomizedResponseExceptionHandler.java @@ -0,0 +1,30 @@ +package br.com.adslima.exceptions; + +import java.time.LocalDateTime; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** + * @author andrews.silva + * + */ +@ControllerAdvice +@RestController +public class CustomizedResponseExceptionHandler extends ResponseEntityExceptionHandler { + + private static final int BAD_REQUEST = 400; + + @ExceptionHandler(InvalidExpenseCategoryException.class) + public final ResponseEntity handleExpenseNotFoundException( + final InvalidExpenseCategoryException ex, final WebRequest request) { + final ExceptionResponse exceptionResponse = new ExceptionResponse(BAD_REQUEST, LocalDateTime.now(), + ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST); + } +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/exceptions/ExceptionResponse.java b/expense-management-command/src/main/java/br/com/adslima/exceptions/ExceptionResponse.java new file mode 100644 index 00000000..d6a68be9 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/exceptions/ExceptionResponse.java @@ -0,0 +1,27 @@ +package br.com.adslima.exceptions; + +import java.time.LocalDateTime; + +import lombok.Getter; + +/** + * @author andrews.silva + * + */ +@Getter +public class ExceptionResponse { + + private final int code; + private final LocalDateTime timestamp; + private final String message; + private final String details; + + public ExceptionResponse(final int code, final LocalDateTime timestamp, final String message, + final String details) { + super(); + this.code = code; + this.timestamp = timestamp; + this.message = message; + this.details = details; + } +} \ No newline at end of file diff --git a/expense-management-command/src/main/java/br/com/adslima/exceptions/InvalidExpenseCategoryException.java b/expense-management-command/src/main/java/br/com/adslima/exceptions/InvalidExpenseCategoryException.java new file mode 100644 index 00000000..a640fe40 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/exceptions/InvalidExpenseCategoryException.java @@ -0,0 +1,21 @@ +/** + * + */ +package br.com.adslima.exceptions; + +/** + * @author andrews.silva + * + */ +public class InvalidExpenseCategoryException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public InvalidExpenseCategoryException(String message) { + super(message); + } + +} diff --git a/expense-management-command/src/main/java/br/com/adslima/model/ExpenseCategory.java b/expense-management-command/src/main/java/br/com/adslima/model/ExpenseCategory.java new file mode 100644 index 00000000..083d6e52 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/model/ExpenseCategory.java @@ -0,0 +1,43 @@ +package br.com.adslima.model; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; + +import br.com.adslima.exceptions.InvalidExpenseCategoryException; +import lombok.Data; + +/** + * @author andrews.silva + * + */ +@Data +@JsonAutoDetect(fieldVisibility = Visibility.ANY) +public class ExpenseCategory { + + private String description; + + public ExpenseCategory(String category) { + if (!isValid(category)) + throw new InvalidExpenseCategoryException("Invalid Expense Category"); + this.description = category; + } + + public static ExpenseCategory of(String category) { + return new ExpenseCategory(category); + } + + private boolean isValid(String category) { + return category != null; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return description; + } + +} diff --git a/expense-management-command/src/main/java/br/com/adslima/response/Response.java b/expense-management-command/src/main/java/br/com/adslima/response/Response.java new file mode 100644 index 00000000..c9081708 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/response/Response.java @@ -0,0 +1,34 @@ +package br.com.adslima.response; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * + * @author andrews.silva + * + * @param + */ +@Setter +@Getter +@NoArgsConstructor +public class Response { + + private T data; + private List errors; + + /** + * @return the errors + */ + public List getErrors() { + if (this.errors == null) { + this.errors = new ArrayList(); + } + return errors; + } + +} diff --git a/expense-management-command/src/main/java/br/com/adslima/security/SecurityConfiguration.java b/expense-management-command/src/main/java/br/com/adslima/security/SecurityConfiguration.java new file mode 100644 index 00000000..82a58d25 --- /dev/null +++ b/expense-management-command/src/main/java/br/com/adslima/security/SecurityConfiguration.java @@ -0,0 +1,55 @@ +package br.com.adslima.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; + +/** + * + * @author andrews.silva + * + */ +@Configuration +@EnableResourceServer +public class SecurityConfiguration extends ResourceServerConfigurerAdapter { + + private final static String resourceId = "resources"; + + /* + * (non-Javadoc) + * + * @see + * org.springframework.security.oauth2.config.annotation.web.configuration. + * ResourceServerConfigurerAdapter#configure(org.springframework.security. + * config.annotation.web.builders.HttpSecurity) + */ + @Override + public void configure(HttpSecurity http) throws Exception { +// http.requestMatchers().antMatchers("/**").and().authorizeRequests().anyRequest().authenticated() +// .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')") +// .antMatchers(HttpMethod.OPTIONS, "/**").access("#oauth2.hasScope('read')") +// .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") +// .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") +// .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')") +// .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')"); + + http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); + http.httpBasic().disable(); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.security.oauth2.config.annotation.web.configuration. + * ResourceServerConfigurerAdapter#configure(org.springframework.security. + * oauth2.config.annotation.web.configurers. + * ResourceServerSecurityConfigurer) + */ + @Override + public void configure(ResourceServerSecurityConfigurer resources) { + resources.resourceId(resourceId); + } +} diff --git a/expense-management-command/src/main/resources/application-test.properties b/expense-management-command/src/main/resources/application-test.properties new file mode 100644 index 00000000..61f03283 --- /dev/null +++ b/expense-management-command/src/main/resources/application-test.properties @@ -0,0 +1,6 @@ +# configura banco in memory H2 +spring.jpa.hibernate.ddl-auto=create +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa \ No newline at end of file diff --git a/expense-management-command/src/main/resources/bootstrap.yml b/expense-management-command/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..d8b7e6e9 --- /dev/null +++ b/expense-management-command/src/main/resources/bootstrap.yml @@ -0,0 +1,65 @@ +message: Greetings from the EXPENSE_MAMAGEMENT-COMMAND microservice. + +server: + port: 8081 + + servlet: + context-path: /commands + +spring: + application: + name: expense-management-command + + rabbitmq: + host: ${RABBITMQ_IP:localhost} + + data: + solr: + host: ${SOLR_IP:localhost} + +eureka: + instance: + leaseRenewalIntervalInSeconds: 1 + leaseExpirationDurationInSeconds: 2 + client: + serviceUrl: + defaultZone: http://${EUREKA_IP:localhost}:8761/eureka/ + healthcheck: + enabled: true + lease: + duration: 5 + +security: + oauth2: + resource: + userInfoUri: http://${OAUTH_IP:localhost}:8787/auth-api/users/authenticate + + datasource: + driver-class-name: com.mysql.jdbc.Driver + password: ' ' + platform: mysql + url: jdbc:mysql://${MYSQL_IP:localhost}:3306/managementcommand?Peoples?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 + username: root + + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + generate-ddl: true + hibernate: + ddl-auto: update + show-sql: true + +axon: + amqp: + exchange: expense-management.events + eventhandling: + processors: + amqpEvents: + source: complaintEventsMethod + +logging: + level: + root: INFO + org.springframework: INFO + org.axonframework: INFO + br.com.adslima: DEBUG + file: ./build/logs/expense-management-command.log \ No newline at end of file diff --git a/expense-management-command/src/test/java/br/com/adslima/ExpenseManagementCommandApplicationTests.java b/expense-management-command/src/test/java/br/com/adslima/ExpenseManagementCommandApplicationTests.java new file mode 100644 index 00000000..62d0d9cb --- /dev/null +++ b/expense-management-command/src/test/java/br/com/adslima/ExpenseManagementCommandApplicationTests.java @@ -0,0 +1,18 @@ +package br.com.adslima; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("test") +public class ExpenseManagementCommandApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/expense-management-command/src/test/java/br/com/adslima/aggregates/ExpenseManagamentAggregateTest.java b/expense-management-command/src/test/java/br/com/adslima/aggregates/ExpenseManagamentAggregateTest.java new file mode 100644 index 00000000..1bdbf13e --- /dev/null +++ b/expense-management-command/src/test/java/br/com/adslima/aggregates/ExpenseManagamentAggregateTest.java @@ -0,0 +1,131 @@ +package br.com.adslima.aggregates; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +import org.axonframework.commandhandling.model.AggregateNotFoundException; +import org.axonframework.test.aggregate.AggregateTestFixture; +import org.axonframework.test.aggregate.FixtureConfiguration; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import br.com.adslima.aggregate.ExpenseManagementAggregate; +import br.com.adslima.command.AddExpenseManagementCommand; +import br.com.adslima.command.UpdateCategoryExpenseManagementCommand; +import br.com.adslima.dto.ExpenseManagementDTO; +import br.com.adslima.events.ExpenseManagementAddedEvent; +import br.com.adslima.events.ExpenseManagementCategoryUpdatedEvent; +import br.com.adslima.model.ExpenseCategory; + +/** + * + * @author andrews.silva + * + */ +@RunWith(MockitoJUnitRunner.class) +public class ExpenseManagamentAggregateTest { + + private FixtureConfiguration fixture; + + String id = UUID.randomUUID().toString(); + LocalDateTime date = LocalDateTime.now(); + + @Before + public void setUp() throws Exception { + fixture = new AggregateTestFixture<>(ExpenseManagementAggregate.class); + + } + + /** + * teste o envio e de um Evento + * + * @throws Exception + */ + @Test + public void testAddExpenseManagementOK() throws Exception { + + ExpenseManagementDTO dto = ObjectExpenseManagement(id, date); + + fixture.given() + .when(new AddExpenseManagementCommand(dto.getId(), dto.getUserCode(), dto.getDescription(), + dto.getDate(), dto.getValue(), dto.getCategory())) + .expectEvents(new ExpenseManagementAddedEvent(dto.getId(), dto.getUserCode(), + dto.getDescription(), dto.getDate(), dto.getValue(), dto.getCategory())); + } + + /** + * Testa passa de campo passando nulo + * + * @throws Exception + */ + @Test + public void testAddExpenseManagementFieldFull() throws Exception { + + ExpenseManagementDTO dto = ObjectExpenseManagement(id, date); + + fixture.given() + .when(new AddExpenseManagementCommand(dto.getId(), null, dto.getDescription(), dto.getDate(), + dto.getValue(), dto.getCategory())) + .expectExceptionMessage("UserCode não deve estar vazio ou nulo."); + } + + /** + * Valida lançamento de exception + * + * @throws Exception + */ + @Test + public void testAggregateNotFoundException() throws Exception { + + ExpenseManagementDTO dto = ObjectExpenseManagement(id, date); + + fixture.given().when(new UpdateCategoryExpenseManagementCommand(dto.getId(), ExpenseCategory.of("Restaurante"))) + .expectException(AggregateNotFoundException.class); + } + + /** + * Valida a atualização da categoria + * + * @throws Exception + */ + @Test + public void testExpenseManagementCategoryUpdatedEvent() throws Exception { + + String id = UUID.randomUUID().toString(); + LocalDateTime date = LocalDateTime.now(); + + ExpenseManagementDTO dto = ObjectExpenseManagement(id, date); + + ExpenseManagementAggregate expenseManagement = new ExpenseManagementAggregate(); + + expenseManagement.on(new ExpenseManagementAddedEvent(dto.getId(), dto.getUserCode(), dto.getDescription(), + dto.getDate(), dto.getValue(), dto.getCategory())); + expenseManagement + .on(new ExpenseManagementCategoryUpdatedEvent(dto.getId(), ExpenseCategory.of("Restaurante"))); + + assertEquals("Restaurante", expenseManagement.getCategory().toString()); + + } + + /** + * @param id + * @param date + * @return + */ + private ExpenseManagementDTO ObjectExpenseManagement(String id, LocalDateTime date) { + ExpenseManagementDTO dto = new ExpenseManagementDTO(); + dto.setId(id); + dto.setUserCode(12345); + dto.setDescription("ExpenseManagement Test"); + dto.setDate(date); + dto.setValue(BigDecimal.ONE); + dto.setCategory(null); + return dto; + } + +} \ No newline at end of file diff --git a/expense-management-command/src/test/java/br/com/adslima/controller/ExpenseManagementControllerTest.java b/expense-management-command/src/test/java/br/com/adslima/controller/ExpenseManagementControllerTest.java new file mode 100644 index 00000000..59c3988b --- /dev/null +++ b/expense-management-command/src/test/java/br/com/adslima/controller/ExpenseManagementControllerTest.java @@ -0,0 +1,158 @@ +package br.com.adslima.controller; + +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +import org.axonframework.commandhandling.gateway.CommandGateway; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import br.com.adslima.command.AddExpenseManagementCommand; +import br.com.adslima.dto.ExpenseManagementDTO; + +/** + * + * @author andrews.silva + * + */ +@RunWith(SpringRunner.class) +@SpringBootConfiguration +@ActiveProfiles("test") +public class ExpenseManagementControllerTest { + + private static final Integer USER_CODE = 12345; + private static final String URL_BASE = "/api-command"; + + final String id = UUID.randomUUID().toString(); + final LocalDateTime date = LocalDateTime.now(); + + private MockMvc mockMvc; + + @MockBean + CommandGateway commandGateway; + + @InjectMocks + ExpenseManagementController expenseManagementController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mockMvc = MockMvcBuilders.standaloneSetup(expenseManagementController).build(); + } + + /** + * + * @throws Exception + */ + @Test + public void testAddExpenseManagementOK() throws Exception { + + ExpenseManagementDTO dto = ObjectExpenseManagement(id, date); + + AddExpenseManagementCommand command = new AddExpenseManagementCommand(dto.getId(), dto.getUserCode(), + dto.getDescription(), dto.getDate(), dto.getValue(), dto.getCategory()); + + BDDMockito.given(commandGateway.send(command)).willReturn(null); + + mockMvc.perform(MockMvcRequestBuilders.post(URL_BASE).content(this.getJsonRequisicaoPost()) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.data.userCode").value(dto.getUserCode())) + .andExpect(jsonPath("$.data.description").value(dto.getDescription())) + .andExpect(jsonPath("$.data.date").value(dto.getDate())) + .andExpect(jsonPath("$.data.value").value(dto.getValue())) + .andExpect(jsonPath("$.data.category").value(dto.getCategory())) + .andExpect(jsonPath("$.errors").isEmpty()).andDo(print()).andReturn(); + } + + /** + * + * @throws Exception + */ + @Test + public void testAddExpenseManagementNOK() throws Exception { + + ExpenseManagementDTO dto = ObjectExpenseManagement(id, date); + + AddExpenseManagementCommand command = new AddExpenseManagementCommand(dto.getId(), dto.getUserCode(), + dto.getDescription(), dto.getDate(), dto.getValue(), dto.getCategory()); + + BDDMockito.given(commandGateway.send(command)).willReturn(null); + + mockMvc.perform(MockMvcRequestBuilders.post(URL_BASE).content(this.getJsonRequisicaoPostBad()) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()).andDo(print()).andReturn(); + } + + /** + * + * @return + * @throws JsonProcessingException + */ + private String getJsonRequisicaoPost() throws JsonProcessingException { + ExpenseManagementDTO expenseDto = new ExpenseManagementDTO(); + expenseDto.setId(id); + expenseDto.setUserCode(USER_CODE); + expenseDto.setDescription("ExpenseManagement Test"); + expenseDto.setDate(null); + expenseDto.setValue(BigDecimal.ONE); + expenseDto.setCategory(null); + + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(expenseDto); + } + + /** + * + * @return + * @throws JsonProcessingException + */ + private String getJsonRequisicaoPostBad() throws JsonProcessingException { + ExpenseManagementDTO expenseDto = new ExpenseManagementDTO(); + expenseDto.setId(null); + expenseDto.setUserCode(USER_CODE); + expenseDto.setDescription("ExpenseManagement Test"); + expenseDto.setDate(date); + expenseDto.setValue(BigDecimal.ONE); + expenseDto.setCategory(null); + + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(expenseDto); + } + + /** + * @param id + * @param date + * @return + */ + private ExpenseManagementDTO ObjectExpenseManagement(String id, LocalDateTime date) { + ExpenseManagementDTO dto = new ExpenseManagementDTO(); + dto.setId(id); + dto.setUserCode(12345); + dto.setDescription("ExpenseManagement Test"); + dto.setDate(null); + dto.setValue(BigDecimal.ONE); + dto.setCategory(null); + return dto; + } + +} diff --git a/expense-management-query/.gitignore b/expense-management-query/.gitignore new file mode 100644 index 00000000..82eca336 --- /dev/null +++ b/expense-management-query/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/expense-management-query/Dockerfile b/expense-management-query/Dockerfile new file mode 100644 index 00000000..b7155b95 --- /dev/null +++ b/expense-management-query/Dockerfile @@ -0,0 +1,9 @@ +FROM java:8-jre-alpine + +RUN mkdir -p /expense-management-query +ADD target/expense-management-query-0.0.1-SNAPSHOT.jar /expense-management-query +ADD target/classes/bootstrap.yml /expense-management-query + +WORKDIR /expense-management-query + +ENTRYPOINT ["java", "-jar", "expense-management-query-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/expense-management-query/pom.xml b/expense-management-query/pom.xml new file mode 100644 index 00000000..f719ad91 --- /dev/null +++ b/expense-management-query/pom.xml @@ -0,0 +1,165 @@ + + + 4.0.0 + + br.com.adslima + expense-management-query + 0.0.1-SNAPSHOT + jar + + expense-management-query + Expense Management Query + + + org.springframework.boot + spring-boot-starter-parent + 2.0.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + 3.3.4 + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + 2.0.0.RELEASE + + + + org.springframework.cloud + spring-cloud-starter-netflix-zuul + 2.0.0.RELEASE + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + 2.0.0.RELEASE + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.cloud + spring-cloud-commons + 2.0.0.RELEASE + + + + + org.apache.solr + solr-solrj + + + + org.springframework.data + spring-data-solr + + + + + + com.h2database + h2 + + + + mysql + mysql-connector-java + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-amqp + + + + org.axonframework + axon-spring-boot-starter + ${axon.version} + + + org.axonframework + axon-amqp + ${axon.version} + + + + org.projectlombok + lombok + provided + + + + org.springframework.boot + spring-boot-starter-test + test + + + + com.google.guava + guava + 19.0 + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + + + + + + + + + + org.axonframework + axon-test + ${axon.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + diff --git a/expense-management-query/src/main/java/br/com/adslima/ExpenseManagementQueryApplication.java b/expense-management-query/src/main/java/br/com/adslima/ExpenseManagementQueryApplication.java new file mode 100644 index 00000000..490d08a9 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/ExpenseManagementQueryApplication.java @@ -0,0 +1,60 @@ +package br.com.adslima; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.ServiceInstance; +import org.springframework.cloud.client.discovery.DiscoveryClient; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author andrews.silva + * + */ +@EnableDiscoveryClient +@SpringBootApplication +@EnableResourceServer +@ComponentScan("br.com.adslima") +public class ExpenseManagementQueryApplication { + + public static void main(String[] args) { + SpringApplication.run(ExpenseManagementQueryApplication.class, args); + } + + @RestController + class ServiceInstanceRestController { + + @Autowired + private DiscoveryClient discoveryClient; + + @Value("${spring.application.name}") + private String appName; + + @RequestMapping("/instances") + public List serviceInstancesByApplicationName() { + return this.discoveryClient.getInstances(appName); + } + } + + @RefreshScope + @RestController + class MessageRestController { + + @Value("${message}") + private String message; + + @RequestMapping("/message") + String getMessage() { + return this.message; + } + } +} diff --git a/expense-management-query/src/main/java/br/com/adslima/components/ExpenseManagementEventProcessor.java b/expense-management-query/src/main/java/br/com/adslima/components/ExpenseManagementEventProcessor.java new file mode 100644 index 00000000..478e7b66 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/components/ExpenseManagementEventProcessor.java @@ -0,0 +1,117 @@ +package br.com.adslima.components; + +import java.util.List; +import java.util.Optional; + +import org.axonframework.config.ProcessingGroup; +import org.axonframework.eventhandling.EventHandler; +import org.springframework.stereotype.Component; + +import br.com.adslima.events.ExpenseManagementAddedEvent; +import br.com.adslima.events.ExpenseManagementCategoryUpdatedEvent; +import br.com.adslima.exception.ExpenseManagementNotFoundException; +import br.com.adslima.model.Category; +import br.com.adslima.model.ExpenseCategory; +import br.com.adslima.model.ExpenseManagement; +import br.com.adslima.repository.CategoryRepository; +import br.com.adslima.repository.ExpenseManagementRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author andrews.silva + * + */ +@Slf4j +@Component +@AllArgsConstructor +@ProcessingGroup("amqpEvents") +public class ExpenseManagementEventProcessor { + + /** + * + */ + private final ExpenseManagementRepository repository; + + /** + * + */ + private final CategoryRepository categoryRepository; + + /** + * Evento responsavel por adicionar um gasto com cartão. + * + * @param event + */ + @EventHandler + public void on(final ExpenseManagementAddedEvent event) { + + List listCategories = this.categoryRepository.findByExpenseDescription(event.getDescription()); + + Optional cat = listCategories.stream().filter(c -> c.getCategoryDescription() != null).findFirst(); + + if (cat.isPresent()) { + + Category category = getCategorySolr(event); + category.setCategoryDescription(cat.get().getCategoryDescription()); + + Category catergoriesSorl = this.categoryRepository.save(category); + log.info("Um gasto no cartão foi adicionado ao Solr {}", catergoriesSorl.toString()); + + event.setCategory(new ExpenseCategory(cat.get().getCategoryDescription())); + ExpenseManagement expenseManagement = this.repository + .save(new ExpenseManagement(event.getId(), event.getUserCode(), event.getDescription(), + event.getDate(), event.getValue(), event.getCategory())); + log.info("Um gasto com cartão foi adcionado! {}", expenseManagement.toString()); + + } else { + + Category category = getCategorySolr(event); + Category expenseSorl = this.categoryRepository.save(category); + + log.info("Um gasto no cartão foi adicionado ao Solr {}", expenseSorl.toString()); + + ExpenseManagement expenseManagement = this.repository + .save(new ExpenseManagement(event.getId(), event.getUserCode(), event.getDescription(), + event.getDate(), event.getValue(), event.getCategory())); + + log.info("Um gasto com cartão foi adcionado! {}", expenseManagement.toString()); + } + } + + /** + * Evento responsavel por atualizar categoria + * + * @param event + */ + @EventHandler + public void on(ExpenseManagementCategoryUpdatedEvent event) { + + ExpenseManagement expenseCard = this.repository.findById(event.getCardExpenseId()) + .orElseThrow(ExpenseManagementNotFoundException::new); + + expenseCard.setCategory(event.getCategory()); + this.repository.save(expenseCard); + + Category category = this.categoryRepository.findByCategoryId(event.getCardExpenseId()); + category.setCategoryDescription(event.getCategory().getDescription()); + this.categoryRepository.save(category); + + log.info("Uma categoria foi atualizada! {}", expenseCard); + } + + /** + * TODO renomear esses metodos para nomes melhores. + * + * @param event + * @return + */ + private Category getCategorySolr(ExpenseManagementAddedEvent event) { + Category category = new Category(); + category.setCategoryId(event.getId()); + category.setExpenseDescription(event.getDescription()); + return category; + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/configuration/AmqpConfiguration.java b/expense-management-query/src/main/java/br/com/adslima/configuration/AmqpConfiguration.java new file mode 100644 index 00000000..37ce6a1e --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/configuration/AmqpConfiguration.java @@ -0,0 +1,41 @@ +package br.com.adslima.configuration; + +import com.rabbitmq.client.Channel; +import lombok.extern.slf4j.Slf4j; +import org.axonframework.amqp.eventhandling.DefaultAMQPMessageConverter; +import org.axonframework.amqp.eventhandling.spring.SpringAMQPMessageSource; +import org.axonframework.serialization.Serializer; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * + * @author andrews.silva + * + */ +@Slf4j +@Configuration +public class AmqpConfiguration { + + @Bean + public SpringAMQPMessageSource complaintEventsMethod(Serializer serializer) { + return new SpringAMQPMessageSource(new DefaultAMQPMessageConverter(serializer)) { + + /* + * (non-Javadoc) + * + * @see org.axonframework.amqp.eventhandling.spring. + * SpringAMQPMessageSource#onMessage(org.springframework.amqp.core. + * Message, com.rabbitmq.client.Channel) + */ + @RabbitListener(queues = "${axon.amqp.exchange}") + @Override + public void onMessage(Message message, Channel channel) { + log.debug("Event Received: {}", message.getBody().toString()); + super.onMessage(message, channel); + } + }; + } +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/configuration/SolrConfig.java b/expense-management-query/src/main/java/br/com/adslima/configuration/SolrConfig.java new file mode 100644 index 00000000..aa1280ab --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/configuration/SolrConfig.java @@ -0,0 +1,41 @@ +/** + * + */ +package br.com.adslima.configuration; + +import org.apache.solr.client.solrj.SolrClient; +import org.apache.solr.client.solrj.impl.HttpSolrClient; +import org.apache.solr.client.solrj.impl.XMLResponseParser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.solr.core.SolrTemplate; +import org.springframework.data.solr.repository.config.EnableSolrRepositories; + +/** + * @author andrews.silva + * + */ +@Configuration +@EnableSolrRepositories(basePackages = { "br.com.adslima.repository" }) +public class SolrConfig { + + private HttpSolrClient solr; + + @Value("${spring.data.solr.host}") + private String solrHost; + + @Bean + public SolrClient solrServer() { + solr = new HttpSolrClient.Builder(solrHost).build(); + solr.setParser(new XMLResponseParser()); + return solr; + + } + + @Bean + public SolrTemplate solrTemplate(SolrClient client) throws Exception { + return new SolrTemplate(client); + } + +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/controller/ExpenseManagementQueryController.java b/expense-management-query/src/main/java/br/com/adslima/controller/ExpenseManagementQueryController.java new file mode 100644 index 00000000..2e272409 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/controller/ExpenseManagementQueryController.java @@ -0,0 +1,137 @@ +package br.com.adslima.controller; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.data.solr.core.query.result.FacetFieldEntry; +import org.springframework.data.solr.core.query.result.FacetPage; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import br.com.adslima.converter.ExpenseManagementConvert; +import br.com.adslima.dto.ExpenseManagementQueryDTO; +import br.com.adslima.handler.ExpenseManagementHandler; +import br.com.adslima.model.Category; +import br.com.adslima.model.ExpenseManagement; +import br.com.adslima.repository.CategoryRepository; +import br.com.adslima.repository.ExpenseManagementRepository; +import br.com.adslima.response.Response; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@AllArgsConstructor +@RestController +@RequestMapping("/api-queries") +public class ExpenseManagementQueryController { + + private static final int NUMBER_PAR_PAG = 25; + private static final long NUMBER_FIVE = 5; + + @Autowired + private ExpenseManagementRepository repository; + + @Autowired + private CategoryRepository categoryRepository; + + /** + * + * @return + */ + @GetMapping + public ResponseEntity>> getAll() { + + Response> response = new Response>(); + + Iterable cardsExpenses = repository.findAll(); + response.setData(cardsExpenses); + + return ResponseEntity.ok().body(response); + } + + /** + * + * @param userCode + * @param pag + * @param ord + * @param dir + * @return + */ + @GetMapping("/{userCode}/expense-menagement") + public ResponseEntity>> findByUserCode( + @PathVariable final Integer userCode, @RequestParam(value = "pag", defaultValue = "0") int pag, + @RequestParam(value = "ord", defaultValue = "date") String ord, + @RequestParam(value = "dir", defaultValue = "DESC") String dir) { + + log.info("Searching for user expenses: {}", userCode); + + Response> response = new Response>(); + + Page cardExpenses = new ExpenseManagementHandler(repository).findLastExpensesCardsByUserCode( + userCode, LocalDateTime.now().minusSeconds(NUMBER_FIVE), + PageRequest.of(pag, NUMBER_PAR_PAG, Direction.valueOf(dir), ord)); + + Page communsDto = cardExpenses + .map(cardExpense -> ExpenseManagementConvert.toConvertFromModelSummary(cardExpense)); + + response.setData(communsDto); + return ResponseEntity.ok(response); + } + + /** + * + * @param dto + * @param pag + * @param ord + * @param dir + * @return + */ + @GetMapping("/expense-menagement") + public ResponseEntity>> findByDateFilter( + final ExpenseManagementQueryDTO dto, @RequestParam(value = "pag", defaultValue = "0") int pag, + @RequestParam(value = "ord", defaultValue = "id") String ord, + @RequestParam(value = "dir", defaultValue = "DESC") String dir) { + log.info(""); + + Response> response = new Response>(); + + Page cardExpenses = new ExpenseManagementHandler(repository).findExpensesCardsByFilter( + dto.getUserCode(), dto.getDate(), PageRequest.of(pag, NUMBER_PAR_PAG, Direction.valueOf(dir), ord)); + + Page communsDto = cardExpenses + .map(cardExpense -> ExpenseManagementConvert.toConvertFromModelSummary(cardExpense)); + response.setData(communsDto); + + return ResponseEntity.ok(response); + } + + /** + * + * @param searchTerm + * @param pag + * @param ord + * @param dir + * @return + */ + @GetMapping("/{categoryDescription}/categories") + public ResponseEntity> findBySuggestionCategory( + @PathVariable final String categoryDescription, @RequestParam(value = "pag", defaultValue = "0") int pag, + @RequestParam(value = "ord", defaultValue = "id") String ord, + @RequestParam(value = "dir", defaultValue = "DESC") String dir) { + + log.info("Buscando sugestões de categorias.." + categoryDescription); + FacetPage categories = this.categoryRepository.findByDescriptionAndFacetOnCategories( + categoryDescription, PageRequest.of(pag, NUMBER_PAR_PAG, Direction.valueOf(dir), ord)); + + return ResponseEntity.ok(categories.getFacetResultPage("categories_txt").getContent()); + } + +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/converter/ExpenseManagementCategoryConverter.java b/expense-management-query/src/main/java/br/com/adslima/converter/ExpenseManagementCategoryConverter.java new file mode 100644 index 00000000..e7f88a47 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/converter/ExpenseManagementCategoryConverter.java @@ -0,0 +1,24 @@ +package br.com.adslima.converter; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import br.com.adslima.model.ExpenseCategory; + + +/** + * @author andrews.silva + * + */ +@Converter +public class ExpenseManagementCategoryConverter implements AttributeConverter { + + public String convertToDatabaseColumn(ExpenseCategory category) { + return category == null ? null : category.toString(); + } + + public ExpenseCategory convertToEntityAttribute(String category) { + return category == null ? null : ExpenseCategory.of(category); + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/converter/ExpenseManagementConvert.java b/expense-management-query/src/main/java/br/com/adslima/converter/ExpenseManagementConvert.java new file mode 100644 index 00000000..ebb023ba --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/converter/ExpenseManagementConvert.java @@ -0,0 +1,59 @@ +package br.com.adslima.converter; + +import java.util.List; +import java.util.stream.Collectors; + +import br.com.adslima.dto.ExpenseManagementQueryDTO; +import br.com.adslima.model.ExpenseCategory; +import br.com.adslima.model.ExpenseManagement; + +/** + * + * @author andrews.silva + * + */ +public class ExpenseManagementConvert { + + /** + * + * @param dto + * @return + */ + public static ExpenseManagement convertToModel(final ExpenseManagementQueryDTO dto) { + + final ExpenseManagement cardExpense = new ExpenseManagement(); + final ExpenseCategory category = new ExpenseCategory(dto.getCategory().toString()); + + cardExpense.setCategory(category); + cardExpense.setId(dto.getId()); + cardExpense.setDescription(dto.getDescription()); + cardExpense.setUserCode(dto.getUserCode()); + cardExpense.setDate(dto.getDate()); + cardExpense.setValue(dto.getValue()); + + return cardExpense; + } + + /** + * + * @param model + * @return + */ + public static ExpenseManagementQueryDTO toConvertFromModelSummary(final ExpenseManagement model) { + final ExpenseManagementQueryDTO communsDto = new ExpenseManagementQueryDTO(); + communsDto.setId(model.getId()); + if (model.getCategory() != null) { + communsDto.setCategory(model.getCategory()); + } + communsDto.setDescription(model.getDescription()); + communsDto.setUserCode(model.getUserCode()); + communsDto.setDate(model.getDate()); + communsDto.setValue(model.getValue()); + return communsDto; + } + + public static List toConvertFromModel(final List listModel) { + return listModel.stream().map(ExpenseManagementConvert::toConvertFromModelSummary).collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/dto/ExpenseManagementQueryDTO.java b/expense-management-query/src/main/java/br/com/adslima/dto/ExpenseManagementQueryDTO.java new file mode 100644 index 00000000..2fa458dc --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/dto/ExpenseManagementQueryDTO.java @@ -0,0 +1,38 @@ +package br.com.adslima.dto; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import org.springframework.format.annotation.DateTimeFormat; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; + +import br.com.adslima.model.ExpenseCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author andrews.silva + * + */ +@Setter +@Getter +@ToString +@NoArgsConstructor +@AllArgsConstructor +@JsonAutoDetect(fieldVisibility = Visibility.ANY) +public class ExpenseManagementQueryDTO { + + private String id; + private Integer userCode; + private String description; + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime date; + private BigDecimal value; + private ExpenseCategory category; + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/events/AbstractEventQuery.java b/expense-management-query/src/main/java/br/com/adslima/events/AbstractEventQuery.java new file mode 100644 index 00000000..726282b1 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/events/AbstractEventQuery.java @@ -0,0 +1,28 @@ +package br.com.adslima.events; + +import java.io.Serializable; + +/** + * @author andrews.silva + * + */ +public abstract class AbstractEventQuery implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private String id; + + public AbstractEventQuery() { + } + + public AbstractEventQuery(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/expense-management-query/src/main/java/br/com/adslima/events/ExpenseManagementAddedEvent.java b/expense-management-query/src/main/java/br/com/adslima/events/ExpenseManagementAddedEvent.java new file mode 100644 index 00000000..b82ab0e6 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/events/ExpenseManagementAddedEvent.java @@ -0,0 +1,47 @@ +package br.com.adslima.events; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import br.com.adslima.model.ExpenseCategory; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author andrews.silva + * + */ +@Getter +@Setter +@ToString +@NoArgsConstructor +public class ExpenseManagementAddedEvent extends AbstractEventQuery { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private String id; + private Integer userCode; + private String description; + private LocalDateTime date; + private BigDecimal value; + private ExpenseCategory category; + + /** + * @param id + */ + public ExpenseManagementAddedEvent(String id, Integer userCode, String description, LocalDateTime date, + BigDecimal value, ExpenseCategory category) { + super(id); + this.userCode = userCode; + this.description = description; + this.date = date; + this.value = value; + this.category = category; + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/events/ExpenseManagementCategoryUpdatedEvent.java b/expense-management-query/src/main/java/br/com/adslima/events/ExpenseManagementCategoryUpdatedEvent.java new file mode 100644 index 00000000..56c8cdde --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/events/ExpenseManagementCategoryUpdatedEvent.java @@ -0,0 +1,19 @@ +package br.com.adslima.events; + +import br.com.adslima.model.ExpenseCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +/** + * @author andrews.silva + * + */ +@Getter +@ToString +@AllArgsConstructor +public class ExpenseManagementCategoryUpdatedEvent { + + private String cardExpenseId; + private ExpenseCategory category; +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/exception/CustomizedResponseExceptionHandler.java b/expense-management-query/src/main/java/br/com/adslima/exception/CustomizedResponseExceptionHandler.java new file mode 100644 index 00000000..c314c8c0 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/exception/CustomizedResponseExceptionHandler.java @@ -0,0 +1,40 @@ +package br.com.adslima.exception; + +import java.time.LocalDateTime; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** + * @author andrews.silva + * + */ +@ControllerAdvice +@RestController +public class CustomizedResponseExceptionHandler extends ResponseEntityExceptionHandler { + + private static final int BAD_REQUEST = 400; + + private static final int NOT_FOUND = 404; + + @ExceptionHandler(ExpenseManagementNotFoundException.class) + public final ResponseEntity handleExpenseNotFoundException( + final ExpenseManagementNotFoundException ex, final WebRequest request) { + final ExceptionResponse exceptionResponse = new ExceptionResponse(NOT_FOUND, LocalDateTime.now(), + ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(exceptionResponse, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(InvalidExpenseCategoryException.class) + public final ResponseEntity handleUserNotFoundException(final InvalidExpenseCategoryException ex, + final WebRequest request) { + final ExceptionResponse exceptionResponse = new ExceptionResponse(BAD_REQUEST, LocalDateTime.now(), + ex.getMessage(), request.getDescription(false)); + return new ResponseEntity<>(exceptionResponse, HttpStatus.BAD_REQUEST); + } +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/exception/ExceptionResponse.java b/expense-management-query/src/main/java/br/com/adslima/exception/ExceptionResponse.java new file mode 100644 index 00000000..b6ceb74b --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/exception/ExceptionResponse.java @@ -0,0 +1,27 @@ +package br.com.adslima.exception; + +import java.time.LocalDateTime; + +import lombok.Getter; + +/** + * @author andrews.silva + * + */ +@Getter +public class ExceptionResponse { + + private final int code; + private final LocalDateTime timestamp; + private final String message; + private final String details; + + public ExceptionResponse(final int code, final LocalDateTime timestamp, final String message, + final String details) { + super(); + this.code = code; + this.timestamp = timestamp; + this.message = message; + this.details = details; + } +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/exception/ExpenseManagementNotFoundException.java b/expense-management-query/src/main/java/br/com/adslima/exception/ExpenseManagementNotFoundException.java new file mode 100644 index 00000000..cb63a0f7 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/exception/ExpenseManagementNotFoundException.java @@ -0,0 +1,20 @@ +package br.com.adslima.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import lombok.AllArgsConstructor; + +@ResponseStatus(HttpStatus.NOT_FOUND) +@AllArgsConstructor +public class ExpenseManagementNotFoundException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public ExpenseManagementNotFoundException(final String exception) { + super(exception); + } +} diff --git a/expense-management-query/src/main/java/br/com/adslima/exception/InvalidExpenseCategoryException.java b/expense-management-query/src/main/java/br/com/adslima/exception/InvalidExpenseCategoryException.java new file mode 100644 index 00000000..ebd4ea36 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/exception/InvalidExpenseCategoryException.java @@ -0,0 +1,25 @@ +/** + * + */ +package br.com.adslima.exception; + +/** + * @author andrews.silva + * + */ +/** + * @author andrews.silva + * + */ +public class InvalidExpenseCategoryException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public InvalidExpenseCategoryException(String message) { + super(message); + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/handler/ExpenseManagementHandler.java b/expense-management-query/src/main/java/br/com/adslima/handler/ExpenseManagementHandler.java new file mode 100644 index 00000000..7034b676 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/handler/ExpenseManagementHandler.java @@ -0,0 +1,64 @@ +package br.com.adslima.handler; + +import java.time.LocalDateTime; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import br.com.adslima.exception.ExpenseManagementNotFoundException; +import br.com.adslima.model.ExpenseManagement; +import br.com.adslima.repository.ExpenseManagementRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@AllArgsConstructor +public class ExpenseManagementHandler { + + @Autowired + ExpenseManagementRepository repository; + + /** + * + * @param userCode + * @param date + * @param pageable + * @return + */ + public Page findExpensesCardsByFilter(final Integer userCode, final LocalDateTime date, + final Pageable pageable) { + log.info("buscando registros por data para o usuario: " + userCode + " e date: " + date); + + Page expensesCards = repository.findByUserCodeAndDateBetween(userCode, + LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), 0, 0, 0), + LocalDateTime.of(date.getYear(), date.getMonth(), date.getDayOfMonth(), 23, 59, 59), pageable); + + if (expensesCards.getContent().isEmpty()) { + throw new ExpenseManagementNotFoundException( + "Não foram encontrados registros por data para o usuario: " + userCode + " e data: " + date); + } + return new PageImpl(expensesCards.getContent(), pageable, expensesCards.getTotalElements()); + + } + + /** + * + * @param userCode + * @param date + * @param pageable + * @return + */ + public Page findLastExpensesCardsByUserCode(final Integer userCode, final LocalDateTime date, + final Pageable pageable) { + log.info("buscando registros por data para o usuario: " + userCode); + final Page expensesCards = repository.findByUserCodeAndDateBefore(userCode, date, pageable); + + if (expensesCards.getContent().isEmpty()) { + throw new ExpenseManagementNotFoundException( + "Não foram encontrados registros relacionados ao usuario informado: " + userCode); + } + return new PageImpl(expensesCards.getContent(), pageable, expensesCards.getTotalElements()); + } +} diff --git a/expense-management-query/src/main/java/br/com/adslima/model/Category.java b/expense-management-query/src/main/java/br/com/adslima/model/Category.java new file mode 100644 index 00000000..1943f8e0 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/model/Category.java @@ -0,0 +1,63 @@ +package br.com.adslima.model; + +import java.io.Serializable; + +import org.apache.solr.client.solrj.beans.Field; +import org.springframework.data.annotation.Id; +import org.springframework.data.solr.core.mapping.Indexed; +import org.springframework.data.solr.core.mapping.SolrDocument; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author andrews.silva + * + */ +@Data +@EqualsAndHashCode +@Builder +@AllArgsConstructor +@SolrDocument(collection = "categories-core") +public class Category implements Serializable { + + + public Category() { + + } + + /** + * @param categoryDescription + */ + public Category(ExpenseCategory categoryDescription) { + this.categoryDescription = categoryDescription.getDescription(); + } + + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + @Field + @Indexed(name = "cid", type = "string") + private String categoryId; + + @Indexed(name = "edesc", type = "string") + private String expenseDescription; + + @Field("categories_txt") + @Indexed(name = "cdesc", type = "string") + private String categoryDescription; + + /** + * @param expenseDescription the expenseDescription to set + */ + @Field("expenseDescription") + public void setExpenseDescription(String expenseDescription) { + this.expenseDescription = expenseDescription; + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/model/ExpenseCategory.java b/expense-management-query/src/main/java/br/com/adslima/model/ExpenseCategory.java new file mode 100644 index 00000000..0869bb6e --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/model/ExpenseCategory.java @@ -0,0 +1,46 @@ +/** + * + */ +package br.com.adslima.model; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; + +import br.com.adslima.exception.InvalidExpenseCategoryException; +import lombok.Data; + +/** + * @author andrews.silva + * + */ +@Data +@JsonAutoDetect(fieldVisibility = Visibility.ANY) +public class ExpenseCategory { + + private String description; + + public ExpenseCategory(String category) { + if (!isValid(category)) + throw new InvalidExpenseCategoryException("Invalid Expense Category"); + this.description = category; + } + + public static ExpenseCategory of(String category) { + return new ExpenseCategory(category); + } + + private boolean isValid(String category) { + return category != null; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return description; + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/model/ExpenseManagement.java b/expense-management-query/src/main/java/br/com/adslima/model/ExpenseManagement.java new file mode 100644 index 00000000..cce3eede --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/model/ExpenseManagement.java @@ -0,0 +1,36 @@ +package br.com.adslima.model; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.Id; + +import br.com.adslima.converter.ExpenseManagementCategoryConverter; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ExpenseManagement implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 1L; + + @Id + private String id; + private Integer userCode; + private String description; + private LocalDateTime date; + private BigDecimal value; + @Convert(converter = ExpenseManagementCategoryConverter.class) + private ExpenseCategory category; + +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/repository/CategoryRepository.java b/expense-management-query/src/main/java/br/com/adslima/repository/CategoryRepository.java new file mode 100644 index 00000000..6b4abad8 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/repository/CategoryRepository.java @@ -0,0 +1,37 @@ +/** + * + */ +package br.com.adslima.repository; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.solr.core.query.result.FacetPage; +import org.springframework.data.solr.repository.Facet; +import org.springframework.data.solr.repository.Query; +import org.springframework.data.solr.repository.SolrCrudRepository; + +import br.com.adslima.model.Category; + +/** + * @author andrews.silva + * + */ +public interface CategoryRepository extends SolrCrudRepository { + + Category findByCategoryId(final String categoryId); + + @Query("edesc:*?0*") + Page findByExpenseDescription(final String description, final Pageable pageable); + + @Query("edesc:*?0* OR cdesc:*?0*") + Page findByCustomerQuery(final String description, final Pageable pageable); + + List findByExpenseDescription(final String description); + + @Query("categories_txt:*?0*") + @Facet(fields = { "categories_txt" }, limit = 5) + FacetPage findByDescriptionAndFacetOnCategories(final String description, final Pageable page); + +} \ No newline at end of file diff --git a/expense-management-query/src/main/java/br/com/adslima/repository/CustomCategoryRepository.java b/expense-management-query/src/main/java/br/com/adslima/repository/CustomCategoryRepository.java new file mode 100644 index 00000000..804439e9 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/repository/CustomCategoryRepository.java @@ -0,0 +1,67 @@ +/** + * + */ +package br.com.adslima.repository; + +import java.util.List; + +import javax.annotation.Resource; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; +import org.springframework.data.solr.core.SolrTemplate; +import org.springframework.data.solr.core.query.Criteria; +import org.springframework.data.solr.core.query.SimpleQuery; +import org.springframework.stereotype.Repository; + +import br.com.adslima.model.Category; + +/** + * + * @author andrews.silva + * + */ +@Repository +public class CustomCategoryRepository { + + @Resource + private SolrTemplate solrTemplate; + + /** + * + * @param searchTerm + * @return + */ + public List dynamicSearch(String searchTerm) { + Criteria conditions = createConditions(searchTerm); + SimpleQuery search = new SimpleQuery(conditions); + + search.addSort(sortByIdDesc()); + + Page results = solrTemplate.queryForPage("Category", search, Category.class); + return results.getContent(); + } + + /** + * + * @param searchTerm + * @return + */ + private Criteria createConditions(String searchTerm) { + Criteria conditions = null; + + for (String term : searchTerm.split(" ")) { + if (conditions == null) { + conditions = new Criteria("cid").contains(term).or(new Criteria("edesc").contains(term)); + } else { + conditions = conditions.or(new Criteria("cid").contains(term)).or(new Criteria("edesc").contains(term)); + } + } + return conditions; + } + + private Sort sortByIdDesc() { + return new Sort(Sort.Direction.DESC, "cid"); + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/repository/ExpenseManagementRepository.java b/expense-management-query/src/main/java/br/com/adslima/repository/ExpenseManagementRepository.java new file mode 100644 index 00000000..d38e6270 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/repository/ExpenseManagementRepository.java @@ -0,0 +1,27 @@ +package br.com.adslima.repository; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import br.com.adslima.model.ExpenseManagement; + +@Transactional(readOnly = true) +public interface ExpenseManagementRepository extends JpaRepository { + + List findExpensesCardsByUserCode(Integer userCode); + + Page findExpensesCardsByUserCode(Integer userCode, Pageable pageable); + + Page findByUserCodeAndDateBefore(Integer userCode, LocalDateTime date, Pageable pageable); + + Page findByUserCodeAndDateBetween(Integer userCode, LocalDateTime initDate, + LocalDateTime endDate, Pageable pageable); + + List findByDescription(final String description); + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/response/Response.java b/expense-management-query/src/main/java/br/com/adslima/response/Response.java new file mode 100644 index 00000000..8cfd65a4 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/response/Response.java @@ -0,0 +1,32 @@ +package br.com.adslima.response; + +import java.util.ArrayList; +import java.util.List; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author andrews.silva + * + */ +@Setter +@Getter +@NoArgsConstructor +public class Response { + + private T data; + private List errors; + + /** + * @return the errors + */ + public List getErrors() { + if (this.errors == null) { + this.errors = new ArrayList(); + } + return errors; + } + +} diff --git a/expense-management-query/src/main/java/br/com/adslima/security/SecurityConfiguration.java b/expense-management-query/src/main/java/br/com/adslima/security/SecurityConfiguration.java new file mode 100644 index 00000000..8ed47ba9 --- /dev/null +++ b/expense-management-query/src/main/java/br/com/adslima/security/SecurityConfiguration.java @@ -0,0 +1,38 @@ +package br.com.adslima.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; +import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter; +import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; + + +@Configuration +@EnableResourceServer +public class SecurityConfiguration extends ResourceServerConfigurerAdapter { + + private final static String resourceId = "resources"; + + @Override + public void configure(HttpSecurity http) throws Exception { +// http.requestMatchers() +// .antMatchers("/**") +// .and() +// .authorizeRequests() +// .anyRequest() +// .authenticated() +// .antMatchers(HttpMethod.GET, "/**").access("#oauth2.hasScope('read')") +// .antMatchers(HttpMethod.OPTIONS, "/**").access("#oauth2.hasScope('read')") +// .antMatchers(HttpMethod.POST, "/**").access("#oauth2.hasScope('write')") +// .antMatchers(HttpMethod.PUT, "/**").access("#oauth2.hasScope('write')") +// .antMatchers(HttpMethod.PATCH, "/**").access("#oauth2.hasScope('write')") +// .antMatchers(HttpMethod.DELETE, "/**").access("#oauth2.hasScope('write')"); + http.authorizeRequests().anyRequest().authenticated().and().httpBasic(); + http.httpBasic().disable(); + } + + @Override + public void configure(ResourceServerSecurityConfigurer resources){ + resources.resourceId(resourceId); + } +} \ No newline at end of file diff --git a/expense-management-query/src/main/resources/application-test.properties b/expense-management-query/src/main/resources/application-test.properties new file mode 100644 index 00000000..8050a855 --- /dev/null +++ b/expense-management-query/src/main/resources/application-test.properties @@ -0,0 +1,17 @@ +# configura banco in memory H2 + +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.platform=h2 +spring.datasource.username=sa +spring.datasource.password=sa + +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console + +spring.jpa.hibernate.ddl-auto=create +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.show_sql=true +spring.jpa.properties.hibernate.use_sql_comments=true +spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file diff --git a/expense-management-query/src/main/resources/bootstrap.yml b/expense-management-query/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..da6de350 --- /dev/null +++ b/expense-management-query/src/main/resources/bootstrap.yml @@ -0,0 +1,68 @@ +message: Greetings from the EXPENSE-MANAGEMENT-QUERY microservice. + +server: + port: 8082 + + servlet: + context-path: /queries + +spring: + application: + name: expense-management-query + + rabbitmq: + host: ${RABBITMQ_IP:localhost} + port: 5672 + username: guest + password: guest + + data: + solr: + host: http://${SOLR_IP:localhost}:8983/solr + +eureka: + instance: + leaseRenewalIntervalInSeconds: 1 + leaseExpirationDurationInSeconds: 2 + client: + serviceUrl: + defaultZone: http://${EUREKA_IP:localhost}:8761/eureka/ + healthcheck: + enabled: true + lease: + duration: 5 + +security: + oauth2: + resource: + userInfoUri: http://${OAUTH_IP:localhost}:8787/auth-api/users/authenticate + + datasource: + driver-class-name: com.mysql.jdbc.Driver + password: ' ' + platform: mysql + url: jdbc:mysql://${MYSQL_IP:localhost}:3306/managementquery?Peoples?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 + username: root + + jpa: + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + generate-ddl: true + hibernate: + ddl-auto: update + show-sql: true + +axon: + amqp: + exchange: expense-management.events + eventhandling: + processors: + amqpEvents: + source: complaintEventsMethod + +logging: + level: + root: INFO + org.springframework: INFO + org.axonframework: INFO + br.com.adslima: DEBUG + file: ./build/logs/expense-management-query.log \ No newline at end of file diff --git a/expense-management-query/src/test/java/br/com/adslima/ExpenseManagementQueryApplicationTests.java b/expense-management-query/src/test/java/br/com/adslima/ExpenseManagementQueryApplicationTests.java new file mode 100644 index 00000000..2cfc4a9f --- /dev/null +++ b/expense-management-query/src/test/java/br/com/adslima/ExpenseManagementQueryApplicationTests.java @@ -0,0 +1,18 @@ +package br.com.adslima; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("test") +public class ExpenseManagementQueryApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/expense-management-query/src/test/java/br/com/adslima/components/ExpenseManagementEventProcessTest.java b/expense-management-query/src/test/java/br/com/adslima/components/ExpenseManagementEventProcessTest.java new file mode 100644 index 00000000..9b78c6fe --- /dev/null +++ b/expense-management-query/src/test/java/br/com/adslima/components/ExpenseManagementEventProcessTest.java @@ -0,0 +1,73 @@ +package br.com.adslima.components; + +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.Mockito; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import br.com.adslima.model.Category; +import br.com.adslima.model.ExpenseManagement; +import br.com.adslima.repository.CategoryRepository; +import br.com.adslima.repository.ExpenseManagementRepository; + +@RunWith(SpringRunner.class) +@SpringBootConfiguration +@ActiveProfiles("test") +public class ExpenseManagementEventProcessTest { + + private static final int NUMBER_PAR_PAG = 10; + + @MockBean + private ExpenseManagementRepository repository; + + @MockBean + private CategoryRepository categoryRepository; + + @Before + public void setUp() throws Exception { + + BDDMockito.given(this.categoryRepository.findByExpenseDescription(Mockito.anyString())) + .willReturn(new ArrayList()); + + BDDMockito.given(this.categoryRepository.save(Mockito.any(Category.class))).willReturn(new Category()); + + BDDMockito + .given(this.repository.findExpensesCardsByUserCode((Mockito.anyInt()), Mockito.any(PageRequest.class))) + .willReturn(new PageImpl(new ArrayList())); + + BDDMockito.given(this.repository.findExpensesCardsByUserCode(Mockito.anyInt())) + .willReturn(new ArrayList()); + + BDDMockito.given(this.repository.save(Mockito.any(ExpenseManagement.class))) + .willReturn(new ExpenseManagement()); + } + + @Test + public void testFindExpensesCardsByUser() { + Page expense = this.repository.findExpensesCardsByUserCode(Integer.valueOf(1), + PageRequest.of(0, NUMBER_PAR_PAG, Direction.DESC, "id")); + + assertNotNull(expense); + } + + @Test + public void testAddExpenseCard() { + ExpenseManagement expense = this.repository.save(new ExpenseManagement()); + + assertNotNull(expense); + } + +} \ No newline at end of file diff --git a/expense-management-query/src/test/java/br/com/adslima/controller/ExpenseManagementQueryTest.java b/expense-management-query/src/test/java/br/com/adslima/controller/ExpenseManagementQueryTest.java new file mode 100644 index 00000000..0f5ce02b --- /dev/null +++ b/expense-management-query/src/test/java/br/com/adslima/controller/ExpenseManagementQueryTest.java @@ -0,0 +1,124 @@ +package br.com.adslima.controller; + +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.InjectMocks; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import br.com.adslima.dto.ExpenseManagementQueryDTO; +import br.com.adslima.events.ExpenseManagementAddedEvent; +import br.com.adslima.model.ExpenseManagement; +import br.com.adslima.repository.ExpenseManagementRepository; + +@RunWith(SpringRunner.class) +@SpringBootConfiguration +@ActiveProfiles("test") +public class ExpenseManagementQueryTest { + + private static final Integer USER_CODE = 12345; + private static final String URL_BASE = "/api-queries"; + + final String id = UUID.randomUUID().toString(); + final LocalDateTime date = LocalDateTime.now(); + + private MockMvc mockMvc; + + @InjectMocks + ExpenseManagementQueryController expenseManagementQueryController; + + @MockBean + private ExpenseManagementRepository repository; + + ExpenseManagementAddedEvent event; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mockMvc = MockMvcBuilders.standaloneSetup(expenseManagementQueryController).build(); + + BDDMockito.given(this.repository.save(Mockito.any(ExpenseManagement.class))) + .willReturn(new ExpenseManagement()); + + this.event = this.getExpenseManagement(id, date); + + this.repository.save(new ExpenseManagement(event.getId(), event.getUserCode(), event.getDescription(), + event.getDate(), event.getValue(), event.getCategory())); + + } + + /** + * + * @throws Exception + */ + @Test + public void testFindAllOK() throws Exception { + + mockMvc.perform(MockMvcRequestBuilders.get(URL_BASE).content(this.getJsonRequestGet()) + .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isEmpty()).andExpect(jsonPath("$.errors").isEmpty()).andDo(print()) + .andReturn(); + + } + + /** + * + * @return + * @throws JsonProcessingException + */ + private String getJsonRequestGet() throws JsonProcessingException { + ExpenseManagementQueryDTO expenseDto = new ExpenseManagementQueryDTO(); + expenseDto.setId(id); + expenseDto.setUserCode(USER_CODE); + expenseDto.setDescription("ExpenseManagement Test"); + expenseDto.setDate(null); + expenseDto.setValue(BigDecimal.ONE); + expenseDto.setCategory(null); + + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(expenseDto); + } + + /** + * + * @param id + * @param date + * @return + */ + private ExpenseManagementAddedEvent getExpenseManagement(String id, LocalDateTime date) { + + ExpenseManagement expense = new ExpenseManagement(); + expense.setId(id); + expense.setUserCode(USER_CODE); + expense.setDescription("ExpenseManagement Test"); + expense.setDate(date); + expense.setValue(BigDecimal.ONE); + expense.setCategory(null); + + ExpenseManagementAddedEvent event = new ExpenseManagementAddedEvent(expense.getId(), expense.getUserCode(), + expense.getDescription(), expense.getDate(), expense.getValue(), expense.getCategory()); + return event; + } + +} diff --git a/expense-management-query/src/test/java/br/com/adslima/repository/RepositoryTest.java b/expense-management-query/src/test/java/br/com/adslima/repository/RepositoryTest.java new file mode 100644 index 00000000..ac0cd1b3 --- /dev/null +++ b/expense-management-query/src/test/java/br/com/adslima/repository/RepositoryTest.java @@ -0,0 +1,95 @@ +package br.com.adslima.repository; + +import static org.junit.Assert.assertEquals; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; + +import br.com.adslima.events.ExpenseManagementAddedEvent; +import br.com.adslima.model.ExpenseManagement; + +@RunWith(SpringRunner.class) +@SpringBootTest +@ActiveProfiles("test") +@Ignore +public class RepositoryTest { + + private static final Integer USER_CODE = 12345; + + private static final int NUMBER_PAR_PAG = 10; + + @Mock + private ExpenseManagementRepository repository; + + ExpenseManagementAddedEvent event; + + @Before + public void setUp() throws Exception { + + String id = UUID.randomUUID().toString(); + LocalDateTime date = LocalDateTime.now(); + + this.event = this.getExpenseManagement(id, date); + this.repository.save(new ExpenseManagement(event.getId(), event.getUserCode(), event.getDescription(), + event.getDate(), event.getValue(), event.getCategory())); + } + + @After + public void tearDown() throws Exception { + this.repository.deleteAll(); + } + + /** + * + */ + @Test + public void testFindExpenseCardByUserCode() { + + List expenses = this.repository.findExpensesCardsByUserCode(USER_CODE); + + assertEquals(1, expenses.size()); + } + + /** + * + */ + @Test + public void testFindExpenseCardByUserCodePage() { + + Page expenses = this.repository.findExpensesCardsByUserCode(USER_CODE, + PageRequest.of(0, NUMBER_PAR_PAG, Direction.DESC, "id")); + + assertEquals(1, expenses.getTotalElements()); + } + + private ExpenseManagementAddedEvent getExpenseManagement(String id, LocalDateTime date) { + + ExpenseManagement expense = new ExpenseManagement(); + expense.setId(id); + expense.setUserCode(USER_CODE); + expense.setDescription("ExpenseManagement Test"); + expense.setDate(date); + expense.setValue(BigDecimal.ONE); + expense.setCategory(null); + + ExpenseManagementAddedEvent event = new ExpenseManagementAddedEvent(expense.getId(), expense.getUserCode(), + expense.getDescription(), expense.getDate(), expense.getValue(), expense.getCategory()); + return event; + } + +} diff --git a/service-gateway/Dockerfile b/service-gateway/Dockerfile new file mode 100644 index 00000000..78702222 --- /dev/null +++ b/service-gateway/Dockerfile @@ -0,0 +1,9 @@ +FROM java:8-jre-alpine + +RUN mkdir -p /service-gateway +ADD target/service-gateway-0.0.1-SNAPSHOT.jar /service-gateway +ADD target/classes/application.properties /service-gateway + +WORKDIR /service-gateway + +ENTRYPOINT ["java", "-jar", "service-gateway-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/service-gateway/pom.xml b/service-gateway/pom.xml new file mode 100644 index 00000000..219b96b5 --- /dev/null +++ b/service-gateway/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + br.com.adslima + service-gateway + 0.0.1-SNAPSHOT + jar + + service-gateway + Gateway Service + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Dalston.SR2 + + + + + + + + + org.springframework.cloud + + spring-cloud-starter-eureka + + + + org.springframework.cloud + spring-cloud-starter-zuul + + + + + + + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.cloud + spring-cloud-starter-security + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/service-gateway/src/main/java/br/com/adslima/ServiceGatewayApplication.java b/service-gateway/src/main/java/br/com/adslima/ServiceGatewayApplication.java new file mode 100644 index 00000000..7a911159 --- /dev/null +++ b/service-gateway/src/main/java/br/com/adslima/ServiceGatewayApplication.java @@ -0,0 +1,23 @@ +package br.com.adslima; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.netflix.zuul.EnableZuulProxy; +import org.springframework.context.annotation.ComponentScan; + +/** + * + * @author andrews.silva + * + */ +@SpringBootApplication +@EnableZuulProxy +@EnableDiscoveryClient +@ComponentScan("br.com.adslima") +public class ServiceGatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(ServiceGatewayApplication.class, args); + } +} \ No newline at end of file diff --git a/service-gateway/src/main/java/br/com/adslima/prefilters/CORSFilter.java b/service-gateway/src/main/java/br/com/adslima/prefilters/CORSFilter.java new file mode 100644 index 00000000..2c5022e0 --- /dev/null +++ b/service-gateway/src/main/java/br/com/adslima/prefilters/CORSFilter.java @@ -0,0 +1,57 @@ +package br.com.adslima.prefilters; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class CORSFilter implements Filter { + + final static Logger logger = LoggerFactory.getLogger(CORSFilter.class); + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + + HttpServletResponse response = (HttpServletResponse) res; + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE"); + response.setHeader("Access-Control-Max-Age", "3600"); + response.setHeader("Access-Control-Allow-Headers", + "X-Requested-With, Content-Type, Authorization, Origin, Accept, Access-Control-Request-Method, Access-Control-Request-Headers"); + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + response.setStatus(HttpServletResponse.SC_OK); + } else { + chain.doFilter(req, res); + } + } + + @Override + public void init(FilterConfig filterConfig) { + logger.info("Implementation not required"); + } + + @Override + public void destroy() { + logger.info("Implementation not required"); + } +} + diff --git a/service-gateway/src/main/java/br/com/adslima/prefilters/SimpleLoggingPreFilter.java b/service-gateway/src/main/java/br/com/adslima/prefilters/SimpleLoggingPreFilter.java new file mode 100644 index 00000000..428d13df --- /dev/null +++ b/service-gateway/src/main/java/br/com/adslima/prefilters/SimpleLoggingPreFilter.java @@ -0,0 +1,47 @@ +package br.com.adslima.prefilters; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.stereotype.Component; + +import com.netflix.zuul.ZuulFilter; +import com.netflix.zuul.context.RequestContext; + +/** + * + * @author andrews.silva + * + */ +@Component +public class SimpleLoggingPreFilter extends ZuulFilter { + + @Override + public String filterType() { + return "pre"; + } + + @Override + public int filterOrder() { + return 1; + } + + @Override + public boolean shouldFilter() { + return true; + } + + @Override + public Object run() { + RequestContext ctx = RequestContext.getCurrentContext(); + HttpServletRequest request = ctx.getRequest(); + /* + * Adding authorization header to zuul request header as zuul omits + * sensitive headers + */ + if (request.getHeader("Authorization") != null) { + ctx.addZuulRequestHeader("Authorization", request.getHeader("Authorization")); + } + return null; + } + +} diff --git a/service-gateway/src/main/java/br/com/adslima/security/GatewaySecurityConfiguration.java b/service-gateway/src/main/java/br/com/adslima/security/GatewaySecurityConfiguration.java new file mode 100644 index 00000000..a2f8d7b2 --- /dev/null +++ b/service-gateway/src/main/java/br/com/adslima/security/GatewaySecurityConfiguration.java @@ -0,0 +1,22 @@ +package br.com.adslima.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class GatewaySecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.csrf().disable().authorizeRequests().antMatchers("/**").permitAll(); + + } + //Allowing all the request to pass + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers("/**"); + } +} + diff --git a/service-gateway/src/main/resources/application.properties b/service-gateway/src/main/resources/application.properties new file mode 100644 index 00000000..b80f2680 --- /dev/null +++ b/service-gateway/src/main/resources/application.properties @@ -0,0 +1,42 @@ +# Port +server.port=8765 + +# Application name +spring.application.name=gateway-server + +# Discovery Server Access +eureka.client.serviceUrl.defaultZone=http://${EUREKA_IP:localhost}:8761/eureka/ +eureka.instance.preferIpAddress=true +eureka.instance.leaseRenewalIntervalInSeconds: 1 +eureka.instance.leaseExpirationDurationInSeconds: 2 + +#Disable Spring Boot basic authentication +security.basic.enabled=false +security.user.password=none + +#zuul routing +zuul.routes.auth-service.path=/auth-api/** +zuul.routes.auth-service.stripPrefix=false +#Hystrix time out +hystrix.command.auth-service.execution.isolation.thread.timeoutInMilliseconds=600000 + +zuul.routes.expense-management-command.path=/commands/** +zuul.routes.expense-management-command.stripPrefix=false +#Hystrix time out +hystrix.command.expense-management-command.execution.isolation.thread.timeoutInMilliseconds=600000 + +zuul.routes.expense-management-query.path=/queries/** +zuul.routes.expense-management-query.stripPrefix=false +#Hystrix time out +hystrix.command.expense-management-query.execution.isolation.thread.timeoutInMilliseconds=600000 + + +ribbon.eager-load.enabled=true + + + +#Ribbon time out +auth-service.ribbon.ReadTimeout=70000 + + + diff --git a/service-gateway/src/test/java/br/com/adslima/ServiceGatewayApplicationTests.java b/service-gateway/src/test/java/br/com/adslima/ServiceGatewayApplicationTests.java new file mode 100644 index 00000000..29e2e8d0 --- /dev/null +++ b/service-gateway/src/test/java/br/com/adslima/ServiceGatewayApplicationTests.java @@ -0,0 +1,16 @@ +package br.com.adslima; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ServiceGatewayApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/service-gateway/target/classes/META-INF/MANIFEST.MF b/service-gateway/target/classes/META-INF/MANIFEST.MF new file mode 100644 index 00000000..95303b7e --- /dev/null +++ b/service-gateway/target/classes/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Implementation-Title: service-gateway +Implementation-Version: 0.0.1-SNAPSHOT +Built-By: andrews.silva +Implementation-Vendor-Id: br.com.adslima +Build-Jdk: 1.8.0_201 +Implementation-URL: http://projects.spring.io/spring-boot/service-gate + way/ +Created-By: Maven Integration for Eclipse +Implementation-Vendor: Pivotal Software, Inc. + diff --git a/service-gateway/target/classes/META-INF/maven/br.com.adslima/service-gateway/pom.properties b/service-gateway/target/classes/META-INF/maven/br.com.adslima/service-gateway/pom.properties new file mode 100644 index 00000000..0638cfea --- /dev/null +++ b/service-gateway/target/classes/META-INF/maven/br.com.adslima/service-gateway/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Thu Mar 14 19:46:13 GMT-03:00 2019 +version=0.0.1-SNAPSHOT +groupId=br.com.adslima +m2e.projectName=service-gateway +m2e.projectLocation=C\:\\Users\\andrews.silva\\Documents\\santander_bkp\\TestBackJava\\service-gateway +artifactId=service-gateway diff --git a/service-gateway/target/classes/META-INF/maven/br.com.adslima/service-gateway/pom.xml b/service-gateway/target/classes/META-INF/maven/br.com.adslima/service-gateway/pom.xml new file mode 100644 index 00000000..219b96b5 --- /dev/null +++ b/service-gateway/target/classes/META-INF/maven/br.com.adslima/service-gateway/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + br.com.adslima + service-gateway + 0.0.1-SNAPSHOT + jar + + service-gateway + Gateway Service + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Dalston.SR2 + + + + + + + + + org.springframework.cloud + + spring-cloud-starter-eureka + + + + org.springframework.cloud + spring-cloud-starter-zuul + + + + + + + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.cloud + spring-cloud-starter-security + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/service-gateway/target/classes/application.properties b/service-gateway/target/classes/application.properties new file mode 100644 index 00000000..b80f2680 --- /dev/null +++ b/service-gateway/target/classes/application.properties @@ -0,0 +1,42 @@ +# Port +server.port=8765 + +# Application name +spring.application.name=gateway-server + +# Discovery Server Access +eureka.client.serviceUrl.defaultZone=http://${EUREKA_IP:localhost}:8761/eureka/ +eureka.instance.preferIpAddress=true +eureka.instance.leaseRenewalIntervalInSeconds: 1 +eureka.instance.leaseExpirationDurationInSeconds: 2 + +#Disable Spring Boot basic authentication +security.basic.enabled=false +security.user.password=none + +#zuul routing +zuul.routes.auth-service.path=/auth-api/** +zuul.routes.auth-service.stripPrefix=false +#Hystrix time out +hystrix.command.auth-service.execution.isolation.thread.timeoutInMilliseconds=600000 + +zuul.routes.expense-management-command.path=/commands/** +zuul.routes.expense-management-command.stripPrefix=false +#Hystrix time out +hystrix.command.expense-management-command.execution.isolation.thread.timeoutInMilliseconds=600000 + +zuul.routes.expense-management-query.path=/queries/** +zuul.routes.expense-management-query.stripPrefix=false +#Hystrix time out +hystrix.command.expense-management-query.execution.isolation.thread.timeoutInMilliseconds=600000 + + +ribbon.eager-load.enabled=true + + + +#Ribbon time out +auth-service.ribbon.ReadTimeout=70000 + + + diff --git a/service-registry/Dockerfile b/service-registry/Dockerfile new file mode 100644 index 00000000..907a59b8 --- /dev/null +++ b/service-registry/Dockerfile @@ -0,0 +1,9 @@ +FROM java:8-jre-alpine + +RUN mkdir -p /service-registry +ADD target/service-registry-0.0.1-SNAPSHOT.jar /service-registry +ADD target/classes/bootstrap.yml /service-registry + +WORKDIR /service-registry + +ENTRYPOINT ["java", "-jar", "service-registry-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/service-registry/pom.xml b/service-registry/pom.xml new file mode 100644 index 00000000..691d14e4 --- /dev/null +++ b/service-registry/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + br.com.adslima + service-registry + 0.0.1-SNAPSHOT + jar + + service-registry + Registry Service + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Dalston.SR2 + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-eureka-server + + + + + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/service-registry/src/main/java/br/com/adslima/ServiceRegistryApplication.java b/service-registry/src/main/java/br/com/adslima/ServiceRegistryApplication.java new file mode 100644 index 00000000..b7081c08 --- /dev/null +++ b/service-registry/src/main/java/br/com/adslima/ServiceRegistryApplication.java @@ -0,0 +1,19 @@ +package br.com.adslima; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; + +/** + * + * @author andrews.silva + * + */ +@SpringBootApplication +@EnableEurekaServer +public class ServiceRegistryApplication { + + public static void main(String[] args) { + SpringApplication.run(ServiceRegistryApplication.class, args); + } +} \ No newline at end of file diff --git a/service-registry/src/main/resources/bootstrap.yml b/service-registry/src/main/resources/bootstrap.yml new file mode 100644 index 00000000..bf196580 --- /dev/null +++ b/service-registry/src/main/resources/bootstrap.yml @@ -0,0 +1,15 @@ +spring: + application: + name: service-registry + +server: + port: 8761 + +eureka: + client: + register-with-eureka: false + fetch-registry: false + +security: + basic: + enabled: false \ No newline at end of file diff --git a/service-registry/src/test/java/br/com/adslima/ServiceRegistryApplicationTests.java b/service-registry/src/test/java/br/com/adslima/ServiceRegistryApplicationTests.java new file mode 100644 index 00000000..6fb96b21 --- /dev/null +++ b/service-registry/src/test/java/br/com/adslima/ServiceRegistryApplicationTests.java @@ -0,0 +1,16 @@ +package br.com.adslima; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ServiceRegistryApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/service-registry/target/classes/META-INF/MANIFEST.MF b/service-registry/target/classes/META-INF/MANIFEST.MF new file mode 100644 index 00000000..f0a48539 --- /dev/null +++ b/service-registry/target/classes/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Implementation-Title: service-registry +Implementation-Version: 0.0.1-SNAPSHOT +Built-By: andrews.silva +Implementation-Vendor-Id: br.com.adslima +Build-Jdk: 1.8.0_201 +Implementation-URL: http://projects.spring.io/spring-boot/service-regi + stry/ +Created-By: Maven Integration for Eclipse +Implementation-Vendor: Pivotal Software, Inc. + diff --git a/service-registry/target/classes/META-INF/maven/br.com.adslima/service-registry/pom.properties b/service-registry/target/classes/META-INF/maven/br.com.adslima/service-registry/pom.properties new file mode 100644 index 00000000..d1fa1947 --- /dev/null +++ b/service-registry/target/classes/META-INF/maven/br.com.adslima/service-registry/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Thu Mar 14 19:46:13 GMT-03:00 2019 +version=0.0.1-SNAPSHOT +groupId=br.com.adslima +m2e.projectName=service-registry +m2e.projectLocation=C\:\\Users\\andrews.silva\\Documents\\santander_bkp\\TestBackJava\\service-registry +artifactId=service-registry diff --git a/service-registry/target/classes/META-INF/maven/br.com.adslima/service-registry/pom.xml b/service-registry/target/classes/META-INF/maven/br.com.adslima/service-registry/pom.xml new file mode 100644 index 00000000..691d14e4 --- /dev/null +++ b/service-registry/target/classes/META-INF/maven/br.com.adslima/service-registry/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + br.com.adslima + service-registry + 0.0.1-SNAPSHOT + jar + + service-registry + Registry Service + + + org.springframework.boot + spring-boot-starter-parent + 1.5.4.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + Dalston.SR2 + + + + + + + + + + + + + org.springframework.cloud + spring-cloud-starter-eureka-server + + + + + + + + org.springframework.cloud + spring-cloud-starter-oauth2 + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + diff --git a/service-registry/target/classes/bootstrap.yml b/service-registry/target/classes/bootstrap.yml new file mode 100644 index 00000000..bf196580 --- /dev/null +++ b/service-registry/target/classes/bootstrap.yml @@ -0,0 +1,15 @@ +spring: + application: + name: service-registry + +server: + port: 8761 + +eureka: + client: + register-with-eureka: false + fetch-registry: false + +security: + basic: + enabled: false \ No newline at end of file