diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4406c10 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git/ +.gitignore +*.md +Dockerfile +.dockerignore diff --git a/.github/workflows/hello.yml b/.github/workflows/hello.yml new file mode 100644 index 0000000..7e9bfbf --- /dev/null +++ b/.github/workflows/hello.yml @@ -0,0 +1,10 @@ +name: hello.yml +on: + push: + +jobs: + hello-cd-ci: + runs-on: ubuntu-latest + steps: + - name: Print Hello CI/CD + run: echo "Hello CI/CD" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c7f4360 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +#FROM amazoncorretto:21-alpine3.22 +# +#WORKDIR /app +# +#COPY target/*.jar app.jar +# +#EXPOSE 8080 +# +#ENTRYPOINT ["java","-jar","app.jar"] + +#===============================Multi-stage Dockerfile================================= + +#Build +FROM maven:3.9-amazoncorretto-21-alpine AS builder + +WORKDIR /app + +COPY pom.xml . + +COPY src ./src + +RUN mvn clean test package -DskipTests=false + +#Runtime +FROM amazoncorretto:21-alpine3.22 AS runtime + +WORKDIR /app + +COPY --from=builder /app/target/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index db92ca3..0000000 --- a/compose.yaml +++ /dev/null @@ -1,13 +0,0 @@ -services: - postgres: - image: 'postgres:latest' - environment: - - 'POSTGRES_DB=mydatabase' - - 'POSTGRES_PASSWORD=secret' - - 'POSTGRES_USER=myuser' - ports: - - '5432' - redis: - image: 'redis:latest' - ports: - - '6379' diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a61bd76 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,83 @@ +services: + postgres: + image: 'postgres:latest' + container_name: postgres_db + environment: + - 'POSTGRES_DB=mydatabase' + - 'POSTGRES_PASSWORD=secret' + - 'POSTGRES_USER=myuser' + ports: + - '5432:5432' + volumes: + - postgres_data:/var/lib/postgresql + - shared_volume:/shared + networks: + - app_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U myuser -d mydatabase"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + + redis: + image: 'redis:latest' + container_name: redis_cache + ports: + - '6379:6379' + volumes: + - redis_data:/data + - shared_volume:/shared + networks: + - app_network + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + + devcalc-api: + image: 'shyuu7/devcalc-api:latest' + container_name: devcalc_app + ports: + - '8080:8080' + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + volumes: + - shared_volume:/app/shared + networks: + - app_network + environment: + - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/mydatabase + - SPRING_DATASOURCE_USERNAME=myuser + - SPRING_DATASOURCE_PASSWORD=secret + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + + test_container: + image: busybox + container_name: connectivity_test + command: sleep 3600 + volumes: + - shared_volume:/shared + networks: + - app_network + depends_on: + - postgres + - redis + +volumes: + postgres_data: + driver: local + redis_data: + driver: local + shared_volume: + driver: local + +networks: + app_network: + driver: bridge diff --git a/evidencias/CalculatorServiceTest.png b/evidencias/CalculatorServiceTest.png new file mode 100644 index 0000000..bb673ad Binary files /dev/null and b/evidencias/CalculatorServiceTest.png differ diff --git a/evidencias/aplicacao rodando pelo docker.png b/evidencias/aplicacao rodando pelo docker.png new file mode 100644 index 0000000..9373116 Binary files /dev/null and b/evidencias/aplicacao rodando pelo docker.png differ diff --git a/evidencias/apontando tags 1.0.0 para latest.png b/evidencias/apontando tags 1.0.0 para latest.png new file mode 100644 index 0000000..3531fc6 Binary files /dev/null and b/evidencias/apontando tags 1.0.0 para latest.png differ diff --git a/evidencias/calculator controller.png b/evidencias/calculator controller.png new file mode 100644 index 0000000..74c8dd8 Binary files /dev/null and b/evidencias/calculator controller.png differ diff --git a/evidencias/calculator service.png b/evidencias/calculator service.png new file mode 100644 index 0000000..4c02847 Binary files /dev/null and b/evidencias/calculator service.png differ diff --git a/evidencias/conectividade com api.png b/evidencias/conectividade com api.png new file mode 100644 index 0000000..dbd8d4e Binary files /dev/null and b/evidencias/conectividade com api.png differ diff --git a/evidencias/conectividade com redis e postgres.png b/evidencias/conectividade com redis e postgres.png new file mode 100644 index 0000000..ca46551 Binary files /dev/null and b/evidencias/conectividade com redis e postgres.png differ diff --git a/evidencias/conteineres rodando apos healthcheck.png b/evidencias/conteineres rodando apos healthcheck.png new file mode 100644 index 0000000..f460067 Binary files /dev/null and b/evidencias/conteineres rodando apos healthcheck.png differ diff --git a/evidencias/criacao da imagem docker.png b/evidencias/criacao da imagem docker.png new file mode 100644 index 0000000..72702e7 Binary files /dev/null and b/evidencias/criacao da imagem docker.png differ diff --git "a/evidencias/criando arquivo em volume compartilhado e checando exist\303\252ncia nos outros conteineres.png" "b/evidencias/criando arquivo em volume compartilhado e checando exist\303\252ncia nos outros conteineres.png" new file mode 100644 index 0000000..1c3fe6d Binary files /dev/null and "b/evidencias/criando arquivo em volume compartilhado e checando exist\303\252ncia nos outros conteineres.png" differ diff --git a/evidencias/docker build multistage apos correcao de teste.png b/evidencias/docker build multistage apos correcao de teste.png new file mode 100644 index 0000000..d1eff07 Binary files /dev/null and b/evidencias/docker build multistage apos correcao de teste.png differ diff --git a/evidencias/erro de build - teste falhando.png b/evidencias/erro de build - teste falhando.png new file mode 100644 index 0000000..7cca6d0 Binary files /dev/null and b/evidencias/erro de build - teste falhando.png differ diff --git a/evidencias/git commit.png b/evidencias/git commit.png new file mode 100644 index 0000000..f22d5f1 Binary files /dev/null and b/evidencias/git commit.png differ diff --git a/evidencias/git push.png b/evidencias/git push.png new file mode 100644 index 0000000..d9da627 Binary files /dev/null and b/evidencias/git push.png differ diff --git a/evidencias/healthcheck postgres.png b/evidencias/healthcheck postgres.png new file mode 100644 index 0000000..6ae91e9 Binary files /dev/null and b/evidencias/healthcheck postgres.png differ diff --git a/evidencias/healthcheck redis.png b/evidencias/healthcheck redis.png new file mode 100644 index 0000000..ab66137 Binary files /dev/null and b/evidencias/healthcheck redis.png differ diff --git "a/evidencias/imagens j\303\241 no dockerhub.png" "b/evidencias/imagens j\303\241 no dockerhub.png" new file mode 100644 index 0000000..d5e83f9 Binary files /dev/null and "b/evidencias/imagens j\303\241 no dockerhub.png" differ diff --git a/evidencias/subindo imagens para o dockerhub.png b/evidencias/subindo imagens para o dockerhub.png new file mode 100644 index 0000000..5773055 Binary files /dev/null and b/evidencias/subindo imagens para o dockerhub.png differ diff --git a/pom.xml b/pom.xml index c88d09b..df190aa 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 4.0.0-SNAPSHOT + 3.4.0 com @@ -13,22 +13,11 @@ 0.0.1-SNAPSHOT DevCalc DevCalc - - - - - - - - - - - - - + 21 + org.springframework.boot @@ -38,12 +27,11 @@ org.springframework.boot spring-boot-starter-thymeleaf - - org.springframework.boot - spring-boot-starter-webmvc - - - + + org.springframework.boot + spring-boot-starter-web + + org.springframework.boot spring-boot-docker-compose runtime @@ -54,65 +42,28 @@ postgresql runtime - - org.projectlombok - lombok - true - org.springframework.boot spring-boot-starter-test test + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.2.0 + + + org.springframework.boot + spring-boot-starter-actuator + - - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/readme.md b/readme.md index dde4f35..6d7eb0a 100644 --- a/readme.md +++ b/readme.md @@ -61,11 +61,65 @@ O projeto utiliza **Apache Maven** como ferramenta de build e gerenciamento de d ```bash mvn clean ``` -4. **Acesse a API** + +### Opção 2: usando Docker + +Pré-requisitos: +- Docker instalado +- Docker Compose instalado + +Executar apenas a API: +1. Pull da imagem do Docker Hub: + ```bash + docker pull shyuu7/devcalc:latest + ``` +2. Executar o container: + ```bash + docker run -d -p 8080:8080 shyuu7/devcalc:latest + ``` + +Executar ambiente completo com Docker Compose: +1. Clone o repositório: + ```bash + git clone https://github.com/Shyuu7/devcalc-api.git + cd devcalc-api + ``` +2. Iniciar os serviços: + ```bash + docker-compose up -d + ``` +3. Verificar os containers em execução: + ```bash + docker ps + ``` +4. Parar os serviços: + ```bash + docker-compose down + ``` +5. Parar e remover todos os containers, redes e volumes: + ```bash + docker-compose down -v + ``` + + +### **Acesse a API** + +Independente do método de execução escolhido, a API estará disponível em: + +URL: http://localhost:8080 + +Swagger UI: http://localhost:8080/swagger-ui/index.html - A API estará disponível em `http://localhost:8080`. +### **Teste os endpoints** -5. **Teste os endpoints** +Você pode testar os endpoints usando: +- Swagger UI - Interface web disponível na página principal +- Curl - Comandos HTTP via terminal +- Postman - Cliente REST gráfico +- Container de teste - Para verificar conectividade interna -Você pode testar os endpoints usando ferramentas como Postman ou cURL. \ No newline at end of file +Exemplo de comando curl para somar dois números: +```bash +curl -X GET "http://localhost:8080/api/calc/sum?a=5&b=3" -H "accept: application/json" +``` \ No newline at end of file diff --git a/src/main/java/com/devcalc/config/SwaggerConfig.java b/src/main/java/com/devcalc/config/SwaggerConfig.java new file mode 100644 index 0000000..e1007b8 --- /dev/null +++ b/src/main/java/com/devcalc/config/SwaggerConfig.java @@ -0,0 +1,23 @@ +package com.devcalc.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.Contact; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("DevCalc API") + .version("1.0.0") + .description("API REST para operações matemáticas básicas") + .contact(new Contact() + .name("DevCalc Team") + .url("https://github.com/Shyuu7/devcalc-api"))); + } +} \ No newline at end of file diff --git a/src/main/java/com/devcalc/controller/CalculatorController.java b/src/main/java/com/devcalc/controller/CalculatorController.java new file mode 100644 index 0000000..06c5f1a --- /dev/null +++ b/src/main/java/com/devcalc/controller/CalculatorController.java @@ -0,0 +1,64 @@ +package com.devcalc.controller; + +import com.devcalc.model.CalculatorModel; +import com.devcalc.model.ErrorResponse; +import com.devcalc.service.CalculatorService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@Tag(name = "Calculadora", description = "Operações matemáticas básicas") +public class CalculatorController { + + private final CalculatorService calculatorService; + + public CalculatorController(CalculatorService calculatorService) { + this.calculatorService = calculatorService; + } + + @GetMapping("/add") + @Operation(summary = "Adição", description = "Realiza a soma de dois números") + public ResponseEntity add( + @Parameter(description = "Primeiro número") @RequestParam double a, + @Parameter(description = "Segundo número") @RequestParam double b) { + CalculatorModel result = calculatorService.adicao(a, b); + return ResponseEntity.ok(result.getResultado()); + } + + @GetMapping("/subtract") + @Operation(summary = "Subtração", description = "Realiza a subtração de dois números") + public ResponseEntity subtract( + @Parameter(description = "Primeiro número") @RequestParam double a, + @Parameter(description = "Segundo número") @RequestParam double b) { + CalculatorModel result = calculatorService.subtracao(a, b); + return ResponseEntity.ok(result.getResultado()); + } + + @GetMapping("/multiply") + @Operation(summary = "Multiplicação", description = "Realiza a multiplicação de dois números") + public ResponseEntity multiply( + @Parameter(description = "Primeiro número") @RequestParam double a, + @Parameter(description = "Segundo número") @RequestParam double b) { + CalculatorModel result = calculatorService.multiplicacao(a, b); + return ResponseEntity.ok(result.getResultado()); + } + + @GetMapping("/divide") + @Operation(summary = "Divisão", description = "Realiza a divisão de dois números") + public ResponseEntity divide( + @Parameter(description = "Dividendo") @RequestParam double a, + @Parameter(description = "Divisor") @RequestParam double b) { + try { + CalculatorModel result = calculatorService.divisao(a, b); + return ResponseEntity.ok(result.getResultado()); + } catch (ArithmeticException e) { + ErrorResponse error = new ErrorResponse("DIVISION_BY_ZERO", e.getMessage()); + return ResponseEntity.badRequest().body(error); + } + } +} diff --git a/src/main/java/com/devcalc/model/CalculatorModel.java b/src/main/java/com/devcalc/model/CalculatorModel.java new file mode 100644 index 0000000..953efb5 --- /dev/null +++ b/src/main/java/com/devcalc/model/CalculatorModel.java @@ -0,0 +1,32 @@ +package com.devcalc.model; + +public class CalculatorModel { + private final double numeroA; + private final double numeroB; + private final double result; + private final String operation; + + public CalculatorModel(double numeroA, double numeroB, double result, String operation) { + this.numeroA = numeroA; + this.numeroB = numeroB; + this.result = result; + this.operation = operation; + } + + public double getNumeroA() { + return numeroA; + } + + public double getNumeroB() { + return numeroB; + } + + public double getResultado() { + return result; + } + + public String getOperacao() { + return operation; + } + +} diff --git a/src/main/java/com/devcalc/model/ErrorResponse.java b/src/main/java/com/devcalc/model/ErrorResponse.java new file mode 100644 index 0000000..3570da5 --- /dev/null +++ b/src/main/java/com/devcalc/model/ErrorResponse.java @@ -0,0 +1,27 @@ +package com.devcalc.model; + +public class ErrorResponse { + private String code; + private String message; + + public ErrorResponse(String code, String message) { + this.code = code; + this.message = message; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/devcalc/service/CalculatorService.java b/src/main/java/com/devcalc/service/CalculatorService.java new file mode 100644 index 0000000..b3d7bb4 --- /dev/null +++ b/src/main/java/com/devcalc/service/CalculatorService.java @@ -0,0 +1,31 @@ +package com.devcalc.service; + +import com.devcalc.model.CalculatorModel; +import org.springframework.stereotype.Service; + +@Service +public class CalculatorService { + + public CalculatorModel adicao(double a, double b) { + double result = a + b; + return new CalculatorModel(a, b, result, "adição"); + } + + public CalculatorModel subtracao(double a, double b) { + double result = a - b; + return new CalculatorModel(a, b, result, "subtração"); + } + + public CalculatorModel multiplicacao(double a, double b) { + double result = a * b; + return new CalculatorModel(a, b, result, "multiplicação"); + } + + public CalculatorModel divisao(double a, double b) { + if (b == 0) { + throw new ArithmeticException("Divisão por zero não é permitida"); + } + double result = a / b; + return new CalculatorModel(a, b, result, "divisão"); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index dd0a88d..cdcedc3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,5 @@ spring.application.name=DevCalc +spring.docker.compose.enabled=false +springdoc.swagger-ui.path=/ +springdoc.api-docs.path=/api-docs + diff --git a/src/test/java/com/devcalc/service/CalculatorServiceTest.java b/src/test/java/com/devcalc/service/CalculatorServiceTest.java new file mode 100644 index 0000000..3089443 --- /dev/null +++ b/src/test/java/com/devcalc/service/CalculatorServiceTest.java @@ -0,0 +1,89 @@ +package com.devcalc.service; + +import com.devcalc.model.CalculatorModel; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorServiceTest { + + private CalculatorService calculatorService; + + @BeforeEach + void setUp() { + calculatorService = new CalculatorService(); + } + + @Test + void adicao() { + double a = 50; + double b = 5; + CalculatorModel result = calculatorService.adicao(a, b); + assertEquals(50, result.getNumeroA()); + assertEquals(5, result.getNumeroB()); + assertEquals(55, result.getResultado()); + assertEquals("adição", result.getOperacao()); + } + + @Test + void subtracao() { + double a = 75; + double b = 5; + CalculatorModel result = calculatorService.subtracao(a, b); + assertEquals(75, result.getNumeroA()); + assertEquals(5, result.getNumeroB()); + assertEquals(70, result.getResultado()); + assertEquals("subtração", result.getOperacao()); + } + + @Test + void multiplicacao() { + double a = 10; + double b = 5; + CalculatorModel result = calculatorService.multiplicacao(a, b); + assertEquals(10, result.getNumeroA()); + assertEquals(5, result.getNumeroB()); + assertEquals(50, result.getResultado()); + assertEquals("multiplicação", result.getOperacao()); + } + + @Test + void divisao() { + double a = 10; + double b = 5; + CalculatorModel result = calculatorService.divisao(a, b); + assertEquals(10, result.getNumeroA()); + assertEquals(5, result.getNumeroB()); + assertEquals(2, result.getResultado()); + assertEquals("divisão", result.getOperacao()); + } + + @Test + void divisaoPorZero() { + double a = 10; + double b = 0; + ArithmeticException exception = assertThrows( + ArithmeticException.class, + () -> calculatorService.divisao(a, b) + ); + assertEquals("Divisão por zero não é permitida", exception.getMessage()); + } + + @Test + void adicaoNumerosNegativos() { + double a = -5; + double b = -3; + CalculatorModel result = calculatorService.adicao(a, b); + assertEquals(-8, result.getResultado()); + assertEquals("adição", result.getOperacao()); + } + + @Test + void multiplicacaoPorZero() { + double a = 10; + double b = 0; + CalculatorModel result = calculatorService.multiplicacao(a, b); + assertEquals(0, result.getResultado()); + assertEquals("multiplicação", result.getOperacao()); + } +}