diff --git a/.env b/.env index 4cca966..730e089 100644 --- a/.env +++ b/.env @@ -1,3 +1,6 @@ +# Sring +SPRING_PROFILES_ACTIVE=dev + # MongoDB Configuration MY_MONGO_INITDB_ROOT_USERNAME=root MY_MONGO_INITDB_ROOT_PASSWORD=qwe123 @@ -9,7 +12,7 @@ AWS_SECRET_ACCESS_KEY=TESTlocalstack AWS_REGION=us-east-1 # Configurações do S3 -S3_BUCKET_NAME=desafio-bucket +S3_BUCKET_NAME=bucket-studios-lab-aws # Configurações do SNS AWS_SNS_ENDPOINT=http://localstack:4566 diff --git a/docker-compose.yml b/docker-compose.yml index 925df30..4796b8b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,14 +30,12 @@ services: - ./localstack/subscribe-sqs-to-sns.sh:/etc/localstack/init/ready.d/subscribe-sqs-to-sns.sh healthcheck: test: [ "CMD", "curl", "-f", "http://localhost:4566/health" ] - interval: 10s - timeout: 5s + interval: 20s + timeout: 10s retries: 5 start_period: 90s networks: - developer-network - # command: > - # /bin/sh -c "apk add --no-cache bash && pip install python-dotenv && docker-entrypoint.sh" # Banco de dados NoSQL mongo-dev: diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/domain/loan/LoanDomain.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/domain/loan/LoanDomain.java new file mode 100644 index 0000000..ad36d49 --- /dev/null +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/domain/loan/LoanDomain.java @@ -0,0 +1,163 @@ +package br.com.springstudiouslabaws.labcore.domain.loan; + +import br.com.springstudiouslabaws.labcore.exceptions.BusinessException; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.Objects; +import java.util.regex.Pattern; + +public class LoanDomain { + + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$"); + private static final int MINIMUM_LOAN_TERM_MONTHS = 4; + private static final BigDecimal MINIMUM_LOAN_AMOUNT = BigDecimal.valueOf(100.00); + private static final BigDecimal MAXIMUM_LOAN_AMOUNT = BigDecimal.valueOf(1000000.00); + public static final int MINIMUM_CHARACTER_NAME = 3; + + String clientId; + String name; + String email; + String description; + BigDecimal amountRequested; + LocalDate dateFinalPayment; + + // Validação completa do domínio + public void validate() { + validateClientId(); + validateName(); + validateEmail(); + validateDescription(); + validateAmountRequested(); + validateDateFinalPayment(); + } + + private void validateClientId() { + if (clientId == null || clientId.isBlank()) { + throw new BusinessException("Identificador do cliente não pode ser vazio"); + } + } + + private void validateName() { + if (name == null || name.isBlank()) { + throw new BusinessException("Nome do cliente não pode ser vazio"); + } + if (name.length() < MINIMUM_CHARACTER_NAME) { + throw new BusinessException(String.format("Nome do cliente deve ter no mínimo %s caracteres", MINIMUM_CHARACTER_NAME)); + } + } + + private void validateEmail() { + if (email == null || email.isBlank()) { + throw new BusinessException("E-mail não pode ser vazio"); + } + if (!EMAIL_PATTERN.matcher(email).matches()) { + throw new BusinessException("E-mail inválido"); + } + } + + private void validateDescription() { + if (description == null || description.isBlank()) { + throw new BusinessException("Descrição do empréstimo não pode ser vazia"); + } + } + + private void validateAmountRequested() { + if (amountRequested == null) { + throw new BusinessException("Valor do empréstimo não pode ser nulo"); + } + if (amountRequested.compareTo(MINIMUM_LOAN_AMOUNT) < 0) { + throw new BusinessException(String.format("Valor do empréstimo deve ser maior que R$ %,.2f", MINIMUM_LOAN_AMOUNT)); + } + if (amountRequested.compareTo(MAXIMUM_LOAN_AMOUNT) > 0) { + throw new BusinessException(String.format("Valor do empréstimo não pode exceder R$ %,.2f", MAXIMUM_LOAN_AMOUNT)); + } + } + + private void validateDateFinalPayment() { + if (dateFinalPayment == null) { + throw new BusinessException("Data final de pagamento não pode ser nula"); + } + if (dateFinalPayment.isBefore(LocalDate.now())) { + throw new BusinessException("Data final de pagamento não pode ser no passado"); + } + + long loanTermMonths = ChronoUnit.MONTHS.between(LocalDate.now(), dateFinalPayment); + if (loanTermMonths < MINIMUM_LOAN_TERM_MONTHS) { + throw new BusinessException(String.format("Prazo mínimo do empréstimo é de %d meses", MINIMUM_LOAN_TERM_MONTHS)); + } + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public BigDecimal getAmountRequested() { + return amountRequested; + } + + public void setAmountRequested(BigDecimal amountRequested) { + this.amountRequested = amountRequested; + } + + public LocalDate getDateFinalPayment() { + return dateFinalPayment; + } + + public void setDateFinalPayment(LocalDate dateFinalPayment) { + this.dateFinalPayment = dateFinalPayment; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + LoanDomain that = (LoanDomain) o; + return Objects.equals(clientId, that.clientId) && Objects.equals(name, that.name) && Objects.equals(email, that.email) && Objects.equals(description, that.description) && Objects.equals(amountRequested, that.amountRequested) && Objects.equals(dateFinalPayment, that.dateFinalPayment); + } + + @Override + public int hashCode() { + return Objects.hash(clientId, name, email, description, amountRequested, dateFinalPayment); + } + + @Override + public String toString() { + return "LoanDomain{" + + "clientId='" + clientId + '\'' + + ", name='" + name + '\'' + + ", email='" + email + '\'' + + ", description='" + description + '\'' + + ", amountRequested=" + amountRequested + + ", dateFinalPayment=" + dateFinalPayment + + '}'; + } +} diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/exceptions/BusinessException.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/exceptions/BusinessException.java new file mode 100644 index 0000000..f58c3da --- /dev/null +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/exceptions/BusinessException.java @@ -0,0 +1,67 @@ +package br.com.springstudiouslabaws.labcore.exceptions; + +import java.io.Serial; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Exceção para representar erros de regras de negócio na aplicação. + * Esta exceção pode conter uma mensagem única ou uma lista de erros de validação. + */ +public class BusinessException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 1L; + + private final List errors; + + /** + * Construtor para uma única mensagem de erro + * + * @param message Mensagem descrevendo o erro de negócio + */ + public BusinessException(String message) { + super(message); + this.errors = Collections.singletonList(message); + } + + /** + * Construtor para uma lista de erros + * + * @param errors Lista de mensagens de erro + */ + public BusinessException(List errors) { + super(String.join("; ", errors)); + this.errors = new ArrayList<>(errors); + } + + /** + * Construtor para mensagem de erro com causa + * + * @param message Mensagem descrevendo o erro de negócio + * @param cause Causa raiz da exceção + */ + public BusinessException(String message, Throwable cause) { + super(message, cause); + this.errors = Collections.singletonList(message); + } + + /** + * Retorna a lista de erros + * + * @return Lista imutável de mensagens de erro + */ + public List getErrors() { + return Collections.unmodifiableList(errors); + } + + /** + * Verifica se há múltiplos erros + * + * @return true se houver mais de um erro, false caso contrário + */ + public boolean hasMultipleErrors() { + return errors.size() > 1; + } +} \ No newline at end of file diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/repositories/LoanRepository.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/repositories/LoanRepository.java new file mode 100644 index 0000000..b265f9d --- /dev/null +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/repositories/LoanRepository.java @@ -0,0 +1,8 @@ +package br.com.springstudiouslabaws.labcore.repositories; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; + +public interface LoanRepository { + + LoanDomain save(LoanDomain loan); +} diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/LoanSQSSendService.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/LoanSQSSendService.java new file mode 100644 index 0000000..b080fe4 --- /dev/null +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/LoanSQSSendService.java @@ -0,0 +1,9 @@ +package br.com.springstudiouslabaws.labcore.services; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; + +public interface LoanSQSSendService { + + void sendLoan(LoanDomain loanDomain); + // Lógica para envio ao SQS do empréstimo solicitado +} diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/SqsReceiverService.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/PaymentSQSReceiverService.java similarity index 89% rename from lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/SqsReceiverService.java rename to lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/PaymentSQSReceiverService.java index 9b16825..4cb6c31 100644 --- a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/SqsReceiverService.java +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/PaymentSQSReceiverService.java @@ -2,7 +2,7 @@ import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; -public interface SqsReceiverService { +public interface PaymentSQSReceiverService { // Lógica para receber mensagens das filas do SQS void receiveFromPartialQueue(PaymentDomain paymentDomain); diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/SqsSendService.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/PaymentSQSSendService.java similarity index 91% rename from lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/SqsSendService.java rename to lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/PaymentSQSSendService.java index 89da5a3..5b09722 100644 --- a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/SqsSendService.java +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/services/PaymentSQSSendService.java @@ -2,7 +2,7 @@ import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; -public interface SqsSendService { +public interface PaymentSQSSendService { void sendToPartialQueue(PaymentDomain paymentDomain); // Lógica para envio ao SQS da fila parcial diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/loan/LoanUseCase.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/loan/LoanUseCase.java new file mode 100644 index 0000000..f67cf69 --- /dev/null +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/loan/LoanUseCase.java @@ -0,0 +1,33 @@ +package br.com.springstudiouslabaws.labcore.usecases.loan; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; +import br.com.springstudiouslabaws.labcore.repositories.LoanRepository; +import br.com.springstudiouslabaws.labcore.services.LoanSQSSendService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class LoanUseCase { + + private static final Logger log = LoggerFactory.getLogger(LoanUseCase.class); + + private final LoanRepository repository; + private final LoanSQSSendService loanSQSSendService; + + public LoanUseCase(LoanRepository loanRepository, LoanSQSSendService loanSQSSendService) { + this.repository = loanRepository; + this.loanSQSSendService = loanSQSSendService; + } + + public LoanDomain processLoan(LoanDomain loanDomain) { + + loanDomain.validate(); + + LoanDomain loan = repository.save(loanDomain); + + log.info("Empréstimo salvo: {}", loan); + + return loan; + } +} diff --git a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/ProcessPaymentUseCase.java b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/payment/ProcessPaymentUseCase.java similarity index 85% rename from lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/ProcessPaymentUseCase.java rename to lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/payment/ProcessPaymentUseCase.java index 61f7de6..6eb20b2 100644 --- a/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/ProcessPaymentUseCase.java +++ b/lab-core/src/main/java/br/com/springstudiouslabaws/labcore/usecases/payment/ProcessPaymentUseCase.java @@ -1,11 +1,10 @@ -package br.com.springstudiouslabaws.labcore.usecases; - +package br.com.springstudiouslabaws.labcore.usecases.payment; import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; import br.com.springstudiouslabaws.labcore.enums.PaymentItemStatusEnum; import br.com.springstudiouslabaws.labcore.exceptions.PaymentNotFoundException; import br.com.springstudiouslabaws.labcore.repositories.PaymentRepository; -import br.com.springstudiouslabaws.labcore.services.SqsSendService; +import br.com.springstudiouslabaws.labcore.services.PaymentSQSSendService; import org.springframework.stereotype.Service; import java.util.Objects; @@ -16,9 +15,9 @@ public class ProcessPaymentUseCase { private final PaymentRepository repository; - private final SqsSendService sqsService; + private final PaymentSQSSendService sqsService; - public ProcessPaymentUseCase(PaymentRepository repository, SqsSendService sqsService) { + public ProcessPaymentUseCase(PaymentRepository repository, PaymentSQSSendService sqsService) { this.repository = repository; this.sqsService = sqsService; } diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoLoanRespository.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoLoanRespository.java new file mode 100644 index 0000000..f51ab34 --- /dev/null +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoLoanRespository.java @@ -0,0 +1,10 @@ +package br.com.springstudiouslabaws.labdataprovider.config.mongodb; + +import br.com.springstudiouslabaws.labdataprovider.entities.LoanEntity; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MongoLoanRespository extends MongoRepository { + // Aqui você pode adicionar consultas específicas se necessário. +} \ No newline at end of file diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoPaymentRepository.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoPaymentRepository.java index a8a0f67..50c938e 100644 --- a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoPaymentRepository.java +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/config/mongodb/MongoPaymentRepository.java @@ -1,10 +1,10 @@ package br.com.springstudiouslabaws.labdataprovider.config.mongodb; -import br.com.springstudiouslabaws.labdataprovider.entities.LoanEntity; +import br.com.springstudiouslabaws.labdataprovider.entities.PaymentsEntity; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; @Repository -public interface MongoPaymentRepository extends MongoRepository { +public interface MongoPaymentRepository extends MongoRepository { // Aqui você pode adicionar consultas específicas se necessário. } \ No newline at end of file diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/entities/LoanEntity.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/entities/LoanEntity.java index 2451f4f..bcf9a78 100644 --- a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/entities/LoanEntity.java +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/entities/LoanEntity.java @@ -4,6 +4,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import java.math.BigDecimal; +import java.time.LocalDate; import java.time.LocalDateTime; @Document(collection = "loan") @@ -17,7 +18,7 @@ public class LoanEntity { private BigDecimal amountRequested; private BigDecimal outstandingBalance; private String status; - private String dateFinalPayment; + private LocalDate dateFinalPayment; private PaymentsEntity payments; private ClientEntity client; private LocalDateTime lastUpdate; @@ -70,11 +71,11 @@ public void setStatus(String status) { this.status = status; } - public String getDateFinalPayment() { + public LocalDate getDateFinalPayment() { return dateFinalPayment; } - public void setDateFinalPayment(String dateFinalPayment) { + public void setDateFinalPayment(LocalDate dateFinalPayment) { this.dateFinalPayment = dateFinalPayment; } diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/CoreToDataMapper.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/CoreToDataMapper.java deleted file mode 100644 index b432de8..0000000 --- a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/CoreToDataMapper.java +++ /dev/null @@ -1,20 +0,0 @@ -package br.com.springstudiouslabaws.labdataprovider.mappers; - -import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; -import br.com.springstudiouslabaws.labdataprovider.entities.LoanEntity; -import org.mapstruct.BeanMapping; -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; - -@Mapper(componentModel = "spring") -public interface CoreToDataMapper { - - @BeanMapping(unmappedTargetPolicy = ReportingPolicy.IGNORE) // ignorar campos não mapeados e a não emitir avisos. -// @Mapping(source = "paymentItems", target = "payments") - LoanEntity toEntity(PaymentDomain domain); - - @BeanMapping(unmappedTargetPolicy = ReportingPolicy.IGNORE) // ignorar campos não mapeados e a não emitir avisos. -// @Mapping(source = "payments", target = "paymentItems") - PaymentDomain toDomain(LoanEntity entity); -} - diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/LoanCoreToDataMapper.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/LoanCoreToDataMapper.java new file mode 100644 index 0000000..de67aff --- /dev/null +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/LoanCoreToDataMapper.java @@ -0,0 +1,24 @@ +package br.com.springstudiouslabaws.labdataprovider.mappers; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; +import br.com.springstudiouslabaws.labdataprovider.entities.LoanEntity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", // Define que o mapper será gerenciado pelo Spring Framework + unmappedTargetPolicy = ReportingPolicy.IGNORE, // ignorar campos não mapeados e a não emitir avisos. + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) // não mapear nulo do objeto fonte +public interface LoanCoreToDataMapper { + + @Mapping(target = "client.clientId", source = "clientId") + @Mapping(target = "client.clientName", source = "name") + @Mapping(target = "client.clientEmail", source = "email") + @Mapping(target = "lastUpdate", expression = "java(java.time.LocalDateTime.now())") + @Mapping(target = "loanId", expression = "java(java.util.UUID.randomUUID().toString())") + @Mapping(target = "status", constant = "PENDING") + LoanEntity toEntity(LoanDomain domain); + + LoanDomain toDomain(LoanEntity entity); +} diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/PaymentCoreToDataMapper.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/PaymentCoreToDataMapper.java new file mode 100644 index 0000000..de47b81 --- /dev/null +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/mappers/PaymentCoreToDataMapper.java @@ -0,0 +1,20 @@ +package br.com.springstudiouslabaws.labdataprovider.mappers; + +import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; +import br.com.springstudiouslabaws.labdataprovider.entities.PaymentsEntity; +import org.mapstruct.Mapper; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", // Define que o mapper será gerenciado pelo Spring Framework + unmappedTargetPolicy = ReportingPolicy.IGNORE, // ignorar campos não mapeados e a não emitir avisos. + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) // não mapear nulo do objeto fonte +public interface PaymentCoreToDataMapper { + + // @Mapping(source = "paymentItems", target = "payments") + PaymentsEntity toEntity(PaymentDomain domain); + + // @Mapping(source = "payments", target = "paymentItems") + PaymentDomain toDomain(PaymentsEntity entity); +} + diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/repositories/LoanRepositoryImpl.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/repositories/LoanRepositoryImpl.java new file mode 100644 index 0000000..628ec66 --- /dev/null +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/repositories/LoanRepositoryImpl.java @@ -0,0 +1,27 @@ +package br.com.springstudiouslabaws.labdataprovider.repositories; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; +import br.com.springstudiouslabaws.labcore.repositories.LoanRepository; +import br.com.springstudiouslabaws.labdataprovider.config.mongodb.MongoLoanRespository; +import br.com.springstudiouslabaws.labdataprovider.entities.LoanEntity; +import br.com.springstudiouslabaws.labdataprovider.mappers.LoanCoreToDataMapper; +import org.springframework.stereotype.Repository; + +@Repository +public class LoanRepositoryImpl implements LoanRepository { + + private final MongoLoanRespository mongoRepository; + private final LoanCoreToDataMapper mapper; + + public LoanRepositoryImpl(MongoLoanRespository mongoRepository, LoanCoreToDataMapper mapper) { + this.mongoRepository = mongoRepository; + this.mapper = mapper; + } + + @Override + public LoanDomain save(LoanDomain loan) { + LoanEntity entity = mongoRepository.save(mapper.toEntity(loan)); + return mapper.toDomain(entity); + } + +} diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/loan/LoanSQSSendServiceImpl.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/loan/LoanSQSSendServiceImpl.java new file mode 100644 index 0000000..9da9339 --- /dev/null +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/loan/LoanSQSSendServiceImpl.java @@ -0,0 +1,14 @@ +package br.com.springstudiouslabaws.labdataprovider.services.loan; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; +import br.com.springstudiouslabaws.labcore.services.LoanSQSSendService; +import org.springframework.stereotype.Service; + +@Service +public class LoanSQSSendServiceImpl implements LoanSQSSendService { + + @Override + public void sendLoan(LoanDomain loanDomain) { + + } +} diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/SqsReceiverServiceImpl.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/payment/PaymentSQSReceiverServiceImpl.java similarity index 72% rename from lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/SqsReceiverServiceImpl.java rename to lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/payment/PaymentSQSReceiverServiceImpl.java index fe9d250..6e9fb09 100644 --- a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/SqsReceiverServiceImpl.java +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/payment/PaymentSQSReceiverServiceImpl.java @@ -1,16 +1,16 @@ -package br.com.springstudiouslabaws.labdataprovider.services; +package br.com.springstudiouslabaws.labdataprovider.services.payment; import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; -import br.com.springstudiouslabaws.labcore.services.SqsReceiverService; +import br.com.springstudiouslabaws.labcore.services.PaymentSQSReceiverService; import io.awspring.cloud.sqs.annotation.SqsListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service -public class SqsReceiverServiceImpl implements SqsReceiverService { +public class PaymentSQSReceiverServiceImpl implements PaymentSQSReceiverService { - private static final Logger log = LoggerFactory.getLogger(SqsReceiverServiceImpl.class); + private static final Logger log = LoggerFactory.getLogger(PaymentSQSReceiverServiceImpl.class); @SqsListener("${sqs.queue.partial}") public void receiveFromPartialQueue(PaymentDomain paymentDomain) { diff --git a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/SqsSendServiceImpl.java b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/payment/PaymentSQSSendServiceImpl.java similarity index 77% rename from lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/SqsSendServiceImpl.java rename to lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/payment/PaymentSQSSendServiceImpl.java index 6aa5834..f2bf3e2 100644 --- a/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/SqsSendServiceImpl.java +++ b/lab-dataprovider/src/main/java/br/com/springstudiouslabaws/labdataprovider/services/payment/PaymentSQSSendServiceImpl.java @@ -1,8 +1,7 @@ -package br.com.springstudiouslabaws.labdataprovider.services; +package br.com.springstudiouslabaws.labdataprovider.services.payment; import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; -import br.com.springstudiouslabaws.labcore.services.SqsSendService; -import io.awspring.cloud.sqs.annotation.SqsListener; +import br.com.springstudiouslabaws.labcore.services.PaymentSQSSendService; import io.awspring.cloud.sqs.operations.SqsTemplate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,9 +9,9 @@ import org.springframework.stereotype.Service; @Service -public class SqsSendServiceImpl implements SqsSendService { +public class PaymentSQSSendServiceImpl implements PaymentSQSSendService { - private static final Logger log = LoggerFactory.getLogger(SqsSendServiceImpl.class); + private static final Logger log = LoggerFactory.getLogger(PaymentSQSSendServiceImpl.class); private final SqsTemplate sqsTemplate; @@ -26,7 +25,7 @@ public class SqsSendServiceImpl implements SqsSendService { @Value("${spring.cloud.aws.sqs.queues.overmeasure}") private String overmeasureQueue; - public SqsSendServiceImpl(SqsTemplate sqsTemplate) { + public PaymentSQSSendServiceImpl(SqsTemplate sqsTemplate) { this.sqsTemplate = sqsTemplate; } diff --git a/lab-dataprovider/src/main/resources/application.yml b/lab-dataprovider/src/main/resources/application.yml index b471246..5ebf857 100644 --- a/lab-dataprovider/src/main/resources/application.yml +++ b/lab-dataprovider/src/main/resources/application.yml @@ -6,8 +6,6 @@ spring: default-property-inclusion: non_null data: mongodb: - # Local - # uri: mongodb://root:qwe123@localhost:27017/spring-studious-lab-aws-database?authSource=admin # Cloud uri: mongodb://root:qwe123@mongo-dev:27017/spring-studious-lab-aws-database?authSource=admin autoconfigure: diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/config/OpenAPIConfig.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/config/OpenAPIConfig.java new file mode 100644 index 0000000..624be20 --- /dev/null +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/config/OpenAPIConfig.java @@ -0,0 +1,57 @@ +package br.com.springstudiouslabaws.labentrypoint.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +public class OpenAPIConfig { + + @Bean + public OpenAPI customOpenAPI() { + Server devServer = new Server() + .url("http://localhost:8082") + .description("Ambiente de Desenvolvimento"); + + Server qaServer = new Server() + .url("https://api-qa.exemplo.com") + .description("Ambiente de QA"); + + Server prodServer = new Server() + .url("https://api.exemplo.com") + .description("Ambiente de Produção"); + + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + .description("JWT token de autenticação"); + + return new OpenAPI() + .info(new Info() + .title("API de Processamento de Empréstimos e Pagamentos") + .version("1.0.0") + .description("API para gerenciamento de empréstimos e processamento de pagamentos com integração AWS") + .termsOfService("https://www.exemplo.com/terms") + .contact(new Contact() + .name("Time de Desenvolvimento") + .email("dev@empresa.com") + .url("https://www.exemplo.com/team")) + .license(new License() + .name("Apache 2.0") + .url("https://www.apache.org/licenses/LICENSE-2.0"))) + .servers(List.of(devServer, qaServer, prodServer)) + .components(new Components() + .addSecuritySchemes("bearer-jwt", securityScheme)) + .addSecurityItem(new SecurityRequirement().addList("bearer-jwt")); + } +} \ No newline at end of file diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/LoanController.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/LoanController.java new file mode 100644 index 0000000..6396f91 --- /dev/null +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/LoanController.java @@ -0,0 +1,30 @@ +package br.com.springstudiouslabaws.labentrypoint.controllers; + +import br.com.springstudiouslabaws.labentrypoint.dtos.request.LoanRequestDTO; +import br.com.springstudiouslabaws.labentrypoint.dtos.response.LoanResponseDTO; +import br.com.springstudiouslabaws.labentrypoint.facade.LoanFacade; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v1/emprestimos") +public class LoanController { + + private final LoanFacade loanFacade; + + public LoanController(LoanFacade loanFacade) { + this.loanFacade = loanFacade; + } + + @PostMapping(consumes = "application/json", produces = "application/json") + public ResponseEntity createLoan(@Valid @RequestBody LoanRequestDTO request) { + LoanResponseDTO response = loanFacade.createLoan(request); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + +} diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/PaymentController.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/PaymentController.java index bc1ee97..597cf97 100644 --- a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/PaymentController.java +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/controllers/PaymentController.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/v1/api") +@RequestMapping("/v1/pagamentos") @Validated public class PaymentController { @@ -24,15 +24,13 @@ public PaymentController(PaymentFacade paymentFacade) { this.paymentFacade = paymentFacade; } - @PostMapping("/payment") + @PostMapping(consumes = "application/json", produces = "application/json") public ResponseEntity createPayment(@Valid @RequestBody PaymentRequestDTO request) { PaymentResponseDTO response = paymentFacade.createPayment(request); return ResponseEntity.status(HttpStatus.CREATED).body(response); } - @PutMapping(value = "/payment", - consumes = "application/json", - produces = "application/json") + @PutMapping(consumes = "application/json", produces = "application/json") public ResponseEntity setPayment(@Valid @RequestBody PaymentRequestDTO request) { PaymentResponseDTO response = paymentFacade.processPayment(request); return ResponseEntity.status(HttpStatus.OK).body(response); diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/dtos/request/LoanRequestDTO.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/dtos/request/LoanRequestDTO.java new file mode 100644 index 0000000..6903ff7 --- /dev/null +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/dtos/request/LoanRequestDTO.java @@ -0,0 +1,45 @@ +package br.com.springstudiouslabaws.labentrypoint.dtos.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@JsonNaming(SnakeCaseStrategy.class) +public record LoanRequestDTO( + + @NotBlank(message = "Identificador do cliente não pode ser nulo ou vazio") + @JsonProperty("identificador_cliente") + String clientId, + + @NotBlank(message = "Nome do cliente não pode ser nulo ou vazio") + @JsonProperty("nome") + String name, + + @NotBlank(message = "E-mail do cliente não pode ser nulo ou vazio") + @Email(message = "E-mail inválido") + @JsonProperty("email") + String email, + + @NotBlank(message = "Descrição do empréstimo não pode ser nulo ou vazio") + @JsonProperty("descricao_emprestimo") + String description, + + @NotNull(message = "Valor solicitado do empréstimo não pode ser nulo") + @DecimalMin(value = "0.01", message = "Valor deve ser maior que zero") + @JsonProperty("valor_solicitado") + BigDecimal amountRequested, + + @NotNull(message = "Data de vencimento do empréstimo não pode ser nula") + @Future(message = "Data de vencimento deve ser uma data futura") + @JsonProperty("data_final_vencimento") + LocalDate dateFinalPayment +) { +} \ No newline at end of file diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/dtos/response/LoanResponseDTO.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/dtos/response/LoanResponseDTO.java new file mode 100644 index 0000000..23fcd52 --- /dev/null +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/dtos/response/LoanResponseDTO.java @@ -0,0 +1,28 @@ +package br.com.springstudiouslabaws.labentrypoint.dtos.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.math.BigDecimal; +import java.time.LocalDate; + +public record LoanResponseDTO( + + @JsonProperty("identificador_cliente") + String clientId, + + @JsonProperty("nome") + String name, + + @JsonProperty("email") + String email, + + @JsonProperty("descricao_emprestimo") + String description, + + @JsonProperty("valor_solicitado") + BigDecimal amountRequested, + + @JsonProperty("data_final_vencimento") + LocalDate dateFinalPayment +) { +} diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/exceptions/RestResponseEntityExceptionHandler.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/exceptions/RestResponseEntityExceptionHandler.java index 31bf8eb..eee6741 100644 --- a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/exceptions/RestResponseEntityExceptionHandler.java +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/exceptions/RestResponseEntityExceptionHandler.java @@ -1,5 +1,6 @@ package br.com.springstudiouslabaws.labentrypoint.exceptions; +import br.com.springstudiouslabaws.labcore.exceptions.BusinessException; import br.com.springstudiouslabaws.labcore.exceptions.PaymentNotFoundException; import br.com.springstudiouslabaws.labdataprovider.exceptions.ApiErrorMessage; import br.com.springstudiouslabaws.labdataprovider.exceptions.ResourceNotFoundException; @@ -36,6 +37,18 @@ private ResponseEntity getApiErrorMessageResponseEntity(WebRequ return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex, WebRequest request) { + ApiErrorMessage error = new ApiErrorMessage(); + error.setTitle("Erro de Validação de Negócio"); + error.setMessage(ex.getMessage()); + error.setDescription(request.getDescription(false)); + error.setStatusCode(HttpStatus.UNPROCESSABLE_ENTITY.value()); + error.setTimestamp(LocalDateTime.now()); + + return new ResponseEntity<>(error, HttpStatus.UNPROCESSABLE_ENTITY); + } + // @ExceptionHandler(value = ElementNotExistsException.class) //Com classe genérica para encapsular um objeto de erro // protected ResponseEntity elementNotExistsException(ElementNotExistsException ex, WebRequest request) { diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/LoanFacade.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/LoanFacade.java new file mode 100644 index 0000000..310f746 --- /dev/null +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/LoanFacade.java @@ -0,0 +1,30 @@ +package br.com.springstudiouslabaws.labentrypoint.facade; + +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; +import br.com.springstudiouslabaws.labcore.usecases.loan.LoanUseCase; +import br.com.springstudiouslabaws.labentrypoint.dtos.request.LoanRequestDTO; +import br.com.springstudiouslabaws.labentrypoint.dtos.response.LoanResponseDTO; +import br.com.springstudiouslabaws.labentrypoint.mappers.DomainToDTOMapper; +import br.com.springstudiouslabaws.labentrypoint.mappers.EntryToCoreMapper; +import org.springframework.stereotype.Component; + +@Component +public class LoanFacade { + + private final LoanUseCase loanUseCase; + private final EntryToCoreMapper entryToCoreMapper; + private final DomainToDTOMapper domainToDTOMapper; + + public LoanFacade(LoanUseCase loanUseCase, EntryToCoreMapper entryToCoreMapper, DomainToDTOMapper domainToDTOMapper) { + this.loanUseCase = loanUseCase; + this.entryToCoreMapper = entryToCoreMapper; + this.domainToDTOMapper = domainToDTOMapper; + } + + public LoanResponseDTO createLoan(LoanRequestDTO loanRequestDTO) { + LoanDomain loanDomain = entryToCoreMapper.toDomain(loanRequestDTO); + LoanDomain processedLoan = loanUseCase.processLoan(loanDomain); + return domainToDTOMapper.toDTO(processedLoan); + } + +} diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/PaymentFacade.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/PaymentFacade.java index 269901d..6003d9f 100644 --- a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/PaymentFacade.java +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/facade/PaymentFacade.java @@ -1,7 +1,7 @@ package br.com.springstudiouslabaws.labentrypoint.facade; import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; -import br.com.springstudiouslabaws.labcore.usecases.ProcessPaymentUseCase; +import br.com.springstudiouslabaws.labcore.usecases.payment.ProcessPaymentUseCase; import br.com.springstudiouslabaws.labentrypoint.dtos.request.PaymentRequestDTO; import br.com.springstudiouslabaws.labentrypoint.dtos.response.PaymentResponseDTO; import br.com.springstudiouslabaws.labentrypoint.mappers.DomainToDTOMapper; diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/DomainToDTOMapper.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/DomainToDTOMapper.java index f96244b..edd0f29 100644 --- a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/DomainToDTOMapper.java +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/DomainToDTOMapper.java @@ -1,6 +1,8 @@ package br.com.springstudiouslabaws.labentrypoint.mappers; +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; +import br.com.springstudiouslabaws.labentrypoint.dtos.response.LoanResponseDTO; import br.com.springstudiouslabaws.labentrypoint.dtos.response.PaymentResponseDTO; import org.mapstruct.Mapper; @@ -8,5 +10,7 @@ public interface DomainToDTOMapper { PaymentResponseDTO toDTO(PaymentDomain paymentDomain); + + LoanResponseDTO toDTO(LoanDomain loanDomain); } diff --git a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/EntryToCoreMapper.java b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/EntryToCoreMapper.java index 3d0c37d..16a1537 100644 --- a/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/EntryToCoreMapper.java +++ b/lab-entrypoint/src/main/java/br/com/springstudiouslabaws/labentrypoint/mappers/EntryToCoreMapper.java @@ -1,6 +1,8 @@ package br.com.springstudiouslabaws.labentrypoint.mappers; +import br.com.springstudiouslabaws.labcore.domain.loan.LoanDomain; import br.com.springstudiouslabaws.labcore.domain.payment.PaymentDomain; +import br.com.springstudiouslabaws.labentrypoint.dtos.request.LoanRequestDTO; import br.com.springstudiouslabaws.labentrypoint.dtos.request.PaymentRequestDTO; import org.mapstruct.Mapper; @@ -8,4 +10,6 @@ public interface EntryToCoreMapper { PaymentDomain toDomain(PaymentRequestDTO paymentRequestDTO); + + LoanDomain toDomain(LoanRequestDTO loanRequestDTO); } diff --git a/lab-entrypoint/src/main/resources/application-dev.yml b/lab-entrypoint/src/main/resources/application-dev.yml new file mode 100644 index 0000000..7722602 --- /dev/null +++ b/lab-entrypoint/src/main/resources/application-dev.yml @@ -0,0 +1,11 @@ +spring: + cloud: + aws: + sqs: + endpoint: http://localstack:4566 + sns: + endpoint: http://localstack:4566 + data: + mongodb: + # Cloud DEV + uri: mongodb://root:qwe123@mongo-dev:27017/spring-studious-lab-aws-database?authSource=admin diff --git a/lab-entrypoint/src/main/resources/application-local.yml b/lab-entrypoint/src/main/resources/application-local.yml new file mode 100644 index 0000000..3c4dac5 --- /dev/null +++ b/lab-entrypoint/src/main/resources/application-local.yml @@ -0,0 +1,12 @@ +# No IntelliJ, adicione a configuração abaixo, para subir local e se comunicar com o docker. +# VM Options: -Dspring.profiles.active=local +spring: + cloud: + aws: + sqs: + endpoint: http://localhost:4566 + sns: + endpoint: http://localhost:4566 + data: + mongodb: + uri: mongodb://root:qwe123@localhost:27017/spring-studious-lab-aws-database?authSource=admin \ No newline at end of file diff --git a/lab-entrypoint/src/main/resources/application.yml b/lab-entrypoint/src/main/resources/application.yml index 41bcdbd..8756f7e 100644 --- a/lab-entrypoint/src/main/resources/application.yml +++ b/lab-entrypoint/src/main/resources/application.yml @@ -4,15 +4,11 @@ server: spring: application: name: lab-entrypoint + profiles: + active: ${SPRING_PROFILES_ACTIVE:dev} jackson: # Não retorna campo nulo no response default-property-inclusion: non_null - data: - mongodb: - # Local - # uri: mongodb://root:qwe123@localhost:27017/spring-studious-lab-aws-database?authSource=admin - # Cloud - uri: mongodb://root:qwe123@mongo-dev:27017/spring-studious-lab-aws-database?authSource=admin autoconfigure: exclude: # Exclusão de autoconfiguração JPA/Hibernate diff --git a/localstack/init-s3-bucket.sh b/localstack/init-s3-bucket.sh index 1269eaa..f0e6f65 100644 --- a/localstack/init-s3-bucket.sh +++ b/localstack/init-s3-bucket.sh @@ -1,18 +1,97 @@ #!/bin/bash -# Carrega variáveis do .env -#export "$(grep -v '^#' .env | xargs)" +############################################################################### +# Script para criar buckets S3 no LocalStack +# +# Este script é responsável por criar os buckets S3 necessários para o sistema. +# Os buckets S3 são usados para armazenamento de arquivos e documentos, +# como contratos, comprovantes e outros documentos relacionados. +# +# Variáveis de ambiente necessárias: +# - AWS_REGION: Região AWS (ex: us-east-1) +# - S3_BUCKET_NAME: Nome do bucket principal +# +# Buckets atuais: +# - bucket-studios-lab-aws: Bucket principal para armazenamento de documentos +# +# Buckets futuros (exemplos): +# - bucket-loan-documents: Para documentos de empréstimos +# - bucket-audit-files: Para arquivos de auditoria +############################################################################### -# Verificar endpoints -echo "AWS_SNS_ENDPOINT=$AWS_SNS_ENDPOINT" -echo "AWS_SQS_ENDPOINT=$AWS_SQS_ENDPOINT" +############################################################################### +# Função para criar um bucket S3 com retry automático +# +# Argumentos: +# $1 - Nome do bucket a ser criado +# +# Retorno: +# 0 - Sucesso +# 1 - Falha após todas as tentativas +# +# Exemplo de uso: +# create_bucket "my-bucket-name" +############################################################################### +create_bucket() { + local bucket_name=$1 + local max_retries=3 # Número máximo de tentativas + local retry_count=0 + local retry_delay=2 # Tempo de espera entre tentativas (segundos) + + echo "Criando bucket S3: $bucket_name" + + while [ $retry_count -lt $max_retries ]; do + if aws --endpoint-url="http://localhost:4566" s3api create-bucket \ + --bucket "$bucket_name" \ + --region "$AWS_REGION"; then + echo "✅ Bucket '$bucket_name' criado com sucesso" + return 0 + fi + + retry_count=$((retry_count + 1)) + if [ $retry_count -lt $max_retries ]; then + echo "Tentativa $retry_count de $max_retries falhou. Tentando novamente..." + sleep $retry_delay + fi + done + + echo "❌ Erro ao criar bucket $bucket_name após $max_retries tentativas" + return 1 +} + +# Exibe as configurações atuais para debug +echo "Configurações AWS:" echo "AWS_REGION=$AWS_REGION" +echo "S3_BUCKET_NAME=$S3_BUCKET_NAME" + +############################################################################### +# Criação dos buckets S3 +# Para adicionar novos buckets: +# 1. Adicione a variável de ambiente no .env +# 2. Adicione a criação do bucket aqui seguindo o padrão +############################################################################### + +echo "======== Criando buckets S3 ========" +create_bucket "$S3_BUCKET_NAME" +echo "===================================" -# Nome do bucket do .env -BUCKET_NAME="$S3_BUCKET_NAME" +# Exemplo de como adicionar novos buckets: +# echo "======== Criando buckets para Empréstimos ========" +# create_bucket "$S3_BUCKET_LOAN_DOCUMENTS" +# echo "===============================================" -# Criação do bucket -awslocal s3api create-bucket --bucket "$BUCKET_NAME" +############################################################################### +# Área para configurações adicionais dos buckets +# Aqui você pode adicionar configurações como: +# - Políticas de acesso +# - Lifecycle rules +# - Versionamento +# - Criptografia +# +# Exemplo: +# aws --endpoint-url="http://localhost:4566" s3api put-bucket-versioning \ +# --bucket "$S3_BUCKET_NAME" \ +# --versioning-configuration Status=Enabled +############################################################################### -# Mensagem de sucesso -echo "Bucket '$BUCKET_NAME' criado com sucesso no LocalStack." +echo "Processo de criação de buckets S3 finalizado!" \ No newline at end of file diff --git a/localstack/init-sns-topic.sh b/localstack/init-sns-topic.sh index 4602042..5f62c5e 100644 --- a/localstack/init-sns-topic.sh +++ b/localstack/init-sns-topic.sh @@ -1,17 +1,80 @@ #!/bin/bash -# Carrega variáveis do .env -#export "$(grep -v '^#' .env | xargs)" +############################################################################### +# Script para criar tópicos SNS no LocalStack +# +# Este script é responsável por criar os tópicos SNS necessários para o sistema. +# Os tópicos SNS são usados para publicação e assinatura de mensagens, +# permitindo comunicação assíncrona entre diferentes componentes do sistema. +# +# Variáveis de ambiente necessárias: +# - AWS_SNS_ENDPOINT: Endpoint do serviço SNS (ex: http://localstack:4566) +# - AWS_REGION: Região AWS (ex: us-east-1) +# - SNS_TOPIC_NAME: Nome do tópico a ser criado +# +# Tópicos atuais: +# - payments-topic: Tópico para notificações relacionadas a pagamentos +# +# Tópicos futuros: +# - loan-topic: Tópico para notificações de empréstimos +############################################################################### -# Verificar endpoints +############################################################################### +# Função para criar um tópico SNS com retry automático +# +# Argumentos: +# $1 - Nome do tópico a ser criado +# +# Retorno: +# 0 - Sucesso +# 1 - Falha após todas as tentativas +############################################################################### +create_topic() { + local topic_name=$1 + local max_retries=3 # Número máximo de tentativas + local retry_count=0 + local retry_delay=2 # Tempo de espera entre tentativas (segundos) + + echo "Criando tópico SNS: $topic_name" + + while [ $retry_count -lt $max_retries ]; do + if aws --endpoint-url="$AWS_SNS_ENDPOINT" sns create-topic \ + --name "$topic_name" \ + --region "$AWS_REGION"; then + echo "✅ Tópico SNS '$topic_name' criado com sucesso" + return 0 + fi + + retry_count=$((retry_count + 1)) + if [ $retry_count -lt $max_retries ]; then + echo "Tentativa $retry_count de $max_retries falhou. Tentando novamente..." + sleep $retry_delay + fi + done + + echo "❌ Erro ao criar tópico $topic_name após $max_retries tentativas" + return 1 +} + +# Exibe as configurações atuais para debug +echo "Configurações AWS:" echo "AWS_SNS_ENDPOINT=$AWS_SNS_ENDPOINT" echo "AWS_REGION=$AWS_REGION" -# Nome do tópico do .env -TOPIC_NAME="$SNS_TOPIC_NAME" +############################################################################### +# Criação dos tópicos SNS +# Para adicionar novos tópicos: +# 1. Adicione a variável de ambiente no .env +# 2. Adicione a criação do tópico aqui seguindo o padrão +############################################################################### + +echo "======== Criando tópicos SNS ========" +create_topic "$SNS_TOPIC_NAME" +echo "====================================" -# Criação do tópico -awslocal sns create-topic --name "$TOPIC_NAME" +# Exemplo de como adicionar novos tópicos: +# echo "======== Criando tópicos de Empréstimo ========" +# create_topic "$SNS_TOPIC_LOAN" +# echo "==============================================" -# Mensagem de sucesso -echo "SNS topic '$TOPIC_NAME' criado com sucesso no LocalStack." +echo "Processo de criação de tópicos SNS finalizado!" \ No newline at end of file diff --git a/localstack/init-sqs-queue.sh b/localstack/init-sqs-queue.sh index 7f5c280..aa8580d 100644 --- a/localstack/init-sqs-queue.sh +++ b/localstack/init-sqs-queue.sh @@ -1,24 +1,102 @@ #!/bin/bash -# Carrega variáveis do .env -#export "$(grep -v '^#' .env | xargs)" +############################################################################### +# Script para criar filas SQS no LocalStack +# +# Este script é responsável por criar as filas SQS necessárias para o sistema. +# Ele está organizado por domínios (Pagamento, Empréstimo, etc.) para facilitar +# a manutenção e expansão do sistema. +# +# Variáveis de ambiente necessárias: +# - AWS_SQS_ENDPOINT: Endpoint do serviço SQS (ex: http://localstack:4566) +# - AWS_REGION: Região AWS (ex: us-east-1) +# +# Filas de Pagamento: +# - SQS_QUEUE_PARTIAL: Fila para pagamentos parciais +# - SQS_QUEUE_TOTAL: Fila para pagamentos totais +# - SQS_QUEUE_OVERMEASURE: Fila para pagamentos excedentes +# +# Filas de Empréstimo (futuro): +# - SQS_QUEUE_LOAN_REQUEST: Fila para solicitações de empréstimo +# - SQS_QUEUE_LOAN_APPROVAL: Fila para aprovações de empréstimo +############################################################################### -# Verificar endpoints +echo "Criando filas SQS no LocalStack..." + +############################################################################### +# Função para criar uma fila SQS com retry automático +# +# Argumentos: +# $1 - Nome da fila a ser criada +# +# Retorno: +# 0 - Sucesso +# 1 - Falha após todas as tentativas +# +# Exemplo de uso: +# create_queue "my-queue-name" +############################################################################### +create_queue() { + local queue_name=$1 + local max_retries=3 # Número máximo de tentativas + local retry_count=0 + local retry_delay=2 # Tempo de espera entre tentativas (segundos) + + echo "Criando fila: $queue_name" + + while [ $retry_count -lt $max_retries ]; do + # Tenta criar a fila usando AWS CLI + if aws --endpoint-url="$AWS_SQS_ENDPOINT" sqs create-queue \ + --queue-name "$queue_name" \ + --region "$AWS_REGION"; then + echo "✅ Fila '$queue_name' criada com sucesso" + return 0 + fi + + # Incrementa contador e tenta novamente se não atingiu o máximo + retry_count=$((retry_count + 1)) + if [ $retry_count -lt $max_retries ]; then + echo "Tentativa $retry_count de $max_retries falhou. Tentando novamente..." + sleep $retry_delay + fi + done + + echo "❌ Erro ao criar fila $queue_name após $max_retries tentativas" + return 1 +} + +# Exibe as configurações atuais para debug +echo "Configurações AWS:" echo "AWS_SQS_ENDPOINT=$AWS_SQS_ENDPOINT" echo "AWS_REGION=$AWS_REGION" -echo "Criando filas SQS no LocalStack..." - -# Cria a fila para pagamento parcial -aws --endpoint-url="$AWS_SQS_ENDPOINT" sqs create-queue --queue-name "$SQS_QUEUE_PARTIAL" --region "$AWS_REGION" -echo "SQS queue '$SQS_QUEUE_PARTIAL' criada com sucesso no LocalStack." +############################################################################### +# Criação das filas de Pagamento +# Adicione novas filas relacionadas a pagamento nesta seção +############################################################################### +echo "======== Criando filas de Pagamento ========" +create_queue "$SQS_QUEUE_PARTIAL" # Fila para pagamentos parciais +create_queue "$SQS_QUEUE_TOTAL" # Fila para pagamentos totais +create_queue "$SQS_QUEUE_OVERMEASURE" # Fila para pagamentos excedentes +echo "============================================" -# Cria a fila para pagamento total -aws --endpoint-url="$AWS_SQS_ENDPOINT" sqs create-queue --queue-name "$SQS_QUEUE_TOTAL" --region "$AWS_REGION" -echo "SQS queue '$SQS_QUEUE_TOTAL' criada com sucesso no LocalStack." +############################################################################### +# Criação das filas de Empréstimo (Futuro) +# Descomente e adicione novas filas relacionadas a empréstimo conforme necessário +############################################################################### +# echo "======== Criando filas de Empréstimo ========" +# create_queue "$SQS_QUEUE_LOAN_REQUEST" # Fila para solicitações de empréstimo +# create_queue "$SQS_QUEUE_LOAN_APPROVAL" # Fila para aprovações de empréstimo +# echo "============================================" -# Cria a fila para pagamento em excesso -aws --endpoint-url="$AWS_SQS_ENDPOINT" sqs create-queue --queue-name "$SQS_QUEUE_OVERMEASURE" --region "$AWS_REGION" -echo "SQS queue '$SQS_QUEUE_OVERMEASURE' criada com sucesso no LocalStack." +############################################################################### +# Área para adicionar novos domínios +# Siga o padrão acima para adicionar novas seções de filas +# Exemplo: +# +# echo "======== Criando filas de [Novo Domínio] ========" +# create_queue "$SQS_QUEUE_NOVO_DOMINIO_ACAO" +# echo "============================================" +############################################################################### -echo "Filas criadas!" +echo "Processo de criação de filas finalizado!" \ No newline at end of file diff --git a/localstack/subscribe-sqs-to-sns.sh b/localstack/subscribe-sqs-to-sns.sh index 19a7b62..094340a 100644 --- a/localstack/subscribe-sqs-to-sns.sh +++ b/localstack/subscribe-sqs-to-sns.sh @@ -1,18 +1,94 @@ #!/bin/bash -# Carrega variáveis do .env -#export $(grep -v '^#' .env | xargs) +############################################################################### +# Script para realizar a subscrição de filas SQS em tópicos SNS no LocalStack +# +# Este script estabelece a conexão entre tópicos SNS e filas SQS, permitindo +# que mensagens publicadas nos tópicos sejam automaticamente enviadas para +# as filas correspondentes. +# +# Variáveis de ambiente necessárias: +# - AWS_SNS_ENDPOINT: Endpoint do serviço SNS +# - AWS_SQS_ENDPOINT: Endpoint do serviço SQS +# - AWS_REGION: Região AWS (ex: us-east-1) +# - SNS_TOPIC_NAME: Nome do tópico SNS +# - SQS_QUEUE_NAME: Nome da fila SQS +# +# Subscrições atuais: +# - payments-topic -> payment-partial-queue +# - payments-topic -> payment-total-queue +# - payments-topic -> payment-overmeasure-queue +############################################################################### -# Verificar endpoints e região +############################################################################### +# Função para subscrever uma fila SQS em um tópico SNS +# +# Argumentos: +# $1 - Nome do tópico SNS +# $2 - Nome da fila SQS +# +# Retorno: +# 0 - Sucesso +# 1 - Falha após todas as tentativas +############################################################################### +subscribe_queue_to_topic() { + local topic_name=$1 + local queue_name=$2 + local max_retries=3 # Número máximo de tentativas + local retry_count=0 + local retry_delay=2 # Tempo de espera entre tentativas (segundos) + + echo "Subscrevendo fila '$queue_name' ao tópico '$topic_name'" + + # Monta os ARNs para tópico e fila + local topic_arn="arn:aws:sns:$AWS_REGION:000000000000:$topic_name" + local queue_arn="arn:aws:sqs:$AWS_REGION:000000000000:$queue_name" + + while [ $retry_count -lt $max_retries ]; do + if aws --endpoint-url="$AWS_SNS_ENDPOINT" sns subscribe \ + --topic-arn "$topic_arn" \ + --protocol sqs \ + --notification-endpoint "$queue_arn" \ + --region "$AWS_REGION"; then + echo "✅ Fila '$queue_name' subscrita com sucesso ao tópico '$topic_name'" + return 0 + fi + + retry_count=$((retry_count + 1)) + if [ $retry_count -lt $max_retries ]; then + echo "Tentativa $retry_count de $max_retries falhou. Tentando novamente..." + sleep $retry_delay + fi + done + + echo "❌ Erro ao subscrever fila '$queue_name' ao tópico '$topic_name'" + return 1 +} + +# Exibe as configurações atuais para debug +echo "Configurações AWS:" echo "AWS_SNS_ENDPOINT=$AWS_SNS_ENDPOINT" echo "AWS_SQS_ENDPOINT=$AWS_SQS_ENDPOINT" echo "AWS_REGION=$AWS_REGION" -# Realiza a assinatura da fila ao tópico -awslocal sns subscribe \ - --topic-arn "arn:aws:sns:$AWS_REGION:000000000000:$SNS_TOPIC_NAME" \ - --protocol sqs \ - --notification-endpoint "arn:aws:sqs:$AWS_REGION:000000000000:$SQS_QUEUE_NAME" +############################################################################### +# Criação das subscrições de Pagamento +# Cada fila de pagamento é subscrita no tópico de pagamentos +############################################################################### +echo "======== Criando subscrições de Pagamento ========" +subscribe_queue_to_topic "$SNS_TOPIC_NAME" "$SQS_QUEUE_PARTIAL" +subscribe_queue_to_topic "$SNS_TOPIC_NAME" "$SQS_QUEUE_TOTAL" +subscribe_queue_to_topic "$SNS_TOPIC_NAME" "$SQS_QUEUE_OVERMEASURE" +echo "===============================================" + +############################################################################### +# Área para futuras subscrições +# +# Exemplo para subscrições de Empréstimo: +# echo "======== Criando subscrições de Empréstimo ========" +# subscribe_queue_to_topic "$SNS_TOPIC_LOAN" "$SQS_QUEUE_LOAN_REQUEST" +# subscribe_queue_to_topic "$SNS_TOPIC_LOAN" "$SQS_QUEUE_LOAN_APPROVAL" +# echo "=================================================" +############################################################################### -# Mensagem de sucesso -echo "Subscribed SQS queue '$SQS_QUEUE_NAME' to SNS topic '$SNS_TOPIC_NAME' com sucesso no LocalStack." +echo "Processo de subscrição de filas SQS aos tópicos SNS finalizado!" \ No newline at end of file diff --git a/pom.xml b/pom.xml index f5dab43..28d2eb3 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,10 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.boot + spring-boot-starter-actuator + @@ -143,6 +147,10 @@ io.awspring.cloud spring-cloud-aws-starter-s3 + + io.awspring.cloud + spring-cloud-aws-core + @@ -204,7 +212,6 @@ pom import - org.mapstruct