This commit is contained in:
2025-12-05 20:43:49 -03:00
parent b136d83f81
commit 3c1751f4bf
4 changed files with 127 additions and 7 deletions

View File

@@ -45,6 +45,7 @@ app.include_router(users.emails, prefix='/users')
app.include_router(users.orgs, prefix='/users') app.include_router(users.orgs, prefix='/users')
app.include_router(users.password, prefix='/users') app.include_router(users.password, prefix='/users')
app.include_router(orders.router, prefix='/orders') app.include_router(orders.router, prefix='/orders')
app.include_router(orgs.router, prefix='/orgs')
app.include_router(orgs.admins, 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(orgs.scheduled, prefix='/orgs') app.include_router(orgs.scheduled, prefix='/orgs')

View File

@@ -0,0 +1,10 @@
from http import HTTPStatus
from aws_lambda_powertools.event_handler.exceptions import (
ServiceError,
)
class ConflictError(ServiceError):
def __init__(self, msg: str | dict):
super().__init__(HTTPStatus.CONFLICT, msg)

View File

@@ -1,13 +1,16 @@
from http import HTTPStatus
from typing import Annotated from typing import Annotated
from uuid import uuid4 from uuid import uuid4
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 NotFoundError
from aws_lambda_powertools.event_handler.openapi.params import Body from aws_lambda_powertools.event_handler.openapi.params import Body
from layercake.dateutils import now from layercake.dateutils import now
from layercake.dynamodb import DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
from layercake.extra_types import CnpjStr, NameStr from layercake.extra_types import CnpjStr, NameStr
from pydantic import UUID4, BaseModel, EmailStr from pydantic import UUID4, BaseModel, EmailStr
from api_gateway import JSONResponse
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import INTERNAL_EMAIL_DOMAIN, USER_TABLE from config import INTERNAL_EMAIL_DOMAIN, USER_TABLE
from exceptions import ConflictError from exceptions import ConflictError
@@ -24,7 +27,13 @@ dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
__all__ = ['admins', 'custom_pricing', 'scheduled', 'users'] __all__ = ['admins', 'custom_pricing', 'scheduled', 'users']
class OrgConflictError(ConflictError): ... class CNPJConflictError(ConflictError): ...
class EmailConflictError(ConflictError): ...
class EmailNotFoundError(NotFoundError): ...
class User(BaseModel): class User(BaseModel):
@@ -33,7 +42,7 @@ class User(BaseModel):
email: EmailStr email: EmailStr
@router.post('/s') @router.post('/')
def add_org( def add_org(
name: Annotated[str, Body(embed=True)], name: Annotated[str, Body(embed=True)],
cnpj: Annotated[CnpjStr, Body(embed=True)], cnpj: Annotated[CnpjStr, Body(embed=True)],
@@ -41,6 +50,7 @@ def add_org(
): ):
now_ = now() now_ = now()
org_id = str(uuid4()) org_id = str(uuid4())
email = f'org+{cnpj}@{INTERNAL_EMAIL_DOMAIN}'
with dyn.transact_writer() as transact: with dyn.transact_writer() as transact:
transact.put( transact.put(
@@ -52,15 +62,80 @@ def add_org(
'created_at': now_, 'created_at': now_,
}, },
cond_expr='attribute_not_exists(sk)', cond_expr='attribute_not_exists(sk)',
exc_cls=OrgConflictError, exc_cls=CNPJConflictError,
)
transact.put(
item={
# Post-migration (users): rename `email` to `EMAIL`
'id': 'email',
'sk': email,
'user_id': org_id,
'created_at': now_,
},
cond_expr='attribute_not_exists(sk)',
exc_cls=EmailConflictError,
) )
transact.put( transact.put(
item={ item={
'id': org_id, 'id': org_id,
'sk': '0', 'sk': '0',
'name': name, 'name': name,
'email': f'org+{cnpj}@{INTERNAL_EMAIL_DOMAIN}', 'email': email,
'cnpj': cnpj, 'cnpj': cnpj,
'created_at': now_, 'created_at': now_,
} }
) )
transact.put(
item={
'id': org_id,
# Post-migration: rename `emails` to `EMAIL`
'sk': f'emails#{email}',
'email_primary': True,
'email_verified': True,
'mx_record_exists': True,
'created_at': now_,
}
)
transact.put(
item={
'id': org_id,
# Post-migration (users): rename `admins#` to `ADMIN#`
'sk': f'admins#{user.id}',
'name': user.name,
'email': user.email,
'created_at': now_,
}
)
transact.put(
item={
'id': user.id,
# Post-migration (users): rename `orgs#` to `ORG#`
'sk': f'orgs#{org_id}',
'name': name,
'cnpj': cnpj,
'created_at': now_,
}
)
transact.put(
item={
# Post-migration (users): rename `orgmembers#` to `MEMBER#ORG#`
'id': f'orgmembers#{org_id}',
'sk': user.id,
'created_at': now_,
}
)
transact.condition(
# Post-migration (users): rename `email` to `EMAIL`
key=KeyPair('email', user.email),
cond_expr='attribute_exists(sk)',
exc_cls=EmailNotFoundError,
)
return JSONResponse(
status_code=HTTPStatus.CREATED,
body={
'id': org_id,
'name': name,
'email': email,
},
)

View File

@@ -1,11 +1,46 @@
import json import json
from http import HTTPMethod, HTTPStatus from http import HTTPMethod, HTTPStatus
from layercake.dynamodb import DynamoDBPersistenceLayer from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair, PartitionKey
from ..conftest import HttpApiProxy, LambdaContext from ..conftest import HttpApiProxy, LambdaContext
def test_add_org(
app,
seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext,
):
user_id = '213a6682-2c59-4404-9189-12eec0a846d4'
r = app.lambda_handler(
http_api_proxy(
raw_path='/orgs',
method=HTTPMethod.POST,
body={
'name': 'Banco Central do Brasil',
'cnpj': '00038166000105',
'user': {
'id': user_id,
'name': 'Sérgio R Siqueira',
'email': 'sergio@somosbeta.com.br',
},
},
),
lambda_context,
)
body = json.loads(r['body'])
assert r['statusCode'] == HTTPStatus.CREATED
org = dynamodb_persistence_layer.collection.query(PartitionKey(body['id']))
assert len(org['items']) == 3
user = dynamodb_persistence_layer.collection.query(KeyPair(user_id, 'orgs#'))
# One item was added from seeds
assert len(user['items']) == 2
def test_get_admins( def test_get_admins(
app, app,
seeds, seeds,
@@ -28,7 +63,6 @@ def test_get_admins(
def test_revoke( def test_revoke(
app, app,
seeds, seeds,
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
http_api_proxy: HttpApiProxy, http_api_proxy: HttpApiProxy,
lambda_context: LambdaContext, lambda_context: LambdaContext,
): ):