Skip to content

Commit

Permalink
Refactor newsletter signup lambda to have own aws function url.
Browse files Browse the repository at this point in the history
Update newsletter signup function.
Unify contact form and newsletter into a single lambda handler.
  • Loading branch information
eugenchio committed Jan 15, 2025
1 parent 73688a7 commit 95cd202
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 206 deletions.
200 changes: 35 additions & 165 deletions cloudformation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Resources:
- lambda:UpdateFunctionCode
- lambda:UpdateFunctionConfiguration
Effect: Allow
Resource: !GetAtt LandingNewsletterSignUpLambda.Arn
Resource: !GetAtt LandingLambda.Arn
PolicyName: manage-static-website

LambdaExecutionRole:
Expand Down Expand Up @@ -158,19 +158,45 @@ Resources:
Runtime: "python3.9"
Timeout: 10

LandingNewsletterSignUpLambda:
LandingLambda:
Type: "AWS::Lambda::Function"
Properties:
Handler: "newsletter_sign_up.lambda_handler"
Handler: "handler.lambda_handler"
Role: !GetAtt LambdaExecutionRole.Arn
Code:
# use CF feature - it compares yaml config with its previous version
# without looking at the actual state of the lambda function
# this way we can update code, deps and env vars during deploy
ZipFile: import this
Runtime: "python3.9"
Runtime: "python3.10"
Timeout: 10

LandingLambdaUrl:
Type: AWS::Lambda::Url
Properties:
AuthType: NONE
Cors:
AllowOrigins:
- https://ivelum.com
ExposeHeaders:
- '*'
AllowHeaders:
- '*'
AllowMethods:
- '*'
MaxAge: 0
AllowCredentials: false
TargetFunctionArn: !GetAtt LandingLambda.Arn

# Create permission for API Gateway to invoke Lambda
LandingPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: lambda:invokeFunctionUrl
FunctionName: !Ref LandingLambda
FunctionUrlAuthType: 'NONE'
Principal: '*'

# IAM Role for API Gateway + CloudWatch Logging
ApiGatewayLoggingRole:
Type: AWS::IAM::Role
Expand Down Expand Up @@ -265,148 +291,10 @@ Resources:
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true

LandingContactFormAPIResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt LandingAPIGateway.RootResourceId
PathPart: 'contact'
RestApiId: !Ref LandingAPIGateway

LandingNewContactFormAPIMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: ANY
ResourceId: !Ref LandingContactFormAPIResource
RestApiId: !Ref LandingAPIGateway
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations
- LambdaArn: !GetAtt LandingContactFormLambda.Arn
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: !Sub "'${CorsAllowedOrigins}'"
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
ResponseTemplates:
application/json: '{"status":"ok"}'
RequestParameters:
integration.request.header.X-Amz-Invocation-Type: "'Event'"
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true
RequestParameters:
method.request.header.Content-Type: false
RequestModels:
application/json: Empty

LandingNewContactFormAPIOptionsMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: OPTIONS
ResourceId: !Ref LandingContactFormAPIResource
RestApiId: !Ref LandingAPIGateway
Integration:
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: !Sub "'${CorsAllowedOrigins}'"
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
ResponseTemplates:
application/json: Empty
PassthroughBehavior: WHEN_NO_MATCH
RequestTemplates:
application/json: '{"statusCode": 200}'
Type: MOCK
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true

LandingNewsletterSignUpAPIResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt LandingAPIGateway.RootResourceId
PathPart: 'newsletter'
RestApiId: !Ref LandingAPIGateway

LandingNewsletterSignUpAPIMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: ANY
ResourceId: !Ref LandingNewsletterSignUpAPIResource
RestApiId: !Ref LandingAPIGateway
Integration:
IntegrationHttpMethod: POST
Type: AWS
Uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaArn}/invocations
- LambdaArn: !GetAtt LandingNewsletterSignUpLambda.Arn
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: !Sub "'${CorsAllowedOrigins}'"
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
ResponseTemplates:
application/json: '{"status":"ok"}'
RequestParameters:
integration.request.header.X-Amz-Invocation-Type: "'Event'"
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true
RequestParameters:
method.request.header.Content-Type: false
RequestModels:
application/json: Empty

LandingNewsletterSignUpAPIOptionsMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
AuthorizationType: NONE
HttpMethod: OPTIONS
ResourceId: !Ref LandingNewsletterSignUpAPIResource
RestApiId: !Ref LandingAPIGateway
Integration:
IntegrationResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: !Sub "'${CorsAllowedOrigins}'"
method.response.header.Access-Control-Allow-Methods: "'*'"
method.response.header.Access-Control-Allow-Headers: "'*'"
ResponseTemplates:
application/json: Empty
PassthroughBehavior: WHEN_NO_MATCH
RequestTemplates:
application/json: '{"statusCode": 200}'
Type: MOCK
MethodResponses:
- StatusCode: 200
ResponseParameters:
method.response.header.Access-Control-Allow-Origin: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Headers: true

LandingAPIDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn:
- LandingContactFormAPIMethod
- LandingNewContactFormAPIMethod
- LandingNewsletterSignUpAPIMethod
Properties:
RestApiId: !Ref LandingAPIGateway

Expand All @@ -432,28 +320,10 @@ Resources:
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${LandingAPIGateway}/*/*/'

# Create permission for API Gateway to invoke Lambda
LandingContactFormAPIPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref LandingContactFormLambda
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${LandingAPIGateway}/*/*/contact'

# Create permission for API Gateway to invoke Lambda
LandingNewsletterSignUpAPIPermission:
Type: 'AWS::Lambda::Permission'
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref LandingNewsletterSignUpLambda
Principal: apigateway.amazonaws.com
SourceArn: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${LandingAPIGateway}/*/*/newsletter'

Outputs:
LandingContactFormInvokeURL:
Value: !Sub 'https://${LandingAPIGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}/contact'
Description: URL for invoking the Contact Form API
LandingNewsletterSignUpInvokeURL:
Value: !Sub 'https://${LandingAPIGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}/newsletter'
Description: URL for invoking the Contact Form API
Value: !Sub 'https://${LandingAPIGateway}.execute-api.${AWS::Region}.amazonaws.com/${apiGatewayStageName}'
Description: URL for invoking the API
LandingInvokeURL:
Value: !GetAtt LandingLambdaUrl.FunctionUrl
Description: URL for invoking the API function
31 changes: 24 additions & 7 deletions lambda-functions/contact_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,35 @@ def send_email(event_body):


def handle_contact_form(event, context):
assert event.get('subject') is not None
assert event.get('text') is not None
assert event.get('email') is not None
assert event.get('name') is not None
assert event.get('company') is not None
if event['body']:
try:
body = json.loads(event['body'])
except JSONDecodeError:
logger.error(
'handle_contact_form(): invalid request body')
return {
'statusCode': 400,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'success': False,
'message': 'invalid request body'
})
}

assert body.get('subject') is not None
assert body.get('text') is not None
assert body.get('email') is not None
assert body.get('name') is not None
assert body.get('company') is not None

logger.info('handle_contact_form(): invoked. event={}'.format(event))
try:
# try to create CRM lead
create_crm_lead(event)
create_crm_lead(body)
except Exception as e:
sentry_sdk.capture_exception(e)
logger.exception('handle_contact_form(): create_lead failed')
# fallback to sending email if CRM lead creation fails
send_email(event)
send_email(body)
15 changes: 12 additions & 3 deletions lambda-functions/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sentry_sdk
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
from contact_form import handle_contact_form
from newsletter_sign_up import handle_newsletter_sign_up


logger = logging.getLogger()
Expand All @@ -21,8 +22,16 @@
def lambda_handler(event, context):
logger.info('lambda_handler(): invoked. event={}'.format(event))
try:
# try to create CRM lead
handle_contact_form(event, context)
rawPath = event['rawPath']
match rawPath:
case '/newsletter':
return handle_newsletter_sign_up(event, context)
case '/contact':
return handle_contact_form(event, context)
case _:
return {
'statusCode': 404,
}
except Exception as e:
sentry_sdk.capture_exception(e)
logger.exception('lambda_handler(): create_lead failed')
logger.exception('lambda_handler(): routing failed')
Loading

0 comments on commit 95cd202

Please sign in to comment.