This commit is contained in:
2025-08-15 00:06:05 -03:00
parent 6e0e5f788d
commit a53f37393a
27 changed files with 617 additions and 338 deletions

View File

@@ -60,12 +60,14 @@ app.include_router(enrollments.cancel, prefix='/enrollments')
app.include_router(enrollments.deduplication_window, prefix='/enrollments') app.include_router(enrollments.deduplication_window, prefix='/enrollments')
app.include_router(orders.router, prefix='/orders') app.include_router(orders.router, prefix='/orders')
app.include_router(users.router, prefix='/users') app.include_router(users.router, prefix='/users')
app.include_router(users.add, prefix='/users')
app.include_router(users.logs, prefix='/users') 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(billing.router, prefix='/billing') app.include_router(billing.router, prefix='/billing')
app.include_router(orgs.policies, prefix='/orgs') app.include_router(orgs.policies, prefix='/orgs')
app.include_router(orgs.address, prefix='/orgs') app.include_router(orgs.address, prefix='/orgs')
app.include_router(orgs.admins, prefix='/orgs')
app.include_router(orgs.custom_pricing, prefix='/orgs') app.include_router(orgs.custom_pricing, 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')

View File

@@ -1,5 +1,6 @@
from .address import router as address from .address import router as address
from .admins import router as admins
from .custom_pricing import router as custom_pricing from .custom_pricing import router as custom_pricing
from .policies import router as policies from .policies import router as policies
__all__ = ['policies', 'address', 'custom_pricing'] __all__ = ['policies', 'address', 'custom_pricing', 'admins']

View File

@@ -0,0 +1,78 @@
from http import HTTPStatus
from typing import Annotated
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.openapi.params import Body
from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from layercake.extra_types import NameStr
from pydantic import BaseModel, EmailStr
from api_gateway import JSONResponse
from boto3clients import dynamodb_client
from config import USER_TABLE
router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
@router.get('/<id>/admins', compress=True)
def admins(id: str):
return user_layer.collection.query(
KeyPair(id, 'admins'),
limit=100,
)
class Admin(BaseModel):
id: str
name: NameStr
email: EmailStr
@router.post('/<id>/admins', compress=True)
def grant(id: str, admin: Admin):
now_ = now()
with user_layer.transact_writer() as transact:
transact.condition(
key=KeyPair(f'orgmembers#{id}', admin.id),
cond_expr='attribute_exists(sk)',
)
# Grant admin privileges
transact.put(
item={
'id': admin.id,
'sk': f'acls#{id}',
'roles': ['ADMIN'],
'created_ad': now_,
},
cond_expr='attribute_not_exists(sk)',
)
# Add user to admin list
transact.put(
item={
'id': id,
'sk': f'admins#{admin.id}',
'name': admin.name,
'email': admin.email,
'created_ad': now_,
},
cond_expr='attribute_not_exists(sk)',
)
return JSONResponse(status_code=HTTPStatus.CREATED)
@router.delete('/<id>/admins', compress=True)
def revoke(
id: str,
user_id: Annotated[str, Body(embed=True)],
):
with user_layer.transact_writer() as transact:
# Revoke admin privileges
transact.delete(key=KeyPair(user_id, f'acls#{id}'))
# Remove user from admin list
transact.delete(key=KeyPair(id, f'admins#{user_id}'))
return JSONResponse(status_code=HTTPStatus.OK)

View File

@@ -24,13 +24,11 @@ course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
@router.get('/<id>/custompricing', compress=True) @router.get('/<id>/custompricing', compress=True)
def get_custom_pricing(id: str): def get_custom_pricing(id: str):
result = course_layer.collection.query( return course_layer.collection.query(
PartitionKey(f'CUSTOM_PRICING#ORG#{id}'), PartitionKey(f'CUSTOM_PRICING#ORG#{id}'),
limit=100, limit=100,
) )
return result
class CustomPricing(BaseModel): class CustomPricing(BaseModel):
course_id: UUID4 course_id: UUID4
@@ -38,7 +36,7 @@ class CustomPricing(BaseModel):
@router.post('/<id>/custompricing', compress=True) @router.post('/<id>/custompricing', compress=True)
def post_custom_pricing(id: str, custom_princing: CustomPricing): def add_custom_pricing(id: str, custom_princing: CustomPricing):
now_ = now() now_ = now()
with course_layer.transact_writer() as transact: with course_layer.transact_writer() as transact:
@@ -61,18 +59,18 @@ def post_custom_pricing(id: str, custom_princing: CustomPricing):
return JSONResponse(status_code=HTTPStatus.CREATED) return JSONResponse(status_code=HTTPStatus.CREATED)
class DeleteCustomPricing(BaseModel): class RemoveCustomPricing(BaseModel):
course_id: UUID4 course_id: UUID4
@router.delete('/<id>/custompricing', compress=True) @router.delete('/<id>/custompricing', compress=True)
def delete_custom_pricing(id: str, custom_princing: DeleteCustomPricing): def remove_custom_pricing(id: str, custom_princing: RemoveCustomPricing):
pair = KeyPair( key = KeyPair(
f'CUSTOM_PRICING#ORG#{id}', f'CUSTOM_PRICING#ORG#{id}',
f'COURSE#{custom_princing.course_id}', f'COURSE#{custom_princing.course_id}',
) )
if course_layer.delete_item(pair): if course_layer.delete_item(key):
return JSONResponse(status_code=HTTPStatus.OK) return JSONResponse(status_code=HTTPStatus.OK)

View File

@@ -23,14 +23,14 @@ from api_gateway import JSONResponse
from boto3clients import dynamodb_client, idp_client from boto3clients import dynamodb_client, idp_client
from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST, USER_POOOL_ID, USER_TABLE from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST, USER_POOOL_ID, USER_TABLE
from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware
from models import User
from rules.user import update_user from rules.user import update_user
from .add import router as add
from .emails import router as emails from .emails import router as emails
from .logs import router as logs from .logs import router as logs
from .orgs import router as orgs from .orgs import router as orgs
__all__ = ['logs', 'emails', 'orgs'] __all__ = ['add', 'logs', 'emails', 'orgs']
class BadRequestError(MissingError, PowertoolsBadRequestError): class BadRequestError(MissingError, PowertoolsBadRequestError):
@@ -82,17 +82,6 @@ def get_users():
) )
@router.post(
'/',
compress=True,
tags=['User'],
summary='Create user',
middlewares=[AuditLogMiddleware('USER_ADD', user_collect)],
)
def post_user(payload: User):
return JSONResponse(status_code=HTTPStatus.CREATED)
class UserData(BaseModel): class UserData(BaseModel):
name: NameStr name: NameStr
cpf: CpfStr cpf: CpfStr
@@ -129,7 +118,7 @@ def put_user(id: str, payload: UserData):
@router.get('/<id>', compress=True, tags=['User'], summary='Get user') @router.get('/<id>', compress=True, tags=['User'], summary='Get user')
def get_user(id: str): def get_user(id: str):
return user_collect.get_items( return user_collect.get_items(
TransactKey(id) + SortKey('0') + SortKey('rate_limit#user_update') TransactKey(id) + SortKey('0') + SortKey('RATE_LIMIT#USER_UPDATE')
) )

View File

@@ -0,0 +1,163 @@
from http import HTTPStatus
from uuid import uuid4
from aws_lambda_powertools import Logger
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError,
NotFoundError,
)
from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, SortKey
from layercake.extra_types import CnpjStr, CpfStr, NameStr
from pydantic import (
UUID4,
BaseModel,
ConfigDict,
EmailStr,
Field,
)
from api_gateway import JSONResponse
from boto3clients import dynamodb_client
from config import USER_TABLE
router = Router()
logger = Logger(__name__)
layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
class Org(BaseModel):
id: str
name: str
cnpj: CnpjStr
class User(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
id: UUID4 = Field(default_factory=uuid4)
name: NameStr
email: EmailStr
email_verified: bool = False
cpf: CpfStr
org: Org = Field(..., exclude=True)
class UserNotFoundError(NotFoundError):
def __init__(self, *_):
super().__init__('User not found')
class CPFConflictError(BadRequestError):
def __init__(self, *_):
super().__init__('CPF already exists')
class EmailConflictError(BadRequestError):
def __init__(self, *_):
super().__init__('Email already exists')
@router.post('/', compress=True)
def add(user: User):
now_ = now()
user_id = user.id
org = user.org
try:
with layer.transact_writer() as transact:
transact.put(
item={
'id': 'cpf',
'sk': user.cpf,
'user_id': user.id,
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
exc_cls=CPFConflictError,
)
transact.put(
item={
'id': 'email',
'sk': user.email,
'user_id': user.id,
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
exc_cls=EmailConflictError,
)
transact.put(
item={
'sk': '0',
'tenant_id': {org.id},
# Post-migration: uncomment the following line
# 'createDate': now_,
'createDate': now_,
}
| user.model_dump()
)
transact.put(
item={
'id': user.id,
'sk': f'orgs#{user.org.id}',
'name': org.name,
'cnpj': org.cnpj,
'created_at': now_,
}
)
transact.put(
item={
'id': f'orgmembers#{org.id}',
'sk': user.id,
'created_at': now_,
}
)
except (CPFConflictError, EmailConflictError):
user_id = layer.collection.get_items(
KeyPair(
pk='cpf',
sk=SortKey(user.cpf, path_spec='user_id'),
rename_key='id',
)
+ KeyPair(
pk='email',
sk=SortKey(user.email, path_spec='user_id'),
rename_key='id',
),
flatten_top=False,
).get('id')
if not user_id:
raise UserNotFoundError()
with layer.transact_writer() as transact:
transact.update(
key=KeyPair(user_id, '0'),
update_expr='ADD tenant_id :org_id',
expr_attr_values={
':org_id': {org.id},
},
)
transact.put(
item={
'id': user_id,
'sk': f'orgs#{user.org.id}',
'name': org.name,
'cnpj': org.cnpj,
'created_at': now_,
}
)
transact.put(
item={
'id': f'orgmembers#{org.id}',
'sk': user_id,
'created_at': now_,
}
)
return JSONResponse(
status_code=HTTPStatus.CREATED,
body={'id': user_id},
)

View File

@@ -1,13 +1,11 @@
from http import HTTPStatus from http import HTTPStatus
from typing import Annotated
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 ( from aws_lambda_powertools.event_handler.openapi.params import Body
BadRequestError as PowertoolsBadRequestError,
)
from layercake.dynamodb import ( from layercake.dynamodb import (
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
KeyPair, KeyPair,
MissingError,
PrefixKey, PrefixKey,
) )
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
@@ -15,23 +13,13 @@ from pydantic import BaseModel, EmailStr
from api_gateway import JSONResponse 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 middlewares import AuditLogMiddleware from rules.user import add_email, remove_email, set_email_as_primary
from rules.user import add_email, del_email, set_email_as_primary
class BadRequestError(MissingError, PowertoolsBadRequestError): ...
router = Router() router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
@router.get( @router.get('/<id>/emails', compress=True)
'/<id>/emails',
compress=True,
tags=['User'],
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) start_key = router.current_event.get_query_string_value('start_key', None)
@@ -45,18 +33,12 @@ class Email(BaseModel):
email: EmailStr email: EmailStr
@router.post( @router.post('/<id>/emails', compress=True)
'/<id>/emails', def add_email_(id: str, email: Annotated[str, Body(embed=True)]):
compress=True, add_email(id, email, persistence_layer=user_layer)
tags=['User'],
summary='Add user email',
middlewares=[AuditLogMiddleware('EMAIL_ADD', user_layer.collection, ('email',))],
)
def post_email(id: str, payload: Email):
add_email(id, payload.email, persistence_layer=user_layer)
return JSONResponse( return JSONResponse(
body=payload, body={'email': email},
status_code=HTTPStatus.CREATED, status_code=HTTPStatus.CREATED,
) )
@@ -67,22 +49,7 @@ class EmailAsPrimary(BaseModel):
email_verified: bool = False email_verified: bool = False
@router.patch( @router.patch('/<id>/emails', compress=True)
'/<id>/emails',
compress=True,
tags=['User'],
summary='Add user email as primary',
middlewares=[
AuditLogMiddleware(
'EMAIL_CHANGE',
user_layer.collection,
(
'new_email',
'old_email',
),
)
],
)
def patch_email(id: str, payload: EmailAsPrimary): def patch_email(id: str, payload: EmailAsPrimary):
set_email_as_primary( set_email_as_primary(
id, id,
@@ -98,15 +65,9 @@ def patch_email(id: str, payload: EmailAsPrimary):
) )
@router.delete( @router.delete('/<id>/emails', compress=True)
'/<id>/emails',
compress=True,
tags=['User'],
summary='Delete user email',
middlewares=[AuditLogMiddleware('EMAIL_DEL', user_layer.collection, ('email',))],
)
def delete_email(id: str, payload: Email): def delete_email(id: str, payload: Email):
del_email( remove_email(
id, id,
payload.email, payload.email,
persistence_layer=user_layer, persistence_layer=user_layer,

View File

@@ -4,7 +4,6 @@ from aws_lambda_powertools.event_handler.exceptions import (
) )
from layercake.dynamodb import ( from layercake.dynamodb import (
ComposeKey, ComposeKey,
DynamoDBCollection,
DynamoDBPersistenceLayer, DynamoDBPersistenceLayer,
MissingError, MissingError,
PartitionKey, PartitionKey,
@@ -13,29 +12,19 @@ from layercake.dynamodb import (
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import USER_TABLE from config import USER_TABLE
from .orgs import router as orgs
__all__ = ['orgs']
class BadRequestError(MissingError, PowertoolsBadRequestError): ... 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, exc_cls=BadRequestError)
@router.get( @router.get('/<id>/logs', compress=True, tags=['User'])
'/<id>/logs',
compress=True,
tags=['User'],
summary='Get user logs',
)
def get_logs(id: str): def get_logs(id: str):
return user_collect.query( return user_layer.collection.query(
# Post-migration: uncomment to enable PartitionKey with a composite key (id with `logs` prefix). # Post-migration: uncomment the following line
# PartitionKey(ComposeKey(id, 'logs')), # PartitionKey(ComposeKey(id, 'LOGS')),
PartitionKey(ComposeKey(id, 'log', delimiter=':')), PartitionKey(ComposeKey(id, 'log', delimiter=':')),
start_key=router.current_event.get_query_string_value('start_key', None), start_key=router.current_event.get_query_string_value('start_key', None),
) )

View File

@@ -16,8 +16,7 @@ from pydantic import BaseModel
from api_gateway import JSONResponse 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 middlewares.audit_log_middleware import AuditLogMiddleware from rules.user import remove_org_member
from rules.user import del_org_member
class BadRequestError(MissingError, PowertoolsBadRequestError): ... class BadRequestError(MissingError, PowertoolsBadRequestError): ...
@@ -30,13 +29,13 @@ user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
@router.get( @router.get(
'/<id>/orgs', '/<id>/orgs',
compress=True, compress=True,
tags=['User'],
summary='Get user orgs',
) )
def get_orgs(id: str): def get_orgs(id: str):
start_key = router.current_event.get_query_string_value('start_key', None)
return user_layer.collection.query( return user_layer.collection.query(
KeyPair(id, PrefixKey('orgs')), KeyPair(id, PrefixKey('orgs')),
start_key=router.current_event.get_query_string_value('start_key', None), start_key=start_key,
) )
@@ -46,17 +45,7 @@ class Unassign(BaseModel):
cnpj: CnpjStr cnpj: CnpjStr
@router.delete( @router.delete('/<id>/orgs', compress=True)
'/<id>/orgs',
compress=True,
tags=['User'],
summary='Delete user org',
middlewares=[
AuditLogMiddleware(
'UNASSIGN_ORG', user_layer.collection, ('id', 'name', 'cnpj')
)
],
)
def delete_org(id: str, payload: Unassign): def delete_org(id: str, payload: Unassign):
del_org_member(id, org_id=payload.id, persistence_layer=user_layer) remove_org_member(id, org_id=payload.id, persistence_layer=user_layer)
return JSONResponse(status_code=HTTPStatus.OK, body=payload) return JSONResponse(status_code=HTTPStatus.OK, body=payload)

View File

View File

@@ -15,14 +15,29 @@ from layercake.dynamodb import (
User = TypedDict('User', {'id': str, 'name': str, 'cpf': str}) User = TypedDict('User', {'id': str, 'name': str, 'cpf': str})
class CPFConflictError(BadRequestError):
def __init__(self, *_):
super().__init__('CPF already exists')
class RateLimitError(BadRequestError):
def __init__(self, *_):
super().__init__('Update limit reached')
class EmailConflictError(BadRequestError):
def __init__(self, *_):
super().__init__('Email already exists')
def update_user( def update_user(
data: User, obj: User,
/, /,
*, *,
persistence_layer: DynamoDBPersistenceLayer, persistence_layer: DynamoDBPersistenceLayer,
) -> bool: ) -> bool:
now_ = now() now_ = now()
user = SimpleNamespace(**data) user = SimpleNamespace(**obj)
# Get the user's CPF, if it exists. # Get the user's CPF, if it exists.
old_cpf = persistence_layer.collection.get_item( old_cpf = persistence_layer.collection.get_item(
KeyPair( KeyPair(
@@ -46,15 +61,11 @@ def update_user(
cond_expr='attribute_exists(sk)', cond_expr='attribute_exists(sk)',
) )
class RateLimitError(BadRequestError):
def __init__(self, msg: str):
super().__init__('Update limit reached')
# Prevent the user from updating more than once every 24 hours # Prevent the user from updating more than once every 24 hours
transact.put( transact.put(
item={ item={
'id': user.id, 'id': user.id,
'sk': 'rate_limit#user_update', 'sk': 'RATE_LIMIT#USER_UPDATE',
'created_at': now_, 'created_at': now_,
'ttl': ttl(start_dt=now_ + timedelta(hours=24)), 'ttl': ttl(start_dt=now_ + timedelta(hours=24)),
}, },
@@ -62,10 +73,6 @@ 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={
@@ -114,10 +121,6 @@ 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')
# Prevent duplicate emails # Prevent duplicate emails
transact.put( transact.put(
item={ item={
@@ -133,7 +136,7 @@ def add_email(
return True return True
def del_email( def remove_email(
id: str, id: str,
email: str, email: str,
/, /,
@@ -207,7 +210,7 @@ def set_email_as_primary(
return True return True
def del_org_member( def remove_org_member(
id: str, id: str,
*, *,
org_id: str, org_id: str,

View File

@@ -1,9 +1,9 @@
from typing import Any, Generator from typing import Generator
import boto3 import boto3
import jsonlines import jsonlines
from aws_lambda_powertools.shared.json_encoder import Encoder from aws_lambda_powertools.shared.json_encoder import Encoder
from layercake.dynamodb import deserialize from layercake.dynamodb import deserialize, serialize
from meilisearch import Client as Meilisearch from meilisearch import Client as Meilisearch
from tqdm import tqdm from tqdm import tqdm
@@ -11,6 +11,14 @@ dynamodb_client = boto3.client('dynamodb', endpoint_url='http://127.0.0.1:8000')
meili_client = Meilisearch('http://127.0.0.1:7700') meili_client = Meilisearch('http://127.0.0.1:7700')
JSONL_FILES = (
# 'test-orders.jsonl',
'test-users.jsonl',
# 'test-enrollments.jsonl',
# 'test-courses.jsonl',
)
class JSONEncoder(Encoder): class JSONEncoder(Encoder):
def default(self, obj): def default(self, obj):
if isinstance(obj, set): if isinstance(obj, set):
@@ -18,19 +26,11 @@ class JSONEncoder(Encoder):
return super(__class__, self).default(obj) return super(__class__, self).default(obj)
jsonl_files = (
'test-orders.jsonl',
'test-users.jsonl',
'test-enrollments.jsonl',
'test-courses.jsonl',
)
def put_item(item: dict, table_name: str, /, dynamodb_client) -> bool: def put_item(item: dict, table_name: str, /, dynamodb_client) -> bool:
try: try:
dynamodb_client.put_item( dynamodb_client.put_item(
TableName=table_name, TableName=table_name,
Item=item, Item=serialize(item),
) )
except Exception: except Exception:
return False return False
@@ -45,7 +45,7 @@ def scan_table(table_name: str, /, dynamodb_client, **kwargs) -> Generator:
yield from () yield from ()
else: else:
for item in r['Items']: for item in r['Items']:
yield deserialize(item) yield item
if 'LastEvaluatedKey' in r: if 'LastEvaluatedKey' in r:
yield from scan_table( yield from scan_table(
@@ -55,22 +55,9 @@ def scan_table(table_name: str, /, dynamodb_client, **kwargs) -> Generator:
) )
def _serialize_to_basic_types(value: Any) -> Any:
if isinstance(value, dict):
return {k: _serialize_to_basic_types(v) for k, v in value.items()}
if isinstance(value, set):
return list(value)
if isinstance(value, list):
return [_serialize_to_basic_types(v) for v in value]
return value
if __name__ == '__main__': if __name__ == '__main__':
# Populate DynamoDB tables with data from JSONL files # Populate DynamoDB tables with data from JSONL files
for file in tqdm(jsonl_files, desc='Processing files'): for file in tqdm(JSONL_FILES, desc='Processing files'):
with open(f'seeds/{file}') as fp: with open(f'seeds/{file}') as fp:
table_name = file.removesuffix('.jsonl') table_name = file.removesuffix('.jsonl')
reader = jsonlines.Reader(fp).iter(skip_invalid=True) reader = jsonlines.Reader(fp).iter(skip_invalid=True)
@@ -79,7 +66,7 @@ if __name__ == '__main__':
put_item(line, table_name, dynamodb_client) # type: ignore put_item(line, table_name, dynamodb_client) # type: ignore
# Scan DynamoDB tables and index the data into Meilisearch # Scan DynamoDB tables and index the data into Meilisearch
for file in tqdm(jsonl_files, desc='Scanning tables'): for file in tqdm(JSONL_FILES, desc='Scanning tables'):
table_name = file.removesuffix('.jsonl') table_name = file.removesuffix('.jsonl')
for doc in tqdm( for doc in tqdm(
@@ -91,6 +78,7 @@ if __name__ == '__main__':
), ),
desc=f'Indexing {table_name}', desc=f'Indexing {table_name}',
): ):
doc = deserialize(doc)
meili_client.index(table_name).add_documents([doc], serializer=JSONEncoder) meili_client.index(table_name).add_documents([doc], serializer=JSONEncoder)
meili_client.index('pytest').add_documents([doc], serializer=JSONEncoder) meili_client.index('pytest').add_documents([doc], serializer=JSONEncoder)
@@ -98,6 +86,6 @@ if __name__ == '__main__':
index.update_settings( index.update_settings(
{ {
'sortableAttributes': ['create_date', 'createDate', 'created_at'], 'sortableAttributes': ['create_date', 'createDate', 'created_at'],
'filterableAttributes': ['tenant_id', 'status'], 'filterableAttributes': ['tenant_id', 'status', 'cnpj'],
} }
) )

View File

@@ -1,76 +1,28 @@
{"id": {"S": "apikey"}, "sk": {"S": "MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw"}, "tenant": {"M": {"id": {"S": "*"}, "name": {"S": "default"}}}, "user": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "Sérgio R Siqueira"}, "email": {"S": "sergio@somosbeta.com.br"}}}} // Post-migration: rename `create_date` to `created_at`
{"updateDate": {"S": "2024-02-08T16:42:33.776409-03:00"}, "createDate": {"S": "2019-03-25T00:00:00-03:00"}, "email_verified": {"BOOL": true}, "cognito__sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"}, "cpf": {"S": "07879819908"}, "sk": {"S": "0"}, "email": {"S": "sergio@somosbeta.com.br"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}, "lastLogin": {"S": "2024-02-08T20:53:45.818126-03:00"}, "tenant_id": {"L": [{"S": "cJtK9SsnJhKPyxESe7g3DG"}, {"S": "edp8njvgQuzNkLx2ySNfAD"}, {"S": "8TVSi5oACLxTiT8ycKPmaQ"}]}}
{"sk": {"S": "acl#admin"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}} // Users
{"email_verified": {"BOOL": true}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2024-01-19T22:53:43.135080-03:00"}, "deliverability": {"S": "DELIVERABLE"}, "sk": {"S": "emails#osergiosiqueira@gmail.com"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "primaryEmail": {"BOOL": false}, "mx_record_exists": {"BOOL": true}} {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "0", "name": "Sérgio R Siqueira", "cpf": "07879819908", "email": "sergio@somosbeta.com.br", "email_verified": true, "emails": ["osergiosiqueira@gmail.com"], "lastLogin": "2025-08-14T14:36:38.758274-03:00", "tenant_id": ["cJtK9SsnJhKPyxESe7g3DG"], "cognito__sub": "58efed8d-d276-41a8-8502-4ab8b5a6415e"}
{"email_verified": {"BOOL": true}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "sk": {"S": "emails#sergio@somosbeta.com.br"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "email_primary": {"BOOL": true}, "mx_record_exists": {"BOOL": true}, "update_date": {"S": "2023-11-09T12:13:04.308986-03:00"}} {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "emails#sergio@somosbeta.com.br", "create_date": "2019-03-25T00:00:00-03:00", "email_primary": true, "email_verified": true, "mx_record_exists": true, "update_date": "2025-04-14T13:29:02.380381-03:00"}
{"email_verified": {"BOOL": false}, "update_date": {"S": "2023-12-29T02:18:27.225158-03:00"}, "create_date": {"S": "2022-09-01T12:23:15.431473-03:00"}, "sk": {"S": "emails#sergio@users.noreply.betaeducacao.com.br"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "email_primary": {"BOOL": false}} {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "emails#osergiosiqueira@gmail.com", "create_date": "2019-03-25T00:00:00-03:00", "email_primary": false, "email_verified": true, "mx_record_exists": true, "update_date": "2025-04-14T13:29:02.380381-03:00"}
{"sk": {"S": "konviva"}, "createDate": {"S": "2023-07-22T21:46:02.527763-03:00"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "konvivaId": {"N": "26943"}} {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "orgs#cJtK9SsnJhKPyxESe7g3DG", "name": "EDUSEG", "cnpj": "15608435000190"}
{"sk": {"S": "org#cJtK9SsnJhKPyxESe7g3DG"}, "createDate": {"S": "2023-12-24T20:50:27.656310-03:00"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "Beta Educa\u00e7\u00e3o"}} {"id": "5OxmMjL-ujoR5IMGegQz", "sk": "acls#*", "roles": ["ADMIN"]}
{"sk": {"S": "sergio@somosbeta.com.br"}, "createDate": {"S": "2019-03-25T00:00:00-03:00"}, "userRefId": {"S": "5OxmMjL-ujoR5IMGegQz"}, "id": {"S": "email"}}
{"sk": {"S": "07879819908"}, "createDate": {"S": "2024-02-06T15:16:18.992509-03:00"}, "userRefId": {"S": "5OxmMjL-ujoR5IMGegQz"}, "id": {"S": "cpf"}} // CPFs
{"updateDate": {"S": "2023-12-22T13:03:20.478342-03:00"}, "createDate": {"S": "2023-09-22T18:27:30.193484-03:00"}, "status": {"S": "CREATED"}, "sk": {"S": "0"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "name": {"S": "Beta Educa\u00e7\u00e3o"}} {"id": "cpf", "sk": "07879819908", "user_id": "5OxmMjL-ujoR5IMGegQz"}
{"updateDate": {"S": "2023-12-22T13:03:20.478342-03:00"}, "createDate": {"S": "2023-09-22T18:27:30.193484-03:00"}, "status": {"S": "CREATED"}, "sk": {"S": "0"}, "cnpj": {"S": "13573332000107"}, "email": {"S": "org+13573332000107@users.noreply.betaeducacao.com.br"}, "id": {"S": "edp8njvgQuzNkLx2ySNfAD"}, "name": {"S": "KORD S.A"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#MqiRQWy9cSBEci93aKBvTd"}, "tag": {"S": "Test"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}} // Emails
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#NQ3YmmTesfoSyHCkPKGKEF"}, "tag": {"S": "F\u00e1brica"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}} {"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "5OxmMjL-ujoR5IMGegQz"}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#bg45io8igarjsA4BzPQyrz"}, "tag": {"S": "Escrit\u00f3rio"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}} {"id": "email", "sk": "osergiosiqueira@gmail.com", "user_id": "5OxmMjL-ujoR5IMGegQz"}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "payment_policy"}, "due_days": {"N": "90"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "billing_policy"}, "billing_day": {"N": "2"}, "payment_method": {"S": "PIX"}} // Orgs
{"sk": {"S": "admin#18606"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "mateushickmann@petrobras.com.br"}, "name": {"S": "MATEUS LATTIK HICKMANN"}} {"id": "cJtK9SsnJhKPyxESe7g3DG", "sk": "0", "name": "EDUSEG", "cnpj": "15608435000190", "email": "org+15608435000190@users.noreply.betaeducacao.com.br"}
{"sk": {"S": "admin#21425"}, "create_date": {"S": "2023-09-22T18:34:06.480230-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "sergio@inbep.com.br"}, "name": {"S": "S\u00e9rgio Siqueira"}} {"id": "edp8njvgQuzNkLx2ySNfAD", "sk": "0", "name": "KORD S.A", "email": "org+13573332000107@users.noreply.betaeducacao.com.br", "cnpj": "13573332000107"}
{"sk": {"S": "admin#2338"}, "create_date": {"S": "2023-09-22T18:28:10.121068-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "empresa@inbep.com.br"}, "name": {"S": "Empresa Teste"}}
{"sk": {"S": "admin#26931"}, "create_date": {"S": "2023-09-22T18:39:04.522084-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "cida@qgog.com.br"}, "name": {"S": "Maria Aparecida do Nascimento"}} // Org admins
{"sk": {"S": "admin#346628ce-a6c8-4fff-a6ee-5378675e220a"}, "create_date": {"S": "2023-10-01T14:25:16.662110-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "leandrodorea@kofre.com.br"}, "name": {"S": "Leandro Barbosa Dorea"}} {"id": "cJtK9SsnJhKPyxESe7g3DG", "sk": "admins#5OxmMjL-ujoR5IMGegQz", "name": "Sérgio Rafael Siqueira", "email": "sergio@somosbeta.com.br", "create_date": "2025-03-14T10:06:34.628078-03:00"}
{"sk": {"S": "admin#5OxmMjL-ujoR5IMGegQz"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio R Siqueira"}}
{"sk": {"S": "admin#9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "create_date": {"S": "2023-09-22T18:31:28.540271-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "tiago@somosbeta.com.br"}, "name": {"S": "Tiago"}} // Org members
{"sk": {"S": "admin#DbMaGtQX4wXPDZyBjdZMzh"}, "create_date": {"S": "2023-09-22T18:34:46.696606-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "sergio+postman@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael Siqueira"}} {"id": "orgmembers#cJtK9SsnJhKPyxESe7g3DG", "sk": "5OxmMjL-ujoR5IMGegQz"}
{"sk": {"S": "admin#JMnHo46tR4aWVzH2dfus52"}, "create_date": {"S": "2023-09-22T18:39:58.373125-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "bruno@inbep.com.br"}, "name": {"S": "Bruno Lins de Albuquerque"}}
{"sk": {"S": "admin#TSxk7P89kRcNuLGy8A88ao"}, "create_date": {"S": "2023-09-22T18:28:02.875368-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "tiago@inbep.com.br"}, "name": {"S": "Tiago Maciel"}} // CNPJs
{"sk": {"S": "admin#YJkbDL7C2htnJVK8DXDiV2"}, "create_date": {"S": "2023-09-22T18:30:18.492717-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "acorianabuffet@gmail.com"}, "name": {"S": "Nadyjanara Leal Albino"}} {"id": "cnpj", "sk": "15608435000190", "user_id": "cJtK9SsnJhKPyxESe7g3DG"}
{"sk": {"S": "admin#hT2CD9lDeeqlOXLjR4mG"}, "create_date": {"S": "2023-09-22T18:27:30.344151-03:00"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "email": {"S": "alessandraw@unc.br"}, "name": {"S": "Alessandra Wagner Jusviacky"}}
{"emailVerified": {"BOOL": true}, "createDate": {"S": "2023-09-22T18:27:30.193484-03:00"}, "sk": {"S": "emails#org+15608435000190@users.noreply.betaeducacao.com.br"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "primaryEmail": {"BOOL": true}, "emailDeliverable": {"BOOL": true}}
{"expiry_date": {"S": "2024-07-08T17:38:29.159112-03:00"}, "user": {"M": {"name": {"S": "S\u00e9rgio R Siqueira"}, "email": {"S": "sergio@somosbeta.com.br"}}}, "ttl": {"N": "1720471109"}, "sk": {"S": "schedules#follow_up#5OxmMjL-ujoR5IMGegQz"}, "id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "create_date": {"S": "2024-01-10T17:38:29.159148-03:00"}}
{"updateDate": {"S": "2022-08-09T09:44:39.386636-03:00"}, "createDate": {"S": "2022-06-13T10:20:00.528685-03:00"}, "status": {"S": "CONFIRMED"}, "cpf": {"S": "08679004901"}, "sk": {"S": "0"}, "email": {"S": "tiago@somosbeta.com.br"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "mobileNumber": {"S": ""}, "name": {"S": "Tiago Maciel"}, "lastLogin": {"S": "2024-02-07T11:14:34.516596-03:00"}, "tenant_id": {"L": [{"S": "cJtK9SsnJhKPyxESe7g3DG"}]}}
{"emailVerified": {"BOOL": true}, "updateDate": {"S": "2022-08-09T09:44:39.400384-03:00"}, "createDate": {"S": "2022-06-13T10:20:00.528685-03:00"}, "sk": {"S": "emails#tiago@somosbeta.com.br"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "primaryEmail": {"BOOL": true}, "emailDeliverable": {"BOOL": true}}
{"emailVerified": {"BOOL": true}, "updateDate": {"S": "2022-08-09T09:44:39.400384-03:00"}, "createDate": {"S": "2022-06-13T10:20:00.528685-03:00"}, "sk": {"S": "emails#tiago+1@somosbeta.com.br"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "primaryEmail": {"BOOL": false}, "emailDeliverable": {"BOOL": true}}
{"emailVerified": {"BOOL": true}, "updateDate": {"S": "2022-08-09T09:44:39.400384-03:00"}, "createDate": {"S": "2022-06-13T10:20:00.528685-03:00"}, "sk": {"S": "emails#tiago+2@somosbeta.com.br"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "primaryEmail": {"BOOL": false}, "emailDeliverable": {"BOOL": true}}
{"sk": {"S": "konviva"}, "createDate": {"S": "2023-07-22T21:46:04.494710-03:00"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "konvivaId": {"N": "26946"}}
{"sk": {"S": "org#VmkwfVGq5r7vEckEM8uiRf"}, "createDate": {"S": "2023-09-22T18:31:14.779525-03:00"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "name": {"S": "MCEND INSPE\u00c7\u00d5ES, CONSULTORIA E CONTROLE DE QUALIDADE"}}
{"sk": {"S": "org#cJtK9SsnJhKPyxESe7g3DG"}, "createDate": {"S": "2023-09-22T18:31:28.540230-03:00"}, "id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "name": {"S": "Funda\u00e7\u00e3o Universidade do Contestado - FUnC Campus Mafra"}}
{"action": {"S": "EMAIL_ADD"}, "data": {"M": {"email": {"S": "re+962834@users.noreply.betaeducacao.com.br"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770420417"}, "sk": {"S": "2024-02-07T20:26:57.434320-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"action": {"S": "EMAIL_ADD"}, "data": {"M": {"email": {"S": "test@xptoa.com"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770420434"}, "sk": {"S": "2024-02-07T20:27:14.874383-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"action": {"S": "EMAIL_DEL"}, "data": {"M": {"email": {"S": "test@xptoa.com"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770420444"}, "sk": {"S": "2024-02-07T20:27:24.615391-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"action": {"S": "user.enable_reverse_email"}, "data": {"M": {"email": {"S": "re+9628@users.noreply.betaeducacao.com.br"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "3477771072"}, "sk": {"S": "2024-02-07T20:45:36.807607-03:00"}, "actorLocation": {"NULL": true}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"action": {"S": "EMAIL_ADD"}, "data": {"M": {"email": {"S": "sergio+12121@somosbeta.com.br"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770492028"}, "sk": {"S": "2024-02-08T16:20:28.159065-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"action": {"S": "PRIMARY_EMAIL_CHANGED"}, "data": {"M": {"old_email": {"S": "sergio@somosbeta.com.br"}, "new_email": {"S": "osergiosiqueira@gmail.com"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770493307"}, "sk": {"S": "2024-02-08T16:41:47.678483-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770493330"}, "sk": {"S": "2024-02-08T16:42:10.010857-03:00"}, "action": {"S": "LOGIN"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}}
{"action": {"S": "PRIMARY_EMAIL_CHANGED"}, "data": {"M": {"old_email": {"S": "osergiosiqueira@gmail.com"}, "new_email": {"S": "sergio@somosbeta.com.br"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770493353"}, "sk": {"S": "2024-02-08T16:42:33.812060-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770493367"}, "sk": {"S": "2024-02-08T16:42:47.095878-03:00"}, "action": {"S": "LOGIN"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}}
{"action": {"S": "EMAIL_DEL"}, "data": {"M": {"email": {"S": "sergio+12121@somosbeta.com.br"}}}, "ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770493382"}, "sk": {"S": "2024-02-08T16:43:02.024191-03:00"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "actor": {"M": {"name": {"S": "S\u00e9rgio Rafael Siqueira"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}}}}
{"ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770507839"}, "sk": {"S": "2024-02-08T20:43:59.040766-03:00"}, "action": {"S": "LOGIN"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}}
{"ip": {"S": "189.73.20.218"}, "ttl": {"N": "1770508260"}, "sk": {"S": "2024-02-08T20:51:00.933637-03:00"}, "action": {"S": "LOGIN"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}}
{"ttl": {"N": "1770508425"}, "sk": {"S": "2024-02-08T20:53:45.329416-03:00"}, "action": {"S": "LOGIN"}, "id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}}
{"id": {"S": "DbMaGtQX4wXPDZyBjdZMzh"}, "sk": {"S": "0"}, "createDate": {"S": "2023-09-22T18:34:46.517274-03:00"}, "email": {"S": "sergio@users.noreply.betaeducacao.com.br"}, "name": {"S": "S\u00e9rgio Rafael Siqueira"}, "status": {"S": "CREATED"}, "updateDate": {"S": "2024-02-26T14:58:50.688790-03:00"}}
{"id": {"S": "DbMaGtQX4wXPDZyBjdZMzh"}, "sk": {"S": "emails#sergio@users.noreply.betaeducacao.com.br"}, "createDate": {"S": "2024-02-26T14:45:04.980636-03:00"}, "deliverability": {"S": "DELIVERABLE"}, "emailVerified": {"BOOL": false}, "primaryEmail": {"BOOL": true}, "updateDate": {"S": "2024-02-26T14:51:30.188894-03:00"}}
{"id": {"S": "email"}, "sk": {"S": "sergio@users.noreply.betaeducacao.com.br"}, "createDate": {"S": "2019-11-12T00:00:00-03:00"}, "userRefId": {"S": "DbMaGtQX4wXPDZyBjdZMzh"}}
{"id": {"S": "5ad1d654-efe5-4bcf-8016-332677c4ba61"}, "sk": {"S": "0"}, "createDate": {"S": "2023-09-22T18:34:46.517274-03:00"}, "email": {"S": "vera@somosbeta.com.br"}, "name": {"S": "Vera L\u00facia Machado"}, "status": {"S": "CREATED"}, "updateDate": {"S": "2024-02-26T14:58:50.688790-03:00"}}
{"id": {"S": "5ad1d654-efe5-4bcf-8016-332677c4ba61"}, "sk": {"S": "emails#vera@somosbeta.com.br"}, "createDate": {"S": "2024-02-26T14:45:04.980636-03:00"}, "deliverability": {"S": "DELIVERABLE"}, "emailVerified": {"BOOL": false}, "primaryEmail": {"BOOL": true}, "updateDate": {"S": "2024-02-26T14:51:30.188894-03:00"}}
{"id": {"S": "email"}, "sk": {"S": "vera@somosbeta.com.br"}, "createDate": {"S": "2019-11-12T00:00:00-03:00"}, "userRefId": {"S": "5ad1d654-efe5-4bcf-8016-332677c4ba61"}}
{"updateDate": {"S": "2024-02-08T16:42:33.776409-03:00"}, "createDate": {"S": "2019-03-25T00:00:00-03:00"}, "status": {"S": "CONFIRMED"}, "sk": {"S": "0"}, "email": {"S": "maite@somosbeta.com.br"}, "id": {"S": "ZoV7w6mZsdAABjXzeAodSQ"}, "name": {"S": "Mait\u00ea Laurenti Siqueira"}, "lastLogin": {"S": "2024-02-08T20:53:45.818126-03:00"}, "tenant_id": {"L": [{"S": "cJtK9SsnJhKPyxESe7g3DG"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "name": {"S": "Beta Educa\u00e7\u00e3o"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#edp8njvgQuzNkLx2ySNfAD"}, "cnpj": {"S": "13573332000107"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "name": {"S": "KORD S.A"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#cJtK9SsnJhKPyxESe7g3DG"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#8TVSi5oACLxTiT8ycKPmaQ"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}, "roles": {"L": [{"S": "USER"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#*"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#8TVSi5oACLxTiT8ycKPmaQ"}, "cnpj": {"S": "07556927000666"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "name": {"S": "Ponsse Latin America Ind\u00fastria de M\u00e1quinas Florestais LTDA"}}
{"id": {"S": "9a41e867-55e1-4573-bd27-7b5d1d1bcfde"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "name": {"S": "Beta Educa\u00e7\u00e3o"}}
{"id": {"S": "ZoV7w6mZsdAABjXzeAodSQ"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "name": {"S": "Beta Educa\u00e7\u00e3o"}}
{"id": {"S": "ZoV7w6mZsdAABjXzeAodSQ"}, "sk": {"S": "orgs#edp8njvgQuzNkLx2ySNfAD"}, "cnpj": {"S": "13573332000107"}, "create_date": {"S": "2023-12-24T20:50:27.656310-03:00"}, "name": {"S": "KORD S.A"}}
{"id": {"S": "webhooks#*"}, "sk": {"S": "0e7f4d2e62ec525fc94465a6dd7299d2"}, "event_type": {"S": "insert"}, "resource": {"S": "users"}, "url": {"S": "https://n8n.sergio.run/webhook/56bb43b8-533c-4e8b-bdaa-3f7c2b0e548f"}, "author": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "S\u00e9rgio R Siqueira"}}}, "create_date": {"S": "2025-01-03T15:52:47.378885+00:00"}}
{"id": {"S": "webhook_requests#*#0e7f4d2e62ec525fc94465a6dd7299d2"}, "sk": {"S": "2025-01-03T15:47:42.039256-03:00"}, "request": {"M": {"id": {"S": "i8vZVsyir5LVVN9RDZ4eCN"}, "headers": {"M": {"Accept": {"S": "*/*"}, "Accept-Encoding": {"S": "gzip, deflate"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "108"}, "Content-Type": {"S": "application/json"}, "User-Agent": {"S": "eduseg/python-requests/2.32.3"}}}, "payload": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}}}, "response": {"M": {"body": {"S": "{\"message\":\"Workflow was started\"}"}, "elapsed_time": {"S": "0.14"}, "headers": {"M": {"alt-svc": {"S": "h3=\":443\"; ma=86400"}, "cf-cache-status": {"S": "DYNAMIC"}, "CF-RAY": {"S": "8fc528b57b62a3f0-GRU"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "34"}, "Content-Type": {"S": "application/json; charset=utf-8"}, "Date": {"S": "Fri, 03 Jan 2025 18:47:44 GMT"}, "etag": {"S": "W/\"22-6OS7cK0FzqnV2NeDHdOSGS1bVUs\""}, "NEL": {"S": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"}, "Report-To": {"S": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=FMkgKCTOnFvNgtE30yiYnM4XtK8q99O62s7Ep57KDNc3YGiy3W1j%2BzfS0vqJNylSAn5viU3MMGJvTIwPsYQ6jQ298t1p0hYd1KPwURhxchME4hc%2BsO0NNAv%2FFEpXv1ZYWw%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}"}, "Server": {"S": "cloudflare"}, "server-timing": {"S": "cfL4;desc=\"?proto=TCP&rtt=1016&min_rtt=998&rtt_var=411&sent=4&recv=7&lost=0&retrans=0&sent_bytes=2836&recv_bytes=999&delivery_rate=2522648&cwnd=251&unsent_bytes=0&cid=1b26053952909423&ts=93&x=0\""}, "Strict-Transport-Security": {"S": "max-age=0; includeSubDomains"}, "vary": {"S": "Accept-Encoding"}}}, "status_code": {"S": "200"}}}, "update_date": {"S": "2025-01-03T15:47:44.537597-03:00"}, "url": {"S": "https://n8n.sergio.run/webhook/56bb43b8-533c-4e8b-bdaa-3f7c2b0e548f"}}
{"id": {"S": "webhook_requests#*#0e7f4d2e62ec525fc94465a6dd7299d2"}, "sk": {"S": "2025-03-03T15:47:42.039256-03:00"}, "request": {"M": {"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "headers": {"M": {"Accept": {"S": "*/*"}, "Accept-Encoding": {"S": "gzip, deflate"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "108"}, "Content-Type": {"S": "application/json"}, "User-Agent": {"S": "eduseg/python-requests/2.32.3"}}}, "payload": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}}}, "response": {"M": {"body": {"S": "{\"message\":\"Workflow was started\"}"}, "elapsed_time": {"S": "0.14"}, "headers": {"M": {"alt-svc": {"S": "h3=\":443\"; ma=86400"}, "cf-cache-status": {"S": "DYNAMIC"}, "CF-RAY": {"S": "8fc528b57b62a3f0-GRU"}, "Connection": {"S": "keep-alive"}, "Content-Length": {"S": "34"}, "Content-Type": {"S": "application/json; charset=utf-8"}, "Date": {"S": "Fri, 03 Jan 2025 18:47:44 GMT"}, "etag": {"S": "W/\"22-6OS7cK0FzqnV2NeDHdOSGS1bVUs\""}, "NEL": {"S": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}"}, "Report-To": {"S": "{\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=FMkgKCTOnFvNgtE30yiYnM4XtK8q99O62s7Ep57KDNc3YGiy3W1j%2BzfS0vqJNylSAn5viU3MMGJvTIwPsYQ6jQ298t1p0hYd1KPwURhxchME4hc%2BsO0NNAv%2FFEpXv1ZYWw%3D%3D\"}],\"group\":\"cf-nel\",\"max_age\":604800}"}, "Server": {"S": "cloudflare"}, "server-timing": {"S": "cfL4;desc=\"?proto=TCP&rtt=1016&min_rtt=998&rtt_var=411&sent=4&recv=7&lost=0&retrans=0&sent_bytes=2836&recv_bytes=999&delivery_rate=2522648&cwnd=251&unsent_bytes=0&cid=1b26053952909423&ts=93&x=0\""}, "Strict-Transport-Security": {"S": "max-age=0; includeSubDomains"}, "vary": {"S": "Accept-Encoding"}}}, "status_code": {"S": "200"}}}, "update_date": {"S": "2025-01-03T15:47:44.537597-03:00"}, "url": {"S": "https://n8n.sergio.run/webhook/56bb43b8-533c-4e8b-bdaa-3f7c2b0e548f"}}
{"id": {"S": "webhooks#*"}, "sk": {"S": "1e3759c9c4cfa7aaf86ac281bdb8fd6f"}, "author": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}}}, "create_date": {"S": "2025-01-06T15:00:39.363207-03:00"}, "event_type": {"S": "insert"}, "resource": {"S": "users"}, "url": {"S": "https://hook.us2.make.com/hgkc5oj5dbpfld5qvu1xmsu22ond93l9"}}
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"},"sk": {"S": "2024-02-26T11:48:17.605911-03:00"},"action": {"S": "OPEN_EMAIL"},"data": {"M": {"ip_address": {"S": "66.249.83.73"},"message_id": {"S": "0103018de5de75cf-670bf01f-7ccf-4ce0-88b4-7c85cd0d06d0-000000"},"recipients": {"L": [{"S": "sergio@somosbeta.com.br"}]},"subject": {"S": "Re: (sem assunto)"},"timestamp": {"S": "2024-02-26T14:48:16.976Z"},"user_agent": {"S": "Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko Firefox/11.0 (via ggpht.com GoogleImageProxy)"}}},"ttl": {"N": "1772030897"}}
{"id": {"S": "7zf52CWrTS3csRBFWU5rkq"},"sk": {"S": "0"},"cognito:sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"},"cpf": {"S": "04330965275"},"createDate": {"S": "2025-04-08T10:24:46.493980-03:00"},"email": {"S": "barbara.gomes@sinobras.com.br"},"email_verified": {"BOOL": true},"konviva:id": {"N": "199205"},"lastLogin": {"S": "2025-04-10T12:01:48.380215-03:00"},"name": {"S": "Barbara Kamyla Vasconcelos Gomes"},"tenant_id": {"SS": ["EkvQwpmmL6vzWtJunM5dCJ"]},"update_date": {"S": "2025-04-10T08:50:22.530758-03:00"}}
{"id": {"S": "7zf52CWrTS3csRBFWU5rkq"},"sk": {"S": "acls#EkvQwpmmL6vzWtJunM5dCJ"},"create_date": {"S": "2025-04-10T09:36:41.133157-03:00"},"roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "7zf52CWrTS3csRBFWU5rkq"},"sk": {"S": "orgs#EkvQwpmmL6vzWtJunM5dCJ"},"cnpj": {"S": "07933914000154"},"create_date": {"S": "2025-04-08T10:24:46.493980-03:00"},"name": {"S": "SIDERURGICA NORTE BRASIL S.A"}}
{"id": {"S": "edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "metadata#billing_policy"},"billing_day": {"N": "1"},"created_at": {"S": "2025-07-23T13:56:42.794693-03:00"},"payment_method": {"S": "MANUAL"}}

View File

@@ -144,9 +144,14 @@ def dynamodb_persistence_layer(dynamodb_client):
@pytest.fixture() @pytest.fixture()
def dynamodb_seeds(dynamodb_client): def dynamodb_seeds(dynamodb_client):
with jsonlines.open('tests/seeds.jsonl') as lines: with open('tests/seeds.jsonl', 'rb') as fp:
for line in lines: reader = jsonlines.Reader(fp)
dynamodb_client.put_item(TableName=PYTEST_TABLE_NAME, Item=line)
for line in reader.iter(type=dict, skip_invalid=True):
dynamodb_client.put_item(
TableName=PYTEST_TABLE_NAME,
Item=line,
)
@pytest.fixture @pytest.fixture

View File

View File

@@ -0,0 +1,57 @@
from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from ...conftest import HttpApiProxy, LambdaContext
def test_post_admins(
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/admins',
method=HTTPMethod.POST,
body={
'id': '15ee05a3-4ceb-4b7e-9979-db75b28c9ade',
'name': 'Sérgio R Siqueira',
'email': 'sergio@somosbeta.com.br',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.CREATED
data = dynamodb_persistence_layer.collection.query(
KeyPair('cJtK9SsnJhKPyxESe7g3DG', 'admins'),
)
assert len(data['items']) == 2
def test_delete_admins(
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/admins',
method=HTTPMethod.DELETE,
body={
'user_id': 'e170d457-bd6b-475d-b6ae-4f3427a04873',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
data = dynamodb_persistence_layer.collection.query(
KeyPair('cJtK9SsnJhKPyxESe7g3DG', 'admins'),
)
assert len(data['items']) == 0

View File

@@ -191,91 +191,3 @@ def test_post_user(
) )
assert r['statusCode'] == HTTPStatus.CREATED assert r['statusCode'] == HTTPStatus.CREATED
def test_post_email(
mock_app,
dynamodb_client,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/users/5OxmMjL-ujoR5IMGegQz/emails',
method=HTTPMethod.POST,
body={
'email': 'sergio+pytest@somosbeta.com.br',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.CREATED
user = dynamodb_persistence_layer.collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['emails'] == {
'sergio@somosbeta.com.br',
'osergiosiqueira@gmail.com',
'sergio+pytest@somosbeta.com.br',
}
def test_patch_email(
mock_app,
dynamodb_client,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/users/5OxmMjL-ujoR5IMGegQz/emails',
method=HTTPMethod.PATCH,
body={
'old_email': 'sergio@somosbeta.com.br',
'new_email': 'osergiosiqueira@gmail.com',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
user = dynamodb_persistence_layer.collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['email'] == 'osergiosiqueira@gmail.com'
def test_delete_email(
mock_app,
dynamodb_client,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/users/5OxmMjL-ujoR5IMGegQz/emails',
method=HTTPMethod.DELETE,
body={
'email': 'osergiosiqueira@gmail.com',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
user = dynamodb_persistence_layer.collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['emails'] == {
'sergio@somosbeta.com.br',
}

View File

View File

@@ -0,0 +1,78 @@
import json
from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
from ...conftest import HttpApiProxy, LambdaContext
def test_add(
mock_app,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
org_id = 'cJtK9SsnJhKPyxESe7g3DG'
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/users',
method=HTTPMethod.POST,
body={
'name': 'Eddie Van Halen',
'email': 'eddie@vanhalen.com',
'cpf': '12974982085',
'org': {
'id': org_id,
'name': 'EDUSEG',
'cnpj': '15608435000190',
},
},
),
lambda_context,
)
user = json.loads(r['body'])
assert r['statusCode'] == HTTPStatus.CREATED
r = dynamodb_persistence_layer.collection.query(PartitionKey(user['id']))
assert len(r['items']) == 2
r = dynamodb_persistence_layer.collection.query(
PartitionKey(f'orgmembers#{org_id}')
)
# 2 items were added from the seed
assert len(r['items']) == 3
def test_add_existing_user(
mock_app,
dynamodb_seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
org_id = 'cJtK9SsnJhKPyxESe7g3DG'
r = mock_app.lambda_handler(
http_api_proxy(
raw_path='/users',
method=HTTPMethod.POST,
body={
'name': 'Sérgio Rafael Siqueira',
'email': 'sergio@somosbeta.com.br',
'cpf': '07879819908',
'org': {
'id': org_id,
'name': 'EDUSEG',
'cnpj': '15608435000190',
},
},
),
lambda_context,
)
r = dynamodb_persistence_layer.collection.query(
PartitionKey(f'orgmembers#{org_id}')
)
# 2 items were added from the seed
assert len(r['items']) == 3

View File

@@ -0,0 +1,93 @@
from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
KeyPair,
)
from ...conftest import HttpApiProxy, LambdaContext
def test_add_email(
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='/users/5OxmMjL-ujoR5IMGegQz/emails',
method=HTTPMethod.POST,
body={
'email': 'sergio+pytest@somosbeta.com.br',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.CREATED
user = dynamodb_persistence_layer.collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['emails'] == {
'sergio@somosbeta.com.br',
'osergiosiqueira@gmail.com',
'sergio+pytest@somosbeta.com.br',
}
def test_update_email(
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='/users/5OxmMjL-ujoR5IMGegQz/emails',
method=HTTPMethod.PATCH,
body={
'old_email': 'sergio@somosbeta.com.br',
'new_email': 'osergiosiqueira@gmail.com',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
user = dynamodb_persistence_layer.collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['email'] == 'osergiosiqueira@gmail.com'
def test_remove_email(
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='/users/5OxmMjL-ujoR5IMGegQz/emails',
method=HTTPMethod.DELETE,
body={
'email': 'osergiosiqueira@gmail.com',
},
),
lambda_context,
)
assert r['statusCode'] == HTTPStatus.OK
user = dynamodb_persistence_layer.collection.get_item(
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
)
assert user['emails'] == {
'sergio@somosbeta.com.br',
}

View File

View File

@@ -1,14 +1,7 @@
{"id": {"S": "apikey"}, "sk": {"S": "MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw"}, "tenant": {"M": {"id": {"S": "*"}, "name": {"S": "default"}}}, "user": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "Sérgio R Siqueira"}, "email": {"S": "sergio@somosbeta.com.br"}}}} {"id": {"S": "apikey"}, "sk": {"S": "MzI1MDQ0NTctZjEzMy00YzAwLTkzNmItNmFhNzEyY2E5ZjQw"}, "tenant": {"M": {"id": {"S": "*"}, "name": {"S": "default"}}}, "user": {"M": {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "Sérgio R Siqueira"}, "email": {"S": "sergio@somosbeta.com.br"}}}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "0"}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "email_verified": {"BOOL": true}, "cognito:sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"}, "cpf": {"S": "07879819908"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}, "last_login": {"S": "2024-02-08T20:53:45.818126-03:00"}, "emails": {"SS": ["sergio@somosbeta.com.br","osergiosiqueira@gmail.com"]}, "tenant:org_id": {"L": [{"S": "cJtK9SsnJhKPyxESe7g3DG"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "cognito"}, "create_date": {"S": "2025-03-03T17:12:26.443507-03:00"}, "sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "emails#sergio@somosbeta.com.br"}, "email_verified": {"BOOL": true}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "email_primary": {"BOOL": true}, "mx_record_exists": {"BOOL": true}, "update_date": {"S": "2023-11-09T12:13:04.308986-03:00"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "emails#osergiosiqueira@gmail.com"}, "email_verified": {"BOOL": true}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "email_primary": {"BOOL": false}, "mx_record_exists": {"BOOL": true}, "update_date": {"S": "2023-11-09T12:13:04.308986-03:00"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#*"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#cJtK9SsnJhKPyxESe7g3DG"}, "create_date": {"S": "2025-03-14T10:06:34.628078-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2025-03-13T16:36:50.073156-03:00"}, "name": {"S": "Beta Educação"}}
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}} {"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2024-02-08T16:42:33.776409-03:00"}, "action": {"S": "OPEN_EMAIL"}}
{"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}} {"id": {"S": "log:5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "2019-03-25T00:00:00-03:00"}, "action": {"S": "CLICK_EMAIL"}}
{"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": "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"}}
@@ -19,9 +12,7 @@
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "lock"}, "create_date": {"S": "2024-11-04T16:27:37.042051-03:00"}, "hash": {"S": "f8f7996aa99d50eb85266be5a9fca1db"}, "ttl": {"N": "1759692457"}, "ttl_date": {"S": "2025-10-05T16:27:37.042051-03:00"}} {"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "lock"}, "create_date": {"S": "2024-11-04T16:27:37.042051-03:00"}, "hash": {"S": "f8f7996aa99d50eb85266be5a9fca1db"}, "ttl": {"N": "1759692457"}, "ttl_date": {"S": "2025-10-05T16:27:37.042051-03:00"}}
{"id": {"S": "QV4sXY3DvSTUMGJ4QqsrwJ"}, "sk": {"S": "0"}} {"id": {"S": "QV4sXY3DvSTUMGJ4QqsrwJ"}, "sk": {"S": "0"}}
{"id": {"S": "QV4sXY3DvSTUMGJ4QqsrwJ"}, "sk": {"S": "generated_items#43ea4475-c369-4f90-b576-135b7df5106b"}} {"id": {"S": "QV4sXY3DvSTUMGJ4QqsrwJ"}, "sk": {"S": "generated_items#43ea4475-c369-4f90-b576-135b7df5106b"}}
{"id": {"S": "email"}, "sk": {"S": "sergio@somosbeta.com.br"}}
{"id": {"S": "cpf"}, "sk": {"S": "07879819908"}}
{"id": {"S": "cpf"}, "sk": {"S": "08679004901"}}
{"id": {"S": "lock"}, "sk": {"S": "c2116a43f8f1aed659a10c83dab17ed3"}} {"id": {"S": "lock"}, "sk": {"S": "c2116a43f8f1aed659a10c83dab17ed3"}}
{"id": {"S": "vacancies#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "3CNrFB9dy2RLit2pdeUWy4#8c9b55ef-e988-43ee-b2da-8594850605d7"}} {"id": {"S": "vacancies#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "3CNrFB9dy2RLit2pdeUWy4#8c9b55ef-e988-43ee-b2da-8594850605d7"}}
{"payment_method": {"S": "CREDIT_CARD"}, "status": {"S": "PAID"}, "assignee": {"M": {"name": {"S": "Alessandra Larivia"}}}, "total": {"N": "149"}, "installments": {"N": "1"}, "due_date": {"S": "2024-02-08T08:46:59.771233-03:00"}, "email": {"S": "financeiro@aquanobile.com.br"}, "name": {"S": "AQUA NOBILE SERVI\u00c7OS LTDA"}, "create_date": {"S": "2024-02-08T08:41:59.773135-03:00"}, "payment_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "phone_number": {"S": "+553130705599"}, "sk": {"S": "0"}, "cnpj": {"S": "11278500000106"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "update_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "tenant": {"S": "cJtK9SsnJhKPyxESe7g3DG"}} {"payment_method": {"S": "CREDIT_CARD"}, "status": {"S": "PAID"}, "assignee": {"M": {"name": {"S": "Alessandra Larivia"}}}, "total": {"N": "149"}, "installments": {"N": "1"}, "due_date": {"S": "2024-02-08T08:46:59.771233-03:00"}, "email": {"S": "financeiro@aquanobile.com.br"}, "name": {"S": "AQUA NOBILE SERVI\u00c7OS LTDA"}, "create_date": {"S": "2024-02-08T08:41:59.773135-03:00"}, "payment_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "phone_number": {"S": "+553130705599"}, "sk": {"S": "0"}, "cnpj": {"S": "11278500000106"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "update_date": {"S": "2024-02-08T08:42:10.910170-03:00"}, "tenant": {"S": "cJtK9SsnJhKPyxESe7g3DG"}}
@@ -34,9 +25,39 @@
{"sk": {"S": "nfse"}, "nfse": {"S": "10384"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T09:05:03.879692-03:00"}} {"sk": {"S": "nfse"}, "nfse": {"S": "10384"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T09:05:03.879692-03:00"}}
{"sk": {"S": "user"}, "user_id": {"S": "5AZXXXCWa2bU4spsxfLznx"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T08:42:05.190415-03:00"}} {"sk": {"S": "user"}, "user_id": {"S": "5AZXXXCWa2bU4spsxfLznx"}, "id": {"S": "KpZTYvu4RzgMJW3A2DF6cC"}, "create_date": {"S": "2024-02-08T08:42:05.190415-03:00"}}
{"id": {"S": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade"}, "sk": {"S": "0"}, "name": {"S": "pytest"}} {"id": {"S": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade"}, "sk": {"S": "0"}, "name": {"S": "pytest"}}
{"id": {"S": "edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "metadata#billing_policy"},"billing_day": {"N": "1"},"created_at": {"S": "2025-07-23T13:56:42.794693-03:00"},"payment_method": {"S": "MANUAL"}}
// User
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "0"}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "email_verified": {"BOOL": true}, "cognito:sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"}, "cpf": {"S": "07879819908"}, "email": {"S": "sergio@somosbeta.com.br"}, "name": {"S": "S\u00e9rgio Rafael de Siqueira"}, "last_login": {"S": "2024-02-08T20:53:45.818126-03:00"}, "emails": {"SS": ["sergio@somosbeta.com.br","osergiosiqueira@gmail.com"]}, "tenant:org_id": {"L": [{"S": "cJtK9SsnJhKPyxESe7g3DG"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "cognito"}, "create_date": {"S": "2025-03-03T17:12:26.443507-03:00"}, "sub": {"S": "58efed8d-d276-41a8-8502-4ab8b5a6415e"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "emails#sergio@somosbeta.com.br"}, "email_verified": {"BOOL": true}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "email_primary": {"BOOL": true}, "mx_record_exists": {"BOOL": true}, "update_date": {"S": "2023-11-09T12:13:04.308986-03:00"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "emails#osergiosiqueira@gmail.com"}, "email_verified": {"BOOL": true}, "update_date": {"S": "2024-02-08T16:42:33.776409-03:00"}, "create_date": {"S": "2019-03-25T00:00:00-03:00"}, "email_primary": {"BOOL": false}, "mx_record_exists": {"BOOL": true}, "update_date": {"S": "2023-11-09T12:13:04.308986-03:00"}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#*"}, "create_date": {"S": "2022-06-13T15:00:24.309410-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "acls#cJtK9SsnJhKPyxESe7g3DG"}, "create_date": {"S": "2025-03-14T10:06:34.628078-03:00"}, "roles": {"L": [{"S": "ADMIN"}]}}
{"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "orgs#cJtK9SsnJhKPyxESe7g3DG"}, "cnpj": {"S": "15608435000190"}, "create_date": {"S": "2025-03-13T16:36:50.073156-03:00"}, "name": {"S": "Beta Educação"}}
// Email
{"id": {"S": "email"}, "sk": {"S": "sergio@somosbeta.com.br"}, "user_id": {"S": "5OxmMjL-ujoR5IMGegQz"}}
// CPF
{"id": {"S": "cpf"}, "sk": {"S": "07879819908"}, "user_id": {"S": "5OxmMjL-ujoR5IMGegQz"}}
{"id": {"S": "cpf"}, "sk": {"S": "08679004901"}}
// Custom pricing
{"id": {"S": "CUSTOM_PRICING#ORG#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "COURSE#281198c2-f293-4acc-b96e-e4a2d5f6b73c"}, "unit_price": {"N": "199"}} {"id": {"S": "CUSTOM_PRICING#ORG#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "COURSE#281198c2-f293-4acc-b96e-e4a2d5f6b73c"}, "unit_price": {"N": "199"}}
// Billing
{"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31"},"created_at": {"S": "2025-07-24T15:46:43.312549-03:00"},"status": {"S": "PENDING"}} {"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31"},"created_at": {"S": "2025-07-24T15:46:43.312549-03:00"},"status": {"S": "PENDING"}}
{"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31#ENROLLMENT#556e99cf-18b2-459c-a46d-f71a807ba551"},"author": {"M": {"id": {"S": "SMEXYk5MQkKCzknJpxqr8n"},"name": {"S": "Carolina Brand"}}},"course": {"M": {"id": {"S": "5c119d4b-573c-4d8d-a99d-63756af2f4c5"},"name": {"S": "NR-06 - Equipamento de Proteção Individual - EPI"}}},"created_at": {"S": "2025-07-24T16:42:41.673797-03:00"},"enrolled_at": {"S": "2025-07-24T15:46:37.162960-03:00"},"unit_price": {"N": "79.2"},"user": {"M": {"id": {"S": "02157895558"},"name": {"S": "ERICK ALVES DOS SANTOS"}}}} {"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31#ENROLLMENT#556e99cf-18b2-459c-a46d-f71a807ba551"},"author": {"M": {"id": {"S": "SMEXYk5MQkKCzknJpxqr8n"},"name": {"S": "Carolina Brand"}}},"course": {"M": {"id": {"S": "5c119d4b-573c-4d8d-a99d-63756af2f4c5"},"name": {"S": "NR-06 - Equipamento de Proteção Individual - EPI"}}},"created_at": {"S": "2025-07-24T16:42:41.673797-03:00"},"enrolled_at": {"S": "2025-07-24T15:46:37.162960-03:00"},"unit_price": {"N": "79.2"},"user": {"M": {"id": {"S": "02157895558"},"name": {"S": "ERICK ALVES DOS SANTOS"}}}}
{"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31#ENROLLMENT#d2124d5a-caaf-4e27-9edb-8380faf15f35"},"author": {"M": {"id": {"S": "SMEXYk5MQkKCzknJpxqr8n"},"name": {"S": "Carolina Brand"}}},"course": {"M": {"id": {"S": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839"},"name": {"S": "NR-11 Transporte, movimentação, armazenagem e manuseio de materiais"}}},"created_at": {"S": "2025-07-25T03:31:17.306858-03:00"},"enrolled_at": {"S": "2025-07-25T03:31:11.736247-03:00"},"unit_price": {"N": "87.2"},"user": {"M": {"id": {"S": "02157895558"},"name": {"S": "ERICK ALVES DOS SANTOS"}}}} {"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31#ENROLLMENT#d2124d5a-caaf-4e27-9edb-8380faf15f35"},"author": {"M": {"id": {"S": "SMEXYk5MQkKCzknJpxqr8n"},"name": {"S": "Carolina Brand"}}},"course": {"M": {"id": {"S": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839"},"name": {"S": "NR-11 Transporte, movimentação, armazenagem e manuseio de materiais"}}},"created_at": {"S": "2025-07-25T03:31:17.306858-03:00"},"enrolled_at": {"S": "2025-07-25T03:31:11.736247-03:00"},"unit_price": {"N": "87.2"},"user": {"M": {"id": {"S": "02157895558"},"name": {"S": "ERICK ALVES DOS SANTOS"}}}}
{"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31#SCHEDULE#AUTO_CLOSE"},"created_at": {"S": "2025-07-24T15:46:43.312549-03:00"},"ttl": {"N": "1754017200"}} {"id": {"S": "BILLING#ORG#edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "START#2025-07-01#END#2025-07-31#SCHEDULE#AUTO_CLOSE"},"created_at": {"S": "2025-07-24T15:46:43.312549-03:00"},"ttl": {"N": "1754017200"}}
{"id": {"S": "edp8njvgQuzNkLx2ySNfAD"},"sk": {"S": "metadata#billing_policy"},"billing_day": {"N": "1"},"created_at": {"S": "2025-07-23T13:56:42.794693-03:00"},"payment_method": {"S": "MANUAL"}}
// Org
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}}
// Org admins
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "admins#e170d457-bd6b-475d-b6ae-4f3427a04873"}, "name": {"S": "Jimi Hendrix"}, "email": {"S": "jimi@hendrix.com"}}
// Org members
{"id": {"S": "orgmembers#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "15ee05a3-4ceb-4b7e-9979-db75b28c9ade"}}
{"id": {"S": "orgmembers#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "e170d457-bd6b-475d-b6ae-4f3427a04873"}}

2
http-api/uv.lock generated
View File

@@ -517,7 +517,7 @@ wheels = [
[[package]] [[package]]
name = "layercake" name = "layercake"
version = "0.9.8" version = "0.9.9"
source = { directory = "../layercake" } source = { directory = "../layercake" }
dependencies = [ dependencies = [
{ name = "arnparse" }, { name = "arnparse" },

View File

@@ -33,7 +33,7 @@ serializer = TypeSerializer()
deserializer = TypeDeserializer() deserializer = TypeDeserializer()
def _serialize_to_basic_types(data: Any) -> str | dict | list: def _serialize_to_basic_types(data: Any) -> str | dict | set:
match data: match data:
case datetime(): case datetime():
return data.isoformat() return data.isoformat()
@@ -41,8 +41,8 @@ def _serialize_to_basic_types(data: Any) -> str | dict | list:
return str(data) return str(data)
case IPv4Address(): case IPv4Address():
return str(data) return str(data)
case tuple(): case tuple() | list():
return [_serialize_to_basic_types(v) for v in data] return set(_serialize_to_basic_types(v) for v in data)
case dict(): case dict():
return {k: _serialize_to_basic_types(v) for k, v in data.items()} return {k: _serialize_to_basic_types(v) for k, v in data.items()}
case _: case _:

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "layercake" name = "layercake"
version = "0.9.8" version = "0.9.9"
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 = [

View File

@@ -16,7 +16,7 @@ Resources:
CompatibleRuntimes: CompatibleRuntimes:
- python3.12 - python3.12
- python3.13 - python3.13
RetentionPolicy: Retain RetentionPolicy: Delete
Metadata: Metadata:
BuildMethod: python3.13 BuildMethod: python3.13
BuildArchitecture: x86_64 BuildArchitecture: x86_64

View File

@@ -38,10 +38,10 @@ class Op:
for op, doc in ops.items(): for op, doc in ops.items():
match op: match op:
case DynamoDBRecordEventName.INSERT: case (
DynamoDBRecordEventName.INSERT | DynamoDBRecordEventName.MODIFY
):
index.add_documents(doc, serializer=JSONEncoder) index.add_documents(doc, serializer=JSONEncoder)
case DynamoDBRecordEventName.MODIFY:
index.update_documents(doc, serializer=JSONEncoder)
case DynamoDBRecordEventName.REMOVE: case DynamoDBRecordEventName.REMOVE:
index.delete_documents(doc) index.delete_documents(doc)