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}, )