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
83 changes: 83 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Deploy Lambda to AWS

on:
- push
- workflow_dispatch

env:
TF_WORKSPACE: default
AWS_REGION: us-east-1

jobs:
deploy:
runs-on: ubuntu-latest
environment: Production
permissions:
id-token: write
contents: read

steps:
# 1️⃣ Checkout do repositório
- uses: actions/checkout@v3

# 2️⃣ Setup Java 17 e cache Maven
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: maven

# 3️⃣ Build do Maven (fat JAR)
- name: Build fat JAR with Maven
run: |
cd app
mvn clean package -DskipTests
echo "📦 Conteúdo da pasta target:"
ls -la target/
echo "➡️ Fat JAR gerado:"
ls -la target/lambda-identification-client.jar

# 4️⃣ Configurar credenciais AWS
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_ACCESS_SECRET }}
aws-region: us-east-1

# 5️⃣ Setup Terraform
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

# 6️⃣ Terraform Init
- name: Terraform Init
run: terraform init
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_ACCESS_SECRET }}

# 7️⃣ Terraform Plan
- name: Terraform Plan
run: terraform plan
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

# 8️⃣ Terraform Apply (apenas em main/master)
- name: Terraform Apply
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
run: terraform apply -auto-approve
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
49 changes: 49 additions & 0 deletions .github/workflows/destroy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Destroy Lambda

on:
workflow_dispatch: # Dispara manualmente

env:
AWS_REGION: us-east-1

jobs:
destroy-lambda:
runs-on: ubuntu-latest
environment: Production

steps:
# 1️⃣ Checkout do repositório
- uses: actions/checkout@v3

# 2️⃣ Configurar credenciais AWS
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
aws-region: ${{ env.AWS_REGION }}

# 3️⃣ Verificar se a Lambda existe
- name: Check if Lambda exists
id: check_lambda
run: |
if aws lambda get-function --function-name lambda-identification-client --region $AWS_REGION > /dev/null 2>&1; then
echo "Lambda exists"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "Lambda does not exist"
echo "exists=false" >> $GITHUB_OUTPUT
fi

# 4️⃣ Deletar Lambda se existir
- name: Delete Lambda
if: steps.check_lambda.outputs.exists == 'true'
run: |
echo "Deleting Lambda..."
aws lambda delete-function --function-name lambda-identification-client --region $AWS_REGION
echo "✅ Lambda deleted successfully"

# 5️⃣ Mensagem caso a Lambda não exista
- name: Lambda not found
if: steps.check_lambda.outputs.exists == 'false'
run: echo "⚠️ Lambda not found, nothing to delete"
10 changes: 8 additions & 2 deletions api_gateway.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ resource "aws_apigatewayv2_integration" "lambda_backend" {
payload_format_version = "2.0"
}

resource "aws_apigatewayv2_route" "auth_token_route" {
resource "aws_apigatewayv2_route" "clientes_post_route" {
api_id = data.aws_apigatewayv2_api.tc_api.id
route_key = "POST /auth/token"
route_key = "POST /clientes"
target = "integrations/${aws_apigatewayv2_integration.lambda_backend.id}"
}

resource "aws_apigatewayv2_route" "clientes_get_route" {
api_id = data.aws_apigatewayv2_api.tc_api.id
route_key = "GET /clientes/{document}"
target = "integrations/${aws_apigatewayv2_integration.lambda_backend.id}"
}
159 changes: 116 additions & 43 deletions app/src/main/java/tech/buildrun/lambda/HandlerClient.java
Original file line number Diff line number Diff line change
@@ -1,80 +1,153 @@
package tech.buildrun.lambda;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.sql.*;
import java.util.Map;
import javax.crypto.SecretKey;

public class HandlerClient implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

private static final String JWT_SECRET = System.getenv("JWT_SECRET");
private static final long EXPIRATION_TIME = 3600_000; // 1 hora
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 final ObjectMapper mapper = new ObjectMapper();

@Override
public APIGatewayProxyResponseEvent handleRequest(
APIGatewayProxyRequestEvent request,
Context context) {

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

Map<String, String> body =
mapper.readValue(request.getBody(), Map.class);
context.getLogger().log("METHOD=" + method);
context.getLogger().log("PATH=" + path);

String username = body.get("user");
Class.forName("org.postgresql.Driver");

if (username == null) {
return response(400, Map.of("message", "user obrigatórios"));
}
try (Connection conn =
DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD)) {

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

return response(200, Map.of("token", token));
// GET /clientes/{document}
if ("GET".equals(method) && path.startsWith("/clientes/")) {
return consultarCliente(path, conn);
}

} catch (Exception e) {
try {
return response(500, Map.of("message", "Erro ao gerar token"));
} catch (Exception ex) {
throw new RuntimeException(ex);
return response(404, Map.of("message", "Endpoint não encontrado"));
}

} catch (Exception e) {
e.printStackTrace();
return response(500, Map.of("message", "Erro interno"));
}
}

private String gerarToken(String username) {
// ===================== CRIAR CLIENTE =====================

private APIGatewayProxyResponseEvent criarCliente(
APIGatewayProxyRequestEvent request,
Connection conn) 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);

String document = body.get("document");
String name = body.get("name");
String email = body.get("email");

if (document == null || name == null || email == null) {
return response(400, Map.of(
"message", "document, name e email são obrigatórios"
));
}

if (JWT_SECRET == null || JWT_SECRET.length() < 32) {
throw new IllegalStateException("JWT_SECRET deve ter no mínimo 32 caracteres");
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;
}
}

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

private APIGatewayProxyResponseEvent consultarCliente(
String path,
Connection conn) 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();

SecretKey key = Keys.hmacShaKeyFor(
JWT_SECRET.getBytes(StandardCharsets.UTF_8)
);

return Jwts.builder()
.setSubject(username)
.claim("role", "USER")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
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")
));
}

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

private APIGatewayProxyResponseEvent response(int status, Object body) throws Exception {
return new APIGatewayProxyResponseEvent()
.withStatusCode(status)
.withHeaders(Map.of("Content-Type", "application/json"))
.withBody(mapper.writeValueAsString(body));
// ===================== RESPONSE =====================

private APIGatewayProxyResponseEvent response(int status, Object body) {
try {
return new APIGatewayProxyResponseEvent()
.withStatusCode(status)
.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"
))
.withBody(mapper.writeValueAsString(body));
} catch (Exception e) {
return new APIGatewayProxyResponseEvent()
.withStatusCode(500)
.withBody("{\"message\":\"Erro ao serializar resposta\"}");
}
}
}
6 changes: 3 additions & 3 deletions lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ data "aws_security_group" "id_lambda" {
}

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

timeout = 6
timeout = 10

# Usa o caminho passado via variável
filename = var.lambda_jar_path
Expand Down
4 changes: 2 additions & 2 deletions vars.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ variable "region" {
variable "tags" {
default = {
Environment = "PRD"
Project = "tc-lambda-identification-auth"
Project = "tc-lambda-identification-client"
}
}

Expand All @@ -31,5 +31,5 @@ variable "jwt_secret" {
variable "lambda_jar_path" {
description = "Caminho do fat JAR da Lambda"
type = string
default = "app/target/lambda-identification-auth.jar"
default = "app/target/lambda-identification-client.jar"
}