fix retain key
This commit is contained in:
@@ -2,7 +2,7 @@ version = 0.1
|
|||||||
[default.deploy.parameters]
|
[default.deploy.parameters]
|
||||||
stack_name = "saladeaula-batch-jobs"
|
stack_name = "saladeaula-batch-jobs"
|
||||||
resolve_s3 = true
|
resolve_s3 = true
|
||||||
s3_prefix = "batchjobs"
|
s3_prefix = "batch_jobs"
|
||||||
region = "sa-east-1"
|
region = "sa-east-1"
|
||||||
confirm_changeset = false
|
confirm_changeset = false
|
||||||
capabilities = "CAPABILITY_IAM"
|
capabilities = "CAPABILITY_IAM"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Globals:
|
|||||||
Architectures:
|
Architectures:
|
||||||
- x86_64
|
- x86_64
|
||||||
Layers:
|
Layers:
|
||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:53
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:55
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
|
|||||||
@@ -16,13 +16,11 @@
|
|||||||
<h1>{{ name }}</h1>
|
<h1>{{ name }}</h1>
|
||||||
<p>
|
<p>
|
||||||
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
|
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
|
||||||
de <strong>{{ course }}</strong> com aproveitamento de
|
de <strong>NR-10 Complementar (SEP)</strong> com aproveitamento
|
||||||
|
de
|
||||||
<strong>{{ progress }}%</strong>
|
<strong>{{ progress }}%</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>Realizado entre {{ start_date }} e {{ finish_date }}</p>
|
||||||
Realizado entre {{ start_date }} e {{ finish_date }}, com
|
|
||||||
validade até {{ due_date }}
|
|
||||||
</p>
|
|
||||||
<p>Florianópolis, SC, {{ today }}</p>
|
<p>Florianópolis, SC, {{ today }}</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,6 @@
|
|||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
div,
|
div,
|
||||||
span,
|
|
||||||
applet,
|
|
||||||
object,
|
|
||||||
iframe,
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
@@ -16,73 +12,7 @@ h4,
|
|||||||
h5,
|
h5,
|
||||||
h6,
|
h6,
|
||||||
p,
|
p,
|
||||||
blockquote,
|
a {
|
||||||
pre,
|
|
||||||
a,
|
|
||||||
abbr,
|
|
||||||
acronym,
|
|
||||||
address,
|
|
||||||
big,
|
|
||||||
cite,
|
|
||||||
code,
|
|
||||||
del,
|
|
||||||
dfn,
|
|
||||||
em,
|
|
||||||
img,
|
|
||||||
ins,
|
|
||||||
kbd,
|
|
||||||
q,
|
|
||||||
s,
|
|
||||||
samp,
|
|
||||||
small,
|
|
||||||
strike,
|
|
||||||
strong,
|
|
||||||
sub,
|
|
||||||
sup,
|
|
||||||
tt,
|
|
||||||
var,
|
|
||||||
b,
|
|
||||||
u,
|
|
||||||
i,
|
|
||||||
center,
|
|
||||||
dl,
|
|
||||||
dt,
|
|
||||||
dd,
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
li,
|
|
||||||
fieldset,
|
|
||||||
form,
|
|
||||||
label,
|
|
||||||
legend,
|
|
||||||
table,
|
|
||||||
caption,
|
|
||||||
tbody,
|
|
||||||
tfoot,
|
|
||||||
thead,
|
|
||||||
tr,
|
|
||||||
th,
|
|
||||||
td,
|
|
||||||
article,
|
|
||||||
aside,
|
|
||||||
canvas,
|
|
||||||
details,
|
|
||||||
embed,
|
|
||||||
figure,
|
|
||||||
figcaption,
|
|
||||||
footer,
|
|
||||||
header,
|
|
||||||
hgroup,
|
|
||||||
menu,
|
|
||||||
nav,
|
|
||||||
output,
|
|
||||||
ruby,
|
|
||||||
section,
|
|
||||||
summary,
|
|
||||||
time,
|
|
||||||
mark,
|
|
||||||
audio,
|
|
||||||
video {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# If UV does not load this file, try running `export UV_ENV_FILE=.env`
|
|
||||||
# See more details at https://docs.astral.sh/uv/configuration/files/#env
|
|
||||||
|
|
||||||
KONVIVA_API_URL=https://saladeaula.digital
|
|
||||||
KONVIVA_SECRET_KEY=
|
|
||||||
MEILISEARCH_HOST=
|
|
||||||
MEILISEARCH_API_KEY=
|
|
||||||
DYNAMODB_PARTITION_KEY=id
|
|
||||||
DYNAMODB_SORT_KEY=sk
|
|
||||||
@@ -26,7 +26,6 @@ Example
|
|||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import boto3
|
|
||||||
from aws_lambda_powertools import Logger, Tracer
|
from aws_lambda_powertools import Logger, Tracer
|
||||||
from aws_lambda_powertools.utilities.data_classes import event_source
|
from aws_lambda_powertools.utilities.data_classes import event_source
|
||||||
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
|
from aws_lambda_powertools.utilities.data_classes.api_gateway_authorizer_event import (
|
||||||
@@ -38,7 +37,7 @@ from botocore.endpoint_provider import Enum
|
|||||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
|
||||||
from layercake.funcs import pick
|
from layercake.funcs import pick
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client, idp_client
|
||||||
from cognito import get_user
|
from cognito import get_user
|
||||||
from conf import USER_TABLE
|
from conf import USER_TABLE
|
||||||
|
|
||||||
@@ -46,7 +45,6 @@ APIKEY_PREFIX = 'sk-'
|
|||||||
|
|
||||||
tracer = Tracer()
|
tracer = Tracer()
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
idp_client = boto3.client('cognito-idp')
|
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
user_collect = DynamoDBCollection(user_layer)
|
user_collect = DynamoDBCollection(user_layer)
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import os
|
|||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
||||||
DYNAMODB_ENDPOINT_URL: str | None = None
|
|
||||||
|
|
||||||
# Only when running `sam local start-api`
|
def get_dynamodb_client():
|
||||||
if 'AWS_SAM_LOCAL' in os.environ:
|
sam_local = os.getenv('AWS_SAM_LOCAL')
|
||||||
DYNAMODB_ENDPOINT_URL = 'http://host.docker.internal:8000'
|
|
||||||
|
|
||||||
# Only when running `pytest`
|
if os.getenv('AWS_LAMBDA_FUNCTION_NAME') and not sam_local:
|
||||||
if 'PYTEST_VERSION' in os.environ:
|
return boto3.client('dynamodb')
|
||||||
DYNAMODB_ENDPOINT_URL = 'http://127.0.0.1:8000'
|
|
||||||
|
|
||||||
dynamodb_client = boto3.client('dynamodb', endpoint_url=DYNAMODB_ENDPOINT_URL)
|
url = 'host.docker.internal' if sam_local else 'localhost'
|
||||||
|
return boto3.client('dynamodb', endpoint_url=f'http://{url}:8000')
|
||||||
|
|
||||||
|
|
||||||
|
dynamodb_client = get_dynamodb_client()
|
||||||
idp_client = boto3.client('cognito-idp')
|
idp_client = boto3.client('cognito-idp')
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from dataclasses import asdict, dataclass
|
|||||||
from urllib.parse import quote as urlquote
|
from urllib.parse import quote as urlquote
|
||||||
from urllib.parse import urlencode, urlparse
|
from urllib.parse import urlencode, urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
from aws_lambda_powertools.event_handler.exceptions import BadRequestError
|
from aws_lambda_powertools.event_handler.exceptions import BadRequestError
|
||||||
from glom import glom
|
from glom import glom
|
||||||
import requests
|
|
||||||
|
|
||||||
from conf import KONVIVA_API_URL, KONVIVA_SECRET_KEY
|
from conf import KONVIVA_API_URL, KONVIVA_SECRET_KEY
|
||||||
|
|
||||||
|
|||||||
@@ -119,12 +119,12 @@ def _tenant(
|
|||||||
# Ensure user has ACL
|
# Ensure user has ACL
|
||||||
collect.get_item(
|
collect.get_item(
|
||||||
KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')),
|
KeyPair(user.id, ComposeKey(tenant_id, prefix='acls')),
|
||||||
exception_cls=ForbiddenError,
|
exc_cls=ForbiddenError,
|
||||||
)
|
)
|
||||||
|
|
||||||
# For root tenant, provide the default Tenant
|
# For root tenant, provide the default Tenant
|
||||||
if tenant_id == '*':
|
if tenant_id == '*':
|
||||||
return Tenant(id=tenant_id, name='default')
|
return Tenant(id=tenant_id, name='default')
|
||||||
|
|
||||||
obj = collect.get_item(KeyPair(tenant_id, '0'), exception_cls=NotFoundError)
|
obj = collect.get_item(KeyPair(tenant_id, '0'), exc_cls=NotFoundError)
|
||||||
return Tenant.parse_obj(obj)
|
return Tenant.model_validate(obj)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ def post_course(payload: Course):
|
|||||||
def get_course(id: str):
|
def get_course(id: str):
|
||||||
return course_collect.get_item(
|
return course_collect.get_item(
|
||||||
KeyPair(id, '0'),
|
KeyPair(id, '0'),
|
||||||
exception_cls=NotFoundError,
|
exc_cls=NotFoundError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from conf import ELASTIC_CONN, ORDER_TABLE
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
order_collect = DynamoDBCollection(order_layer, exception_cls=BadRequestError)
|
order_collect = DynamoDBCollection(order_layer, exc_cls=BadRequestError)
|
||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ 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, exception_cls=BadRequestError)
|
org_collect = DynamoDBCollection(org_layer, exc_cls=BadRequestError)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -31,7 +31,9 @@ org_collect = DynamoDBCollection(org_layer, exception_cls=BadRequestError)
|
|||||||
)
|
)
|
||||||
def get_policies(id: str):
|
def get_policies(id: str):
|
||||||
return org_collect.get_items(
|
return org_collect.get_items(
|
||||||
TransactKey(id) + SortKey('billing_policy') + SortKey('payment_policy'),
|
TransactKey(id)
|
||||||
|
+ SortKey('metadata#billing_policy', remove_prefix='metadata#')
|
||||||
|
+ SortKey('metadata#payment_policy', remove_prefix='metadata#'),
|
||||||
flatten_top=False,
|
flatten_top=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError):
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
|
user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
|
||||||
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
elastic_client = Elasticsearch(**ELASTIC_CONN)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
|
user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
|
user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class BadRequestError(MissingError, PowertoolsBadRequestError): ...
|
|||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
user_collect = DynamoDBCollection(user_layer, exception_cls=BadRequestError)
|
user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def create_course(
|
|||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': course.id,
|
'id': course.id,
|
||||||
'sk': 'tenant',
|
'sk': 'metadata#tenant',
|
||||||
'org_id': org.id,
|
'org_id': org.id,
|
||||||
'name': org.name,
|
'name': org.name,
|
||||||
'create_date': now_,
|
'create_date': now_,
|
||||||
@@ -42,7 +42,8 @@ def update_course(
|
|||||||
transact = TransactItems(persistence_layer.table_name)
|
transact = TransactItems(persistence_layer.table_name)
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, '0'),
|
key=KeyPair(id, '0'),
|
||||||
update_expr='SET #name = :name, access_period = :access_period, cert = :cert, update_date = :update_date',
|
update_expr='SET #name = :name, access_period = :access_period, \
|
||||||
|
cert = :cert, update_date = :update_date',
|
||||||
expr_attr_names={
|
expr_attr_names={
|
||||||
'#name': 'name',
|
'#name': 'name',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,24 +17,24 @@ def update_policies(
|
|||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': id,
|
'id': id,
|
||||||
'sk': 'payment_policy',
|
'sk': 'metadata#payment_policy',
|
||||||
'create_date': now_,
|
'create_date': now_,
|
||||||
}
|
}
|
||||||
| payment_policy
|
| payment_policy
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
transact.delete(key=KeyPair(id, 'payment_policy'))
|
transact.delete(key=KeyPair(id, 'metadata#payment_policy'))
|
||||||
|
|
||||||
if billing_policy:
|
if billing_policy:
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': id,
|
'id': id,
|
||||||
'sk': 'billing_policy',
|
'sk': 'metadata#billing_policy',
|
||||||
'create_date': now_,
|
'create_date': now_,
|
||||||
}
|
}
|
||||||
| billing_policy
|
| billing_policy
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
transact.delete(key=KeyPair(id, 'billing_policy'))
|
transact.delete(key=KeyPair(id, 'metadata#billing_policy'))
|
||||||
|
|
||||||
return persistence_layer.transact_write_items(transact)
|
return persistence_layer.transact_write_items(transact)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from typing import TypedDict
|
|||||||
from aws_lambda_powertools.event_handler.exceptions import (
|
from aws_lambda_powertools.event_handler.exceptions import (
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
)
|
)
|
||||||
from botocore.exceptions import ClientError
|
|
||||||
from botocore.tokens import timedelta
|
from botocore.tokens import timedelta
|
||||||
from layercake.dateutils import now, ttl
|
from layercake.dateutils import now, ttl
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
@@ -14,11 +13,6 @@ from layercake.dynamodb import (
|
|||||||
TransactItems,
|
TransactItems,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CPFConflictError(BadRequestError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
User = TypedDict('User', {'id': str, 'name': str, 'cpf': str})
|
User = TypedDict('User', {'id': str, 'name': str, 'cpf': str})
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +54,10 @@ def update_user(
|
|||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class CPFConflictError(BadRequestError):
|
||||||
|
def __init__(self, msg: str):
|
||||||
|
super().__init__('Cpf already exists')
|
||||||
|
|
||||||
if user.cpf != old_cpf:
|
if user.cpf != old_cpf:
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
@@ -69,18 +67,14 @@ def update_user(
|
|||||||
'create_date': now_,
|
'create_date': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
|
exc_cls=CPFConflictError,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ensures that the old CPF is discarded
|
# Ensures that the old CPF is discarded
|
||||||
if old_cpf:
|
if old_cpf:
|
||||||
transact.delete(key=KeyPair('cpf', old_cpf))
|
transact.delete(key=KeyPair('cpf', old_cpf))
|
||||||
|
|
||||||
try:
|
return persistence_layer.transact_write_items(transact)
|
||||||
persistence_layer.transact_write_items(transact)
|
|
||||||
except ClientError:
|
|
||||||
raise CPFConflictError('CPF is already in use.')
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def add_email(
|
def add_email(
|
||||||
@@ -109,6 +103,11 @@ def add_email(
|
|||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class EmailConflictError(BadRequestError):
|
||||||
|
def __init__(self, msg: str):
|
||||||
|
super().__init__('Email already exists')
|
||||||
|
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': 'email',
|
'id': 'email',
|
||||||
@@ -117,12 +116,10 @@ def add_email(
|
|||||||
'create_date': now_,
|
'create_date': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
|
exc_cls=EmailConflictError,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
return persistence_layer.transact_write_items(transact)
|
||||||
return persistence_layer.transact_write_items(transact)
|
|
||||||
except ClientError:
|
|
||||||
raise BadRequestError('Email already exists.')
|
|
||||||
|
|
||||||
|
|
||||||
def del_email(
|
def del_email(
|
||||||
@@ -141,6 +138,7 @@ def del_email(
|
|||||||
key=KeyPair(id, ComposeKey(email, prefix='emails')),
|
key=KeyPair(id, ComposeKey(email, prefix='emails')),
|
||||||
cond_expr='email_primary <> :primary',
|
cond_expr='email_primary <> :primary',
|
||||||
expr_attr_values={':primary': True},
|
expr_attr_values={':primary': True},
|
||||||
|
exc_cls=BadRequestError,
|
||||||
)
|
)
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, '0'),
|
key=KeyPair(id, '0'),
|
||||||
@@ -150,10 +148,7 @@ def del_email(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
return persistence_layer.transact_write_items(transact)
|
||||||
return persistence_layer.transact_write_items(transact)
|
|
||||||
except ClientError:
|
|
||||||
raise BadRequestError('Cannot remove the primary email.')
|
|
||||||
|
|
||||||
|
|
||||||
def set_email_as_primary(
|
def set_email_as_primary(
|
||||||
@@ -188,10 +183,8 @@ def set_email_as_primary(
|
|||||||
)
|
)
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, '0'),
|
key=KeyPair(id, '0'),
|
||||||
update_expr=(
|
update_expr='SET email = :email, email_verified = :email_verified, \
|
||||||
'SET email = :email, email_verified = :email_verified, '
|
update_date = :update_date',
|
||||||
'update_date = :update_date'
|
|
||||||
),
|
|
||||||
expr_attr_values={
|
expr_attr_values={
|
||||||
':email': new_email,
|
':email': new_email,
|
||||||
':email_verified': email_verified,
|
':email_verified': email_verified,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"Parameters": {
|
"Parameters": {
|
||||||
"KONVIVA_API_KEY": "",
|
"KONVIVA_API_KEY": "",
|
||||||
"USER_TABLE": "test-users",
|
"USER_TABLE": "",
|
||||||
"ORDER_TABLE": "test-orders",
|
"ORDER_TABLE": "",
|
||||||
"ENROLLMENT_TABLE": "test-enrollments"
|
"ENROLLMENT_TABLE": ""
|
||||||
"COURSE_TABLE": "test-courses"
|
"COURSE_TABLE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Globals:
|
|||||||
Architectures:
|
Architectures:
|
||||||
- x86_64
|
- x86_64
|
||||||
Layers:
|
Layers:
|
||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:48
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:59
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
@@ -39,7 +39,7 @@ Globals:
|
|||||||
ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}"
|
ELASTIC_AUTH_PASS: "{{resolve:ssm:/betaeducacao/elastic/auth_pass/str}}"
|
||||||
KONVIVA_API_URL: https://saladeaula.digital
|
KONVIVA_API_URL: https://saladeaula.digital
|
||||||
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}"
|
KONVIVA_SECRET_KEY: "{{resolve:ssm:/betaeducacao/konviva/secret_key/str}}"
|
||||||
MEILISEARCH_HOST: https://meili.vps.eduseg.com.br
|
MEILISEARCH_HOST: https://meili.eduseg.com.br
|
||||||
MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}"
|
MEILISEARCH_API_KEY: "{{resolve:ssm:/saladeaula/meili_api_key}}"
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
|
|||||||
@@ -6,17 +6,21 @@ from http import HTTPMethod
|
|||||||
|
|
||||||
import jsonlines
|
import jsonlines
|
||||||
import pytest
|
import pytest
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer
|
|
||||||
|
|
||||||
PYTEST_TABLE_NAME = 'pytest'
|
PYTEST_TABLE_NAME = 'pytest'
|
||||||
PK = os.getenv('DYNAMODB_PARTITION_KEY')
|
PK = 'id'
|
||||||
SK = os.getenv('DYNAMODB_SORT_KEY')
|
SK = 'sk'
|
||||||
|
|
||||||
|
|
||||||
patch = pytest.MonkeyPatch()
|
# https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.hookspec.pytest_configure
|
||||||
patch.setenv('USER_TABLE', PYTEST_TABLE_NAME)
|
def pytest_configure():
|
||||||
patch.setenv('COURSE_TABLE', PYTEST_TABLE_NAME)
|
os.environ['TZ'] = 'America/Sao_Paulo'
|
||||||
patch.setenv('ENROLLMENT_TABLE', PYTEST_TABLE_NAME)
|
os.environ['KONVIVA_API_URL'] = 'https://saladeaula.digital'
|
||||||
|
os.environ['DYNAMODB_PARTITION_KEY'] = PK
|
||||||
|
os.environ['DYNAMODB_SORT_KEY'] = SK
|
||||||
|
os.environ['USER_TABLE'] = PYTEST_TABLE_NAME
|
||||||
|
os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME
|
||||||
|
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -132,7 +136,9 @@ def dynamodb_client():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def dynamodb_persistence_layer(dynamodb_client) -> DynamoDBPersistenceLayer:
|
def dynamodb_persistence_layer(dynamodb_client):
|
||||||
|
from layercake.dynamodb import DynamoDBPersistenceLayer
|
||||||
|
|
||||||
return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client)
|
return DynamoDBPersistenceLayer(PYTEST_TABLE_NAME, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
http-api/uv.lock
generated
2
http-api/uv.lock
generated
@@ -522,7 +522,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.2.16"
|
version = "0.3.0"
|
||||||
source = { directory = "../layercake" }
|
source = { directory = "../layercake" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "arnparse" },
|
{ name = "arnparse" },
|
||||||
|
|||||||
@@ -146,11 +146,15 @@ if TYPE_CHECKING:
|
|||||||
Optional specification for nested data extraction.
|
Optional specification for nested data extraction.
|
||||||
remove_prefix: str, optional
|
remove_prefix: str, optional
|
||||||
Optional prefix to remove from the key when forming the result dict.
|
Optional prefix to remove from the key when forming the result dict.
|
||||||
|
retain_key: bool, optional
|
||||||
|
Use the key itself as value if True; otherwise, use the extracted value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sk: str
|
sk: str
|
||||||
path_spec: str | None = None
|
path_spec: str | None = None
|
||||||
remove_prefix: str | None = None
|
remove_prefix: str | None = None
|
||||||
|
retain_key: bool = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class SortKey(str):
|
class SortKey(str):
|
||||||
@@ -166,6 +170,8 @@ else:
|
|||||||
Optional specification for nested data extraction.
|
Optional specification for nested data extraction.
|
||||||
remove_prefix: str, optional
|
remove_prefix: str, optional
|
||||||
Optional prefix to remove from the key when forming the result dict.
|
Optional prefix to remove from the key when forming the result dict.
|
||||||
|
retain_key: bool, optional
|
||||||
|
Use the key itself as value if True; otherwise, use the extracted value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(
|
def __new__(
|
||||||
@@ -174,6 +180,7 @@ else:
|
|||||||
*,
|
*,
|
||||||
path_spec: str | None = None,
|
path_spec: str | None = None,
|
||||||
remove_prefix: str | None = None,
|
remove_prefix: str | None = None,
|
||||||
|
retain_key: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
return super().__new__(cls, sk)
|
return super().__new__(cls, sk)
|
||||||
|
|
||||||
@@ -183,12 +190,14 @@ else:
|
|||||||
*,
|
*,
|
||||||
path_spec: str | None = None,
|
path_spec: str | None = None,
|
||||||
remove_prefix: str | None = None,
|
remove_prefix: str | None = None,
|
||||||
|
retain_key: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
# __init__ is used to store the parameters for later reference.
|
# __init__ is used to store the parameters for later reference.
|
||||||
# For immutable types like str, __init__ cannot change the instance's value.
|
# For immutable types like str, __init__ cannot change the instance's value.
|
||||||
self.sk = sk
|
self.sk = sk
|
||||||
self.path_spec = path_spec
|
self.path_spec = path_spec
|
||||||
self.remove_prefix = remove_prefix
|
self.remove_prefix = remove_prefix
|
||||||
|
self.retain_key = retain_key
|
||||||
|
|
||||||
|
|
||||||
class Key(ABC, dict):
|
class Key(ABC, dict):
|
||||||
@@ -929,29 +938,32 @@ class DynamoDBCollection:
|
|||||||
else:
|
else:
|
||||||
head, tail = {}, items
|
head, tail = {}, items
|
||||||
|
|
||||||
def _getin(pair: KeyPair, v: dict) -> dict:
|
def _getin(pair: KeyPair, obj: dict) -> dict:
|
||||||
v = omit((PK, SK), v)
|
obj = omit((PK, SK), obj)
|
||||||
sk = pair[SK]
|
sk = pair[SK]
|
||||||
path_spec = getattr(sk, 'path_spec', None)
|
path_spec = getattr(sk, 'path_spec', None)
|
||||||
|
|
||||||
if path_spec:
|
if path_spec:
|
||||||
from glom import glom
|
from glom import glom
|
||||||
|
|
||||||
return glom(v, path_spec)
|
return glom(obj, path_spec)
|
||||||
return v
|
return obj
|
||||||
|
|
||||||
def _removeprefix(pair: KeyPair) -> str:
|
def _removeprefix(pair: KeyPair) -> str:
|
||||||
|
pk = pair[PK]
|
||||||
sk = pair[SK]
|
sk = pair[SK]
|
||||||
|
|
||||||
if not isinstance(sk, SortKey):
|
if not isinstance(sk, SortKey):
|
||||||
return pair[PK]
|
return pk
|
||||||
|
|
||||||
return sk.removeprefix(sk.remove_prefix or '')
|
key = pk if sk.retain_key else sk
|
||||||
|
|
||||||
|
return key.removeprefix(sk.remove_prefix or '')
|
||||||
|
|
||||||
return head | {
|
return head | {
|
||||||
_removeprefix(pair): _getin(pair, item)
|
_removeprefix(pair): _getin(pair, obj)
|
||||||
for pair, item in zip(sortkeys, tail)
|
for pair, obj in zip(sortkeys, tail)
|
||||||
if item
|
if obj
|
||||||
}
|
}
|
||||||
|
|
||||||
def query(
|
def query(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "layercake"
|
name = "layercake"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
|
description = "Packages shared dependencies to optimize deployment and ensure consistency across functions."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from decimal import Decimal
|
|||||||
from ipaddress import IPv4Address
|
from ipaddress import IPv4Address
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from layercake.dateutils import ttl
|
from layercake.dateutils import ttl
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
ComposeKey,
|
ComposeKey,
|
||||||
@@ -371,3 +370,23 @@ def test_collection_get_items_pair_unflatten(
|
|||||||
'cpf': {'user_id': '5OxmMjL-ujoR5IMGegQz'},
|
'cpf': {'user_id': '5OxmMjL-ujoR5IMGegQz'},
|
||||||
'email': {'user_id': '5OxmMjL-ujoR5IMGegQz'},
|
'email': {'user_id': '5OxmMjL-ujoR5IMGegQz'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_collection_get_items_pair_path_spec(
|
||||||
|
dynamodb_seeds,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
):
|
||||||
|
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
||||||
|
doc = collect.get_items(
|
||||||
|
KeyPair('cpf', SortKey('07879819908', path_spec='user_id', retain_key=True))
|
||||||
|
+ KeyPair(
|
||||||
|
'email',
|
||||||
|
SortKey('osergiosiqueira@gmail.com', path_spec='user_id', retain_key=True),
|
||||||
|
),
|
||||||
|
flatten_top=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert doc == {
|
||||||
|
'cpf': '5OxmMjL-ujoR5IMGegQz',
|
||||||
|
'email': '5OxmMjL-ujoR5IMGegQz',
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user