Skip to content

Commit 3351787

Browse files
Add configuration management and AWS service integration
- Introduced a new config.py file for managing environment variables using Pydantic. - Implemented S3Service and SQSService classes for AWS S3 and SQS interactions, respectively. - Updated lambda_function.py to utilize these services for file uploads and message sending. - Enhanced the CertifiedBuilder class to generate certificate keys and handle file uploads. - Updated requirements.txt to include necessary AWS SDK dependencies.
1 parent 7b1f708 commit 3351787

8 files changed

Lines changed: 175 additions & 8 deletions

File tree

aws/__init__.py

Whitespace-only changes.

aws/boto_aws.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from boto3 import client
2+
from enum import Enum
3+
from config import config
4+
5+
class ServiceNameAWS(Enum):
6+
S3 = 's3'
7+
SQS = 'sqs'
8+
9+
def get_instance_aws(service_name: ServiceNameAWS):
10+
return client(
11+
service_name.value,
12+
region_name=config.REGION,
13+
aws_access_key_id=config.KEY_ACCESS,
14+
aws_secret_access_key=config.KEY_SECRET
15+
)

aws/s3_service.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from aws.boto_aws import get_instance_aws, ServiceNameAWS
2+
from config import config
3+
import logging
4+
5+
logger = logging.getLogger(__name__)
6+
7+
class S3Service:
8+
def __init__(self):
9+
self.aws = get_instance_aws(
10+
ServiceNameAWS.S3
11+
)
12+
self.bucket_name = config.BUCKET_NAME
13+
14+
def upload_file(self, file_path: str, key: str):
15+
try:
16+
response = self.aws.upload_file(
17+
file_path,
18+
self.bucket_name,
19+
key
20+
)
21+
logger.info(f"Arquivo {file_path} enviado para o bucket {self.bucket_name} com sucesso")
22+
except Exception as e:
23+
logger.error(f"Erro ao enviar o arquivo {file_path} para o bucket {self.bucket_name}: {e}")
24+
raise e

aws/sqs_service.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from aws.boto_aws import get_instance_aws, ServiceNameAWS
2+
from config import config
3+
from typing import Dict
4+
import json
5+
import logging
6+
import boto3
7+
from botocore.exceptions import ClientError
8+
9+
logger = logging.getLogger(__name__)
10+
11+
class SQSService:
12+
def __init__(self):
13+
self.aws = get_instance_aws(ServiceNameAWS.SQS)
14+
self.queue_url = config.QUEUE_URL
15+
16+
def send_message(self, message: Dict):
17+
try:
18+
# Add MessageGroupId for FIFO queue
19+
# Using order_id as MessageGroupId to ensure messages for the same order are processed in order
20+
message_group_id = str(message.get('order_id', 'default'))
21+
22+
response = self.aws.send_message(
23+
QueueUrl=self.queue_url,
24+
MessageBody=json.dumps(message),
25+
MessageGroupId=message_group_id,
26+
MessageDeduplicationId=f"{message_group_id}_{message.get('email', '')}" # Ensure unique messages per order/email
27+
)
28+
logger.info(f"Mensagem enviada com sucesso: {response['MessageId']}")
29+
return response
30+
except ClientError as e:
31+
logger.error(f"Erro ao enviar mensagem para a fila {self.queue_url}: {str(e)}")
32+
raise

certified_builder/certified_builder.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import os
77
from certified_builder.utils.fetch_file_certificate import fetch_file_certificate
88
import tempfile
9-
109
FONT_NAME = os.path.join(os.path.dirname(__file__), "fonts/PinyonScript/PinyonScript-Regular.ttf")
1110
VALIDATION_CODE = os.path.join(os.path.dirname(__file__), "fonts/ChakraPetch/ChakraPetch-SemiBold.ttf")
1211
DETAILS_FONT = os.path.join(os.path.dirname(__file__), "fonts/ChakraPetch/ChakraPetch-Regular.ttf")
@@ -19,6 +18,7 @@ def __init__(self):
1918
# Ensure temp directory exists
2019
self.temp_dir = "/tmp/certificates"
2120
os.makedirs(self.temp_dir, exist_ok=True)
21+
2222

2323
def build_certificates(self, participants: List[Participant]):
2424
"""Build certificates for all participants."""
@@ -39,8 +39,10 @@ def build_certificates(self, participants: List[Participant]):
3939
results.append({
4040
"participant": participant.model_dump(),
4141
"certificate_path": certificate_path,
42+
"certificate_key": f"certificates/{participant.event.product_id}/{participant.event.order_id}/{participant.create_name_certificate()}",
4243
"success": True
4344
})
45+
4446

4547
logger.info(f"Certificado gerado para {participant.name_completed()} com codigo de validação {participant.formated_validation_code()}")
4648
except Exception as e:
@@ -50,7 +52,7 @@ def build_certificates(self, participants: List[Participant]):
5052
"error": str(e),
5153
"success": False
5254
})
53-
55+
5456
return results
5557
except Exception as e:
5658
logger.error(f"Erro geral na geração de certificados: {str(e)}")
@@ -186,12 +188,11 @@ def save_certificate(self, certificate: Image, participant: Participant) -> str:
186188
try:
187189
name_certificate = participant.create_name_certificate()
188190
file_path = os.path.join(self.temp_dir, name_certificate)
189-
190191
# Optimize image before saving
191192
certificate = certificate.convert('RGB')
192-
certificate.save(file_path, format="PNG", optimize=True)
193-
193+
certificate.save(file_path, format="PNG", optimize=True)
194194
return file_path
195+
195196
except Exception as e:
196197
logger.error(f"Erro ao salvar certificado: {str(e)}")
197198
raise

config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import logging
2+
from pydantic_settings import BaseSettings
3+
4+
logger = logging.getLogger(__name__)
5+
6+
class Config(BaseSettings):
7+
KEY_ACCESS: str
8+
KEY_SECRET: str
9+
REGION: str
10+
BUCKET_NAME: str
11+
QUEUE_URL: str
12+
13+
14+
class Config:
15+
env_file = ".env"
16+
env_file_encoding = "utf-8"
17+
18+
19+
config = Config()
20+
21+

lambda_function.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from datetime import datetime
88
import base64
99
import os
10+
from aws.s3_service import S3Service
11+
from aws.sqs_service import SQSService
1012

1113
# Configure logging for CloudWatch
1214
logger = logging.getLogger()
@@ -92,6 +94,7 @@ def format_result(result):
9294
def lambda_handler(event, context):
9395
# Log the start of the Lambda execution
9496
try:
97+
9598
logger.info("Starting Lambda execution")
9699
body = extract_data_body(event)
97100
participants_data = body
@@ -105,7 +108,9 @@ def lambda_handler(event, context):
105108
'message': 'Nenhum participante encontrado para processamento'
106109
})
107110
}
108-
111+
s3_service = S3Service()
112+
sqs_service = SQSService()
113+
109114
logger.info(f"Processing {len(participants_data)} participants")
110115

111116
# Create list of participants
@@ -129,8 +134,23 @@ def lambda_handler(event, context):
129134
builder = CertifiedBuilder()
130135
certificates_results = builder.build_certificates(participants)
131136
# Format results before adding to response
132-
formatted_results = [format_result(result) for result in certificates_results]
133-
results.extend(formatted_results)
137+
formatted_results = []
138+
139+
for result in certificates_results:
140+
formatted_results.extend(format_result(result))
141+
142+
if result.get('success'):
143+
s3_service.upload_file(result.get('certificate_path'), result.get('certificate_key'))
144+
145+
sqs_service.send_message({
146+
"order_id": result.get('participant', {}).get('event', {}).get('order_id', ""),
147+
"product_id": result.get('participant', {}).get('event', {}).get('product_id', ""),
148+
"product_name": result.get('participant', {}).get('event', {}).get('product_name', ""),
149+
"email": result.get('participant', {}).get('email', ""),
150+
"certificate_key": result.get('certificate_key', ""),
151+
"success": result.get('success', False)
152+
})
153+
134154

135155
logger.info("Certificados gerados com sucesso")
136156
return {
@@ -161,3 +181,48 @@ def lambda_handler(event, context):
161181
})
162182
}
163183

184+
if __name__ == "__main__":
185+
lambda_handler({
186+
"Records": [
187+
{
188+
"body": [
189+
{
190+
"order_id": 452,
191+
"first_name": "Jardel",
192+
"last_name": "Godinho",
193+
"email": "jardelgodinho@gmail.com",
194+
"phone": "(48) 98866-7447",
195+
"cpf": "",
196+
"city": "São José",
197+
"product_id": 316,
198+
"product_name": "Evento de Teste",
199+
"certificate_details": "In recognition of their participation in the 84st edition of the Python Floripa Community Meeting, held on March 29, 2025, in Florianópolis, Brazil.",
200+
"certificate_logo": "https://tech.floripa.br/wp-content/uploads/2025/03/84o-Python-Floripa-e1741729144453.png",
201+
"certificate_background": "https://tech.floripa.br/wp-content/uploads/2025/03/Background.png",
202+
"order_date": "2025-03-26 20:55:25",
203+
"checkin_latitude": "-27.5460492",
204+
"checkin_longitude": "-48.6227075",
205+
"time_checkin": "2025-03-26 20:55:44"
206+
},
207+
{
208+
"order_id": 317,
209+
"first_name": "Jardel",
210+
"last_name": "Godinho",
211+
"email": "jardel.godinho@gmail.com",
212+
"phone": "(48) 98866-7447",
213+
"cpf": "",
214+
"city": "",
215+
"product_id": 316,
216+
"product_name": "Evento de Teste",
217+
"certificate_details": "In recognition of their participation in the 84st edition of the Python Floripa Community Meeting, held on March 29, 2025, in Florianópolis, Brazil.",
218+
"certificate_logo": "https://tech.floripa.br/wp-content/uploads/2025/03/84o-Python-Floripa-e1741729144453.png",
219+
"certificate_background": "https://tech.floripa.br/wp-content/uploads/2025/03/Background.png",
220+
"order_date": "2025-03-19 06:04:51",
221+
"checkin_latitude": "-27.5460492",
222+
"checkin_longitude": "-48.6227075",
223+
"time_checkin": "2025-03-19 16:04:16"
224+
}
225+
]
226+
}
227+
]
228+
}, {})

requirements.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
annotated-types==0.7.0
22
anyio==4.6.2.post1
3+
boto3==1.37.22
4+
botocore==1.37.22
35
certifi==2024.8.30
46
dnspython==2.7.0
57
email_validator==2.2.0
@@ -8,11 +10,18 @@ httpcore==1.0.7
810
httpx==0.27.2
911
idna==3.10
1012
iniconfig==2.0.0
13+
jmespath==1.0.1
1114
packaging==24.2
1215
pillow==11.0.0
1316
pluggy==1.5.0
1417
pydantic==2.10.1
18+
pydantic-settings==2.8.1
1519
pydantic_core==2.27.1
1620
pytest==8.3.3
21+
python-dateutil==2.9.0.post0
22+
python-dotenv==1.1.0
23+
s3transfer==0.11.4
24+
six==1.17.0
1725
sniffio==1.3.1
1826
typing_extensions==4.12.2
27+
urllib3==2.3.0

0 commit comments

Comments
 (0)