diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
old mode 100644
new mode 100755
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
old mode 100644
new mode 100755
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
old mode 100644
new mode 100755
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..57b4014
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: java -jar target/clickbus-0.0.1-SNAPSHOT.jar
\ No newline at end of file
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index c9b6851..96cfa5b
--- a/README.md
+++ b/README.md
@@ -1,3 +1,9 @@
+# Desafio que resolve outro desafio
+- Estou participando de um bootcamp do Santander na DIO, e como desafio,
+foi proposto o desenvolvimento e publicação de uma API RESTFULL.
+- Encontrei o desafio da @Clickbus e achei que seria interessante
+entender e melhorar este desafio de código proposto pela empresa.
+
# Backend Developer Challenge
Esse desafio serve para avaliarmos suas habilidades em construir APIs.
diff --git a/docker-compose.yml b/docker-compose.yml
old mode 100644
new mode 100755
index a788dcf..6890a04
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -14,7 +14,9 @@ services:
container_name: clickbus_challenge_app
restart: always
image: maven:3.3-jdk-8
- build: .
+ build:
+ context: ./docker/app/
+ dockerfile: Dockerfile
working_dir: /app
volumes:
- ./:/app
diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile
new file mode 100755
index 0000000..d655234
--- /dev/null
+++ b/docker/app/Dockerfile
@@ -0,0 +1,13 @@
+# Stage 1: Build the application
+FROM maven:3.8.6-jdk-8 as build
+WORKDIR /home/app
+COPY ../../src /home/app/src
+COPY ../../pom.xml /home/app
+RUN mvn clean package
+
+# Stage 2: Create the final image
+FROM openjdk:8-jre
+WORKDIR /home/app
+COPY --from=build /home/app/target/*.jar app.jar
+EXPOSE 8080
+ENTRYPOINT ["java", "-jar", "app.jar"]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index cf21f4f..8c603bc 100755
--- a/pom.xml
+++ b/pom.xml
@@ -28,11 +28,13 @@
org.springframework.boot
spring-boot-starter-web
+ 2.2.1.RELEASE
org.springframework.boot
spring-boot-starter-data-jpa
+ 2.4.0
@@ -41,15 +43,10 @@
runtime
-
- com.h2database
- h2
- runtime
-
-
org.projectlombok
lombok
+ 1.18.30
true
@@ -95,6 +92,7 @@
h2
runtime
+
org.apache.commons
commons-lang3
diff --git a/src/main/java/br/com/clickbus/challenge/ClickbusApplication.java b/src/main/java/br/com/clickbus/challenge/ClickbusApplication.java
old mode 100644
new mode 100755
diff --git a/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java b/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java
old mode 100644
new mode 100755
index bbd5d5c..d7e08c0
--- a/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java
+++ b/src/main/java/br/com/clickbus/challenge/controller/PlaceController.java
@@ -6,25 +6,24 @@
import br.com.clickbus.challenge.exception.PlaceNotFoundException;
import br.com.clickbus.challenge.service.PlaceService;
import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.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 org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
+import java.util.List;
+import java.util.stream.Collectors;
@Api("places")
@RestController
@RequestMapping("places")
public class PlaceController {
- private PlaceService service;
+ private final PlaceService service;
+
+ public PlaceController(PlaceService service) {
+ this.service = service;
+ }
@PostMapping
public ResponseEntity create(@RequestBody @Valid PlaceDTO dto) {
@@ -35,7 +34,21 @@ public ResponseEntity create(@RequestBody @Valid PlaceDTO dto) {
public ResponseEntity findById(@PathVariable Long id) {
return service.findById(id)
.map(place -> ResponseEntity.ok(place.convertToDTO()))
- .orElseThrow(() -> new PlaceNotFoundException(HttpStatus.NOT_FOUND));
+ .orElseThrow(() -> new PlaceNotFoundException(HttpStatus.NOT_FOUND.toString()));
+ }
+
+ @GetMapping(name = "findByName", path = "/")
+ public ResponseEntity> findByName(@RequestParam(value = "name") String name) {
+ List places = service.findByName(name);
+
+ if (places.isEmpty()) {
+ throw new PlaceNotFoundException("There are no places associated with the given name.");
+ }
+
+ return ResponseEntity.ok(service.findByName(name)
+ .stream()
+ .map(Place::convertToDTO)
+ .collect(Collectors.toList()));
}
@GetMapping
diff --git a/src/main/java/br/com/clickbus/challenge/dto/PlaceDTO.java b/src/main/java/br/com/clickbus/challenge/dto/PlaceDTO.java
old mode 100644
new mode 100755
diff --git a/src/main/java/br/com/clickbus/challenge/entity/Place.java b/src/main/java/br/com/clickbus/challenge/entity/Place.java
old mode 100644
new mode 100755
index ccfc550..6576164
--- a/src/main/java/br/com/clickbus/challenge/entity/Place.java
+++ b/src/main/java/br/com/clickbus/challenge/entity/Place.java
@@ -41,7 +41,7 @@ public class Place {
public Place(String name, String slug, String city, String state) {
this.name = name;
- this.slug = slug;
+ this.slug = Place.slugfy(slug);
this.city = city;
this.state = state;
this.createdAt = LocalDateTime.now();
@@ -51,7 +51,23 @@ public static Place of(String name, String slug, String city, String state) {
return new Place(name, slug, city, state);
}
+ private static String slugfy(String slug) {
+ return slug.toLowerCase()
+ .replaceAll("[^a-zA-Z0-9\\s+]", "")
+ .replaceAll("\\s+", "-");
+ }
+
public PlaceDTO convertToDTO() {
return PlaceDTO.of(this.name, this.slug, this.city, this.state);
}
+
+ public Place alter(PlaceDTO dto) {
+ this.name = dto.getName();
+ this.slug = Place.slugfy(dto.getSlug());
+ this.city = dto.getCity();
+ this.state = dto.getState();
+ this.updatedAt = LocalDateTime.now();
+
+ return this;
+ }
}
diff --git a/src/main/java/br/com/clickbus/challenge/exception/GlobalExceptionHandler.java b/src/main/java/br/com/clickbus/challenge/exception/GlobalExceptionHandler.java
new file mode 100755
index 0000000..bcfafcf
--- /dev/null
+++ b/src/main/java/br/com/clickbus/challenge/exception/GlobalExceptionHandler.java
@@ -0,0 +1,54 @@
+package br.com.clickbus.challenge.exception;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
+
+ @ExceptionHandler(NotImplementedException.class)
+ public ResponseEntity handleNotImplementedException(NotImplementedException notImplementedException) {
+ String message = notImplementedException.getMessage().isEmpty()
+ ? "Resource not implemented yet."
+ : notImplementedException.getMessage();
+ return new ResponseEntity<>(this.responseError(message, HttpStatus.NOT_IMPLEMENTED), HttpStatus.NOT_IMPLEMENTED);
+ }
+
+ @ExceptionHandler(PlaceNotFoundException.class)
+ public ResponseEntity handlePlaceNotFoundException(PlaceNotFoundException placeNotFoundException) {
+ String message = placeNotFoundException.getMessage().isEmpty()
+ ? "Resource ID not found."
+ : placeNotFoundException.getMessage();
+ return new ResponseEntity<>(this.responseError(message, HttpStatus.NOT_FOUND), HttpStatus.NOT_FOUND);
+ }
+
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ public ResponseEntity handleBusinessException(MethodArgumentNotValidException methodArgumentNotValidException) {
+ String message = methodArgumentNotValidException.getMessage().isEmpty()
+ ? HttpStatus.BAD_REQUEST.toString()
+ : methodArgumentNotValidException.getMessage();
+ return new ResponseEntity<>(this.responseError(message, HttpStatus.BAD_REQUEST), HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(Throwable.class)
+ public ResponseEntity handleUnexpectedException(Throwable unexpectedException) {
+ String message = "Unexpected server error.";
+ logger.error("", unexpectedException);
+ return new ResponseEntity<>(this.responseError(message, HttpStatus.INTERNAL_SERVER_ERROR), HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ private ResponseErrorBody responseError(String message, HttpStatus statusCode){
+ ResponseErrorBody responseError = new ResponseErrorBody();
+ responseError.setStatus("error");
+ responseError.setError(message);
+ responseError.setStatusCode(statusCode.value());
+ return responseError;
+ }
+}
diff --git a/src/main/java/br/com/clickbus/challenge/exception/PlaceNotFoundException.java b/src/main/java/br/com/clickbus/challenge/exception/PlaceNotFoundException.java
old mode 100644
new mode 100755
index d96301d..6047b9b
--- a/src/main/java/br/com/clickbus/challenge/exception/PlaceNotFoundException.java
+++ b/src/main/java/br/com/clickbus/challenge/exception/PlaceNotFoundException.java
@@ -1,15 +1,9 @@
package br.com.clickbus.challenge.exception;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(HttpStatus.NOT_FOUND)
public class PlaceNotFoundException extends RuntimeException{
- private String message;
-
- public PlaceNotFoundException(HttpStatus status){
- super(status.toString());
+ public PlaceNotFoundException(String message){
+ super(message);
}
}
diff --git a/src/main/java/br/com/clickbus/challenge/exception/ResponseErrorBody.java b/src/main/java/br/com/clickbus/challenge/exception/ResponseErrorBody.java
new file mode 100644
index 0000000..eb8499a
--- /dev/null
+++ b/src/main/java/br/com/clickbus/challenge/exception/ResponseErrorBody.java
@@ -0,0 +1,13 @@
+package br.com.clickbus.challenge.exception;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class ResponseErrorBody {
+ private Date timestamp = new Date();
+ private String status = "error";
+ private int statusCode = 400;
+ private String error;
+}
diff --git a/src/main/java/br/com/clickbus/challenge/repository/PlaceRepository.java b/src/main/java/br/com/clickbus/challenge/repository/PlaceRepository.java
old mode 100644
new mode 100755
diff --git a/src/main/java/br/com/clickbus/challenge/service/PlaceService.java b/src/main/java/br/com/clickbus/challenge/service/PlaceService.java
old mode 100644
new mode 100755
index 799d1a8..6f612f1
--- a/src/main/java/br/com/clickbus/challenge/service/PlaceService.java
+++ b/src/main/java/br/com/clickbus/challenge/service/PlaceService.java
@@ -5,36 +5,37 @@
import br.com.clickbus.challenge.entity.Place;
import br.com.clickbus.challenge.repository.PlaceRepository;
import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Optional;
-import org.apache.commons.lang3.NotImplementedException;
@Service
@AllArgsConstructor
public class PlaceService {
- private PlaceRepository repository;
+ @Autowired
+ private PlaceRepository placeRepository;
public List findAll() {
- throw new NotImplementedException("Metodo nao implementado");
+ return placeRepository.findAll();
}
public Optional findById(@NotNull Long id) {
- throw new NotImplementedException("Metodo nao implementado");
+ return placeRepository.findById(id);
}
- public Place save(@NotNull Place place) {
- throw new NotImplementedException("Metodo nao implementado");
+ public Place save(Place place) {
+ return placeRepository.save(place);
}
public List findByName(@NotNull String name) {
- throw new NotImplementedException("Metodo nao implementado");
+ return placeRepository.findByName(name);
}
- public Place alter(@NotNull Place place,@NotNull PlaceDTO placeDTO) {
- throw new NotImplementedException("Metodo nao implementado");
+ public Place alter(@NotNull Place place, @NotNull PlaceDTO placeDTO) {
+ return placeRepository.save(place.alter(placeDTO));
}
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
old mode 100644
new mode 100755
index 9bd4fe5..7fe46be
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -7,6 +7,8 @@ spring:
console:
enabled: true
path: /h2
+ settings:
+ web-allow-others: true
datasource:
url: jdbc:h2:mem:testdb
username: sa
diff --git a/src/test/java/br/com/clickbus/challenge/contoller/PlaceControllerTest.java b/src/test/java/br/com/clickbus/challenge/contoller/PlaceControllerTest.java
old mode 100644
new mode 100755
diff --git a/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java b/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java
old mode 100644
new mode 100755
index cf0f0a9..4adc293
--- a/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java
+++ b/src/test/java/br/com/clickbus/challenge/service/PlaceServiceTest.java
@@ -11,7 +11,6 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -19,7 +18,6 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.atLeastOnce;
@@ -45,6 +43,26 @@ void setUp() {
place = Place.of(NAME_PLACE, "bt", "Sao Paulo", "SP");
}
+ @Test
+ void whenFindAllNotEmpty() {
+ when(repository.findAll()).thenReturn(Collections.singletonList(place));
+
+ List places = service.findAll();
+
+ assertEquals(1, places.size());
+ verify(repository, atLeastOnce()).findAll();
+ }
+
+ @Test
+ void whenFindAllEmpty() {
+ when(repository.findAll()).thenReturn(Collections.emptyList());
+
+ List places = service.findAll();
+
+ assertEquals(0, places.size());
+ verify(repository, atLeastOnce()).findAll();
+ }
+
@Test
void whenFindByIdOk() {
when(repository.findById(1L)).thenReturn(Optional.of(place));
@@ -60,7 +78,6 @@ void whenFindByIdOk() {
verify(repository, atLeastOnce()).findById(anyLong());
}
-
@Test
void whenFindByIdThenReturnEmpty() {
when(repository.findById(1L)).thenReturn(Optional.empty());