Skip to content
Merged
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
6 changes: 0 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ jobs:
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_ACCESS_SECRET }}
TF_VAR_db_user: ${{ secrets.DB_USER }}
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
TF_VAR_jwt_secret: ${{ secrets.JWT_SECRET_KEY }}
aws-region: us-east-1
TF_VAR_lambda_jar_path: app/target/lambda-identification-client.jar

Expand All @@ -76,8 +73,5 @@ jobs:
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_ACCESS_SECRET }}
TF_VAR_db_user: ${{ secrets.DB_USER }}
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
TF_VAR_jwt_secret: ${{ secrets.JWT_SECRET_KEY }}
aws-region: us-east-1
TF_VAR_lambda_jar_path: app/target/lambda-identification-client.jar
5 changes: 5 additions & 0 deletions app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
<version>2.25.10</version>
</dependency>
</dependencies>

<build>
Expand Down
154 changes: 83 additions & 71 deletions app/src/main/java/tech/buildrun/lambda/HandlerClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.*;

import java.sql.*;
import java.util.Map;
import java.util.*;

public class HandlerClient implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
public class HandlerClient implements
RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

private static final String DB_URL = System.getenv("DB_URL");
private static final String DB_USER = System.getenv("DB_USER");
private static final String DB_PASSWORD = System.getenv("DB_PASSWORD");
private static final String TABLE_NAME = "tc-identification-table";

private final ObjectMapper mapper = new ObjectMapper();
private final DynamoDbClient dynamo = DynamoDbClient.create();

@Override
public APIGatewayProxyResponseEvent handleRequest(
Expand All @@ -29,24 +30,18 @@ public APIGatewayProxyResponseEvent handleRequest(
context.getLogger().log("METHOD=" + method);
context.getLogger().log("PATH=" + path);

Class.forName("org.postgresql.Driver");

try (Connection conn =
DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {

// POST /clientes
if ("POST".equals(method) && "/clientes".equals(path)) {
return criarCliente(request, conn);
}

// GET /clientes/{document}
if ("GET".equals(method) && path.startsWith("/clientes/")) {
return consultarCliente(path, conn);
}
// POST /clientes
if ("POST".equals(method) && "/clientes".equals(path)) {
return criarCliente(request);
}

return response(404, Map.of("message", "Endpoint não encontrado"));
// GET /clientes/{document}
if ("GET".equals(method) && path.startsWith("/clientes/")) {
return consultarCliente(path);
}

return response(404, Map.of("message", "Endpoint não encontrado"));

} catch (Exception e) {
e.printStackTrace();
return response(500, Map.of("message", "Erro interno"));
Expand All @@ -56,14 +51,14 @@ public APIGatewayProxyResponseEvent handleRequest(
// ===================== CRIAR CLIENTE =====================

private APIGatewayProxyResponseEvent criarCliente(
APIGatewayProxyRequestEvent request,
Connection conn) throws Exception {
APIGatewayProxyRequestEvent request) throws Exception {

if (request.getBody() == null || request.getBody().isBlank()) {
return response(400, Map.of("message", "Body obrigatório"));
}

Map<String, String> body = mapper.readValue(request.getBody(), Map.class);
Map<String, String> body =
mapper.readValue(request.getBody(), Map.class);

String document = body.get("document");
String name = body.get("name");
Expand All @@ -75,63 +70,78 @@ private APIGatewayProxyResponseEvent criarCliente(
));
}

String sql = """
INSERT INTO tb_cliente (nr_documento, nm_cliente, ds_email)
VALUES (?, ?, ?)
""";

try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, document);
ps.setString(2, name);
ps.setString(3, email);
ps.executeUpdate();

return response(201, Map.of(
"message", "Cliente criado com sucesso",
"document", document
));

} catch (SQLException e) {
if ("23505".equals(e.getSQLState())) {
return response(409, Map.of("message", "Cliente já existe"));
}
throw e;
// 🔍 Verifica se já existe (regra do SQL 23505)
if (clienteExistePorDocumento(document)) {
return response(409, Map.of("message", "Cliente já existe"));
}

Map<String, AttributeValue> item = new HashMap<>();
item.put("id", AttributeValue.builder()
.s(UUID.randomUUID().toString()).build());
item.put("nr_documento", AttributeValue.builder().s(document).build());
item.put("nm_cliente", AttributeValue.builder().s(name).build());
item.put("ds_email", AttributeValue.builder().s(email).build());

dynamo.putItem(PutItemRequest.builder()
.tableName(TABLE_NAME)
.item(item)
.build());

return response(201, Map.of(
"message", "Cliente criado com sucesso",
"document", document
));
}

// ===================== CONSULTAR CLIENTE =====================

private APIGatewayProxyResponseEvent consultarCliente(
String path,
Connection conn) throws Exception {
private APIGatewayProxyResponseEvent consultarCliente(String path)
throws Exception {

// /clientes/123456
String document = path.substring("/clientes/".length());

String sql = """
SELECT id, nr_documento, nm_cliente, ds_email
FROM tb_cliente
WHERE nr_documento = ?
""";

try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, document);
ResultSet rs = ps.executeQuery();

if (rs.next()) {
return response(200, Map.of(
"id", rs.getLong("id"),
"document", rs.getString("nr_documento"),
"name", rs.getString("nm_cliente"),
"email", rs.getString("ds_email")
));
}
QueryRequest query = QueryRequest.builder()
.tableName(TABLE_NAME)
.indexName("DocumentoIndex")
.keyConditionExpression("nr_documento = :doc")
.expressionAttributeValues(Map.of(
":doc", AttributeValue.builder().s(document).build()
))
.limit(1)
.build();

QueryResponse response = dynamo.query(query);

if (response.count() == 0) {
return response(404, Map.of("message", "Cliente não encontrado"));
}

Map<String, AttributeValue> item = response.items().get(0);

return response(200, Map.of(
"id", item.get("id").s(),
"document", item.get("nr_documento").s(),
"name", item.get("nm_cliente").s(),
"email", item.get("ds_email").s()
));
}

// ===================== RESPONSE =====================
// ===================== UTIL =====================

private boolean clienteExistePorDocumento(String document) {

QueryRequest query = QueryRequest.builder()
.tableName(TABLE_NAME)
.indexName("DocumentoIndex")
.keyConditionExpression("nr_documento = :doc")
.expressionAttributeValues(Map.of(
":doc", AttributeValue.builder().s(document).build()
))
.limit(1)
.build();

return dynamo.query(query).count() > 0;
}

private APIGatewayProxyResponseEvent response(int status, Object body) {
try {
Expand All @@ -140,8 +150,10 @@ private APIGatewayProxyResponseEvent response(int status, Object body) {
.withHeaders(Map.of(
"Content-Type", "application/json",
"Access-Control-Allow-Origin", "*",
"Access-Control-Allow-Headers", "Content-Type,Authorization",
"Access-Control-Allow-Methods", "GET,POST,OPTIONS"
"Access-Control-Allow-Headers",
"Content-Type,Authorization",
"Access-Control-Allow-Methods",
"GET,POST,OPTIONS"
))
.withBody(mapper.writeValueAsString(body));
} catch (Exception e) {
Expand All @@ -150,4 +162,4 @@ private APIGatewayProxyResponseEvent response(int status, Object body) {
.withBody("{\"message\":\"Erro ao serializar resposta\"}");
}
}
}
}
46 changes: 36 additions & 10 deletions lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,58 @@ data "aws_iam_role" "lambda_exec_role" {
name = "tc-infra-id-lambda-exec-role"
}


data "aws_dynamodb_table" "identification_table" {
name = "tc-identification-table"
}

resource "aws_iam_role_policy" "lambda_dynamodb_policy" {
role = data.aws_iam_role.lambda_exec_role.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:GetItem"
]
Resource = [
data.aws_dynamodb_table.identification_table.arn,
"${data.aws_dynamodb_table.identification_table.arn}/index/*"
]
}]
})
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = data.aws_iam_role.lambda_exec_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

data "aws_security_group" "id_lambda" {
name = "tc-id-lambda-sg"
vpc_id = data.aws_vpc.tc_lambda_vpc.id
}

resource "aws_lambda_function" "id_lambda" {
function_name = "lambda-identification-client"
depends_on = []
role = data.aws_iam_role.lambda_exec_role.arn
handler = "tech.buildrun.lambda.HandlerClient::handleRequest"
runtime = "java17"

timeout = 10
role = data.aws_iam_role.lambda_exec_role.arn
handler = "tech.buildrun.lambda.HandlerClient::handleRequest"
runtime = "java17"
timeout = 30

# Usa o caminho passado via variável
filename = var.lambda_jar_path
source_code_hash = filebase64sha256(var.lambda_jar_path)

environment {
variables = {
DB_URL = local.jdbc_url
DB_USER = var.db_user
DB_PASSWORD = var.db_password
JWT_SECRET = var.jwt_secret
TABLE_NAME = "tc-identification-table"
}
}

vpc_config {
subnet_ids = data.aws_subnets.tc_lambda_subnets.ids
security_group_ids = [data.aws_security_group.id_lambda.id]
Expand Down
3 changes: 0 additions & 3 deletions locals.tf

This file was deleted.

17 changes: 0 additions & 17 deletions vars.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,6 @@ variable "tags" {
}
}

# Banco de dados
variable "db_user" {
description = "Usuário do banco de dados"
sensitive = true
}

variable "db_password" {
description = "Senha do banco de dados"
sensitive = true
}

variable "jwt_secret" {
description = "Chave secreta para geração de tokens JWT"
type = string
sensitive = true
}

variable "lambda_jar_path" {
description = "Caminho do fat JAR da Lambda"
type = string
Expand Down