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(orders.router, prefix='/orders')
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.emails, prefix='/users')
app.include_router(users.orgs, prefix='/users')
app.include_router(billing.router, prefix='/billing')
app.include_router(orgs.policies, 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(webhooks.router, prefix='/webhooks')
app.include_router(settings.router, prefix='/settings')

View File

@@ -1,5 +1,6 @@
from .address import router as address
from .admins import router as admins
from .custom_pricing import router as custom_pricing
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)
def get_custom_pricing(id: str):
result = course_layer.collection.query(
return course_layer.collection.query(
PartitionKey(f'CUSTOM_PRICING#ORG#{id}'),
limit=100,
)
return result
class CustomPricing(BaseModel):
course_id: UUID4
@@ -38,7 +36,7 @@ class CustomPricing(BaseModel):
@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()
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)
class DeleteCustomPricing(BaseModel):
class RemoveCustomPricing(BaseModel):
course_id: UUID4
@router.delete('/<id>/custompricing', compress=True)
def delete_custom_pricing(id: str, custom_princing: DeleteCustomPricing):
pair = KeyPair(
def remove_custom_pricing(id: str, custom_princing: RemoveCustomPricing):
key = KeyPair(
f'CUSTOM_PRICING#ORG#{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)

View File

@@ -23,14 +23,14 @@ from api_gateway import JSONResponse
from boto3clients import dynamodb_client, idp_client
from config import MEILISEARCH_API_KEY, MEILISEARCH_HOST, USER_POOOL_ID, USER_TABLE
from middlewares import AuditLogMiddleware, Tenant, TenantMiddleware
from models import User
from rules.user import update_user
from .add import router as add
from .emails import router as emails
from .logs import router as logs
from .orgs import router as orgs
__all__ = ['logs', 'emails', 'orgs']
__all__ = ['add', 'logs', 'emails', 'orgs']
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):
name: NameStr
cpf: CpfStr
@@ -129,7 +118,7 @@ def put_user(id: str, payload: UserData):
@router.get('/<id>', compress=True, tags=['User'], summary='Get user')
def get_user(id: str):
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 typing import Annotated
from aws_lambda_powertools.event_handler.api_gateway import Router
from aws_lambda_powertools.event_handler.exceptions import (
BadRequestError as PowertoolsBadRequestError,
)
from aws_lambda_powertools.event_handler.openapi.params import Body
from layercake.dynamodb import (
DynamoDBPersistenceLayer,
KeyPair,
MissingError,
PrefixKey,
)
from pydantic import BaseModel, EmailStr
@@ -15,23 +13,13 @@ from pydantic import BaseModel, EmailStr
from api_gateway import JSONResponse
from boto3clients import dynamodb_client
from config import USER_TABLE
from middlewares import AuditLogMiddleware
from rules.user import add_email, del_email, set_email_as_primary
class BadRequestError(MissingError, PowertoolsBadRequestError): ...
from rules.user import add_email, remove_email, set_email_as_primary
router = Router()
user_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
@router.get(
'/<id>/emails',
compress=True,
tags=['User'],
summary='Get user emails',
)
@router.get('/<id>/emails', compress=True)
def get_emails(id: str):
start_key = router.current_event.get_query_string_value('start_key', None)
@@ -45,18 +33,12 @@ class Email(BaseModel):
email: EmailStr
@router.post(
'/<id>/emails',
compress=True,
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)
@router.post('/<id>/emails', compress=True)
def add_email_(id: str, email: Annotated[str, Body(embed=True)]):
add_email(id, email, persistence_layer=user_layer)
return JSONResponse(
body=payload,
body={'email': email},
status_code=HTTPStatus.CREATED,
)
@@ -67,22 +49,7 @@ class EmailAsPrimary(BaseModel):
email_verified: bool = False
@router.patch(
'/<id>/emails',
compress=True,
tags=['User'],
summary='Add user email as primary',
middlewares=[
AuditLogMiddleware(
'EMAIL_CHANGE',
user_layer.collection,
(
'new_email',
'old_email',
),
)
],
)
@router.patch('/<id>/emails', compress=True)
def patch_email(id: str, payload: EmailAsPrimary):
set_email_as_primary(
id,
@@ -98,15 +65,9 @@ def patch_email(id: str, payload: EmailAsPrimary):
)
@router.delete(
'/<id>/emails',
compress=True,
tags=['User'],
summary='Delete user email',
middlewares=[AuditLogMiddleware('EMAIL_DEL', user_layer.collection, ('email',))],
)
@router.delete('/<id>/emails', compress=True)
def delete_email(id: str, payload: Email):
del_email(
remove_email(
id,
payload.email,
persistence_layer=user_layer,

View File

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

View File

View File

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

View File

@@ -1,9 +1,9 @@
from typing import Any, Generator
from typing import Generator
import boto3
import jsonlines
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 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')
JSONL_FILES = (
# 'test-orders.jsonl',
'test-users.jsonl',
# 'test-enrollments.jsonl',
# 'test-courses.jsonl',
)
class JSONEncoder(Encoder):
def default(self, obj):
if isinstance(obj, set):
@@ -18,19 +26,11 @@ class JSONEncoder(Encoder):
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:
try:
dynamodb_client.put_item(
TableName=table_name,
Item=item,
Item=serialize(item),
)
except Exception:
return False
@@ -45,7 +45,7 @@ def scan_table(table_name: str, /, dynamodb_client, **kwargs) -> Generator:
yield from ()
else:
for item in r['Items']:
yield deserialize(item)
yield item
if 'LastEvaluatedKey' in r:
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__':
# 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:
table_name = file.removesuffix('.jsonl')
reader = jsonlines.Reader(fp).iter(skip_invalid=True)
@@ -79,7 +66,7 @@ if __name__ == '__main__':
put_item(line, table_name, dynamodb_client) # type: ignore
# 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')
for doc in tqdm(
@@ -91,6 +78,7 @@ if __name__ == '__main__':
),
desc=f'Indexing {table_name}',
):
doc = deserialize(doc)
meili_client.index(table_name).add_documents([doc], serializer=JSONEncoder)
meili_client.index('pytest').add_documents([doc], serializer=JSONEncoder)
@@ -98,6 +86,6 @@ if __name__ == '__main__':
index.update_settings(
{
'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"}}}}
{"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"}}
{"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}}
{"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"}}
{"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}}
{"sk": {"S": "konviva"}, "createDate": {"S": "2023-07-22T21:46:02.527763-03:00"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "konvivaId": {"N": "26943"}}
{"sk": {"S": "org#cJtK9SsnJhKPyxESe7g3DG"}, "createDate": {"S": "2023-12-24T20:50:27.656310-03:00"}, "id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "name": {"S": "Beta Educa\u00e7\u00e3o"}}
{"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"}}
{"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"}}
{"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"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#NQ3YmmTesfoSyHCkPKGKEF"}, "tag": {"S": "F\u00e1brica"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "tags#bg45io8igarjsA4BzPQyrz"}, "tag": {"S": "Escrit\u00f3rio"}, "create_date": {"S": "2023-09-22T18:31:11.475449-03:00"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
{"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"}}
// Post-migration: rename `create_date` to `created_at`
// Users
{"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"}
{"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"}
{"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"}
{"id": "5OxmMjL-ujoR5IMGegQz", "sk": "orgs#cJtK9SsnJhKPyxESe7g3DG", "name": "EDUSEG", "cnpj": "15608435000190"}
{"id": "5OxmMjL-ujoR5IMGegQz", "sk": "acls#*", "roles": ["ADMIN"]}
// CPFs
{"id": "cpf", "sk": "07879819908", "user_id": "5OxmMjL-ujoR5IMGegQz"}
// Emails
{"id": "email", "sk": "sergio@somosbeta.com.br", "user_id": "5OxmMjL-ujoR5IMGegQz"}
{"id": "email", "sk": "osergiosiqueira@gmail.com", "user_id": "5OxmMjL-ujoR5IMGegQz"}
// Orgs
{"id": "cJtK9SsnJhKPyxESe7g3DG", "sk": "0", "name": "EDUSEG", "cnpj": "15608435000190", "email": "org+15608435000190@users.noreply.betaeducacao.com.br"}
{"id": "edp8njvgQuzNkLx2ySNfAD", "sk": "0", "name": "KORD S.A", "email": "org+13573332000107@users.noreply.betaeducacao.com.br", "cnpj": "13573332000107"}
// Org admins
{"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"}
// Org members
{"id": "orgmembers#cJtK9SsnJhKPyxESe7g3DG", "sk": "5OxmMjL-ujoR5IMGegQz"}
// CNPJs
{"id": "cnpj", "sk": "15608435000190", "user_id": "cJtK9SsnJhKPyxESe7g3DG"}

View File

@@ -144,9 +144,14 @@ def dynamodb_persistence_layer(dynamodb_client):
@pytest.fixture()
def dynamodb_seeds(dynamodb_client):
with jsonlines.open('tests/seeds.jsonl') as lines:
for line in lines:
dynamodb_client.put_item(TableName=PYTEST_TABLE_NAME, Item=line)
with open('tests/seeds.jsonl', 'rb') as fp:
reader = jsonlines.Reader(fp)
for line in reader.iter(type=dict, skip_invalid=True):
dynamodb_client.put_item(
TableName=PYTEST_TABLE_NAME,
Item=line,
)
@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
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": "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": "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#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"}}
@@ -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": "QV4sXY3DvSTUMGJ4QqsrwJ"}, "sk": {"S": "0"}}
{"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": "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"}}
@@ -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": "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": "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"}}
// 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#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#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]]
name = "layercake"
version = "0.9.8"
version = "0.9.9"
source = { directory = "../layercake" }
dependencies = [
{ name = "arnparse" },