diff --git a/.gitignore b/.gitignore index 524f096..8dd030f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,55 @@ -# Compiled class file +target/ +release/ +dependency-reduced-pom.xml +.mvn/wrapper/ + +*.class +*.jar +*.war +*.ear +*.log + +.classpath +.project +.settings/ + +.idea/ + +*.iml +*.ipr +*.iws +.idea/ *.class -# Log file +logs/ *.log -# BlueJ files -*.ctxt +log/ +target/ -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +*.idea/ +*.iml +*.ipr +*.iws -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar +.gradle/ +build/ -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* -replay_pid* +nbproject/private/ +build/ + +.vscode/ + +nb-configuration.xml + +.cache/ +.cproject +.settings/ +.tmproj +*.log +*.tmp +*.bak +*.swp +Thumbs.db +Desktop.ini +.DS_Store diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..64eeabb --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + br.com.grupo63.techchallenge + service-common + jar + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + 1.0 + http://maven.apache.org + service-common + FIAP SOAT1 2023 - Group 63 - Common files + + 17 + + + + org.projectlombok + lombok + 1.18.24 + true + + + org.springframework.boot + 3.2.1 + spring-boot-starter-web + + + org.springframework.session + 3.2.1 + spring-session-core + + + org.springframework.boot + 3.2.1 + spring-boot-starter-validation + + + org.springframework.boot + 3.2.1 + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-devtools + 3.2.1 + runtime + true + + + org.springframework.boot + 3.2.1 + spring-boot-starter-actuator + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.1.0 + + + diff --git a/src/main/java/br/com/grupo63/techchallenge/common/api/controller/AbstractAPIController.java b/src/main/java/br/com/grupo63/techchallenge/common/api/controller/AbstractAPIController.java new file mode 100644 index 0000000..435fe09 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/api/controller/AbstractAPIController.java @@ -0,0 +1,61 @@ +package br.com.grupo63.techchallenge.common.api.controller; + +import br.com.grupo63.techchallenge.common.api.controller.dto.DefaultResponseDTO; +import br.com.grupo63.techchallenge.common.exception.GenericException; +import br.com.grupo63.techchallenge.common.exception.NotFoundException; +import br.com.grupo63.techchallenge.common.exception.ValidationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; + +import java.util.Objects; +import java.util.stream.Collectors; + +public abstract class AbstractAPIController { + + @Autowired + private MessageSource messageSource; + + @ExceptionHandler + public ResponseEntity handleException(Exception exception) { + exception.printStackTrace(); + DefaultResponseDTO responseDTO = new DefaultResponseDTO( + messageSource.getMessage("default.title.unknownError", null, LocaleContextHolder.getLocale()), + messageSource.getMessage("default.title.unknownError.description", null, LocaleContextHolder.getLocale())); + + if (exception instanceof ValidationException validationException) { + responseDTO.setTitle(messageSource.getMessage(validationException.getName(), null, LocaleContextHolder.getLocale())); + responseDTO.setDescription(messageSource.getMessage(validationException.getDescription(), null, LocaleContextHolder.getLocale())); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseDTO); + } else if (exception instanceof MethodArgumentNotValidException methodArgumentNotValidException) { + responseDTO.setTitle(messageSource.getMessage("default.title.validationError", null, LocaleContextHolder.getLocale())); + responseDTO.setDescription( + methodArgumentNotValidException + .getBindingResult().getAllErrors() + .stream().map(ObjectError::getDefaultMessage) + .filter(Objects::nonNull) + .map(message -> messageSource.getMessage(message, null, LocaleContextHolder.getLocale())) + .collect(Collectors.joining("; "))); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(responseDTO); + } else if (exception instanceof NotFoundException) { + responseDTO.setTitle(messageSource.getMessage("default.title.notFoundError", null, LocaleContextHolder.getLocale())); + responseDTO.setDescription(messageSource.getMessage("default.title.notFoundError.description", null, LocaleContextHolder.getLocale())); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(responseDTO); + } else if (exception instanceof GenericException genericException) { + responseDTO.setTitle(genericException.getName()); + responseDTO.setDescription(genericException.getDescription()); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(responseDTO); + } + + return ResponseEntity.internalServerError().body(responseDTO); + } +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/api/controller/dto/DefaultResponseDTO.java b/src/main/java/br/com/grupo63/techchallenge/common/api/controller/dto/DefaultResponseDTO.java new file mode 100644 index 0000000..20fdc23 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/api/controller/dto/DefaultResponseDTO.java @@ -0,0 +1,16 @@ +package br.com.grupo63.techchallenge.common.api.controller.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor + +public class DefaultResponseDTO { + private String title; + private String description; +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/controller/dto/AbstractControllerDTO.java b/src/main/java/br/com/grupo63/techchallenge/common/controller/dto/AbstractControllerDTO.java new file mode 100644 index 0000000..67f98d1 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/controller/dto/AbstractControllerDTO.java @@ -0,0 +1,14 @@ +package br.com.grupo63.techchallenge.common.controller.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public abstract class AbstractControllerDTO { + + @Schema(defaultValue = "1") + protected Long id; + +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/domain/Entity.java b/src/main/java/br/com/grupo63/techchallenge/common/domain/Entity.java new file mode 100644 index 0000000..46b1957 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/domain/Entity.java @@ -0,0 +1,29 @@ +package br.com.grupo63.techchallenge.common.domain; + +import br.com.grupo63.techchallenge.common.domain.validation.group.Update; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public abstract class Entity implements Serializable { + + @NotNull(message = "order.create.idNotNull", groups = {Update.class}) + @Min(value = 1, message = "order.create.idNotNull", groups = {Update.class}) + protected Long id; + + protected boolean deleted = false; + + public void delete() { + this.deleted = true; + } + +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Create.java b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Create.java new file mode 100644 index 0000000..8babd0c --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Create.java @@ -0,0 +1,4 @@ +package br.com.grupo63.techchallenge.common.domain.validation.group; + +public interface Create { +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Delete.java b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Delete.java new file mode 100644 index 0000000..24715b6 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Delete.java @@ -0,0 +1,4 @@ +package br.com.grupo63.techchallenge.common.domain.validation.group; + +public interface Delete { +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Read.java b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Read.java new file mode 100644 index 0000000..586ed10 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Read.java @@ -0,0 +1,4 @@ +package br.com.grupo63.techchallenge.common.domain.validation.group; + +public interface Read { +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Update.java b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Update.java new file mode 100644 index 0000000..d0bd08e --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/domain/validation/group/Update.java @@ -0,0 +1,4 @@ +package br.com.grupo63.techchallenge.common.domain.validation.group; + +public interface Update { +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/exception/GenericException.java b/src/main/java/br/com/grupo63/techchallenge/common/exception/GenericException.java new file mode 100644 index 0000000..3627be3 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/exception/GenericException.java @@ -0,0 +1,15 @@ +package br.com.grupo63.techchallenge.common.exception; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class GenericException extends Exception { + private String name; + private String description; +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/exception/NotFoundException.java b/src/main/java/br/com/grupo63/techchallenge/common/exception/NotFoundException.java new file mode 100644 index 0000000..dc27ff7 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/exception/NotFoundException.java @@ -0,0 +1,4 @@ +package br.com.grupo63.techchallenge.common.exception; + +public class NotFoundException extends GenericException { +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/exception/ValidationException.java b/src/main/java/br/com/grupo63/techchallenge/common/exception/ValidationException.java new file mode 100644 index 0000000..e9d4745 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/exception/ValidationException.java @@ -0,0 +1,9 @@ +package br.com.grupo63.techchallenge.common.exception; + + +public class ValidationException extends GenericException { + + public ValidationException(String name, String description) { + super(name, description); + } +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/gateway/IPersistenceEntityGateway.java b/src/main/java/br/com/grupo63/techchallenge/common/gateway/IPersistenceEntityGateway.java new file mode 100644 index 0000000..4ff9b25 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/gateway/IPersistenceEntityGateway.java @@ -0,0 +1,14 @@ +package br.com.grupo63.techchallenge.common.gateway; + +import java.util.List; +import java.util.Optional; + +public interface IPersistenceEntityGateway { + + List findByDeletedFalse(); + + T saveAndFlush(T entity); + + Optional findByIdAndDeletedFalse(Long id); + +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/gateway/repository/IJpaRepository.java b/src/main/java/br/com/grupo63/techchallenge/common/gateway/repository/IJpaRepository.java new file mode 100644 index 0000000..c786952 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/gateway/repository/IJpaRepository.java @@ -0,0 +1,12 @@ +package br.com.grupo63.techchallenge.common.gateway.repository; + +import java.util.List; +import java.util.Optional; + +public interface IJpaRepository { + + Optional findByIdAndDeletedFalse(Long id); + + List findByDeletedFalse(); + +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/gateway/repository/entity/PersistenceEntity.java b/src/main/java/br/com/grupo63/techchallenge/common/gateway/repository/entity/PersistenceEntity.java new file mode 100644 index 0000000..3853c66 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/gateway/repository/entity/PersistenceEntity.java @@ -0,0 +1,50 @@ +package br.com.grupo63.techchallenge.common.gateway.repository.entity; + +import br.com.grupo63.techchallenge.common.domain.Entity; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter + +@MappedSuperclass +@EntityListeners({AuditingEntityListener.class}) +public abstract class PersistenceEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Id + @Access(AccessType.FIELD) + @Column(name = "id", nullable = false, unique = true) + @GeneratedValue(strategy = GenerationType.IDENTITY) + protected Long id; + + @Basic + @Column(name = "deleted", nullable = false) + protected boolean deleted = false; + + @Column(name = "creation_date", nullable = false) + @LastModifiedDate + private LocalDateTime creationDate; + + @Column(name = "last_update_date", nullable = false) + @LastModifiedDate + private LocalDateTime lastUpdateDate; + + public PersistenceEntity(Entity entity) { + this.id = entity.getId(); + this.deleted = entity.isDeleted(); + } +} diff --git a/src/main/java/br/com/grupo63/techchallenge/common/usecase/Validator.java b/src/main/java/br/com/grupo63/techchallenge/common/usecase/Validator.java new file mode 100644 index 0000000..2dad829 --- /dev/null +++ b/src/main/java/br/com/grupo63/techchallenge/common/usecase/Validator.java @@ -0,0 +1,30 @@ +package br.com.grupo63.techchallenge.common.usecase; + +import br.com.grupo63.techchallenge.common.exception.ValidationException; +import jakarta.validation.ConstraintViolation; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Set; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class Validator { + + private final jakarta.validation.Validator validator; + + public Set> validate(T entity, Class... groups) throws ValidationException { + Set> violations = validator.validate(entity, groups); + + if (!violations.isEmpty()) { + throw new ValidationException( + violations.stream().collect(Collectors.toList()).get(0).getMessage(), + violations.stream().collect(Collectors.toList()).get(0).getMessage()); + } + + return violations; + } + +} +