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 ( ServiceError, ) from aws_lambda_powertools.event_handler.openapi.params import Body from layercake.dateutils import now, ttl from layercake.dynamodb import ( DynamoDBPersistenceLayer, KeyPair, SortKey, TransactKey, ) from layercake.extra_types import CpfStr, NameStr from api_gateway import JSONResponse from boto3clients import dynamodb_client from config import USER_TABLE from exceptions import CPFConflictError, UserNotFoundError from .emails import router as emails from .orgs import router as orgs from .password import router as password __all__ = ['emails', 'orgs', 'password'] router = Router() dyn = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client) class RateLimitExceededError(ServiceError): def __init__(self, msg: str | dict): super().__init__(HTTPStatus.TOO_MANY_REQUESTS, msg) @router.get('/') def get_user(user_id: str): user = dyn.collection.get_items( TransactKey(user_id) + SortKey('0') + SortKey( sk='RATE_LIMIT_EXCEEDED', rename_key='rate_limit_exceeded', ) + SortKey( sk='CREATED_BY', rename_key='created_by', ), ) if not user: raise UserNotFoundError('User not found') return user @router.patch('/') def update( user_id: str, name: Annotated[NameStr, Body(embed=True)], new_cpf: Annotated[CpfStr, Body(embed=True, alias='cpf')], ): now_ = now() old_cpf = dyn.collection.get_item( KeyPair( pk=user_id, sk=SortKey('0', path_spec='cpf'), ), ) with dyn.transact_writer() as transact: transact.put( item={ 'id': user_id, 'sk': 'RATE_LIMIT_EXCEEDED', 'ttl': ttl(start_dt=now_, hours=24), 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', exc_cls=RateLimitExceededError, ) transact.update( key=KeyPair(user_id, '0'), update_expr='SET #name = :name, cpf = :cpf, updated_at = :now', expr_attr_names={ '#name': 'name', }, expr_attr_values={ ':name': name, ':cpf': new_cpf, ':now': now_, }, cond_expr='attribute_exists(sk)', ) if old_cpf != new_cpf: transact.put( item={ 'id': 'cpf', 'cpf': new_cpf, 'user_id': user_id, 'created_at': now_, }, cond_expr='attribute_not_exists(sk)', exc_cls=CPFConflictError, ) if old_cpf: transact.delete(key=KeyPair('cpf', old_cpf)) return JSONResponse(status_code=HTTPStatus.OK)