add server to gen pdf

This commit is contained in:
2025-07-28 21:43:29 -03:00
parent 369bdc9db3
commit c6e8978dcc
14 changed files with 174 additions and 297 deletions

View File

@@ -22,8 +22,8 @@
} }
@font-face { @font-face {
font-family: "SF-Pro"; font-family: "Arial";
src: url("fonts/SF-Pro.ttf") format("truetype"); src: url("fonts/Arial.ttf") format("truetype");
} }
@page { @page {
@@ -32,7 +32,7 @@
} }
html { html {
font-family: SF-Pro; font-family: Arial;
font-size: 13pt; font-size: 13pt;
line-height: 1.4; line-height: 1.4;
} }
@@ -115,7 +115,7 @@
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1072.73 329.6" viewBox="0 0 1072.73 329.6"
style="width: 14rem" style="width: 14rem; margin-bottom: 1.5rem"
> >
<g> <g>
<g> <g>
@@ -181,8 +181,8 @@
de de
<strong>{{ progress }}%</strong> <strong>{{ progress }}%</strong>
</p> </p>
<p>Realizado entre {{ started_date }} e {{ finished_date }}</p> <p>Realizado entre {{ started_at }} e {{ completed_at }}</p>
<p>Florianópolis, SC, {{ today }}</p> <p>São José, SC, {{ today }}</p>
<div class="signatures"> <div class="signatures">
<div class="sign1"></div> <div class="sign1"></div>

View File

@@ -11,3 +11,4 @@ def get_dynamodb_client():
dynamodb_client = get_dynamodb_client() dynamodb_client = get_dynamodb_client()
s3_client = boto3.client('s3')

View File

@@ -5,6 +5,8 @@ ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore
# Post-migration: Remove the following lines # Post-migration: Remove the following lines
if os.getenv('AWS_LAMBDA_FUNCTION_NAME'): if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):
SQLITE_DATABASE = 'courses_export_2025-06-18_110214.db' SQLITE_DATABASE = 'courses_export_2025-06-18_110214.db'

View File

@@ -1,3 +1,5 @@
import json
import sqlite3
from datetime import datetime, time, timedelta from datetime import datetime, time, timedelta
from aws_lambda_powertools import Logger from aws_lambda_powertools import Logger
@@ -14,9 +16,16 @@ from layercake.dynamodb import (
TransactKey, TransactKey,
) )
from layercake.funcs import pick from layercake.funcs import pick
from sqlite_utils import Database
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE from config import (
COURSE_TABLE,
ENROLLMENT_TABLE,
ORDER_TABLE,
SQLITE_DATABASE,
SQLITE_TABLE,
)
from utils import get_billing_period from utils import get_billing_period
logger = Logger(__name__) logger = Logger(__name__)
@@ -24,6 +33,8 @@ order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client) course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
sqlite3.register_converter('json', json.loads)
@event_source(data_class=EventBridgeEvent) @event_source(data_class=EventBridgeEvent)
@logger.inject_lambda_context @logger.inject_lambda_context
@@ -41,6 +52,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
logger.debug('Enrollment not found') logger.debug('Enrollment not found')
return False return False
# Keep it until the migration has been completed
old_course = _get_course(data['course']['id'])
if old_course:
data['course'] = old_course
start_date, end_date = get_billing_period( start_date, end_date = get_billing_period(
new_image['billing_day'], new_image['billing_day'],
year=created_at.year, year=created_at.year,
@@ -52,6 +68,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
end=end_date.isoformat(), end=end_date.isoformat(),
) )
logger.info('Enrollment found', data=data)
try: try:
with order_layer.transact_writer() as transact: with order_layer.transact_writer() as transact:
transact.put( transact.put(
@@ -78,47 +96,44 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
pass pass
try: try:
with order_layer.transact_writer() as transact: author = data['author']
author = data['author'] course_id = data['course']['id']
course_id = data['course']['id'] course = course_layer.collection.get_items(
course = course_layer.collection.get_items( KeyPair(
KeyPair( pk=course_id,
pk=course_id, sk=SortKey('0', path_spec='metadata__unit_price'),
sk=SortKey('0', path_spec='metadata__unit_price'), rename_key='unit_price',
rename_key='unit_price',
)
+ KeyPair(
pk=f'CUSTOM_PRICING#ORG#{org_id}',
sk=SortKey(f'COURSE#{course_id}', path_spec='unit_price'),
rename_key='unit_price',
),
flatten_top=False,
) )
+ KeyPair(
transact.condition( pk=f'CUSTOM_PRICING#ORG#{org_id}',
key=KeyPair(pk, sk), sk=SortKey(f'COURSE#{course_id}', path_spec='unit_price'),
cond_expr='attribute_exists(sk)', rename_key='unit_price',
exc_cls=BillingNotFoundError, ),
) flatten_top=False,
transact.put( )
item={ order_layer.put_item(
'id': pk, item={
'sk': f'{sk}#ENROLLMENT#{enrollment_id}', 'id': pk,
'user': pick(('id', 'name'), data['user']), 'sk': f'{sk}#ENROLLMENT#{enrollment_id}',
'course': pick(('id', 'name'), data['course']), 'user': pick(('id', 'name'), data['user']),
'unit_price': course['unit_price'], 'course': pick(('id', 'name'), data['course']),
'author': { 'unit_price': course['unit_price'],
'id': author['user_id'], 'author': {
'name': author['name'], 'id': author['user_id'],
}, 'name': author['name'],
# Post-migration: uncomment the following line
# 'enrolled_at': data['created_at'],
'enrolled_at': data['create_date'],
'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', # Post-migration: uncomment the following line
) # 'enrolled_at': data['created_at'],
except Exception: 'enrolled_at': data['create_date'],
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
)
except Exception as exc:
logger.exception(
exc,
keypair={'pk': pk, 'sk': sk},
)
return False return False
else: else:
return True return True
@@ -128,3 +143,18 @@ class ExistingBillingConflictError(Exception): ...
class BillingNotFoundError(Exception): ... class BillingNotFoundError(Exception): ...
def _get_course(course_id: str) -> dict | None:
with sqlite3.connect(
database=SQLITE_DATABASE, detect_types=sqlite3.PARSE_DECLTYPES
) as conn:
db = Database(conn)
rows = db[SQLITE_TABLE].rows_where(
"json->>'$.metadata__betaeducacao_id' = ?", [course_id]
)
for row in rows:
return row['json']
return None

View File

@@ -1,68 +1,93 @@
import locale import json
import os
from datetime import date
import requests
from aws_lambda_powertools import Logger from aws_lambda_powertools import Logger
from aws_lambda_powertools.shared.json_encoder import Encoder
from aws_lambda_powertools.utilities.data_classes import ( from aws_lambda_powertools.utilities.data_classes import (
EventBridgeEvent, EventBridgeEvent,
event_source, event_source,
) )
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from jinja2 import Environment, FileSystemLoader from layercake.dateutils import now
from layercake.dateutils import fromisoformat
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from weasyprint import HTML
from boto3clients import dynamodb_client from boto3clients import dynamodb_client, s3_client
from config import ORDER_TABLE from config import BUCKET_NAME, ORDER_TABLE
logger = Logger(__name__) logger = Logger(__name__)
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
curdir = os.path.dirname(__file__)
env = Environment(loader=FileSystemLoader(curdir))
locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
def currency(value: float | int) -> str:
return locale.currency(value, grouping=True)
def datetime_format(dt: date, fmt='%H:%M %d-%m-%y'):
if isinstance(dt, str):
dt = fromisoformat(dt) # type: ignore
return dt.strftime(fmt)
env.filters['datetime_format'] = datetime_format
env.filters['currency'] = currency
@event_source(data_class=EventBridgeEvent) @event_source(data_class=EventBridgeEvent)
@logger.inject_lambda_context @logger.inject_lambda_context
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
new_image = event.detail['new_image'] keys = event.detail['keys']
_, start_date, _, end_date, *_ = new_image['sk'].split('#') now_ = now()
# Key pattern `BILLING#ORG#{org_id}`
*_, org_id = keys['id'].split('#')
# Key pattern `START#{start_date}#END#{end_date}#SCHEDULE#AUTO_CLOSE`
_, start_date, _, end_date, *_ = keys['sk'].split('#')
result = order_layer.collection.query( result = order_layer.collection.query(
KeyPair( KeyPair(
pk=new_image['id'], pk=keys['id'],
sk=f'START#{start_date}#END#{end_date}#ENROLLMENT', sk=f'START#{start_date}#END#{end_date}#ENROLLMENT',
), ),
) )
template = env.get_template('tmpl.html') r = requests.post(
html_rendered = template.render( 'https://weasyprint.saladeaula.digital',
start_date=start_date, data=json.dumps(
end_date=end_date, {
items=result['items'], 'template_s3_uri': 's3://saladeaula.digital/billing/template.html',
'template_vars': {
'start_date': start_date,
'end_date': end_date,
'items': result['items'],
},
},
cls=Encoder,
),
) )
r.raise_for_status()
HTML(string=html_rendered, base_url='').write_pdf('cert.pdf') object_key = f'billing/{org_id}/{start_date}_{end_date}.pdf'
s3_uri = f's3://{BUCKET_NAME}/{object_key}'
return order_layer.update_item( try:
key=KeyPair(new_image['id'], new_image['sk']), s3_client.put_object(
update_expr='SET #status = :status', Bucket=BUCKET_NAME,
expr_attr_names={'#status': 'status'}, Key=object_key,
expr_attr_values={':status': 'CLOSED'}, Body=r.content,
) ContentType='application/pdf',
)
except Exception as exc:
logger.exception(exc)
raise
with order_layer.transact_writer() as transact:
transact.update(
key=KeyPair(
pk=keys['id'],
sk=f'START#{start_date}#END#{end_date}',
),
update_expr='SET #status = :status, s3_uri = :s3_uri, \
updated_at = :updated_at',
expr_attr_names={'#status': 'status'},
expr_attr_values={
':status': 'CLOSED',
':s3_uri': s3_uri,
':updated_at': now_,
},
cond_expr='attribute_exists(sk)',
)
transact.put(
item={
'id': keys['id'],
'sk': '{sk}#EXECUTED'.format(sk=keys['sk']),
'created_at': now_,
}
)
logger.info(f'PDF uploaded successfully to {s3_uri}')
return True

View File

@@ -1,200 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
@page {
size: A4 portrait;
margin: 0.458cm;
}
@font-face {
font-family: "Arial";
src: url("fonts/Arial.ttf") format("truetype");
}
html,
body,
div,
h1,
h2,
ul,
p,
a {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
html {
font-family: SF-Pro;
font-size: 11pt;
line-height: 1.4;
margin: 0;
padding: 0;
box-sizing: border-box;
}
section {
width: 100%;
break-after: page;
box-sizing: border-box;
}
strong {
font-weight: bold;
}
table {
table-layout: auto;
width: 100%;
border-spacing: 0;
border-radius: 0.5rem;
border: 1px solid #efefef;
overflow: visible;
}
td {
padding: 0.625rem;
}
tbody > tr > td {
border-top: 1px solid #efefef;
}
thead {
background-color: #f3f4f680;
font-weight: 600;
display: table-header-group;
}
tr {
page-break-inside: avoid;
}
.space-y-0\.5 > :not(:last-child) {
margin-bottom: 0.125rem;
}
.space-y-2\.5 > :not(:last-child) {
margin-bottom: 0.625rem;
}
</style>
</head>
<body>
<section class="space-y-2.5">
<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1143.4 257.88"
style="width: 10rem"
>
<defs>
<style>
.cls-1 {
fill: #8cd366;
}
.cls-2 {
fill: #2e3524;
}
</style>
</defs>
<g>
<g>
<path
class="cls-1"
d="M119.06,170.25l-53.68-24.18c-1.47-.94-3.35-.94-4.82,0l-53.68,24.18c-2.98,1.9-6.89-.24-6.89-3.77V7.01C0,4.54,2.01,2.54,4.48,2.54h117c2.47,0,4.48,2.01,4.48,4.48v159.46c0,3.54-3.91,5.68-6.89,3.77Z"
/>
<g>
<path
class="cls-2"
d="M73.52,57.89H20.82v15.77h52.7v-15.77Z"
/>
<path
class="cls-2"
d="M84.06,86.96H20.82v20.97h63.24v-20.97Z"
/>
<path
class="cls-2"
d="M84.06,23.67H20.82v20.97h63.24v-20.97Z"
/>
<path
class="cls-2"
d="M105.14,102.66c0-2.91-2.36-5.27-5.27-5.27s-5.27,2.36-5.27,5.27,2.36,5.27,5.27,5.27,5.27-2.36,5.27-5.27Z"
/>
</g>
</g>
<g>
<path
class="cls-2"
d="M191.46,2.54h72.24v34.76h-34.54v69.52h30.81v31h-30.81v82.76h34.54v34.76h-72.24V2.54Z"
/>
<path
class="cls-2"
d="M283.8,2.54h45.22c8.38,0,16.01,1.84,22.92,5.52,6.91,3.68,12.35,8.75,16.33,15.17,3.98,6.42,5.97,13.65,5.97,21.65v168.12c0,8-1.94,15.23-5.8,21.65-3.88,6.42-9.26,11.49-16.18,15.17-6.91,3.68-14.65,5.54-23.23,5.54h-45.22V2.54ZM334.52,222.11c1.36-1.46,2.03-3.27,2.03-5.37V41.12c-.2-2.1-1.04-3.89-2.52-5.37-1.47-1.46-3.25-2.22-5.34-2.22h-10.96v190.79h11.27c2.29,0,4.12-.73,5.49-2.22h.02Z"
/>
<path
class="cls-2"
d="M415.84,252.32c-6.81-3.68-12.2-8.73-16.18-15.17-3.97-6.42-5.97-13.63-5.97-21.65V2.54h37.68v218.61c0,2.1.73,3.91,2.2,5.37,1.46,1.48,3.25,2.22,5.34,2.22,2.29,0,4.12-.73,5.49-2.22,1.36-1.46,2.03-3.27,2.03-5.37V2.54h37.68v212.98c0,8.02-1.94,15.23-5.8,21.65-3.88,6.42-9.26,11.49-16.18,15.17-6.92,3.68-14.65,5.54-23.23,5.54s-16.27-1.84-23.08-5.54l.04-.02Z"
/>
<path
class="cls-2"
d="M526.39,252.32c-6.81-3.68-12.2-8.73-16.18-15.17-3.97-6.42-5.97-13.63-5.97-21.65v-61.61h37.68v67.24c0,2.1.73,3.91,2.2,5.37,1.45,1.48,3.25,2.21,5.34,2.21,2.29,0,4.12-.73,5.49-2.21,1.36-1.47,2.03-3.27,2.03-5.37v-60.94c0-4.43-1.73-8.06-5.19-10.91-3.45-2.84-9.05-6.27-16.8-10.27-6.49-3.36-11.78-6.37-15.86-9.01-4.09-2.63-7.6-6.16-10.52-10.59-2.93-4.43-4.4-9.58-4.4-15.49v-61.57c0-8,1.98-15.21,5.97-21.65,3.98-6.42,9.37-11.47,16.18-15.17,6.79-3.68,14.5-5.52,23.08-5.52s16.33,1.84,23.23,5.52c6.91,3.68,12.3,8.75,16.18,15.17,3.88,6.42,5.8,13.65,5.8,21.65v55.62h-37.68v-61.25c0-2.1-.69-3.89-2.03-5.37-1.36-1.46-3.19-2.22-5.49-2.22-2.09,0-3.88.73-5.34,2.22-1.48,1.48-2.2,3.27-2.2,5.37v54.61c0,4.84,1.83,8.81,5.51,11.85,3.66,3.06,9.57,6.91,17.75,11.53,6.29,3.38,11.4,6.33,15.4,8.84,3.97,2.53,7.33,5.84,10.04,9.95,2.72,4.11,4.09,8.81,4.09,14.06v67.94c0,8.02-1.94,15.23-5.8,21.65-3.88,6.42-9.26,11.49-16.18,15.17-6.92,3.68-14.65,5.54-23.23,5.54s-16.27-1.84-23.08-5.54Z"
/>
<path
class="cls-2"
d="M613.84,2.54h72.24v34.76h-34.54v69.52h30.81v31h-30.81v82.76h34.54v34.76h-72.24V2.54Z"
/>
<path
class="cls-2"
d="M720.31,252.32c-4.4-3.27-7.91-7.9-10.52-13.9-2.61-6.01-3.92-12.9-3.92-20.69V42.03c0-7.79,1.98-14.91,5.97-21.33,3.98-6.42,9.43-11.47,16.33-15.17,6.91-3.68,14.56-5.52,22.94-5.52s16.07,1.84,23.08,5.52c7.02,3.68,12.45,8.75,16.33,15.17,3.88,6.42,5.8,13.54,5.8,21.33v73.95h-37.68V36.71c0-2.1-.69-3.89-2.03-5.37-1.36-1.46-3.19-2.22-5.49-2.22-2.09,0-3.88.73-5.34,2.22-1.48,1.48-2.2,3.27-2.2,5.37v181.02c0,2.1.73,3.91,2.2,5.37,1.45,1.48,3.25,2.22,5.34,2.22,2.3,0,4.13-.73,5.49-2.22,1.36-1.47,2.03-3.27,2.03-5.37v-36.02h-9.11v-40.13h46.78v113.76h-37.68v-11.06c-2.31,4.23-5.34,7.42-9.11,9.63-3.77,2.22-8.47,3.32-14.13,3.32s-10.67-1.63-15.08-4.9l-.02-.02Z"
/>
<path
class="cls-2"
d="M824.08,19.6h-4.8l-.04-2.89h4.29c.65-.01,1.26-.12,1.82-.31.56-.21,1.01-.5,1.36-.89.34-.4.51-.89.51-1.47,0-.73-.13-1.3-.38-1.73-.24-.43-.65-.73-1.24-.91-.58-.19-1.36-.29-2.35-.29h-2.96v15.98h-3.22V8.24h6.18c1.47,0,2.73.21,3.8.64,1.08.41,1.91,1.05,2.49,1.91.59.84.89,1.9.89,3.18,0,.8-.19,1.51-.56,2.13-.37.62-.92,1.16-1.64,1.62-.71.44-1.59.81-2.62,1.09-.04,0-.1.05-.16.16-.04.1-.09.16-.13.16-.25.15-.42.26-.49.33-.06.06-.13.1-.2.11-.06.01-.24.02-.53.02ZM823.86,19.6l.47-2.2c2.31,0,3.89.5,4.73,1.51.85.99,1.27,2.26,1.27,3.8v1.2c0,.55.02,1.07.07,1.58.06.49.16.9.31,1.24v.36h-3.31c-.15-.39-.24-.93-.27-1.64-.02-.71-.02-1.23-.02-1.56v-1.16c0-1.08-.24-1.87-.73-2.38-.49-.5-1.33-.76-2.51-.76ZM810.38,17.93c0,1.97.33,3.81,1,5.51.68,1.69,1.63,3.17,2.84,4.44,1.22,1.26,2.61,2.24,4.2,2.96,1.6.7,3.3,1.04,5.11,1.04s3.53-.35,5.11-1.04c1.59-.71,2.98-1.7,4.18-2.96,1.2-1.27,2.14-2.76,2.82-4.44.68-1.7,1.02-3.54,1.02-5.51s-.34-3.8-1.02-5.49c-.68-1.69-1.62-3.16-2.82-4.42-1.2-1.26-2.59-2.24-4.18-2.93-1.59-.71-3.29-1.07-5.11-1.07s-3.51.36-5.11,1.07c-1.59.7-2.98,1.67-4.2,2.93-1.22,1.26-2.16,2.73-2.84,4.42-.67,1.69-1,3.52-1,5.49ZM807.75,17.93c0-2.36.41-4.54,1.22-6.55.81-2.01,1.95-3.77,3.4-5.27,1.45-1.51,3.13-2.68,5.02-3.51,1.91-.84,3.96-1.27,6.13-1.27s4.21.42,6.11,1.27c1.91.83,3.59,2,5.02,3.51,1.45,1.5,2.59,3.25,3.4,5.27.83,2.01,1.25,4.2,1.25,6.55s-.42,4.54-1.25,6.55c-.81,2.01-1.95,3.78-3.4,5.31-1.44,1.51-3.11,2.69-5.02,3.53-1.9.84-3.93,1.27-6.11,1.27s-4.22-.42-6.13-1.27c-1.9-.84-3.57-2.02-5.02-3.53-1.45-1.53-2.58-3.3-3.4-5.31-.82-2.01-1.22-4.2-1.22-6.55Z"
/>
</g>
</g>
</svg>
<p>
Matrículas realizadas entre
<strong>{{ start_date|datetime_format('%d/%m/%Y') }}</strong> e
<strong>{{ end_date|datetime_format('%d/%m/%Y') }}</strong>
</p>
<table class="table-layout border border-gray-300">
<thead>
<tr>
<td>Curso</td>
<td>Colaborador</td>
<td>Matrículado em</td>
<td>Valor unit.</td>
<td>Autor</td>
</tr>
</thead>
<tbody>
{% for x in items %}
<tr>
<td>{{ x.course.name }}</td>
<td>{{ x.user.name }}</td>
<td>
{{ x.enrolled_at|datetime_format('%d/%m/%Y, %H:%M')
}}
</td>
<td>{{ x.unit_price|currency }}</td>
<td>{{ x.author.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</section>
</body>
</html>

View File

@@ -2,6 +2,9 @@ AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31 Transform: AWS::Serverless-2016-10-31
Parameters: Parameters:
BucketName:
Type: String
Default: saladeaula.digital
UserTable: UserTable:
Type: String Type: String
Default: betaeducacao-prod-users_d2o3r5gmm4it7j Default: betaeducacao-prod-users_d2o3r5gmm4it7j
@@ -35,6 +38,7 @@ Globals:
ORDER_TABLE: !Ref OrderTable ORDER_TABLE: !Ref OrderTable
ENROLLMENT_TABLE: !Ref EnrollmentTable ENROLLMENT_TABLE: !Ref EnrollmentTable
COURSE_TABLE: !Ref CourseTable COURSE_TABLE: !Ref CourseTable
BUCKET_NAME: !Ref BucketName
Resources: Resources:
EventLog: EventLog:
@@ -45,7 +49,7 @@ Resources:
EventBillingAddEnrollmentFunction: EventBillingAddEnrollmentFunction:
Type: AWS::Serverless::Function Type: AWS::Serverless::Function
Properties: Properties:
Handler: events.billing.add_enrollment.lambda_handler Handler: events.billing.append_enrollment.lambda_handler
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies:
@@ -70,11 +74,14 @@ Resources:
Type: AWS::Serverless::Function Type: AWS::Serverless::Function
Properties: Properties:
Handler: events.billing.close_window.lambda_handler Handler: events.billing.close_window.lambda_handler
Timeout: 26
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies:
- DynamoDBCrudPolicy: - DynamoDBCrudPolicy:
TableName: !Ref OrderTable TableName: !Ref OrderTable
- S3WritePolicy:
BucketName: !Ref BucketName
Events: Events:
Event: Event:
Type: EventBridgeRule Type: EventBridgeRule
@@ -83,7 +90,7 @@ Resources:
resources: [!Ref OrderTable] resources: [!Ref OrderTable]
detail-type: [EXPIRE] detail-type: [EXPIRE]
detail: detail:
new_image: keys:
sk: sk:
- suffix: SCHEDULE#AUTO_CLOSE - suffix: SCHEDULE#AUTO_CLOSE

View File

@@ -20,6 +20,7 @@ def pytest_configure():
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
os.environ['LOG_LEVEL'] = 'DEBUG' os.environ['LOG_LEVEL'] = 'DEBUG'
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
@dataclass @dataclass

View File

@@ -1,10 +1,14 @@
from aws_lambda_powertools.utilities.typing import LambdaContext from aws_lambda_powertools.utilities.typing import LambdaContext
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey from layercake.dynamodb import (
DynamoDBPersistenceLayer,
SortKey,
TransactKey,
)
import events.billing.close_window as app import events.billing.close_window as app
def test_append_enrollment( def test_close_window(
dynamodb_seeds, dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer, dynamodb_persistence_layer: DynamoDBPersistenceLayer,
lambda_context: LambdaContext, lambda_context: LambdaContext,
@@ -22,8 +26,15 @@ def test_append_enrollment(
assert app.lambda_handler(event, lambda_context) # type: ignore assert app.lambda_handler(event, lambda_context) # type: ignore
# r = dynamodb_persistence_layer.collection.query( # r = dynamodb_persistence_layer.collection.get_items(
# PartitionKey('BILLING#ORG#edp8njvgQuzNkLx2ySNfAD') # TransactKey('BILLING#ORG#BES6dmWgTMXRYmfDyYYXUF')
# + SortKey('START#2025-07-01#END#2025-07-31')
# + SortKey('START#2025-07-01#END#2025-07-31#SCHEDULE#AUTO_CLOSE#EXECUTED'),
# flatten_top=False,
# ) # )
# print(r) # assert 's3_uri' in r['START#2025-07-01#END#2025-07-31']
# assert 'created_at' in r['START#2025-07-01#END#2025-07-31']
# assert 'updated_at' in r['START#2025-07-01#END#2025-07-31']
# assert r['START#2025-07-01#END#2025-07-31']['status'] == 'CLOSED'
# assert 'START#2025-07-01#END#2025-07-31#SCHEDULE#AUTO_CLOSE#EXECUTED' in r

View File

@@ -68,7 +68,7 @@ def record_handler(record: DynamoDBRecord):
] ]
) )
logger.info('Event result', result=result) logger.info('Event result', detail=detail, detail_type=detail_type, result=result)
@tracer.capture_lambda_handler @tracer.capture_lambda_handler

View File

@@ -11,7 +11,7 @@ Globals:
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:83 - !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:83
Environment: Environment:
Variables: Variables:
LOG_LEVEL: DEBUG LOG_LEVEL: INFO
TZ: America/Sao_Paulo TZ: America/Sao_Paulo
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1 POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
POWERTOOLS_LOGGER_LOG_EVENT: true POWERTOOLS_LOGGER_LOG_EVENT: true