changeset 5:54e71cf6e324

refactor code
author Dennis Concepcion Martin <dennisconcepcionmartin@gmail.com>
date Fri, 17 Sep 2021 17:42:30 +0200
parents cfd876570008
children db2ce7097ff3
files README.md dependencies/__init__.py dependencies/python/__init__.py dependencies/python/event_controller.py dependencies/python/requirements.txt dependencies/python/secrets_controller.py dependencies/python/url_controller.py events/event.json src/__init__.py src/events/sentiment_event.json src/requirements.txt template.yaml
diffstat 9 files changed, 121 insertions(+), 71 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Thu Sep 16 18:03:26 2021 +0200
+++ b/README.md	Fri Sep 17 17:42:30 2021 +0200
@@ -80,7 +80,7 @@
 Run functions locally and invoke them with the `sam local invoke` command.
 
 ```bash
-tweet-analysis$ sam local invoke HelloWorldFunction --event events/event.json
+tweet-analysis$ sam local invoke HelloWorldFunction --event events/sentiment_event.json
 ```
 
 The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dependencies/python/event_controller.py	Fri Sep 17 17:42:30 2021 +0200
@@ -0,0 +1,24 @@
+def unwrap_sentiment_string_parameters(event):
+    """
+    Unwrap string parameters from /sentiment api call
+    :param event: dict, required
+        API Gateway Lambda Proxy Input Format
+    :return:
+    """
+
+    twitter_user = 'Twitter'
+    number_of_tweets = '100'
+
+    query_string_parameters = event['queryStringParameters']
+    if event['queryStringParameters'] is not None:
+        if 'twitterUser' in query_string_parameters:
+            twitter_user = query_string_parameters['twitterUser']
+            if not twitter_user:
+                twitter_user = 'Twitter'
+
+        if 'numberOfTweets' in query_string_parameters:
+            number_of_tweets = query_string_parameters['numberOfTweets']
+            if not number_of_tweets:
+                number_of_tweets = '100'
+
+    return twitter_user, number_of_tweets
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dependencies/python/requirements.txt	Fri Sep 17 17:42:30 2021 +0200
@@ -0,0 +1,1 @@
+boto3
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dependencies/python/secrets_controller.py	Fri Sep 17 17:42:30 2021 +0200
@@ -0,0 +1,46 @@
+import boto3
+import base64
+import json
+from botocore.exceptions import ClientError
+
+
+def get_secret(secret_name, region_name="eu-west-2"):
+    """
+    Get Secret Keys from AWS Secrets Manager
+    :return:
+    """
+
+    # Create a Secrets Manager client
+    session = boto3.session.Session()
+    client = session.client(
+        service_name='secretsmanager',
+        region_name=region_name
+    )
+
+    try:
+        get_secret_value_response = client.get_secret_value(SecretId=secret_name)
+    except ClientError as e:
+        if e.response['Error']['Code'] == 'DecryptionFailureException':
+            # Secrets Manager can't decrypt the protected secret text using the provided KMS key.
+            raise e
+        elif e.response['Error']['Code'] == 'InternalServiceErrorException':
+            # An error occurred on the server side.
+            raise e
+        elif e.response['Error']['Code'] == 'InvalidParameterException':
+            # You provided an invalid value for a parameter.
+            raise e
+        elif e.response['Error']['Code'] == 'InvalidRequestException':
+            # You provided a parameter value that is not valid for the current state of the resource.
+            raise e
+        elif e.response['Error']['Code'] == 'ResourceNotFoundException':
+            # AWS Can't find the resource that you asked for.
+            raise e
+    else:
+        # Decrypts secret using the associated KMS CMK.
+        # Depending on whether the secret is a string or binary, one of these fields will be populated.
+        if 'SecretString' in get_secret_value_response:
+            secret = get_secret_value_response['SecretString']
+        else:
+            secret = base64.b64decode(get_secret_value_response['SecretBinary'])
+
+        return json.loads(secret)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dependencies/python/url_controller.py	Fri Sep 17 17:42:30 2021 +0200
@@ -0,0 +1,13 @@
+def create_twitter_url(twitter_user, number_of_tweets):
+    """
+    Create url to fetch `max_results` of tweets from `user`
+    :param twitter_user: string, required
+    :param number_of_tweets: int, required
+    :return: string url
+    """
+
+    formatted_max_results = 'max_results={}'.format(number_of_tweets)
+    formatted_user = 'query=from:{}'.format(twitter_user)
+    url = "https://api.twitter.com/2/tweets/search/recent?{}&{}".format(formatted_max_results, formatted_user)
+
+    return url
--- a/events/event.json	Thu Sep 16 18:03:26 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-{
-  "body": "{\"message\": \"hello world\"}",
-  "resource": "/sentiment",
-  "path": "/sentiment",
-  "httpMethod": "GET",
-  "isBase64Encoded": false,
-  "queryStringParameters": {
-    "foo": "bar"
-  },
-  "pathParameters": {
-    "proxy": "/path/to/resource"
-  },
-  "stageVariables": {
-    "baz": "qux"
-  },
-  "headers": {
-    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
-    "Accept-Encoding": "gzip, deflate, sdch",
-    "Accept-Language": "en-US,en;q=0.8",
-    "Cache-Control": "max-age=0",
-    "CloudFront-Forwarded-Proto": "https",
-    "CloudFront-Is-Desktop-Viewer": "true",
-    "CloudFront-Is-Mobile-Viewer": "false",
-    "CloudFront-Is-SmartTV-Viewer": "false",
-    "CloudFront-Is-Tablet-Viewer": "false",
-    "CloudFront-Viewer-Country": "US",
-    "Host": "1234567890.execute-api.us-east-1.amazonaws.com",
-    "Upgrade-Insecure-Requests": "1",
-    "User-Agent": "Custom User Agent String",
-    "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
-    "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
-    "X-Forwarded-For": "127.0.0.1, 127.0.0.2",
-    "X-Forwarded-Port": "443",
-    "X-Forwarded-Proto": "https"
-  },
-  "requestContext": {
-    "accountId": "123456789012",
-    "resourceId": "123456",
-    "stage": "prod",
-    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
-    "requestTime": "09/Apr/2015:12:34:56 +0000",
-    "requestTimeEpoch": 1428582896000,
-    "identity": {
-      "cognitoIdentityPoolId": null,
-      "accountId": null,
-      "cognitoIdentityId": null,
-      "caller": null,
-      "accessKey": null,
-      "sourceIp": "127.0.0.1",
-      "cognitoAuthenticationType": null,
-      "cognitoAuthenticationProvider": null,
-      "userArn": null,
-      "userAgent": "Custom User Agent String",
-      "user": null
-    },
-    "path": "/prod/hello",
-    "resourcePath": "/hello",
-    "httpMethod": "POST",
-    "apiId": "1234567890",
-    "protocol": "HTTP/1.1"
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/events/sentiment_event.json	Fri Sep 17 17:42:30 2021 +0200
@@ -0,0 +1,10 @@
+{
+  "resource": "/sentiment",
+  "path": "/sentiment",
+  "httpMethod": "GET",
+  "isBase64Encoded": false,
+  "queryStringParameters": {
+    "twitterUser": "dennisconcep",
+    "numberOfTweets": "50"
+  }
+}
--- a/src/requirements.txt	Thu Sep 16 18:03:26 2021 +0200
+++ b/src/requirements.txt	Fri Sep 17 17:42:30 2021 +0200
@@ -1,2 +1,1 @@
-requests
-boto3
\ No newline at end of file
+requests
\ No newline at end of file
--- a/template.yaml	Thu Sep 16 18:03:26 2021 +0200
+++ b/template.yaml	Fri Sep 17 17:42:30 2021 +0200
@@ -13,6 +13,7 @@
       ApiKeyRequired: true
 
 Resources:
+
   ##
   ### START API GATEWAY CONFIGURATION ###
   ##
@@ -38,7 +39,7 @@
       Description: Deployment of Api version 1
       RestApiId: !Ref ServerlessRestApi
 
-  # Create usage plan
+  # Create PaidUsagePlan usage plan
   PaidUsagePlan:
     Type: AWS::ApiGateway::UsagePlan
     Properties:
@@ -59,7 +60,7 @@
         - Key: "Name"
           Value: "tweet-analysis::rest-api::paid-usage-plan"
 
-  # Create Api key
+  # Create Api key for PaidUsagePlan
   PaidApiKey:
     Type: AWS::ApiGateway::ApiKey
     Properties:
@@ -119,21 +120,22 @@
         - Key: "application-id"
           Value: "tweet-analysis"
         - Key: "Name"
-          Value: "tweet-analysis::certificate::dennistech.io"
+          Value: "tweet-analysis::domain-certificate::dennistech.io"
 
-  # Map domain to the regional domain generated by Api Gateway
+  # Map tweet-analysis.dennistech.io to the regional domain generated by Api Gateway
   DomainMapping:
     Type: AWS::Route53::RecordSet
     Properties:
+      Name: tweet-analysis.dennistech.io
+      Comment: Map domain to the regional domain generated by Api Gateway
       HostedZoneId: Z0937998E3C5GEK4NHO9
-      Name: tweet-analysis.dennistech.io
       Type: A
       AliasTarget:
         DNSName: !GetAtt Domain.RegionalDomainName
         EvaluateTargetHealth: true
         HostedZoneId: !GetAtt Domain.RegionalHostedZoneId
 
-  # Map paths from your domain name to your API stages
+  # Configure mapping in API Gateway custom domain to point to v1 stage
   PathMapping:
     Type: AWS::ApiGateway::BasePathMapping
     Properties:
@@ -153,6 +155,7 @@
   GetTweetSentimentFunction:
     Type: AWS::Serverless::Function
     Properties:
+      FunctionName: GetTweetSentimentFunction
       Description: Fetch tweets and analyse sentiment using AWS Comprehend
       CodeUri: src/
       Handler: handlers/sentiment.get_tweet_sentiment
@@ -161,15 +164,31 @@
         - AWSSecretsManagerGetSecretValuePolicy:
             SecretArn:
               arn:aws:secretsmanager:eu-west-2:339008578167:secret:tweet-analysis-keys-gKo6DQ
+      Layers:
+        - !Ref TweetAnalysisSharedFunctions
       Events:
         CallGetTweetSentiment:
           Type: Api
           Properties:
             Path: /sentiment
             Method: get
+            RequestParameters:
+              - method.request.querystring.twitterUser:
+                  Required: false
+              - method.request.querystring.numberOfTweets:
+                  Required: false
       Tags:
         Name: "tweet-analysis::get-tweet-sentiment-function"
 
+  # Define dependencies
+  TweetAnalysisSharedFunctions:
+    Type: AWS::Serverless::LayerVersion
+    Properties:
+      Description: Shared functions across lambda functions
+      CompatibleRuntimes:
+        - python3.9
+      ContentUri: dependencies/
+
   ##
   ### END FUNCTION CONFIGURATION ###
   ##