update address

This commit is contained in:
2025-07-15 16:00:41 -03:00
parent fcbfc3b97d
commit 5c80502715
12 changed files with 426 additions and 52 deletions

View File

@@ -63,6 +63,7 @@ app.include_router(users.logs, prefix='/users')
app.include_router(users.emails, prefix='/users') app.include_router(users.emails, prefix='/users')
app.include_router(users.orgs, prefix='/users') app.include_router(users.orgs, prefix='/users')
app.include_router(orgs.policies, prefix='/orgs') app.include_router(orgs.policies, prefix='/orgs')
app.include_router(orgs.address, prefix='/orgs')
app.include_router(webhooks.router, prefix='/webhooks') app.include_router(webhooks.router, prefix='/webhooks')
app.include_router(settings.router, prefix='/settings') app.include_router(settings.router, prefix='/settings')
app.include_router(lookup.router, prefix='/lookup') app.include_router(lookup.router, prefix='/lookup')

View File

@@ -83,7 +83,7 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
self.collection.put_item( self.collection.put_item(
key=KeyPair( key=KeyPair(
# Post-migration: remove `delimiter` and update prefix # Post-migration: remove `delimiter` and update prefix
# from `log` to `logs` in ComposeKey. # from `log` to `logs#user` in ComposeKey.
pk=ComposeKey(user.id, prefix='log', delimiter=':'), pk=ComposeKey(user.id, prefix='log', delimiter=':'),
sk=now_.isoformat(), sk=now_.isoformat(),
), ),

View File

@@ -1,3 +1,4 @@
from .address import router as address
from .policies import router as policies from .policies import router as policies
__all__ = ['policies'] __all__ = ['policies', 'address']

View File

@@ -0,0 +1,51 @@
from http import HTTPStatus
from aws_lambda_powertools.event_handler.api_gateway import Router
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
KeyPair,
)
from pydantic import BaseModel
from api_gateway import JSONResponse
from boto3clients import dynamodb_client
from config import USER_TABLE
router = Router()
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
@router.get(
'/<id>/address',
compress=True,
tags=['Organization'],
summary='Get organization address',
)
def get_address(id: str):
return org_layer.collection.get_item(
KeyPair(id, 'metadata#address'),
)
class Address(BaseModel):
postcode: str
address1: str
address2: str | None = None
neighborhood: str
city: str
state: str
@router.post('/<id>/address', compress=True, tags=['Organization'])
def post_address(id: str, payload: Address):
address = payload.model_dump()
org_layer.collection.put_item(
key=KeyPair(id, 'metadata#address'),
cond_expr='attribute_exists(sk)',
**address,
)
return JSONResponse(
body=payload,
status_code=HTTPStatus.OK,
)

View File

@@ -1,26 +1,21 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Literal from typing import Literal
from aws_lambda_powertools.event_handler import Response, content_types
from aws_lambda_powertools.event_handler.api_gateway import Router from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError,
)
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
SortKey, SortKey,
TransactKey, TransactKey,
) )
from pydantic.main import BaseModel from pydantic import BaseModel
from api_gateway import JSONResponse
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import USER_TABLE from config import USER_TABLE
from rules.org import update_policies from rules.org import update_policies
router = Router() router = Router()
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
org_collect = DynamoDBCollection(org_layer, exc_cls=BadRequestError)
@router.get( @router.get(
@@ -30,7 +25,7 @@ org_collect = DynamoDBCollection(org_layer, exc_cls=BadRequestError)
summary='Get organization policies', summary='Get organization policies',
) )
def get_policies(id: str): def get_policies(id: str):
return org_collect.get_items( return org_layer.collection.get_items(
TransactKey(id) TransactKey(id)
+ SortKey('metadata#billing_policy', remove_prefix='metadata#') + SortKey('metadata#billing_policy', remove_prefix='metadata#')
+ SortKey('metadata#payment_policy', remove_prefix='metadata#'), + SortKey('metadata#payment_policy', remove_prefix='metadata#'),
@@ -40,7 +35,7 @@ def get_policies(id: str):
class BillingPolicy(BaseModel): class BillingPolicy(BaseModel):
billing_day: int billing_day: int
payment_method: Literal['PIX', 'BANK_SLIP', 'MANUAL'] payment_method: Literal['BANK_SLIP', 'MANUAL']
class PaymentPolicy(BaseModel): class PaymentPolicy(BaseModel):
@@ -64,8 +59,7 @@ def put_policies(id: str, payload: Policies):
persistence_layer=org_layer, persistence_layer=org_layer,
) )
return Response( return JSONResponse(
body=payload, body=payload,
content_type=content_types.APPLICATION_JSON,
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
) )

View File

@@ -35,9 +35,11 @@ user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
summary='Get user emails', summary='Get user emails',
) )
def get_emails(id: str): def get_emails(id: str):
start_key = router.current_event.get_query_string_value('start_key', None)
return user_collect.query( return user_collect.query(
KeyPair(id, PrefixKey('emails')), KeyPair(id, PrefixKey('emails')),
start_key=router.current_event.get_query_string_value('start_key', None), start_key=start_key,
) )
@@ -54,6 +56,7 @@ class Email(BaseModel):
) )
def post_email(id: str, payload: Email): def post_email(id: str, payload: Email):
add_email(id, payload.email, persistence_layer=user_layer) add_email(id, payload.email, persistence_layer=user_layer)
return JSONResponse( return JSONResponse(
body=payload, body=payload,
status_code=HTTPStatus.CREATED, status_code=HTTPStatus.CREATED,
@@ -90,7 +93,11 @@ def patch_email(id: str, payload: EmailAsPrimary):
email_verified=payload.email_verified, email_verified=payload.email_verified,
persistence_layer=user_layer, persistence_layer=user_layer,
) )
return JSONResponse(body=payload, status_code=HTTPStatus.OK)
return JSONResponse(
body=payload,
status_code=HTTPStatus.OK,
)
@router.delete( @router.delete(
@@ -101,5 +108,9 @@ def patch_email(id: str, payload: EmailAsPrimary):
middlewares=[AuditLogMiddleware('EMAIL_DEL', user_collect, ('email',))], middlewares=[AuditLogMiddleware('EMAIL_DEL', user_collect, ('email',))],
) )
def delete_email(id: str, payload: Email): def delete_email(id: str, payload: Email):
del_email(id, payload.email, persistence_layer=user_layer) del_email(
id,
payload.email,
persistence_layer=user_layer,
)
return payload return payload

View File

@@ -5,36 +5,38 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
def update_policies( def update_policies(
id: str, id: str,
/, /,
payment_policy: dict = {}, payment_policy: dict | None = None,
billing_policy: dict = {}, billing_policy: dict | None = None,
*, *,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
): ):
now_ = now() now_ = now()
payment_sk = 'metadata#payment_policy'
billing_sk = 'metadata#billing_policy'
with persistence_layer.transact_writer() as transact: with persistence_layer.transact_writer() as transact:
if payment_policy: if payment_policy:
transact.put( transact.put(
item={ item={
'id': id, 'id': id,
'sk': 'metadata#payment_policy', 'sk': payment_sk,
'create_date': now_, 'created_at': now_,
} }
| payment_policy | payment_policy
) )
else: else:
transact.delete(key=KeyPair(id, 'metadata#payment_policy')) transact.delete(key=KeyPair(id, payment_sk))
if billing_policy: if billing_policy:
transact.put( transact.put(
item={ item={
'id': id, 'id': id,
'sk': 'metadata#billing_policy', 'sk': billing_sk,
'create_date': now_, 'created_at': now_,
} }
| billing_policy | billing_policy
) )
else: else:
transact.delete(key=KeyPair(id, 'metadata#billing_policy')) transact.delete(key=KeyPair(id, billing_sk))
return True return True

View File

@@ -7,7 +7,6 @@ from aws_lambda_powertools.event_handler.exceptions import (
) )
from layercake.dateutils import now, ttl from layercake.dateutils import now, ttl
from layercake.dynamodb import ( from layercake.dynamodb import (
ComposeKey,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
SortKey, SortKey,
@@ -35,14 +34,14 @@ def update_user(
with persistence_layer.transact_writer() as transact: with persistence_layer.transact_writer() as transact:
transact.update( transact.update(
key=KeyPair(user.id, '0'), key=KeyPair(user.id, '0'),
update_expr='SET #name = :name, cpf = :cpf, update_date = :update_date', update_expr='SET #name = :name, cpf = :cpf, updated_at = :updated_at',
expr_attr_names={ expr_attr_names={
'#name': 'name', '#name': 'name',
}, },
expr_attr_values={ expr_attr_values={
':name': user.name, ':name': user.name,
':cpf': user.cpf, ':cpf': user.cpf,
':update_date': now_, ':updated_at': now_,
}, },
cond_expr='attribute_exists(sk)', cond_expr='attribute_exists(sk)',
) )
@@ -56,7 +55,7 @@ def update_user(
item={ item={
'id': user.id, 'id': user.id,
'sk': 'rate_limit#user_update', 'sk': 'rate_limit#user_update',
'create_date': now_, 'created_at': now_,
'ttl': ttl(start_dt=now_ + timedelta(hours=24)), 'ttl': ttl(start_dt=now_ + timedelta(hours=24)),
}, },
exc_cls=RateLimitError, exc_cls=RateLimitError,
@@ -73,7 +72,7 @@ def update_user(
'id': 'cpf', 'id': 'cpf',
'sk': user.cpf, 'sk': user.cpf,
'user_id': user.id, 'user_id': user.id,
'create_date': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
exc_cls=CPFConflictError, exc_cls=CPFConflictError,
@@ -107,7 +106,7 @@ def add_email(
transact.put( transact.put(
item={ item={
'id': id, 'id': id,
'sk': ComposeKey(email, prefix='emails'), 'sk': f'emails#{email}',
'email_primary': False, 'email_primary': False,
'email_verified': False, 'email_verified': False,
'create_date': now_, 'create_date': now_,
@@ -145,7 +144,7 @@ def del_email(
with persistence_layer.transact_writer() as transact: with persistence_layer.transact_writer() as transact:
transact.delete(key=KeyPair('email', email)) transact.delete(key=KeyPair('email', email))
transact.delete( transact.delete(
key=KeyPair(id, ComposeKey(email, prefix='emails')), key=KeyPair(id, f'emails#{email}'),
cond_expr='email_primary <> :email_primary', cond_expr='email_primary <> :email_primary',
expr_attr_values={ expr_attr_values={
':email_primary': True, ':email_primary': True,
@@ -177,7 +176,7 @@ def set_email_as_primary(
with persistence_layer.transact_writer() as transact: with persistence_layer.transact_writer() as transact:
# Set the old email as non-primary # Set the old email as non-primary
transact.update( transact.update(
key=KeyPair(id, ComposeKey(old_email, prefix='emails')), key=KeyPair(id, f'emails#{old_email}'),
update_expr=expr, update_expr=expr,
expr_attr_values={ expr_attr_values={
':email_primary': False, ':email_primary': False,
@@ -186,7 +185,7 @@ def set_email_as_primary(
) )
# Set the new email as primary # Set the new email as primary
transact.update( transact.update(
key=KeyPair(id, ComposeKey(new_email, 'emails')), key=KeyPair(id, f'emails#{new_email}'),
update_expr=expr, update_expr=expr,
expr_attr_values={ expr_attr_values={
':email_primary': True, ':email_primary': True,
@@ -214,19 +213,22 @@ def del_org_member(
org_id: str, org_id: str,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
) -> bool: ) -> bool:
now_ = now()
with persistence_layer.transact_writer() as transact: with persistence_layer.transact_writer() as transact:
# Remove the user's relationship with the organization and their privileges # Remove the user's relationship with the organization and their privileges
transact.delete(key=KeyPair(id, ComposeKey(org_id, prefix='acls'))) transact.delete(key=KeyPair(id, f'acls#{org_id}'))
transact.delete(key=KeyPair(id, ComposeKey(org_id, prefix='orgs'))) transact.delete(key=KeyPair(id, f'orgs#{org_id}'))
transact.update( transact.update(
key=KeyPair(id, '0'), key=KeyPair(id, '0'),
update_expr='DELETE #tenant :org_id', update_expr='DELETE tenant_id :tenant_id SET updated_at = :updated_at',
expr_attr_names={'#tenant': 'tenant__org_id'}, expr_attr_values={
expr_attr_values={':org_id': {org_id}}, ':tenant_id': {org_id},
':updated_at': now_,
},
) )
# Remove the user from the organization's admins and members list # Remove the user from the organization's admins and members list
transact.delete(key=KeyPair(org_id, ComposeKey(id, prefix='admins'))) transact.delete(key=KeyPair(org_id, f'admins#{id}'))
transact.delete(key=KeyPair(ComposeKey(org_id, prefix='orgmembers'), id)) transact.delete(key=KeyPair(f'orgmembers#{org_id}', id))
return True return True

View File

@@ -0,0 +1,251 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>NR-10 Complementar (SEP)</title>
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
<style>
html,
body,
div,
h1,
h2,
ul,
p,
a {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
@font-face {
font-family: "SF-Pro";
src: url("fonts/SF-Pro.ttf") format("truetype");
}
@page {
size: A4 landscape;
margin: 0;
}
html {
font-family: SF-Pro, Helvetica, Arial, sans-serif;
font-size: 13pt;
line-height: 1.4;
}
section {
width: 29.7cm;
height: 21cm;
break-after: page;
box-sizing: border-box;
padding: 5rem;
}
strong {
font-weight: bold;
}
#cover {
background-color: #a7e400;
justify-content: center;
position: relative;
display: flex;
flex-direction: column;
gap: 1rem;
}
#cover h1 {
font-weight: bolder;
font-size: 26pt;
}
#cover .qrcode {
width: 120px;
height: 120px;
background-color: #fff;
position: absolute;
top: 5rem;
right: 5rem;
}
#cover .signatures {
display: flex;
justify-content: space-between;
margin-top: 2.5rem;
}
.sign1 {
width: 250px;
border-top: #000 solid 1px;
}
#back {
background-color: white;
display: flex;
flex-direction: row;
gap: 0.5rem;
}
#back h1,
#back h2 {
font-weight: bold;
font-size: 16pt;
}
#back ul {
padding-left: 1rem;
}
.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 id="cover">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1072.73 329.6"
style="width: 14rem"
>
<g>
<g>
<path
fill="#8cd366"
d="M152.18,217.62l-68.61-30.91c-1.88-1.2-4.28-1.2-6.16,0l-68.61,30.91c-3.81,2.43-8.8-.3-8.8-4.82V8.98C0,5.82,2.56,3.26,5.72,3.26h149.54c3.16,0,5.72,2.56,5.72,5.72v203.81c0,4.52-5,7.26-8.8,4.82Z"
></path>
<path
fill="#2e3524"
d="M93.97,74.01H26.61v20.16h67.36v-20.16Z"
></path>
<path
fill="#2e3524"
d="M107.44,111.16H26.61v26.8h80.83v-26.8Z"
></path>
<path
fill="#2e3524"
d="M107.44,30.27H26.61v26.8h80.83v-26.8Z"
></path>
<path
fill="#2e3524"
d="M134.38,131.23c0-3.72-3.02-6.73-6.73-6.73s-6.73,3.02-6.73,6.73,3.02,6.73,6.73,6.73,6.73-3.02,6.73-6.73Z"
></path>
</g>
<g>
<path
fill="#f9f7e8"
d="M244.7,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
></path>
<path
fill="#f9f7e8"
d="M362.72,3.24h57.79c10.71,0,20.47,2.35,29.29,7.06,8.83,4.7,15.79,11.18,20.87,19.39,5.08,8.21,7.63,17.45,7.63,27.67v214.88c0,10.22-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.83,4.7-18.73,7.08-29.7,7.08h-57.79V3.24ZM427.55,283.88c1.74-1.87,2.6-4.18,2.6-6.86V52.56c-.26-2.69-1.34-4.97-3.22-6.86-1.88-1.87-4.15-2.83-6.82-2.83h-14v243.85h14.41c2.93,0,5.27-.94,7.01-2.83h.02Z"
></path>
<path
fill="#f9f7e8"
d="M531.5,322.49c-8.71-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67V3.24h48.15v279.41c0,2.69.93,4.99,2.82,6.86,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.86V3.24h48.16v272.21c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.7-18.73,7.08-29.7,7.08s-20.8-2.35-29.5-7.08l.05-.02Z"
></path>
<path
fill="#f9f7e8"
d="M672.79,322.49c-8.7-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67v-78.75h48.16v85.95c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-77.88c0-5.66-2.22-10.3-6.63-13.94-4.41-3.62-11.57-8.02-21.47-13.13-8.3-4.3-15.05-8.14-20.27-11.52-5.22-3.36-9.71-7.87-13.45-13.54-3.75-5.66-5.63-12.24-5.63-19.8V54.12c0-10.22,2.53-19.44,7.63-27.67,5.08-8.21,11.97-14.66,20.68-19.39,8.68-4.7,18.53-7.06,29.5-7.06s20.87,2.35,29.69,7.06c8.83,4.7,15.72,11.18,20.68,19.39,4.96,8.21,7.42,17.45,7.42,27.67v71.09h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v69.79c0,6.19,2.34,11.26,7.04,15.14,4.67,3.91,12.24,8.83,22.68,14.74,8.04,4.32,14.57,8.09,19.68,11.3,5.08,3.24,9.37,7.46,12.83,12.72,3.48,5.26,5.22,11.26,5.22,17.98v86.83c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.71-18.72,7.08-29.7,7.08s-20.8-2.35-29.5-7.08Z"
></path>
<path
fill="#f9f7e8"
d="M784.56,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
></path>
<path
fill="#f9f7e8"
d="M920.63,322.49c-5.63-4.18-10.11-10.1-13.45-17.76-3.34-7.68-5.01-16.49-5.01-26.45V53.71c0-9.96,2.53-19.06,7.63-27.26,5.08-8.21,12.05-14.66,20.87-19.39,8.83-4.7,18.6-7.06,29.32-7.06s20.54,2.35,29.5,7.06c8.97,4.7,15.91,11.18,20.87,19.39,4.96,8.21,7.42,17.3,7.42,27.26v94.51h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v231.36c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-46.03h-11.64v-51.29h59.8v145.4h-48.16v-14.14c-2.96,5.4-6.82,9.48-11.64,12.31-4.82,2.83-10.83,4.25-18.06,4.25s-13.64-2.09-19.27-6.26l-.02-.02Z"
></path>
<path
fill="#f9f7e8"
d="M1053.27,25.05h-6.13l-.06-3.69h5.48c.83-.02,1.61-.15,2.33-.4.72-.27,1.3-.64,1.73-1.14.44-.51.65-1.14.65-1.87,0-.93-.16-1.67-.48-2.22-.3-.55-.83-.94-1.59-1.16-.74-.25-1.74-.37-3.01-.37h-3.78v20.42h-4.12V10.54h7.9c1.87,0,3.49.27,4.86.82,1.38.53,2.44,1.34,3.18,2.44.76,1.08,1.14,2.43,1.14,4.06,0,1.02-.24,1.93-.71,2.73-.47.8-1.17,1.49-2.1,2.07-.91.57-2.03,1.03-3.35,1.39-.06,0-.12.07-.2.2-.06.13-.11.2-.17.2-.32.19-.53.33-.63.43-.08.08-.16.12-.25.14-.08.02-.31.03-.68.03ZM1052.99,25.05l.6-2.81c2.95,0,4.97.64,6.05,1.93,1.08,1.27,1.62,2.89,1.62,4.86v1.53c0,.7.03,1.37.08,2.02.08.62.21,1.15.4,1.59v.45h-4.23c-.19-.49-.3-1.19-.34-2.1-.02-.91-.03-1.57-.03-1.99v-1.48c0-1.38-.31-2.39-.94-3.04s-1.69-.97-3.21-.97ZM1035.75,22.92c0,2.52.43,4.87,1.28,7.04.87,2.16,2.08,4.05,3.64,5.68,1.55,1.61,3.34,2.87,5.37,3.78,2.05.89,4.22,1.33,6.53,1.33s4.51-.44,6.53-1.33c2.03-.91,3.8-2.17,5.34-3.78,1.53-1.63,2.74-3.52,3.61-5.68.87-2.18,1.31-4.52,1.31-7.04s-.44-4.86-1.31-7.01c-.87-2.16-2.07-4.04-3.61-5.65-1.53-1.61-3.31-2.86-5.34-3.75-2.03-.91-4.2-1.36-6.53-1.36s-4.49.45-6.53,1.36c-2.03.89-3.81,2.14-5.37,3.75-1.55,1.61-2.77,3.49-3.64,5.65-.85,2.16-1.28,4.5-1.28,7.01ZM1032.4,22.92c0-3.01.52-5.8,1.56-8.38,1.04-2.57,2.49-4.82,4.34-6.73,1.86-1.93,4-3.43,6.42-4.49,2.44-1.08,5.06-1.62,7.84-1.62s5.39.54,7.81,1.62c2.44,1.06,4.58,2.56,6.42,4.49,1.86,1.91,3.31,4.16,4.35,6.73,1.06,2.57,1.59,5.37,1.59,8.38s-.53,5.8-1.59,8.38c-1.04,2.57-2.49,4.84-4.35,6.79-1.83,1.93-3.97,3.44-6.42,4.52-2.42,1.08-5.03,1.62-7.81,1.62s-5.39-.54-7.84-1.62c-2.42-1.08-4.56-2.58-6.42-4.52-1.85-1.95-3.3-4.21-4.34-6.79-1.04-2.57-1.56-5.37-1.56-8.38Z"
></path>
</g>
</g>
</svg>
<p>Certificamos que</p>
<h1>{{ name }}</h1>
<p>
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
de <strong>NR-10 Complementar (SEP)</strong> com aproveitamento
de
<strong>{{ progress }}%</strong>
</p>
<p>Realizado entre {{ started_date }} e {{ finished_date }}</p>
<p>Florianópolis, SC, {{ today }}</p>
<div class="signatures">
<div class="sign1"></div>
<div class="sign2">
<p>Tiago Maciel do Santos</p>
<p>CEO/Diretor</p>
</div>
</div>
<div class="qrcode">
<img src="{{ qrcode }}" />
</div>
</section>
<section id="back">
<div class="space-y-2.5">
<h1>Conteúdo programático ministrado</h1>
<ul>
<li>Organização do sistema elétrico de potência</li>
<li>Organização do trabalho</li>
<li>Aspectos comportamentais</li>
<li>Condições impeditivas para serviços</li>
<li>Riscos típicos no SEP e sua prevenção</li>
<li>Técnicas de análise de riscos no SEP</li>
<li>Procedimentos de trabalho (análise e discussão)</li>
<li>Técnicas de análise de riscos no SEP</li>
<li>Equipamentos e ferramentas de trabalho</li>
<li>Sistemas de proteção coletiva</li>
<li>Equipamentos de proteção individual</li>
<li>Posturas e vestuários de trabalhos</li>
<li>
Segurança com veículos e transporte de pessoas,
materiais e equipamentos
</li>
<li>Sinalização e isolamento de áreas de trabalho</li>
<li>
Liberação de instalação para serviço, operação e uso
</li>
<li>
Treinamento em técnicas de remoção, atendimento e
transporte de acidentados
</li>
<li>Acidentes típicos</li>
<li>Responsabilidades</li>
</ul>
</div>
<div class="space-y-2.5">
<dd class="space-y-0.5">
<h2>Carga horária</h2>
<p>40 horas</p>
</dd>
<dd class="space-y-0.5">
<h2>Instrutor e responsável técnico</h2>
<div>
<p>Francis Ricardo Baretta</p>
<p>CPF 039.539.409-02</p>
<p>Eng. de Segurança no Trabalho Eng. Eletricista</p>
<p>CREA/SC 126693-0</p>
</div>
</dd>
</div>
</section>
</body>
</html>

View File

@@ -1,7 +1,7 @@
import json import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
@@ -46,6 +46,66 @@ def test_put_policies(
) )
assert r['statusCode'] == HTTPStatus.OK assert r['statusCode'] == HTTPStatus.OK
collect = DynamoDBCollection(dynamodb_persistence_layer) course = dynamodb_persistence_layer.collection.get_item(
course = collect.get_item(KeyPair('cJtK9SsnJhKPyxESe7g3DG', '0')) KeyPair('cJtK9SsnJhKPyxESe7g3DG', '0')
)
assert course['name'] == 'EDUSEG' assert course['name'] == 'EDUSEG'
def test_get_address(
mock_app,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/orgs/cJtK9SsnJhKPyxESe7g3DG/address',
method=HTTPMethod.GET,
body={'payment_policy': None},
),
lambda_context,
)
address = {
'id': 'cJtK9SsnJhKPyxESe7g3DG',
'sk': 'metadata#address',
'postcode': '88101001',
'address1': 'Av. Presidente Kennedy, 815',
'address2': 'Sala 1',
'neighborhood': 'Campinas',
'city': 'São José',
'state': 'SC',
}
assert r['statusCode'] == HTTPStatus.OK
assert json.loads(r['body']) == address
def test_post_address(
mock_app,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/orgs/cJtK9SsnJhKPyxESe7g3DG/address',
method=HTTPMethod.POST,
body={
'postcode': '81280350',
'address1': 'Rua Monsenhor Ivo Zanlorenzi, 5190',
'address2': 'ap 1802',
'neighborhood': 'Cidade Industrial',
'city': 'Curitiba',
'state': 'PR',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
data = dynamodb_persistence_layer.collection.get_item(
KeyPair('cJtK9SsnJhKPyxESe7g3DG', 'metadata#address')
)
assert data['address1'] == 'Rua Monsenhor Ivo Zanlorenzi, 5190'

View File

@@ -2,7 +2,6 @@ import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
SortKey, SortKey,
@@ -32,13 +31,12 @@ def test_update_user_cpf(
) )
assert r['statusCode'] == HTTPStatus.OK assert r['statusCode'] == HTTPStatus.OK
collect = DynamoDBCollection(dynamodb_persistence_layer) user = dynamodb_persistence_layer.collection.get_items(
user = collect.get_items(
TransactKey('5OxmMjL-ujoR5IMGegQz') TransactKey('5OxmMjL-ujoR5IMGegQz')
+ SortKey('0') + SortKey('0')
+ SortKey('last_profile_edit') + SortKey('rate_limit#user_update')
) )
assert 'last_profile_edit' in user assert 'rate_limit#user_update' in user
def test_update_user_name( def test_update_user_name(
@@ -216,8 +214,9 @@ def test_post_email(
assert r['statusCode'] == HTTPStatus.CREATED assert r['statusCode'] == HTTPStatus.CREATED
collect = DynamoDBCollection(dynamodb_persistence_layer) user = dynamodb_persistence_layer.collection.get_item(
user = collect.get_item(KeyPair('5OxmMjL-ujoR5IMGegQz', '0')) KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['emails'] == { assert user['emails'] == {
'sergio@somosbeta.com.br', 'sergio@somosbeta.com.br',
'osergiosiqueira@gmail.com', 'osergiosiqueira@gmail.com',
@@ -274,8 +273,9 @@ def test_delete_email(
assert r['statusCode'] == HTTPStatus.OK assert r['statusCode'] == HTTPStatus.OK
collect = DynamoDBCollection(dynamodb_persistence_layer) user = dynamodb_persistence_layer.collection.get_item(
user = collect.get_item(KeyPair('5OxmMjL-ujoR5IMGegQz', '0')) KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['emails'] == { assert user['emails'] == {
'sergio@somosbeta.com.br', 'sergio@somosbeta.com.br',
} }

View File

@@ -11,6 +11,7 @@
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}} {"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#payment_policy"}, "due_days": {"N": "90"}} {"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#payment_policy"}, "due_days": {"N": "90"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#billing_policy"}, "billing_day": {"N": "1"}, "payment_method": {"S": "PIX"}} {"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#billing_policy"}, "billing_day": {"N": "1"}, "payment_method": {"S": "PIX"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#address"}, "address1": {"S": "Av. Presidente Kennedy, 815"}, "address2": {"S": "Sala 1"}, "city": {"S": "São José"}, "state": {"S": "SC"}, "postcode": {"S": "88101001"}, "neighborhood": {"S": "Campinas"}}
{"id": {"S": "90d7f0d2-d9a4-4467-a31c-f9a7955964cf"}, "sk": {"S": "0"}, "access_period": {"N": "720"}, "create_date": {"S": "2024-12-30T00:00:33.088916-03:00"},"konviva__class_id": {"N": "266"},"name": {"S": "Reciclagem em NR-18 Básico"},"tenant__org_id": {"SS": ["cJtK9SsnJhKPyxESe7g3DG"]}} {"id": {"S": "90d7f0d2-d9a4-4467-a31c-f9a7955964cf"}, "sk": {"S": "0"}, "access_period": {"N": "720"}, "create_date": {"S": "2024-12-30T00:00:33.088916-03:00"},"konviva__class_id": {"N": "266"},"name": {"S": "Reciclagem em NR-18 Básico"},"tenant__org_id": {"SS": ["cJtK9SsnJhKPyxESe7g3DG"]}}
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "0"}, "course": {"M": {"id": {"S": "a6775b71-d68a-4263-8ab4-acb3a4f8a8b9"}, "name": {"S": "NR-18 PEMT PTA"}, "time_in_days": {"N": "365"}}}, "status": {"S": "PENDING"}} {"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "0"}, "course": {"M": {"id": {"S": "a6775b71-d68a-4263-8ab4-acb3a4f8a8b9"}, "name": {"S": "NR-18 PEMT PTA"}, "time_in_days": {"N": "365"}}}, "status": {"S": "PENDING"}}
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "cancel_policy"}} {"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "cancel_policy"}}