def lambda_handler(event, context):
    """
    Update cart table to use user identifier instead of anonymous cookie value as a key. This will be called when a user
    is logged in.
    """
    cart_id, _ = get_cart_id(event["headers"])

    try:
        # Because this method is authorized at API gateway layer, we don't need to validate the JWT claims here
        user_id = event["requestContext"]["authorizer"]["claims"]["sub"]
    except KeyError:

        return {
            "statusCode": 400,
            "headers": get_headers(cart_id),
            "body": json.dumps({"message": "Invalid user"}),
        }

    # Get all cart items belonging to the user's identity
    response = table.query(
        KeyConditionExpression=Key("pk").eq(f"user#{user_id}")
        & Key("sk").begins_with("product#"),
        ConsistentRead=
        True,  # Perform a strongly consistent read here to ensure we get correct and up to date cart
    )

    cart_items = response.get("Items")
    # batch_writer will be used to update status for cart entries belonging to the user
    with table.batch_writer() as batch:
        for item in cart_items:
            # Delete ordered items
            batch.delete_item(Key={"pk": item["pk"], "sk": item["sk"]})

    metrics.add_metric(name="CartCheckedOut", unit="Count", value=1)
    logger.info({"action": "CartCheckedOut", "cartItems": cart_items})

    return {
        "statusCode":
        200,
        "headers":
        get_headers(cart_id),
        "body":
        json.dumps({"products": response.get("Items")},
                   default=handle_decimal_type),
    }
def lambda_handler(event, context):
    """
    List items in shopping cart.
    """

    cart_id, generated = get_cart_id(event["headers"])

    # Because this method can be called anonymously, we need to check there's a logged in user
    jwt_token = event["headers"].get("Authorization")
    if jwt_token:
        user_sub = get_user_sub(jwt_token)
        key_string = f"user#{user_sub}"
        logger.structure_logs(append=True, cart_id=f"user#{user_sub}")
    else:
        key_string = f"cart#{cart_id}"
        logger.structure_logs(append=True, cart_id=f"cart#{cart_id}")

    # No need to query database if the cart_id was generated rather than passed into the function
    if generated:
        logger.info(
            "cart ID was generated in this request, not fetching cart from DB")
        product_list = []
    else:
        logger.info("Fetching cart from DB")
        response = table.query(
            KeyConditionExpression=Key("pk").eq(key_string)
            & Key("sk").begins_with("product#"),
            ProjectionExpression="sk,quantity,productDetail",
            FilterExpression=
            "quantity > :val",  # Only return items with more than 0 quantity
            ExpressionAttributeValues={":val": 0},
        )
        product_list = response.get("Items", [])

    for product in product_list:
        product.update((k, v.replace("product#", ""))
                       for k, v in product.items() if k == "sk")

    return {
        "statusCode":
        200,
        "headers":
        get_headers(cart_id),
        "body":
        json.dumps({"products": product_list}, default=handle_decimal_type),
    }
def lambda_handler(event, context):
    """
    Update cart table to use user identifier instead of anonymous cookie value as a key. This will be called when a user
    is logged in.
    """
    logger.debug(event)

    cart_id, _ = get_cart_id(event["headers"])
    try:
        # Because this method is authorized at API gateway layer, we don't need to validate the JWT claims here
        user_id = event["requestContext"]["authorizer"]["claims"]["sub"]
    except KeyError:

        return {
            "statusCode": 400,
            "headers": get_headers(cart_id),
            "body": json.dumps({"message": "Invalid user"}),
        }

    # Get all cart items belonging to the user's anonymous identity
    response = table.query(
        KeyConditionExpression=Key("pk").eq(f"cart#{cart_id}")
        & Key("sk").begins_with("product#"))
    unauth_cart = response["Items"]

    # Since there's no batch operation available for updating items, and there's no dependency between them, we can
    # run them in parallel threads.
    thread_list = []

    for item in unauth_cart:
        # Store items with user identifier as pk instead of "unauthenticated" cart ID
        # Using threading library to perform updates in parallel
        ddb_updateitem_thread = threading.Thread(target=update_item,
                                                 args=(user_id, item))
        thread_list.append(ddb_updateitem_thread)
        ddb_updateitem_thread.start()

        # Delete items with unauthenticated cart ID
        # Rather than deleting directly, push to SQS queue to handle asynchronously
        queue.send_message(
            MessageBody=json.dumps(item, default=handle_decimal_type))

    for ddb_thread in thread_list:
        ddb_thread.join()  # Block main thread until all updates finished

    response = table.query(
        KeyConditionExpression=Key("pk").eq(f"user#{user_id}")
        & Key("sk").begins_with("product#"),
        ProjectionExpression="sk,quantity,productDetail",
        ConsistentRead=
        True,  # Perform a strongly consistent read here to ensure we get correct values after updates
    )

    product_list = response.get("Items", [])
    for product in product_list:
        product.update((k, v.replace("product#", ""))
                       for k, v in product.items() if k == "sk")

    return {
        "statusCode":
        200,
        "headers":
        get_headers(cart_id),
        "body":
        json.dumps({"products": product_list}, default=handle_decimal_type),
    }
Ejemplo n.º 4
0
def lambda_handler(event, context):
    """
    Idempotent update quantity of products in a cart. Quantity provided will overwrite existing quantity for a
    specific product in cart, rather than adding to it.
    """

    try:
        request_payload = json.loads(event["body"])
    except KeyError:
        return {
            "statusCode": 400,
            "headers": get_headers(),
            "body": json.dumps({"message": "No Request payload"}),
        }

    # retrieve the product_id that was specified in the url
    product_id = event["pathParameters"]["product_id"]

    quantity = int(request_payload["quantity"])
    cart_id, _ = get_cart_id(event["headers"])

    # Because this method can be called anonymously, we need to check if there's a logged in user
    user_sub = None
    jwt_token = event["headers"].get("Authorization")
    if jwt_token:
        user_sub = get_user_sub(jwt_token)

    try:
        product = get_product_from_external_service(product_id)
    except NotFoundException:
        logger.info("No product found with product_id: %s", product_id)
        return {
            "statusCode": 404,
            "headers": get_headers(cart_id=cart_id),
            "body": json.dumps({"message": "product not found"}),
        }

    # Prevent storing negative quantities of things
    if quantity < 0:
        return {
            "statusCode": 400,
            "headers": get_headers(cart_id),
            "body": json.dumps(
                {
                    "productId": product_id,
                    "message": "Quantity must not be lower than 0",
                }
            ),
        }

    # Use logged in user's identifier if it exists, otherwise use the anonymous identifier

    if user_sub:
        pk = f"user#{user_sub}"
        ttl = generate_ttl(
            7
        )  # Set a longer ttl for logged in users - we want to keep their cart for longer.
    else:
        pk = f"cart#{cart_id}"
        ttl = generate_ttl()

    table.put_item(
        Item={
            "pk": pk,
            "sk": f"product#{product_id}",
            "quantity": quantity,
            "expirationTime": ttl,
            "productDetail": product,
        }
    )
    logger.info("about to add metrics...")
    metrics.add_metric(name="CartUpdated", unit="Count", value=1)

    return {
        "statusCode": 200,
        "headers": get_headers(cart_id),
        "body": json.dumps(
            {"productId": product_id, "quantity": quantity, "message": "cart updated"}
        ),
    }
def lambda_handler(event, context):
    """
    Add a the provided quantity of a product to a cart. Where an item already exists in the cart, the quantities will
    be summed.
    """
    logger.debug(event)

    try:
        request_payload = json.loads(event["body"])
    except KeyError:
        return {
            "statusCode": 400,
            "headers": get_headers(),
            "body": json.dumps({"message": "No Request payload"}),
        }
    product_id = request_payload["productId"]
    quantity = request_payload.get("quantity", 1)
    cart_id, _ = get_cart_id(event["headers"])

    # Because this method can be called anonymously, we need to check there's a logged in user
    user_sub = None
    jwt_token = event["headers"].get("Authorization")
    if jwt_token:
        user_sub = get_user_sub(jwt_token)

    try:
        product = get_product_from_external_service(product_id)
    except NotFoundException:
        return {
            "statusCode": 404,
            "headers": get_headers(cart_id=cart_id),
            "body": json.dumps({"message": "product not found"}),
        }

    if user_sub:
        pk = f"user#{user_sub}"
        ttl = generate_ttl(
            7
        )  # Set a longer ttl for logged in users - we want to keep their cart for longer.
    else:
        pk = f"cart#{cart_id}"
        ttl = generate_ttl()

    if int(quantity) < 0:
        table.update_item(
            Key={
                "pk": pk,
                "sk": f"product#{product_id}"
            },
            ExpressionAttributeNames={
                "#quantity": "quantity",
                "#expirationTime": "expirationTime",
                "#productDetail": "productDetail",
            },
            ExpressionAttributeValues={
                ":val": quantity,
                ":ttl": ttl,
                ":productDetail": product,
                ":limit": abs(quantity),
            },
            UpdateExpression=
            "ADD #quantity :val SET #expirationTime = :ttl, #productDetail = :productDetail",
            # Prevent quantity less than 0
            ConditionExpression="quantity >= :limit",
        )
    else:
        table.update_item(
            Key={
                "pk": pk,
                "sk": f"product#{product_id}"
            },
            ExpressionAttributeNames={
                "#quantity": "quantity",
                "#expirationTime": "expirationTime",
                "#productDetail": "productDetail",
            },
            ExpressionAttributeValues={
                ":val": quantity,
                ":ttl": generate_ttl(),
                ":productDetail": product,
            },
            UpdateExpression=
            "ADD #quantity :val SET #expirationTime = :ttl, #productDetail = :productDetail",
        )

    return {
        "statusCode":
        200,
        "headers":
        get_headers(cart_id),
        "body":
        json.dumps({
            "productId": product_id,
            "message": "product added to cart"
        }),
    }
def lambda_handler(event, context):
    """
    API Handler

    The API catch the request and builds a record in dynamodb
    for each request with sender's IP, build and id and timestamp

    """
    logger.info(event)
    logger.info(f"Table is {tableName}")

    # Controls body
    try:
        request_payload = json.loads(event["body"])
    except:
        return {
            "statusCode": 400,
            "body": json.dumps({"message": "No Request payload"}),
        }

    # Controls payload : clientId is mandatory`
    client_id = ""
    try:
        client_id = request_payload["client_id"]
    except KeyError:
        return {
            "statusCode": 400,
            "headers": get_headers(client_id),
            "body": json.dumps({"message": "No client_id in payload ?"}),
        }
    # Build the record

    ip = requests.get("http://checkip.amazonaws.com/").text.replace("\n", "")
    pk = str(uuid.uuid4())
    timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
    week = datetime.datetime.today() + datetime.timedelta(days=7)
    expiry_date_time = int(time.mktime(week.timetuple()))

    try:
        result = dynamodb.put_item(
            TableName=tableName,
            Item={
                'pk': {'S': pk},
                'sk': {'S': ip},
                'client_id': {'S': client_id},
                'timestamp': {'S': timestamp},
                'ttl': {'N': str(expiry_date_time)}
            })
        print(result)
    except Exception as e:
        logger.error(e)

    # result = table.update_item(
    #     Key={
    #         "pk": {"S": pk}
    #     },
    #     ExpressionAttributeValues={
    #         ":ip": {"S": ip},
    #         ":ttl": {"N": ttl},
    #         ":client_id": {"S": client_id},
    #         ":timestamp": {"S": timestamp}
    #     },
    #     UpdateExpression="SET sk=:ip, ttl=:ttl, client_id=:client_id, timestamp=:timestamp",
    #     ReturnValues="ALL_NEW"
    # )

    # metrics.add_metric(name="GreetingAdded", unit="Count", value=1)


    return {
        "statusCode": 200,
        "headers": get_headers(pk),
        "body": json.dumps(
            {"client_id": client_id, "ip": ip, "message": "Greetings added !"}
        ),
    }
    def test_lambda_handler(self):
        """
        API Handler tester

        The API catch the request and builds a record in dynamodb
        for each request with sender's IP, build and id and timestamp

        """

        # Controls body
        try:
            request_payload = json.loads(self.event["body"])
        except KeyError or ValueError:
            return {
                "statusCode": 400,
                "body": json.dumps({"message": "No Request payload"}),
            }

        # Controls payload : clientId is mandatory`
        client_id = ""
        try:
            client_id = request_payload["client_id"]
        except KeyError:
            return {
                "statusCode": 400,
                "headers": get_headers(client_id),
                "body": json.dumps({"message": "No client_id in payload ?"}),
            }
        # Build the record

        ip = requests.get("http://checkip.amazonaws.com/").text.replace("\n", "")
        pk = str(uuid.uuid4())
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        ttl = str(generate_ttl(7))

        result = self.dynamodb.update_item(
            TableName=self.tableName,
            Key={
                "pk": {"S": pk},
                "sk": {"S": f"ip#{ip}"}
            },
            ExpressionAttributeNames={
                "#ip": "ip",
                "#ttl": "ttl",
                "#client_id": "client_id",
                "#timestamp": "timestamp"
            },
            ExpressionAttributeValues={
                ":ip": {"S": ip},
                ":ttl": {"N": ttl},
                ":client_id": {"S": client_id},
                ":timestamp": {"S": timestamp}
            },
            UpdateExpression="SET #ttl=:ttl, #client_id=:client_id, #timestamp=:timestamp",
            ReturnValues="ALL_NEW"
        )

        pprint(result)
        # metrics.add_metric(name="GreetingAdded", unit="Count", value=1)

        response = {
            "statusCode": 200,
            "headers": get_headers(pk),
            "body": json.dumps(
                {"client_id": client_id, "ip": ip, "message": "Greetings added !"}
            ),
        }

        pprint(response)

        self.assertEqual(response['statusCode'], 200)