From f16199a2a2ef52a5a8132e72f32c6938e97d5a6c Mon Sep 17 00:00:00 2001 From: enrique Date: Mon, 20 May 2024 21:18:59 -0600 Subject: [PATCH] Adding UpdateClientFunction --- src/clients/client_modules/dao/client_dao.py | 13 +++ .../data_mapper/client_mapper.py | 8 +- src/clients/client_modules/models/client.py | 4 + src/clients/lambda_function.py | 81 ++++++++++++++++++- src/clients/template.yaml | 71 +++++++++++++++- 5 files changed, 171 insertions(+), 6 deletions(-) diff --git a/src/clients/client_modules/dao/client_dao.py b/src/clients/client_modules/dao/client_dao.py index ea3fd2c..c79cf5d 100644 --- a/src/clients/client_modules/dao/client_dao.py +++ b/src/clients/client_modules/dao/client_dao.py @@ -28,3 +28,16 @@ def create_client(self, item: dict) -> dict: """ response = self.clients_db.insert_record(item) return response + + def update_client(self, item: dict) -> dict: + """ + Attempts to update an existing record for a client in the DynamoDB table. + If the client does not exist, a new record will be created. + + :param item: Client representation. + :type item: dict + :return: A dictionary that contains the response object. + :rtype: dict + """ + response = self.clients_db.update_record(item) + return response \ No newline at end of file diff --git a/src/clients/client_modules/data_mapper/client_mapper.py b/src/clients/client_modules/data_mapper/client_mapper.py index 06af08a..0280e66 100644 --- a/src/clients/client_modules/data_mapper/client_mapper.py +++ b/src/clients/client_modules/data_mapper/client_mapper.py @@ -64,13 +64,13 @@ def get_geolocation_data( return geolocation def build_client(self, username: str) -> Dict[str, Any]: - """This function will create a dictionary to send to DynamoDB to create a new record + """This function will create a dictionary to send to DynamoDB to create/update a new record Arguments: username -- who is sending the request Returns: - Object needed by DynamoDB to create a record + Object needed by DynamoDB to create/update a record """ client_errors = [] @@ -109,7 +109,7 @@ def build_client(self, username: str) -> Dict[str, Any]: "discount": self.client_data["discount"], "email": self.client_data["email"], "errors": client_errors, - "created_by": username, - "created_at": datetime.now().isoformat(), + "last_modified_by": username, + "last_modified_at": datetime.now().isoformat(), } return data diff --git a/src/clients/client_modules/models/client.py b/src/clients/client_modules/models/client.py index f5eb8ff..240f5ff 100644 --- a/src/clients/client_modules/models/client.py +++ b/src/clients/client_modules/models/client.py @@ -18,4 +18,8 @@ class HIBerryBaseClient(BaseModel): class HIBerryClient(HIBerryBaseClient): + pass + + +class HIBerryClientUpdate(HIBerryBaseClient): pass \ No newline at end of file diff --git a/src/clients/lambda_function.py b/src/clients/lambda_function.py index 97b652e..0efe2c1 100644 --- a/src/clients/lambda_function.py +++ b/src/clients/lambda_function.py @@ -4,7 +4,7 @@ # Own's modules from client_modules.dao.client_dao import ClientDAO -from client_modules.models.client import HIBerryClient +from client_modules.models.client import HIBerryClient, HIBerryClientUpdate from client_modules.utils.doorman import DoormanUtil from client_modules.errors.auth_error import AuthError from client_modules.data_mapper.client_mapper import ClientHelper @@ -93,3 +93,82 @@ def create_client(event: Dict[str, Any], context: LambdaContext) -> Dict[str, An payload={"message": error_details}, status_code=500 ) + + +def update_client(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]: + """ + This function is the entry point of the process that will receive a payload as an input + and will attempt to update an existing client entry in DynamoDB. + + :param event: Custom object that can come from an APIGateway or EventBridge. + :type event: Dict + :param context: Regular lambda function context. + :type context: LambdaContext + :return: Custom object with the response from the lambda, it could be a 200 if the update was successful, + or >= 400 if there was an error. + :rtype: Dict + """ + + logger = Logger() + logger.info("Initializing Update Client function") + doorman = DoormanUtil(event, logger) + + try: + username = doorman.get_username_from_context() + is_auth = doorman.auth_user() + if not is_auth: + raise AuthError(f"User {username} is not authorized to update a client") + + body = doorman.get_body_from_request() + + logger.debug(f"Incoming data is {body=} and {username=}") + + updated_client_data = HIBerryClientUpdate(**body) + builder = ClientHelper(updated_client_data.model_dump()) + client_db_data = builder.build_client(username=username) + logger.info(f"Updating client with phone: {updated_client_data.phone_number}") + + dao = ClientDAO() + update_response = dao.update_client(client_db_data) + + if update_response.get("status") == "success": + logger.info("Client data received and updated") + output_data = { + "address_latitude": client_db_data["address_latitude"], + "address_longitude": client_db_data["address_longitude"], + "second_address_latitude": client_db_data["second_address_latitude"], + "second_address_longitude": client_db_data["second_address_longitude"], + "errors": client_db_data["errors"] + } + + logger.debug(f"Outgoing data is {output_data=}") + return doorman.build_response( + payload=output_data, + status_code=update_response["status_code"], + ) + else: + return doorman.build_response( + payload={"message": update_response.get("message", "Update failed")}, + status_code=update_response.get("status_code", 500), + ) + + except ValidationError as validation_error: + error_details = "Some fields failed validation: " + str(validation_error) + logger.error(error_details) + return doorman.build_response( + payload={"message": error_details}, status_code=400 + ) + + except AuthError as auth_error: + error_details = str(auth_error) + logger.error(error_details) + return doorman.build_response( + payload={"message": error_details}, status_code=403 + ) + + except Exception as e: + error_details = f"Error updating the client: {e}" + logger.error(error_details, exc_info=True) + return doorman.build_response( + payload={"message": error_details}, status_code=500 + ) diff --git a/src/clients/template.yaml b/src/clients/template.yaml index 61e0cff..55e5e6e 100644 --- a/src/clients/template.yaml +++ b/src/clients/template.yaml @@ -79,6 +79,29 @@ Resources: Authorizer: HiBerryCognitoAuthorizer Role: !GetAtt CreateClientRole.Arn + UpdateClientFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: !Sub "UpdateClientFunction-${StageName}" + CodeUri: . + Handler: lambda_function.update_client + Runtime: python3.11 + Environment: + Variables: + POWERTOOLS_SERVICE_NAME: update-client + POWERTOOLS_LOG_LEVEL: !Ref LogLevel + APP_ENVIRONMENT: !Ref StageName + Events: + HttpPut: + Type: Api + Properties: + RestApiId: !Ref ApiGateway + Path: /clients + Method: put + Auth: + Authorizer: HiBerryCognitoAuthorizer + Role: !GetAtt UpdateClientsRole.Arn + ApiGateway: Type: AWS::Serverless::Api Properties: @@ -118,7 +141,6 @@ Resources: - Effect: Allow Action: - geo:SearchPlaceIndexForText - #Resource: !GetAtt HiBerrySearchLocationIndex.Arn Resource: !ImportValue HiBerryLocationIndexArn - PolicyName: CloudWatchLogsPolicy PolicyDocument: @@ -130,10 +152,57 @@ Resources: - logs:CreateLogStream - logs:PutLogEvents Resource: "*" + + UpdateClientsRole: + Type: AWS::IAM::Role + Properties: + RoleName: !Sub "UpdateClientsRole-${StageName}" + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Policies: + - PolicyName: DynamoDBUpdateItemPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - dynamodb:PutItem + - dynamodb:Query + - dynamodb:DeleteItem + Resource: !GetAtt ClientsTable.Arn + - PolicyName: LocationServiceAccess + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - geo:SearchPlaceIndexForText + Resource: !ImportValue HiBerryLocationIndexArn + - PolicyName: CloudWatchLogsPolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: "*" + Outputs: CreateClientFunction: Description: "Lambda Function ARN" Value: !GetAtt CreateClientFunction.Arn + UpdateClientFunction: + Description: "Lambda Function ARN" + Value: !GetAtt UpdateClientFunction.Arn HttpApiEndpoint: Description: The default endpoint ApiGateway. Value: !Sub "https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/"