Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM maven:3.9.6-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
9 changes: 9 additions & 0 deletions app-nodejs-codechallenge.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
</component>
</module>
64 changes: 64 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>

<groupId>com.transactions</groupId>
<artifactId>transaction-system</artifactId>
<version>1.0.0</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- H2 Database (EN MEMORIA - MÁS FÁCIL) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

<!-- Utilities -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
18 changes: 18 additions & 0 deletions src/main/java/com/transactions/TransactionApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.transactions;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class TransactionApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionApplication.class, args);
System.out.println("🚀 Transaction Service started on port 8080");
System.out.println("📊 H2 Console: http://localhost:8080/h2-console");
System.out.println("📝 JDBC URL: jdbc:h2:mem:transactionsdb");
System.out.println("👤 Username: sa");
System.out.println("🔑 Password: (empty)");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.transactions.controller;

import com.transactions.model.dto.CreateTransactionRequest;
import com.transactions.model.dto.TransactionResponse;
import com.transactions.service.TransactionService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/v1/transactions")
@RequiredArgsConstructor
public class TransactionController {

private final TransactionService transactionService;

@PostMapping
public ResponseEntity<TransactionResponse> createTransaction(
@Valid @RequestBody CreateTransactionRequest request) {
TransactionResponse response = transactionService.createTransaction(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@GetMapping("/{id}")
public ResponseEntity<TransactionResponse> getTransaction(@PathVariable UUID id) {
TransactionResponse response = transactionService.getTransaction(id);
return ResponseEntity.ok(response);
}

@GetMapping
public ResponseEntity<List<TransactionResponse>> getAllTransactions() {
List<TransactionResponse> responses = transactionService.getAllTransactions();
return ResponseEntity.ok(responses);
}

@GetMapping("/health")
public ResponseEntity<Map<String, Object>> healthCheck() {
return ResponseEntity.ok(Map.of(
"status", "ok",
"timestamp", java.time.Instant.now(),
"service", "transaction-anti-fraud-service"
));
}
}
59 changes: 59 additions & 0 deletions src/main/java/com/transactions/model/Transaction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.transactions.model;

import com.transactions.model.enums.TransactionStatus;
import jakarta.persistence.*;
import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Table(name = "transactions")
@Data
public class Transaction {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;

@Column(name = "transaction_external_id", unique = true)
private UUID transactionExternalId;

@Column(name = "account_external_id_debit")
private UUID accountExternalIdDebit;

@Column(name = "account_external_id_credit")
private UUID accountExternalIdCredit;

@Column(name = "transfer_type_id")
private Integer transferTypeId;

@Column(name = "transaction_value", precision = 10, scale = 2)
private BigDecimal transactionValue;

@Enumerated(EnumType.STRING)
private TransactionStatus status;

@Column(name = "rejection_reason")
private String rejectionReason;

@CreationTimestamp
@Column(name = "created_at")
private LocalDateTime createdAt;

@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;

@PrePersist
public void prePersist() {
if (transactionExternalId == null) {
transactionExternalId = UUID.randomUUID();
}
if (status == null) {
status = TransactionStatus.PENDING;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.transactions.model.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.math.BigDecimal;
import java.util.UUID;

@Data
public class CreateTransactionRequest {

@NotNull(message = "accountExternalIdDebit is required")
@JsonProperty("accountExternalIdDebit")
private UUID accountExternalIdDebit;

@NotNull(message = "accountExternalIdCredit is required")
@JsonProperty("accountExternalIdCredit")
private UUID accountExternalIdCredit;

@NotNull(message = "transferTypeId is required")
@Min(value = 1, message = "transferTypeId must be at least 1")
@Max(value = 4, message = "transferTypeId must be at most 4")
@JsonProperty("transferTypeId")
private Integer transferTypeId;

@NotNull(message = "value is required")
@Positive(message = "value must be positive")
@Digits(integer = 10, fraction = 2, message = "value must have max 10 integer digits and 2 decimal digits")
@JsonProperty("value")
private BigDecimal value;

// Validación personalizada para el límite de 1000
public boolean exceedsMaxAmount() {
return value != null && value.compareTo(new BigDecimal("1000")) > 0;
}
}
Loading