diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/app-java-codechallenge.iml b/.idea/app-java-codechallenge.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/app-java-codechallenge.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..750c82b
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..4955411
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..712ab9d
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b7376dd
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5e7c38b
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 7f832ad..4205021 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,15 @@
+# Solution
+
+To solve the technical challenge, apply everything requested, first create a microservice that subscribes to Kafka topics and with this can create transactions in the DB and in the respective Topic and the second microservice is responsible for subscribing to the Kafka topic. creation to obtain the amount and be able to validate whether it is fraudulent or not according to the condition provided, in case the fraud microservice is restarted it will resume all the requests that were pending to analyze whether or not it was a fraud, the technological stack en Reactive programming with Java 17 (webflux), as well as GraphQL, H2 database with JPA, were used for the requests.
+
+# Deploy Local
+
+To deploy only run "docker compose up --build -d" with this command all microservices and components will be start and ready for use.
+
+# Test
+
+You can test the endpoints of microservices with the postman collection, it's located in directory "postman"
+
# Yape Code Challenge :rocket:
Our code challenge will let you marvel us with your Jedi coding skills :smile:.
@@ -12,8 +24,8 @@ Don't forget that the proper way to submit your work is to fork the repo and cre
# Problem
-Every time a financial transaction is created it must be validated by our anti-fraud microservice and then the same service sends a message back to update the transaction status.
-For now, we have only three transaction statuses:
+Every time a financial transactions is created it must be validated by our anti-fraud microservice and then the same service sends a message back to update the transactions status.
+For now, we have only three transactions statuses:
pending
@@ -21,15 +33,15 @@ For now, we have only three transaction statuses:
rejected
-Every transaction with a value greater than 1000 should be rejected.
+Every transactions with a value greater than 1000 should be rejected.
```mermaid
flowchart LR
Transaction -- Save Transaction with pending Status --> transactionDatabase[(Database)]
- Transaction --Send transaction Created event--> Anti-Fraud
- Anti-Fraud -- Send transaction Status Approved event--> Transaction
- Anti-Fraud -- Send transaction Status Rejected event--> Transaction
- Transaction -- Update transaction Status event--> transactionDatabase[(Database)]
+ Transaction --Send transactions Created event--> Anti-Fraud
+ Anti-Fraud -- Send transactions Status Approved event--> Transaction
+ Anti-Fraud -- Send transactions Status Rejected event--> Transaction
+ Transaction -- Update transactions Status event--> transactionDatabase[(Database)]
```
# Tech Stack
@@ -44,7 +56,7 @@ We do provide a `Dockerfile` to help you get started with a dev environment.
You must have two resources:
-1. Resource to create a transaction that must containt:
+1. Resource to create a transactions that must containt:
```json
{
@@ -55,7 +67,7 @@ You must have two resources:
}
```
-2. Resource to retrieve a transaction
+2. Resource to retrieve a transactions
```json
{
@@ -73,7 +85,7 @@ You must have two resources:
## Optional
-You can use any approach to store transaction data but you should consider that we may deal with high volume scenarios where we have a huge amount of writes and reads for the same data at the same time. How would you tackle this requirement?
+You can use any approach to store transactions data but you should consider that we may deal with high volume scenarios where we have a huge amount of writes and reads for the same data at the same time. How would you tackle this requirement?
You can use Graphql;
@@ -81,4 +93,6 @@ You can use Graphql;
When you finish your challenge, after forking a repository, you **must** open a pull request to our repository. There are no limitations to the implementation, you can follow the programming paradigm, modularization, and style that you feel is the most appropriate solution.
-If you have any questions, please let us know.
\ No newline at end of file
+If you have any questions, please let us know.
+
+
diff --git a/app-antifraud-microservice-kafka/.gitignore b/app-antifraud-microservice-kafka/.gitignore
new file mode 100644
index 0000000..dd13b20
--- /dev/null
+++ b/app-antifraud-microservice-kafka/.gitignore
@@ -0,0 +1,212 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij+all,eclipse,netbeans,maven,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,intellij+all,eclipse,netbeans,maven,gradle
+
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Eclipse Patch ###
+# Spring Boot Tooling
+.sts4-cache/
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+all Patch ###
+# Ignores the whole .idea folder and all .iml files
+# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
+
+.idea/
+
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+### NetBeans ###
+**/nbproject/private/
+**/nbproject/Makefile-*.mk
+**/nbproject/Package-*.bash
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/tasks.json
+!.vscode/launch.json
+*.code-workspace
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Gradle ###
+.gradle
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij+all,eclipse,netbeans,maven,gradle
diff --git a/app-antifraud-microservice-kafka/Dockerfile b/app-antifraud-microservice-kafka/Dockerfile
new file mode 100644
index 0000000..3cd81a3
--- /dev/null
+++ b/app-antifraud-microservice-kafka/Dockerfile
@@ -0,0 +1,13 @@
+# Etapa 1: Compilación de la aplicación
+FROM maven:3.8.8-eclipse-temurin-17 AS build
+WORKDIR /app
+COPY pom.xml .
+COPY src ./src
+RUN mvn clean package -DskipTests
+
+# Etapa 2: Construcción de la imagen
+FROM openjdk:17-jdk-slim
+WORKDIR /app
+COPY --from=build /app/target/app-antifraud-microservice-kafka-1.0-SNAPSHOT.jar app.jar
+EXPOSE 8080
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
\ No newline at end of file
diff --git a/app-antifraud-microservice-kafka/pom.xml b/app-antifraud-microservice-kafka/pom.xml
new file mode 100644
index 0000000..202cb70
--- /dev/null
+++ b/app-antifraud-microservice-kafka/pom.xml
@@ -0,0 +1,114 @@
+
+
+ 4.0.0
+
+ org.example
+ app-antifraud-microservice-kafka
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.12
+
+
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.14.0
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ io.projectreactor.kafka
+ reactor-kafka
+ 1.3.7
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.13.3
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.36
+
+
+ org.projectlombok
+ lombok
+ 1.18.28
+ provided
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ com.graphql-java-kickstart
+ graphql-spring-boot-starter
+ 12.0.0
+
+
+ com.graphql-java
+ graphql-java
+
+
+
+
+ com.graphql-java-kickstart
+ graphql-java-tools
+ 12.0.0
+
+
+ com.graphql-java
+ graphql-java-extended-scalars
+ 17.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.1.0
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/AntifraudApplication.java b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/AntifraudApplication.java
new file mode 100644
index 0000000..893caaa
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/AntifraudApplication.java
@@ -0,0 +1,12 @@
+package com.yape.reto.tecnico.antifraud;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AntifraudApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AntifraudApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/AntifraudResponse.java b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/AntifraudResponse.java
new file mode 100644
index 0000000..96e2151
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/AntifraudResponse.java
@@ -0,0 +1,17 @@
+package com.yape.reto.tecnico.antifraud.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.UUID;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class AntifraudResponse {
+ private UUID transactionId;
+ private boolean isFraudulent;
+}
\ No newline at end of file
diff --git a/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionMessage.java b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionMessage.java
new file mode 100644
index 0000000..ac886dd
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionMessage.java
@@ -0,0 +1,26 @@
+package com.yape.reto.tecnico.antifraud.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionMessage {
+
+ private UUID transactionExternalId;
+ private TransactionTypeDto transactionType;
+ private TransactionStatusDto transactionStatus;
+ private Double amount;
+ private LocalDateTime createdAt;
+
+}
diff --git a/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionStatusDto.java b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionStatusDto.java
new file mode 100644
index 0000000..8de6ca7
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionStatusDto.java
@@ -0,0 +1,19 @@
+package com.yape.reto.tecnico.antifraud.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionStatusDto {
+
+ private Long id;
+ private String name;
+
+}
diff --git a/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionTypeDto.java b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionTypeDto.java
new file mode 100644
index 0000000..c589ea6
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/model/TransactionTypeDto.java
@@ -0,0 +1,18 @@
+package com.yape.reto.tecnico.antifraud.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionTypeDto {
+ private Long id;
+ private String name;
+
+}
diff --git a/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/service/AntifraudService.java b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/service/AntifraudService.java
new file mode 100644
index 0000000..fbadf5a
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/java/com/yape/reto/tecnico/antifraud/service/AntifraudService.java
@@ -0,0 +1,88 @@
+package com.yape.reto.tecnico.antifraud.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.yape.reto.tecnico.antifraud.model.AntifraudResponse;
+import com.yape.reto.tecnico.antifraud.model.TransactionMessage;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+import reactor.kafka.receiver.KafkaReceiver;
+import reactor.kafka.receiver.ReceiverOptions;
+import reactor.kafka.sender.KafkaSender;
+import reactor.kafka.sender.SenderOptions;
+import reactor.kafka.sender.SenderRecord;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class AntifraudService {
+
+ private final KafkaReceiver kafkaReceiver;
+ private final KafkaSender kafkaSender;
+ private final String inputTopicName = "transaction-created";
+ private final String outputTopicName = "antifraud-responses";
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+ public AntifraudService() {
+ objectMapper.registerModule(new JavaTimeModule());
+ Map props = new HashMap<>();
+ props.put("bootstrap.servers", "kafka:29092");
+ props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
+ props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
+ props.put("auto.offset.reset", "earliest");
+ props.put("group.id", "antifraud-group");
+
+ ReceiverOptions receiverOptions = ReceiverOptions.create(props);
+ this.kafkaReceiver = KafkaReceiver.create(receiverOptions.subscription(java.util.Collections.singleton(inputTopicName)));
+
+ props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+ props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+ SenderOptions senderOptions = SenderOptions.create(props);
+ this.kafkaSender = KafkaSender.create(senderOptions);
+
+ this.kafkaReceiver.receive()
+ .flatMap(record -> {
+ TransactionMessage transactionMessage = deserializeTransaction(record.value());
+ return analyzeTransaction(transactionMessage)
+ .then(record.receiverOffset().commit());
+ })
+ .doOnError(e -> {
+ System.err.println("Error processing transaction: " + e.getMessage());
+ })
+ .subscribe();
+ }
+
+ private TransactionMessage deserializeTransaction(String json) {
+ try {
+ return objectMapper.readValue(json, TransactionMessage.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to deserialize transaction", e);
+ }
+ }
+
+ private Mono analyzeTransaction(TransactionMessage transactions) {
+ boolean isFraudulent = transactions.getAmount() > 1000;
+
+ AntifraudResponse response = new AntifraudResponse();
+ response.setTransactionId(transactions.getTransactionExternalId());
+ response.setFraudulent(isFraudulent);
+
+ return publishAntifraudResponse(response);
+ }
+
+ private Mono publishAntifraudResponse(AntifraudResponse response) {
+ return Mono.just(response)
+ .map(r -> {
+ try {
+ return objectMapper.writeValueAsString(r);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to serialize antifraud response", e);
+ }
+ })
+ .flatMap(jsonResponse -> {
+ SenderRecord senderRecord = SenderRecord.create(outputTopicName, null, null, response.getTransactionId().toString(), jsonResponse, null);
+ return kafkaSender.send(Mono.just(senderRecord)).then();
+ });
+ }
+}
\ No newline at end of file
diff --git a/app-antifraud-microservice-kafka/src/main/resources/application.yml b/app-antifraud-microservice-kafka/src/main/resources/application.yml
new file mode 100644
index 0000000..12f5435
--- /dev/null
+++ b/app-antifraud-microservice-kafka/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+server:
+ port: 8090
+
+spring:
+ kafka:
+ bootstrap-servers: kafka:9092
\ No newline at end of file
diff --git a/app-microservice-kafka/.gitignore b/app-microservice-kafka/.gitignore
new file mode 100644
index 0000000..dd13b20
--- /dev/null
+++ b/app-microservice-kafka/.gitignore
@@ -0,0 +1,212 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij+all,eclipse,netbeans,maven,gradle
+# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,intellij+all,eclipse,netbeans,maven,gradle
+
+### Eclipse ###
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.settings/
+.loadpath
+.recommenders
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# PyDev specific (Python IDE for Eclipse)
+*.pydevproject
+
+# CDT-specific (C/C++ Development Tooling)
+.cproject
+
+# CDT- autotools
+.autotools
+
+# Java annotation processor (APT)
+.factorypath
+
+# PDT-specific (PHP Development Tools)
+.buildpath
+
+# sbteclipse plugin
+.target
+
+# Tern plugin
+.tern-project
+
+# TeXlipse plugin
+.texlipse
+
+# STS (Spring Tool Suite)
+.springBeans
+
+# Code Recommenders
+.recommenders/
+
+# Annotation Processing
+.apt_generated/
+.apt_generated_test/
+
+# Scala IDE specific (Scala & Java development for Eclipse)
+.cache-main
+.scala_dependencies
+.worksheet
+
+# Uncomment this line if you wish to ignore the project description file.
+# Typically, this file would be tracked if it contains build/dependency configurations:
+#.project
+
+### Eclipse Patch ###
+# Spring Boot Tooling
+.sts4-cache/
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+all Patch ###
+# Ignores the whole .idea folder and all .iml files
+# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
+
+.idea/
+
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+### NetBeans ###
+**/nbproject/private/
+**/nbproject/Makefile-*.mk
+**/nbproject/Package-*.bash
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/tasks.json
+!.vscode/launch.json
+*.code-workspace
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Gradle ###
+.gradle
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
+# gradle/wrapper/gradle-wrapper.properties
+
+### Gradle Patch ###
+**/build/
+
+# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,intellij+all,eclipse,netbeans,maven,gradle
diff --git a/app-microservice-kafka/Dockerfile b/app-microservice-kafka/Dockerfile
new file mode 100644
index 0000000..3250663
--- /dev/null
+++ b/app-microservice-kafka/Dockerfile
@@ -0,0 +1,13 @@
+# Etapa 1: Compilación de la aplicación
+FROM maven:3.8.8-eclipse-temurin-17 AS build
+WORKDIR /app
+COPY pom.xml .
+COPY src ./src
+RUN mvn clean package -DskipTests
+
+# Etapa 2: Construcción de la imagen
+FROM openjdk:17-jdk-slim
+WORKDIR /app
+COPY --from=build /app/target/app-microservice-kafka-1.0-SNAPSHOT.jar app.jar
+EXPOSE 8081
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
\ No newline at end of file
diff --git a/app-microservice-kafka/pom.xml b/app-microservice-kafka/pom.xml
new file mode 100644
index 0000000..ba7e0dc
--- /dev/null
+++ b/app-microservice-kafka/pom.xml
@@ -0,0 +1,114 @@
+
+
+ 4.0.0
+
+ org.example
+ app-microservice-kafka
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.12
+
+
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.14.0
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ io.projectreactor.kafka
+ reactor-kafka
+ 1.3.7
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.13.3
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+
+
+ org.slf4j
+ slf4j-simple
+ 1.7.36
+
+
+ org.projectlombok
+ lombok
+ 1.18.28
+ provided
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ com.graphql-java-kickstart
+ graphql-spring-boot-starter
+ 12.0.0
+
+
+ com.graphql-java
+ graphql-java
+
+
+
+
+ com.graphql-java-kickstart
+ graphql-java-tools
+ 12.0.0
+
+
+ com.graphql-java
+ graphql-java-extended-scalars
+ 17.0
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 3.1.0
+
+
+
+ repackage
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/KafkaTransactionApplication.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/KafkaTransactionApplication.java
new file mode 100644
index 0000000..27bef47
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/KafkaTransactionApplication.java
@@ -0,0 +1,12 @@
+package com.yape.reto.tecnico.kafka;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class KafkaTransactionApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(KafkaTransactionApplication.class, args);
+ }
+}
\ No newline at end of file
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/TransactionStatus.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/TransactionStatus.java
new file mode 100644
index 0000000..281f6f3
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/TransactionStatus.java
@@ -0,0 +1,31 @@
+package com.yape.reto.tecnico.kafka.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "transaction_status")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionStatus {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String name;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/TransactionType.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/TransactionType.java
new file mode 100644
index 0000000..a15e249
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/TransactionType.java
@@ -0,0 +1,31 @@
+package com.yape.reto.tecnico.kafka.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "TRANSACTION_TYPE")
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionType {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ private String name;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/Transactions.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/Transactions.java
new file mode 100644
index 0000000..743d60c
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/entity/Transactions.java
@@ -0,0 +1,52 @@
+package com.yape.reto.tecnico.kafka.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+import org.hibernate.annotations.GenericGenerator;
+import org.hibernate.annotations.Type;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import java.util.UUID;
+
+@Entity
+@Getter
+@Table(name = "transactions")
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class Transactions {
+
+ @Id
+ @GeneratedValue(generator = "UUID")
+ @GenericGenerator(
+ name = "UUID",
+ strategy = "org.hibernate.id.UUIDGenerator"
+ )
+ @Type(type = "org.hibernate.type.UUIDCharType")
+ private UUID id;
+ private String accountExternalIdDebit;
+ private String accountExternalIdCredit;
+ private Double amount;
+
+
+ @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @JoinColumn(name = "transaction_type_id", referencedColumnName = "id")
+ private TransactionType transactionType;
+
+
+ @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @JoinColumn(name = "transaction_status_id", referencedColumnName = "id")
+ private TransactionStatus transactionStatus;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/AntifraudResponse.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/AntifraudResponse.java
new file mode 100644
index 0000000..04cf2e3
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/AntifraudResponse.java
@@ -0,0 +1,17 @@
+package com.yape.reto.tecnico.kafka.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.UUID;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class AntifraudResponse {
+ private UUID transactionId;
+ private boolean isFraudulent;
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionMessage.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionMessage.java
new file mode 100644
index 0000000..a40b260
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionMessage.java
@@ -0,0 +1,26 @@
+package com.yape.reto.tecnico.kafka.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionMessage {
+
+ private UUID transactionExternalId;
+ private TransactionTypeDto transactionType;
+ private TransactionStatusDto transactionStatus;
+ private Double amount;
+ private LocalDateTime createdAt;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionRequest.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionRequest.java
new file mode 100644
index 0000000..3156a17
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionRequest.java
@@ -0,0 +1,21 @@
+package com.yape.reto.tecnico.kafka.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionRequest {
+
+ private String accountExternalIdDebit ;
+ private String accountExternalIdCredit;
+ private Long tranferTypeId ;
+ private Double amount;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionStatusDto.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionStatusDto.java
new file mode 100644
index 0000000..d8e3a5f
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionStatusDto.java
@@ -0,0 +1,19 @@
+package com.yape.reto.tecnico.kafka.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionStatusDto {
+
+ private Long id;
+ private String name;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionTypeDto.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionTypeDto.java
new file mode 100644
index 0000000..13e64ef
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/model/TransactionTypeDto.java
@@ -0,0 +1,18 @@
+package com.yape.reto.tecnico.kafka.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.experimental.SuperBuilder;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@SuperBuilder
+public class TransactionTypeDto {
+ private Long id;
+ private String name;
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionRepository.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionRepository.java
new file mode 100644
index 0000000..3e75986
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionRepository.java
@@ -0,0 +1,13 @@
+package com.yape.reto.tecnico.kafka.repository;
+
+import com.yape.reto.tecnico.kafka.entity.Transactions;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.UUID;
+
+@Repository
+public interface TransactionRepository extends JpaRepository {
+
+
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionStatusRepository.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionStatusRepository.java
new file mode 100644
index 0000000..a836624
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionStatusRepository.java
@@ -0,0 +1,7 @@
+package com.yape.reto.tecnico.kafka.repository;
+
+import com.yape.reto.tecnico.kafka.entity.TransactionStatus;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TransactionStatusRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionTypeRepository.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionTypeRepository.java
new file mode 100644
index 0000000..11e7fb9
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/repository/TransactionTypeRepository.java
@@ -0,0 +1,7 @@
+package com.yape.reto.tecnico.kafka.repository;
+
+import com.yape.reto.tecnico.kafka.entity.TransactionType;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TransactionTypeRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/service/TransactionService.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/service/TransactionService.java
new file mode 100644
index 0000000..86b8cd2
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/service/TransactionService.java
@@ -0,0 +1,153 @@
+package com.yape.reto.tecnico.kafka.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.yape.reto.tecnico.kafka.entity.Transactions;
+import com.yape.reto.tecnico.kafka.model.AntifraudResponse;
+import com.yape.reto.tecnico.kafka.model.TransactionMessage;
+import com.yape.reto.tecnico.kafka.model.TransactionRequest;
+import com.yape.reto.tecnico.kafka.entity.TransactionStatus;
+import com.yape.reto.tecnico.kafka.entity.TransactionType;
+import com.yape.reto.tecnico.kafka.model.TransactionStatusDto;
+import com.yape.reto.tecnico.kafka.model.TransactionTypeDto;
+import com.yape.reto.tecnico.kafka.repository.TransactionRepository;
+import com.yape.reto.tecnico.kafka.repository.TransactionStatusRepository;
+import com.yape.reto.tecnico.kafka.repository.TransactionTypeRepository;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.kafka.receiver.KafkaReceiver;
+import reactor.kafka.receiver.ReceiverOptions;
+import reactor.kafka.sender.KafkaSender;
+import reactor.kafka.sender.SenderOptions;
+import reactor.kafka.sender.SenderRecord;
+
+import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+@Log4j2
+@Service
+public class TransactionService {
+
+ private final TransactionRepository transactionRepository;
+ private final TransactionStatusRepository transactionStatusRepository;
+ private final TransactionTypeRepository transactionTypeRepository;
+ private final KafkaSender kafkaSender;
+ private final KafkaReceiver kafkaReceiver;
+ private final String topicName = "transaction-created";
+ private final ObjectMapper objectMapper = new ObjectMapper();
+
+
+ public TransactionService(TransactionRepository transactionRepository, TransactionStatusRepository transactionStatusRepository,
+ TransactionTypeRepository transactionTypeRepository) {
+ this.transactionRepository = transactionRepository;
+ this.transactionStatusRepository = transactionStatusRepository;
+ this.transactionTypeRepository = transactionTypeRepository;
+ objectMapper.registerModule(new JavaTimeModule());
+
+ Map props = new HashMap<>();
+ props.put("bootstrap.servers", "kafka:29092");
+ props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+ props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
+ SenderOptions senderOptions = SenderOptions.create(props);
+ this.kafkaSender = KafkaSender.create(senderOptions);
+
+ props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
+ props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
+ props.put("group.id", "transaction-group");
+ ReceiverOptions receiverOptions = ReceiverOptions.create(props);
+ this.kafkaReceiver = KafkaReceiver.create(receiverOptions.subscription(java.util.Collections.singleton("antifraud-responses")));
+
+ this.kafkaReceiver.receive()
+ .flatMap(record -> {
+ AntifraudResponse response = deserializeAntifraudResponse(record.value());
+ return handleAntifraudResponse(response);
+ })
+ .subscribe();
+ }
+
+ private AntifraudResponse deserializeAntifraudResponse(String json) {
+ try {
+ return objectMapper.readValue(json, AntifraudResponse.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to deserialize antifraud response", e);
+ }
+ }
+
+ @Transactional
+ public Mono createTransaction(TransactionRequest request) {
+ TransactionStatus transactionStatus = transactionStatusRepository.findById(1L).get();
+ TransactionType transactionType = transactionTypeRepository.findById(request.getTranferTypeId()).get();
+
+ Transactions savedTransactions = transactionRepository.save(Transactions
+ .builder()
+ .accountExternalIdCredit(request.getAccountExternalIdCredit())
+ .accountExternalIdDebit(request.getAccountExternalIdDebit())
+ .transactionStatus(transactionStatus)
+ .transactionType(transactionType)
+ .amount(request.getAmount())
+ .build());
+
+ TransactionMessage transactionMessage = TransactionMessage
+ .builder()
+ .transactionExternalId(savedTransactions.getId())
+ .transactionStatus(TransactionStatusDto.builder().name(savedTransactions.getTransactionStatus().getName()).build())
+ .transactionType(TransactionTypeDto.builder().name(savedTransactions.getTransactionType().getName()).build())
+ .amount(savedTransactions.getAmount())
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ return publishTransactionEvent(transactionMessage)
+ .thenReturn(savedTransactions);
+ }
+
+ private Mono handleAntifraudResponse(AntifraudResponse response) {
+ return Mono.just(response)
+ .flatMap(r -> {
+ return Mono.justOrEmpty(transactionRepository.findById(r.getTransactionId()))
+ .flatMap(transactions -> {
+ if (r.isFraudulent()) {
+ TransactionStatus transactionStatus = transactionStatusRepository.findById(3L).get();
+ transactions.setTransactionStatus(transactionStatus);
+ } else {
+ TransactionStatus transactionStatus = transactionStatusRepository.findById(2L).get();
+ transactions.setTransactionStatus(transactionStatus);
+ }
+ transactionRepository.save(transactions);
+ return Mono.empty();
+ });
+ });
+ }
+
+ public Mono getTransaction(UUID id) {
+ return Mono.justOrEmpty(transactionRepository.findById(id));
+ }
+
+ public Flux getAllTransactions() {
+ return Flux.fromIterable(transactionRepository.findAll());
+ }
+
+ private Mono publishTransactionEvent(TransactionMessage transactionMessage) {
+ return Mono.just(transactionMessage)
+ .map(t -> {
+ try {
+ log.info("1Publicación exitosa de transactionMessage: {} ", objectMapper.writeValueAsString(t));
+ return objectMapper.writeValueAsString(t);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to serialize transaction", e);
+ }
+ })
+ .flatMap(jsonTransaction -> {
+ log.info("2Publicación exitosa de transactionMessage: {} ", jsonTransaction);
+ SenderRecord senderRecord = SenderRecord.create(topicName, null,
+ null, transactionMessage.getTransactionExternalId().toString(), jsonTransaction, null);
+ return kafkaSender.send(Mono.just(senderRecord)).then();
+ });
+ }
+}
diff --git a/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/service/graphql/TransactionGraphQLResolver.java b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/service/graphql/TransactionGraphQLResolver.java
new file mode 100644
index 0000000..548d581
--- /dev/null
+++ b/app-microservice-kafka/src/main/java/com/yape/reto/tecnico/kafka/service/graphql/TransactionGraphQLResolver.java
@@ -0,0 +1,34 @@
+package com.yape.reto.tecnico.kafka.service.graphql;
+
+import com.yape.reto.tecnico.kafka.entity.Transactions;
+import com.yape.reto.tecnico.kafka.model.TransactionRequest;
+import com.yape.reto.tecnico.kafka.service.TransactionService;
+import org.springframework.stereotype.Component;
+import graphql.kickstart.tools.GraphQLQueryResolver;
+import graphql.kickstart.tools.GraphQLMutationResolver;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.UUID;
+
+@Component
+public class TransactionGraphQLResolver implements GraphQLQueryResolver, GraphQLMutationResolver {
+
+ private final TransactionService transactionService;
+
+ public TransactionGraphQLResolver(TransactionService transactionService) {
+ this.transactionService = transactionService;
+ }
+
+ public Mono getTransactionById(UUID id) {
+ return transactionService.getTransaction(id);
+ }
+
+ public Mono> allTransactions() {
+ return transactionService.getAllTransactions().collectList();
+ }
+
+ public Mono createTransaction(TransactionRequest request) {
+ return transactionService.createTransaction(request);
+ }
+}
diff --git a/app-microservice-kafka/src/main/resources/application.yml b/app-microservice-kafka/src/main/resources/application.yml
new file mode 100644
index 0000000..9f239a2
--- /dev/null
+++ b/app-microservice-kafka/src/main/resources/application.yml
@@ -0,0 +1,24 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ driver-class-name: org.h2.Driver
+ username: sa
+ password: password
+ jpa:
+ hibernate:
+ ddl-auto: create-drop
+ database-platform: org.hibernate.dialect.H2Dialect
+ defer-datasource-initialization: true
+ h2:
+ console:
+ enabled: true
+ path: /h2-console
+ sql:
+ init:
+ mode: always
+ data-locations: classpath:query.sql
+ kafka:
+ bootstrap-servers: kafka:9092
+
+server:
+ port: 8080
diff --git a/app-microservice-kafka/src/main/resources/graphql/schema.graphqls b/app-microservice-kafka/src/main/resources/graphql/schema.graphqls
new file mode 100644
index 0000000..34dfe9e
--- /dev/null
+++ b/app-microservice-kafka/src/main/resources/graphql/schema.graphqls
@@ -0,0 +1,34 @@
+input TransactionRequest {
+ accountExternalIdDebit: String!
+ accountExternalIdCredit: String!
+ tranferTypeId: Int!
+ amount: Float!
+}
+
+type Transaction {
+ id: ID!
+ amount: Float!
+ accountExternalIdDebit: String
+ accountExternalIdCredit: String
+ transactionType: TransactionType
+ transactionStatus: TransactionStatus
+}
+
+type TransactionType {
+ id: ID!
+ name: String
+}
+
+type TransactionStatus {
+ id: ID!
+ name: String
+}
+
+type Query {
+ transactionById(id: ID!): Transaction
+ allTransactions: [Transaction!]!
+}
+
+type Mutation {
+ createTransaction(input: TransactionRequest!): Transaction
+}
diff --git a/app-microservice-kafka/src/main/resources/query.sql b/app-microservice-kafka/src/main/resources/query.sql
new file mode 100644
index 0000000..2250842
--- /dev/null
+++ b/app-microservice-kafka/src/main/resources/query.sql
@@ -0,0 +1,6 @@
+INSERT INTO TRANSACTION_TYPE (id, name) VALUES (1, 'INTERBANCARIA');
+INSERT INTO TRANSACTION_TYPE (id, name) VALUES (2, 'DIRECTA');
+
+INSERT INTO transaction_status (id, name) VALUES (1, 'PENDING');
+INSERT INTO transaction_status (id, name) VALUES (2, 'APPROVED');
+INSERT INTO transaction_status (id, name) VALUES (3, 'REJECTED');
diff --git a/docker-compose.yml b/docker-compose.yml
index 6e9a9c5..11a325d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -22,4 +22,31 @@ services:
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_JMX_PORT: 9991
ports:
- - 9092:9092
\ No newline at end of file
+ - 9092:9092
+ kafka-manager:
+ image: hlebalbau/kafka-manager:stable
+ ports:
+ - "9000:9000"
+ environment:
+ ZK_HOSTS: zookeeper:2181
+ depends_on:
+ - zookeeper
+ - kafka
+ app-microservice:
+ build:
+ context: ./app-microservice-kafka
+ dockerfile: Dockerfile
+ ports:
+ - "8080:8080"
+ depends_on:
+ - postgres
+ - kafka
+
+ app-antifraud:
+ build:
+ context: ./app-antifraud-microservice-kafka
+ dockerfile: Dockerfile
+ ports:
+ - "8081:8081"
+ depends_on:
+ - kafka
\ No newline at end of file
diff --git a/postman/Yape collection.postman_collection.json b/postman/Yape collection.postman_collection.json
new file mode 100644
index 0000000..f111a28
--- /dev/null
+++ b/postman/Yape collection.postman_collection.json
@@ -0,0 +1,106 @@
+{
+ "info": {
+ "_postman_id": "c71a9ece-4559-4ef3-b777-7d4ee0a4c42f",
+ "name": "Yape collection",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+ "_exporter_id": "5720344"
+ },
+ "item": [
+ {
+ "name": "Get Transaction By Id",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "graphql",
+ "graphql": {
+ "query": "query {\r\n transactionById(id: \"1\") {\r\n id\r\n amount\r\n status\r\n }\r\n}",
+ "variables": ""
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8080/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8080",
+ "path": [
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Get Transaction ALL",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "graphql",
+ "graphql": {
+ "query": "query {\r\n allTransactions {\r\n id\r\n amount\r\n accountExternalIdDebit\r\n accountExternalIdCredit\r\n transactionType {\r\n name\r\n }\r\n transactionStatus {\r\n name\r\n }\r\n }\r\n}",
+ "variables": ""
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8080/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8080",
+ "path": [
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "create Transaction",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "graphql",
+ "graphql": {
+ "query": "mutation {\r\n createTransaction(input: {\r\n accountExternalIdDebit: \"123456789\",\r\n accountExternalIdCredit: \"987654321\",\r\n tranferTypeId: 1,\r\n amount: 2222.50\r\n }) {\r\n id\r\n amount\r\n accountExternalIdDebit\r\n accountExternalIdCredit\r\n }\r\n}\r\n",
+ "variables": ""
+ }
+ },
+ "url": {
+ "raw": "http://localhost:8080/graphql",
+ "protocol": "http",
+ "host": [
+ "localhost"
+ ],
+ "port": "8080",
+ "path": [
+ "graphql"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+}
\ No newline at end of file