diff --git a/cloudformation.yaml b/cloudformation.yaml index a3d91bb..4393fb2 100644 --- a/cloudformation.yaml +++ b/cloudformation.yaml @@ -119,7 +119,7 @@ Resources: - lambda:UpdateFunctionCode - lambda:UpdateFunctionConfiguration Effect: Allow - Resource: !GetAtt LandingNewsletterSignUpLambda.Arn + Resource: !GetAtt LandingLambda.Arn PolicyName: manage-static-website LambdaExecutionRole: @@ -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 @@ -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 @@ -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 diff --git a/lambda-functions/contact_form.py b/lambda-functions/contact_form.py index 07f458e..1e3d55e 100644 --- a/lambda-functions/contact_form.py +++ b/lambda-functions/contact_form.py @@ -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) diff --git a/lambda-functions/handler.py b/lambda-functions/handler.py index 686b17c..be32b19 100644 --- a/lambda-functions/handler.py +++ b/lambda-functions/handler.py @@ -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() @@ -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') diff --git a/lambda-functions/newsletter_sign_up.py b/lambda-functions/newsletter_sign_up.py index e7ecb60..1e73a5e 100755 --- a/lambda-functions/newsletter_sign_up.py +++ b/lambda-functions/newsletter_sign_up.py @@ -1,9 +1,11 @@ +import json import logging import os +from json import JSONDecodeError import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration -from crisp_api import Crisp +from crisp_api import Crisp, RouteError logger = logging.getLogger() @@ -19,7 +21,7 @@ ) -def add_crisp_contact(event_body): +def add_crisp_contact(email): logger.info('add_crisp_contact(): start') crisp = Crisp() crisp.set_tier('plugin') @@ -34,34 +36,104 @@ def add_crisp_contact(event_body): crisp.authenticate(token_id, token_key) try: - crisp.website.add_new_people_profile( - website_id=website_id, - data={ - 'email': event_body['email'], - 'person': {'nickname': event_body['email']}, - 'segments': 'newsletter', - }, - ) + contact_just_created = False + try: + contact_data = crisp.website.get_people_profile( + website_id=website_id, + people_id=email, + ) + contact_just_created = True + except RouteError: + contact_data = crisp.website.add_new_people_profile( + website_id=website_id, + data={ + 'email': email, + 'person': {'nickname': email}, + 'segments': ['newsletter'], + }, + ) + + if contact_just_created: + subscription_status = crisp.website.get_people_subscription_status( + website_id=website_id, + people_id=contact_data['people_id'], + ) + if not subscription_status['email']: + crisp.website.update_people_subscription_status( + website_id=website_id, + people_id=contact_data['people_id'], + data={'email': True}, + ) + + return True except Exception as e: sentry_sdk.capture_exception(e) logger.exception('add_crisp_contact(): add_crisp_contact failed') - return + return False logger.info('add_crisp_contact(): success') -def handle_newsletter_sign_up(event): - assert event.get('email') is not None +def handle_newsletter_sign_up(event, context): + if event['body']: + try: + body = json.loads(event['body']) + except JSONDecodeError: + logger.error( + 'handle_newsletter_sign_up(): invalid request body') + return { + 'statusCode': 400, + 'headers': { + 'Content-Type': 'application/json' + }, + 'body': json.dumps({ + 'success': False, + 'message': 'invalid request body' + }) + } + + assert body['email'] is not None logger.info( 'handle_newsletter_sign_up():' 'add_crisp_contact invoked. event={}'.format(event) ) + email = body['email'] try: - add_crisp_contact(event) + result = add_crisp_contact(email) + if result: + return { + 'statusCode': 200, + 'headers': { + 'Content-Type': 'application/json' + }, + 'body': json.dumps({ + 'success': True, + }) + } + else: + return { + 'statusCode': 500, + 'headers': { + 'Content-Type': 'application/json' + }, + 'body': json.dumps({ + 'success': False, + }) + } + except Exception as e: sentry_sdk.capture_exception(e) logger.exception( 'handle_newsletter_sign_up(): ' 'add_crisp_contact failed' ) + return { + 'statusCode': 500, + 'headers': { + 'Content-Type': 'application/json' + }, + 'body': json.dumps({ + 'success': False + }) + } diff --git a/lambda-functions/pyproject.toml b/lambda-functions/pyproject.toml index aacbafe..f78e64e 100644 --- a/lambda-functions/pyproject.toml +++ b/lambda-functions/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "landing" version = "0.1.0" -requires-python = "== 3.9.*" +requires-python = "== 3.10.*" dependencies = [ "boto3==1.29.1", "requests==2.31.0", diff --git a/lambda-functions/uv.lock b/lambda-functions/uv.lock index e4b107d..36d032f 100644 --- a/lambda-functions/uv.lock +++ b/lambda-functions/uv.lock @@ -1,5 +1,5 @@ version = 1 -requires-python = "==3.9.*" +requires-python = "==3.10.*" [[package]] name = "boto3" @@ -44,24 +44,36 @@ version = "3.4.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/2f/28659eee7f5d003e0f5a3b572765bf76d6e0fe6601ab1f1b1dd4cba7e4f1/charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", size = 196326 }, - { url = "https://files.pythonhosted.org/packages/d1/18/92869d5c0057baa973a3ee2af71573be7b084b3c3d428fe6463ce71167f8/charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", size = 125614 }, - { url = "https://files.pythonhosted.org/packages/d6/27/327904c5a54a7796bb9f36810ec4173d2df5d88b401d2b95ef53111d214e/charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", size = 120450 }, - { url = "https://files.pythonhosted.org/packages/a4/23/65af317914a0308495133b2d654cf67b11bbd6ca16637c4e8a38f80a5a69/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", size = 140135 }, - { url = "https://files.pythonhosted.org/packages/f2/41/6190102ad521a8aa888519bb014a74251ac4586cde9b38e790901684f9ab/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", size = 150413 }, - { url = "https://files.pythonhosted.org/packages/7b/ab/f47b0159a69eab9bd915591106859f49670c75f9a19082505ff16f50efc0/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", size = 142992 }, - { url = "https://files.pythonhosted.org/packages/28/89/60f51ad71f63aaaa7e51a2a2ad37919985a341a1d267070f212cdf6c2d22/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", size = 144871 }, - { url = "https://files.pythonhosted.org/packages/0c/48/0050550275fea585a6e24460b42465020b53375017d8596c96be57bfabca/charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", size = 146756 }, - { url = "https://files.pythonhosted.org/packages/dc/b5/47f8ee91455946f745e6c9ddbb0f8f50314d2416dd922b213e7d5551ad09/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", size = 141034 }, - { url = "https://files.pythonhosted.org/packages/84/79/5c731059ebab43e80bf61fa51666b9b18167974b82004f18c76378ed31a3/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", size = 149434 }, - { url = "https://files.pythonhosted.org/packages/ca/f3/0719cd09fc4dc42066f239cb3c48ced17fc3316afca3e2a30a4756fe49ab/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", size = 152443 }, - { url = "https://files.pythonhosted.org/packages/f7/0e/c6357297f1157c8e8227ff337e93fd0a90e498e3d6ab96b2782204ecae48/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", size = 150294 }, - { url = "https://files.pythonhosted.org/packages/54/9a/acfa96dc4ea8c928040b15822b59d0863d6e1757fba8bd7de3dc4f761c13/charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", size = 145314 }, - { url = "https://files.pythonhosted.org/packages/73/1c/b10a63032eaebb8d7bcb8544f12f063f41f5f463778ac61da15d9985e8b6/charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", size = 94724 }, - { url = "https://files.pythonhosted.org/packages/c5/77/3a78bf28bfaa0863f9cfef278dbeadf55efe064eafff8c7c424ae3c4c1bf/charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", size = 102159 }, + { url = "https://files.pythonhosted.org/packages/69/8b/825cc84cf13a28bfbcba7c416ec22bf85a9584971be15b21dd8300c65b7f/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", size = 196363 }, + { url = "https://files.pythonhosted.org/packages/23/81/d7eef6a99e42c77f444fdd7bc894b0ceca6c3a95c51239e74a722039521c/charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", size = 125639 }, + { url = "https://files.pythonhosted.org/packages/21/67/b4564d81f48042f520c948abac7079356e94b30cb8ffb22e747532cf469d/charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", size = 120451 }, + { url = "https://files.pythonhosted.org/packages/c2/72/12a7f0943dd71fb5b4e7b55c41327ac0a1663046a868ee4d0d8e9c369b85/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", size = 140041 }, + { url = "https://files.pythonhosted.org/packages/67/56/fa28c2c3e31217c4c52158537a2cf5d98a6c1e89d31faf476c89391cd16b/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", size = 150333 }, + { url = "https://files.pythonhosted.org/packages/f9/d2/466a9be1f32d89eb1554cf84073a5ed9262047acee1ab39cbaefc19635d2/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", size = 142921 }, + { url = "https://files.pythonhosted.org/packages/f8/01/344ec40cf5d85c1da3c1f57566c59e0c9b56bcc5566c08804a95a6cc8257/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", size = 144785 }, + { url = "https://files.pythonhosted.org/packages/73/8b/2102692cb6d7e9f03b9a33a710e0164cadfce312872e3efc7cfe22ed26b4/charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", size = 146631 }, + { url = "https://files.pythonhosted.org/packages/d8/96/cc2c1b5d994119ce9f088a9a0c3ebd489d360a2eb058e2c8049f27092847/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", size = 140867 }, + { url = "https://files.pythonhosted.org/packages/c9/27/cde291783715b8ec30a61c810d0120411844bc4c23b50189b81188b273db/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", size = 149273 }, + { url = "https://files.pythonhosted.org/packages/3a/a4/8633b0fc1a2d1834d5393dafecce4a1cc56727bfd82b4dc18fc92f0d3cc3/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", size = 152437 }, + { url = "https://files.pythonhosted.org/packages/64/ea/69af161062166b5975ccbb0961fd2384853190c70786f288684490913bf5/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", size = 150087 }, + { url = "https://files.pythonhosted.org/packages/3b/fd/e60a9d9fd967f4ad5a92810138192f825d77b4fa2a557990fd575a47695b/charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", size = 145142 }, + { url = "https://files.pythonhosted.org/packages/6d/02/8cb0988a1e49ac9ce2eed1e07b77ff118f2923e9ebd0ede41ba85f2dcb04/charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", size = 94701 }, + { url = "https://files.pythonhosted.org/packages/d6/20/f1d4670a8a723c46be695dff449d86d6092916f9e99c53051954ee33a1bc/charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", size = 102191 }, { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, ] +[[package]] +name = "crisp-api" +version = "1.1.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/ac/31d2cae90e2e979c2afa88d06ac92b8de20556180d5a20a9c675cd5dfc48/crisp-api-1.1.19.tar.gz", hash = "sha256:fb86656539e634bb262d60afa94dc76403e11fe07175ee1e4427c7eb4cae7f32", size = 21317 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/be/c8a249983db2552240236917a596bb4c30739c17b28599f27fbaed5ec335/crisp_api-1.1.19-py3-none-any.whl", hash = "sha256:c2f4d74495c00161167e95939325d2546fe2615f67d4d7e20f77343b078b053b", size = 12379 }, +] + [[package]] name = "idna" version = "3.10" @@ -86,6 +98,7 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "boto3" }, + { name = "crisp-api" }, { name = "requests" }, { name = "sentry-sdk" }, ] @@ -93,6 +106,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "boto3", specifier = "==1.29.1" }, + { name = "crisp-api", specifier = "==1.1.19" }, { name = "requests", specifier = "==2.31.0" }, { name = "sentry-sdk", specifier = "==1.35.0" }, ]