AWSTemplateFormatVersion: 2010-09-09
Description: >
  Amazon Connect Customer IVR Journey Map

Mappings:
  CFTParameters:
    S3Bucket:
      Name: "venky-connect-blogs"
    S3Prefix:
      Value: "2022"
    CFLexBotName:
      Name: "customer-ivr-journey-map-lexbot"
 
Parameters:
  AmazonConnectInstanceARN:
    Default:  "arn:aws:connect:REGION:ACCOUNTID:instance/INSTANCEID"
    Type: String    
    Description: Enter Amazon Connect Instance ARN
  CFS3BucketForWebSite:
    Default:  ""
    Type: String
    #AllowedPattern: '(?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$)'
    ConstraintDescription: 'Invalid S3 Bucket name'
    Description: Enter the (globally unique) name you would like to use for the Amazon S3 bucket where we will store the website assets for dashboard. This template will fail to deploy if the bucket name you chose is currently in use.
  # PinpointSMSProjectID:
  #   Default:  "1234567890"
  #   Type: String
  #   AllowedPattern: '(?=^.{3,63}$)(?!^(\d+\.)+\d+$)(^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$)'
  #   ConstraintDescription: 'Invalid Pinpoint sms porject id'
  #   Description: Enter the project id which you have created in AWS pinpoint service to send SMS
            
Metadata:
  'AWS::CloudFormation::Interface':
    ParameterGroups:
      - Label:
          default: Amazon Connect Instance ARN 
        Parameters:
          - AmazonConnectInstanceARN
      # - Label:
      #     default: Pinpoint SMS ProjectID 
      #   Parameters:
      #     - PinpointSMSProjectID

Outputs:
  ProdDataEndpoint:
    Description: "API Prod stage endpoint"
    Value: !Sub "https://${CJMAPIGateway}.execute-api.${AWS::Region}.amazonaws.com/Prod/customerJourney"
  CloudfrontEndpoint:
      Description: Endpoint for Cloudfront distribution
      Value: !Join
        - ''
        - - 'https://'
          - !GetAtt [CFCloudFrontDistribution, DomainName]
          - '/index.html'
      
Resources:
    
  BlogArtifacts:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Delete
    UpdateReplacePolicy: Retain
    Properties:
      OwnershipControls:
        Rules:
          - ObjectOwnership: ObjectWriter
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      LoggingConfiguration:
        DestinationBucketName: !Ref BlogBucketLogs
        LogFilePrefix: blog-logs

  BlogBucketLogs:
    Type: 'AWS::S3::Bucket'
    DeletionPolicy: Delete
    UpdateReplacePolicy: Retain
    Properties:
      OwnershipControls:
        Rules:
          - ObjectOwnership: ObjectWriter
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  BlogBucketLogsPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref BlogBucketLogs
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action:
              - 's3:PutObject'
            Effect: Allow
            Principal:
              Service: logging.s3.amazonaws.com
            Resource: !Join 
              - ''
              - - 'arn:aws:s3:::'
                - !Ref BlogBucketLogs
                - /*
            Condition:
              ArnLike:
                'aws:SourceArn': !GetAtt 
                  - BlogArtifacts
                  - Arn
              StringEquals:
                'aws:SourceAccount': !Sub '${AWS::AccountId}'

  CopyArtifactsLambdaIamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 's3:PutObject'
                Resource: !Sub 'arn:aws:s3:::${BlogArtifacts}/*'
              - Effect: Allow
                Action:
                  - 's3:GetObject'
                  - 's3:GetObjectVersion'
                Resource:
                  - !Sub 
                    - 'arn:aws:s3:::${S3BucketName}/*'
                    - S3BucketName: !FindInMap 
                        - CFTParameters
                        - S3Bucket
                        - Name
              - Effect: Allow
                Action:
                  - 's3:ListBucket'
                  - 's3:ListBucketVersions'
                Resource:
                  - !Sub 
                    - 'arn:aws:s3:::${S3BucketName}/*'
                    - S3BucketName: !FindInMap 
                        - CFTParameters
                        - S3Bucket
                        - Name
                  - !Sub 'arn:aws:s3:::${BlogArtifacts}/*'
              - Effect: Allow
                Action:
                  - 's3:DeleteObject'
                Resource:
                  - !Sub 'arn:aws:s3:::${BlogArtifacts}/*'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
    
  #### Pinpoint project creation ####
  PinpointApp:
    Type: 'AWS::Pinpoint::App'
    Properties:
      Name: 'customerIVRJourneyMapPinpointProject'
  
  EnableSMSChannelToPinPointProject:
    Type: 'AWS::Pinpoint::SMSChannel'
    Properties:
      ApplicationId: !Ref PinpointApp
      Enabled: 'True'

  #### DynamoDB table creation - customerIVRJourneyMap #####
  DDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: ID
          AttributeType: N
      KeySchema:
        - AttributeName: ID
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      TableName: customerIVRJourneyMap
      TimeToLiveSpecification:
        AttributeName: expiryTime
        Enabled: true

  #### Lambda function FetchCustomerJourneyMap #####
  FetchCustomerJourneyMap:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
            const { DynamoDBDocumentClient, PutCommand, ScanCommand } = require('@aws-sdk/lib-dynamodb');
            const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');

            const client = new DynamoDBClient({});
            const docClient = DynamoDBDocumentClient.from(client);
                      
            exports.handler = async (event, context, cb) => {
              // TODO implement
              
              var items;
              var resultMap;
                            
              const d_t = new Date();

              let year = d_t.getFullYear();
              let month = ("0" + (d_t.getMonth() + 1)).slice(-2);
              let day = ("0" + d_t.getDate()).slice(-2);
              let hour = d_t.getHours();
              let minute = d_t.getMinutes();
              let seconds = d_t.getSeconds();
              
              let currentDateTime = year + "" + month + "" + day + "" + hour + "" + minute + "" + seconds;
              
              const command = new ScanCommand({
                ProjectionExpression: "#ID, Channel, ContactID, CustomerNumber, FlowType, InitialContactID, InteractionDateTime, CustomerJourneyFlow",
                ExpressionAttributeNames: { "#ID": "ID",  },
                TableName: "customerIVRJourneyMap",
              });
              
              let callStatsDataObj = [];
              
              //var tableData = await docClient.send({ TableName: "customerIVRJourneyMap", Select: "COUNT" });
              var tableData = await docClient.send(command);
              
              for (const item of tableData.Items) {
                console.log(`${item.ID}`);
              }
              
              console.log("No Of Items in table : " + tableData.Count + ", currentDateTime : " + currentDateTime.toString());
              
              for (const element of tableData.Items) {
                                  
                console.log("printing : " + JSON.stringify(element));
                var data = {
                      ID: element.ID,
                      Channel: element.Channel,
                      ContactID : element.ContactID,
                      CustomerNumber : element.CustomerNumber,
                      FlowType : element.FlowType,
                      InitialContactID : element.InitialContactID,
                      InteractionDateTime : element.InteractionDateTime,
                      CustomerJourneyFlow : element.CustomerJourneyFlow
                  }
                  
                  callStatsDataObj.push(data);
                  console.log("callStatsDataObj : " + JSON.stringify(callStatsDataObj));
              }
              
              let body = {
                      "eventName": "OnGetCallStatsData",
                      "channel":"",
                      "contactID":"",
                      "eventInfo": callStatsDataObj
              };
              
              return {
                  "statusCode": 200,
                  "headers": {
                      "Content-Type": "application/json",
                      "Access-Control-Allow-Origin" : "*", // Required for CORS support to work
                      "Access-Control-Allow-Credentials" : true
                  },
                  "body": JSON.stringify(body)
              };
            };

      FunctionName: FetchCustomerJourneyMap
      Handler: index.handler
      Role: !GetAtt IAMRole.Arn
      Runtime: nodejs20.x
      Timeout: 120
    
  #### Lambda function SaveCFAttributeData #####
  SaveCFAttributeData:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
            'use strict';                
            const { DynamoDBDocumentClient, PutCommand, ScanCommand } = require('@aws-sdk/lib-dynamodb');
            const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
            const client = new DynamoDBClient({});
            const docClient = DynamoDBDocumentClient.from(client);

            exports.handler = async (event, context, cb) => 
            {
              // TODO implement
              
              var items;
              var resultMap;
                            
              const d_t = new Date();

              let year = d_t.getFullYear();
              let month = ("0" + (d_t.getMonth() + 1)).slice(-2);
              let day = ("0" + d_t.getDate()).slice(-2);
              let hour = d_t.getHours();
              let minute = d_t.getMinutes();
              let seconds = d_t.getSeconds();
              
              let currentDateTime = year + "" + month + "" + day + "" + hour + "" + minute + "" + seconds;
              
              const command = new ScanCommand({
                ProjectionExpression: "#ID, Channel, ContactId, CustomerNumber, FlowType, InitialContactID, InteractionDateTime, CustomerJourneyFlow",
                ExpressionAttributeNames: { "#ID": "ID" },
                TableName: "customerIVRJourneyMap",
              });
              
              //var tableData = await docClient.send({ TableName: "customerIVRJourneyMap", Select: "COUNT" });
              var tableData = await docClient.send(command);
              
              console.log("No Of Items in table : " + tableData.Count + ", currentDateTime : " + currentDateTime.toString());
                            
              var id = ++tableData.Count; 
              
              console.log("inserting item id : " + id); 
              
              var item = {
                ID: id,
                Channel: event.Details.ContactData.Channel.toString(),
                ContactID: event.Details.ContactData.ContactId.toString(),
                CustomerNumber: event.Details.ContactData.CustomerEndpoint.Address.toString(),
                FlowType: event.Details.Parameters.flowType.toString(),
                InitialContactID: event.Details.ContactData.InitialContactId.toString(),
                InteractionDateTime: currentDateTime.toString(),
                CustomerJourneyFlow: event.Details.Parameters.Intent.toString()
              }
              
              const put_command = new PutCommand({
                TableName: "customerIVRJourneyMap",
                Item: item,
              });
              
              const response = await docClient.send(put_command);
              console.log(response);
              
                return resultMap = {
                    RetVal:'Success'
                };
            };


      FunctionName: SaveCFAttributeData
      Handler: index.handler
      Role: !GetAtt IAMRole.Arn
      Runtime: nodejs20.x
      Timeout: 120

    
  #### Lambda function SendSMSFromContactFlow #####
  SendSMSFromContactFlow:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
            
            const { PinpointClient, SendMessagesCommand } = require('@aws-sdk/client-pinpoint'); // ES Modules import
            const client = new PinpointClient({});

            //main entry 
            exports.handler = async (event, context, cb) => 
            {
              console.log("Incoming Event: " + JSON.stringify(event));

              //You must have these attributes set in your Contact Flow prior to invoking lambda function
              var destinationNumber = event.Details.ContactData.CustomerEndpoint.Address;
              var messageContent = event.Details.Parameters.message;
              var projectID = process.env.pinpointSMSProjectID;
              
              console.log("destinationNumber : " + destinationNumber + ", messageContent : " + messageContent + ", projectID : " + projectID);
              
              var params = {
                  //ApplicationId must match the ID of the application you created in AWS Mobile Hub
                  ApplicationId: projectID,
                  MessageRequest: {
                      Addresses: {
                          [destinationNumber]: {
                              ChannelType: "SMS",
                          },
                      },
                      MessageConfiguration: {
                          SMSMessage: {
                              Body: messageContent,
                              MessageType: "TRANSACTIONAL",
                              SenderId: "AWS",
                          }
                      },
                  }
              };
              
              const command = new SendMessagesCommand(params);
              const response = await client.send(command);
              console.log("sms response : " + JSON.stringify(response))
              
              if (response.$metadata.httpStatusCode == 200)
              {
                  console.log('buildResponse true')
                  return {
                      lambdaResult: "Success"
                  }
              }
              else
              {
                  console.log('buildResponse false')
                  return {
                      lambdaResult: "Error"
                  }
              }
            };                

      FunctionName: SendSMSFromContactFlow
      Handler: index.handler
      Role: !GetAtt IAMRole.Arn
      Runtime: nodejs20.x
      Timeout: 120
      Environment:
        Variables:
          pinpointSMSProjectID: !Ref PinpointApp


  #### Lambda function CJMApiGatewayAuthorizer #####
  CJMApiGatewayAuthorizer:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
            'use strict';
          
            exports.handler = async (event, context, callback) => {

              console.log("event data : " + JSON.stringify(event));
            
              // Retrieve request parameters from the Lambda function input:
              var headers = event.headers;
              var queryStringParameters = event.queryStringParameters;
              var pathParameters = event.pathParameters;
              var stageVariables = event.stageVariables;
                  
              // Parse the input for the parameter values
              var tmp = event.methodArn.split(':');
              var apiGatewayArnTmp = tmp[5].split('/');
              var awsAccountId = tmp[4];
              var region = tmp[3];
              var restApiId = apiGatewayArnTmp[0];
              var stage = apiGatewayArnTmp[1];
              var method = apiGatewayArnTmp[2];
              var resource = '/'; // root resource
              if (apiGatewayArnTmp[3]) {
                  resource += apiGatewayArnTmp[3];
              }
              
              // Perform authorization to return the Allow policy for correct parameters and 
              // the 'Unauthorized' error, otherwise.
              var authResponse = {};
              var condition = {};
              condition.IpAddress = {};
              
              console.log("queryStringParameters.queryValue1 : " + queryStringParameters.queryValue1);
              
              if (queryStringParameters.queryValue1 === "CJMApiAuth") 
              {
                  callback(null, generateAllow('me', event.methodArn));
              }  else {
                  callback("Unauthorized");
              }
            };

            // Help function to generate an IAM policy
            var generatePolicy = function(principalId, effect, resource) {
              // Required output:
              var authResponse = {};
              authResponse.principalId = principalId;
              if (effect && resource) {
                  var policyDocument = {};
                  policyDocument.Version = '2012-10-17'; // default version
                  policyDocument.Statement = [];
                  var statementOne = {};
                  statementOne.Action = 'execute-api:Invoke'; // default action
                  statementOne.Effect = effect;
                  statementOne.Resource = resource;
                  policyDocument.Statement[0] = statementOne;
                  authResponse.policyDocument = policyDocument;
              }
              // Optional output with custom properties of the String, Number or Boolean type.
              authResponse.context = {
                  "stringKey": "stringval",
                  "numberKey": 123,
                  "booleanKey": true
              };
              return authResponse;
            }

            var generateAllow = function(principalId, resource) {
              return generatePolicy(principalId, 'Allow', resource);
            }
              
            var generateDeny = function(principalId, resource) {
              return generatePolicy(principalId, 'Deny', resource);
            }


      FunctionName: CJMApiGatewayAuthorizer
      Handler: index.handler
      Role: !GetAtt IAMRole.Arn
      Runtime: nodejs20.x
      Timeout: 120

    
  #### Lambda function to copy source files from AWS resources #####
  CustomResourceCopySourceFunction:
    Type: 'AWS::Lambda::Function'
    Properties:
      ReservedConcurrentExecutions: 10
      Role: !GetAtt CopyArtifactsLambdaIamRole.Arn
      Handler: index.handler
      Runtime: nodejs18.x
      Code:
        ZipFile: >
          // Copyright 2022 Amazon.com and its affiliates; all rights reserved.
          // This file is Amazon Web Services Content and may not be duplicated or
          // distributed without permission.

          const {S3Client, CopyObjectCommand, DeleteObjectsCommand } = require("@aws-sdk/client-s3");
          const s3 = new S3Client();
          const response = require('cfn-response');

          const sourceObjectArray = [
            'CustomerIVRJourneyMap.zip'            
          ];

          exports.handler = async (event, context) => {
              const sourceBucket = event.ResourceProperties.SourceBucket;
              const sourcePrefix = event.ResourceProperties.SourcePrefix;
              console.log(JSON.stringify(event));
              var result = {responseStatus: 'FAILED', responseData: {Data: 'Never updated'}};
              try {
                  console.log(`Received event with type ${event.RequestType}`); 
                  if(event.RequestType === 'Create' || event.RequestType === 'Update') {
                      var copyResult = await Promise.all(
                          sourceObjectArray.map( async (object) => {
                            console.log('Copying with', object, `${sourceBucket}/${sourcePrefix}/${object}`)
                            const input = {
                              "Bucket": event.ResourceProperties.BlogArtifacts,
                              "CopySource": `${sourceBucket}/${sourcePrefix}/${object}`,
                              "Key": object
                            };
                            const command = new CopyObjectCommand(input);
                            const s3Result = await s3.send(command);
                            console.log(`Finished uploading File with result ${JSON.stringify(s3Result, 0, 4)}`);
                          }),
                      );
                      result.responseStatus = 'SUCCESS';
                      result.responseData['Data'] = 'Successfully uploaded files';
                  } else if (event.RequestType === 'Delete') {
                      console.log('Deleting objects from S3 : ' + event.ResourceProperties.BlogArtifacts);
                      var deleteobjects = [];
                      var cleanupResult = await Promise.all(
                          sourceObjectArray.map( async (object) => {
                            deleteobjects.push({Key : object});
                          }),
                      );                        
                      var input = {
                            Bucket: event.ResourceProperties.BlogArtifacts,
                            Delete: { Objects: deleteobjects}
                        };
                      const command = new DeleteObjectsCommand(input);
                      console.log(JSON.stringify(command));
                      try{
                        const s3Result = await s3.send(command);                  
                        console.log(`Finished deleting files with result ${JSON.stringify(s3Result, 0, 4)}`);
                      }catch(e){
                        console.log(e);
                      }
                      
                      result.responseStatus = 'SUCCESS',
                      result.responseData['Data'] = 'Successfully deleted files';
                  }
              } catch (error) {
                  console.log(JSON.stringify(error, 0, 4));
                  result.responseStatus = 'FAILED';
                  result.responseData['Data'] = 'Failed to process event';
              } finally {
                console.log('Sending the response back to CloudFormation');    
                var resp;
                try{
                  resp = await responsePromise(event, context, result.responseStatus, result.responseData, `mainstack`);
                  console.log(JSON.stringify(resp));
                  return resp;
                }catch(e){
                  console.log(e);        
                }
                  
              }
          };
          function responsePromise(event, context, responseStatus, responseData, physicalResourceId) {
              return new Promise(() => response.send(event, context, responseStatus, responseData, physicalResourceId));
          }
      Timeout: 50
      
  #### IAM Role #####
  IAMRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      Policies:
        - PolicyName: Policy_api-lambda-db
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - 'dynamodb:BatchGetItem'
                  - 'dynamodb:BatchWriteItem'
                  - 'dynamodb:TagResource'
                  - 'dynamodb:UntagResource'
                  - 'dynamodb:PutItem'
                  - 'dynamodb:DeleteItem'
                  - 'dynamodb:GetItem'
                  - 'dynamodb:Scan'
                  - 'dynamodb:Query'
                  - 'dynamodb:UpdateItem'
                  - 'connect:list*'
                  - 'connect:update*'
                  - 'connect:create*'
                  - 'connect:Disassociate*'
                  - 'connect:associate*'
                Resource: '*'
              - Effect: Allow
                Action:
                  - 'logs:CreateLogStream'
                  - 'logs:CreateLogGroup'
                  - 'logs:PutLogEvents'
                Resource: '*'

  ConnectFunctionPolicyFetchCustomerJourneyMap:
    Type: AWS::Lambda::Permission
    DependsOn: FetchCustomerJourneyMap
    Properties:
      FunctionName: !Sub 'FetchCustomerJourneyMap'
      Action: "lambda:InvokeFunction"
      Principal:  "apigateway.amazonaws.com"
      #SourceAccount: !Sub ${AWS::AccountId}

  ConnectFunctionPolicySaveCFAttributeData:
    Type: AWS::Lambda::Permission
    DependsOn: SaveCFAttributeData
    Properties:
      FunctionName: !Sub 'SaveCFAttributeData'
      Action: "lambda:InvokeFunction"
      Principal:  "connect.amazonaws.com"
      #SourceAccount: !Sub ${AWS::AccountId}

  ConnectFunctionPolicyCJMApiGatewayAuthorizer:
    Type: AWS::Lambda::Permission
    DependsOn: CJMApiGatewayAuthorizer
    Properties:
      FunctionName: !Sub 'CJMApiGatewayAuthorizer'
      Action: "lambda:InvokeFunction"
      Principal:  "apigateway.amazonaws.com"
      #SourceAccount: !Sub ${AWS::AccountId}

  #### S3 bucket creation #####
  createWebSiteS3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
        BucketName: !Ref CFS3BucketForWebSite
        AccessControl: BucketOwnerFullControl
        PublicAccessBlockConfiguration:
            BlockPublicAcls: False
            BlockPublicPolicy: True
            IgnorePublicAcls: False
            RestrictPublicBuckets: True
        WebsiteConfiguration:
            IndexDocument: index.html
            ErrorDocument: error.html
        OwnershipControls:
            Rules:
              - ObjectOwnership: BucketOwnerPreferred          
    DependsOn: ConnectFunctionPolicyFetchCustomerJourneyMap    

  CFCloudFrontDistributionAccessIdentity:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
        OriginAccessControlConfig:
            Name: 'CloudFront endpoint for IVR Journey Map'
            OriginAccessControlOriginType: s3
            SigningBehavior: never
            SigningProtocol: sigv4
    
  #### CloudFront distribution creation #####
  CFCloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
        - DomainName:
            !Join
                - ''
                - - !Ref CFS3BucketForWebSite
                  - .s3.amazonaws.com
          Id: !Ref CFS3BucketForWebSite
          OriginPath: '/ivrjourneymap'
          S3OriginConfig: 
            OriginAccessIdentity: ''
          OriginAccessControlId: !GetAtt CFCloudFrontDistributionAccessIdentity.Id            
        Enabled: 'true'
        Logging:
            Bucket: !GetAtt createWebSiteS3Bucket.DomainName
            Prefix: 'logs/'
            IncludeCookies: 'true'
        Comment: CustomerIVRJourneyMapDashboard
        DefaultRootObject: index.html
        DefaultCacheBehavior:
            AllowedMethods:
                - DELETE
                - GET
                - HEAD
                - OPTIONS
                - PATCH
                - POST
                - PUT
            TargetOriginId: !Ref CFS3BucketForWebSite
            ForwardedValues:
                QueryString: true
                Cookies:
                    Forward: all
            ViewerProtocolPolicy: redirect-to-https  

  CFS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    DependsOn:
        - CFCloudFrontDistributionAccessIdentity
    Properties:
        Bucket: !Ref createWebSiteS3Bucket
        PolicyDocument:
            Statement:
                -
                    Action:
                        - "s3:GetObject"
                    Effect: "Allow"
                    Principal: 
                        Service: cloudfront.amazonaws.com
                    Resource:
                        !Sub ${createWebSiteS3Bucket.Arn}/ivrjourneymap/*
                    Condition:
                        StringEquals: 
                          'AWS:SourceArn': !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/${CFCloudFrontDistribution}

    
  #### Trigger Lambda function to copy source files from AWS resources #####
  CopyCfnStacksLambdaTrigger:
    Type: 'Custom::CopyCfnResources'
    Properties:
      ServiceToken: !GetAtt CustomResourceCopySourceFunction.Arn
      BlogArtifacts: !Ref BlogArtifacts
      SourceBucket: !FindInMap 
        - CFTParameters
        - S3Bucket
        - Name
      SourcePrefix: !FindInMap 
        - CFTParameters
        - S3Prefix
        - Value
    DependsOn: CustomResourceCopySourceFunction
    
  CFWebsiteCreatorRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service:
                - "lambda.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        -
          PolicyName: !Sub ${AWS::StackName}-queue-creator-policy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: "Allow"
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream'
                  - 'logs:PutLogEvents'
                Resource:
                  - !Sub "arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
              -
                Effect: "Allow"
                Action:
                  - "s3:PutObject"
                  - "s3:GetObject"
                  - "s3:PutObjectAcl"
                Resource:
                  -  !Join
                      - ''
                      - - 'arn:'
                        - !Ref 'AWS::Partition'
                        - ':s3:::'
                        - !Ref CFS3BucketForWebSite
                        - '/*'
              -
                Effect: "Allow"
                Action:
                  - "s3:PutBucketPublicAccessBlock"
                Resource:
                  -  !Join
                      - ''
                      - - 'arn:'
                        - !Ref 'AWS::Partition'
                        - ':s3:::'
                        - !Ref CFS3BucketForWebSite
              -
                Effect: "Allow"
                Action:
                  - "s3:GetObject"
                Resource:
                  -  !Join
                      - ''
                      - - 'arn:'
                        - !Ref 'AWS::Partition'
                        - ':s3:::'
                        - 'amazon-connect-blogs2'
                        - '/*'

  #### Website creator #####
  webSiteCreator:
    Type: "AWS::Lambda::Function"
    Properties:
        Description:  >
            AWS Lambda Function that will create the website and upload it to the S3 bucket
        Handler: "index.handler"
        Role: !GetAtt CFWebsiteCreatorRole.Arn
        Runtime: "nodejs20.x"
        MemorySize: 256
        Timeout: 120
        Code:
            S3Bucket: !Ref BlogArtifacts
            S3Key: CustomerIVRJourneyMap.zip
    DependsOn: CopyCfnStacksLambdaTrigger

  #### API Gateway #####
  CJMAPIGateway:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: "CustomerJourneyMap"
      Description: "Customer Journey Map"                     
    DependsOn: createWebSiteS3Bucket

  CJMAPIGatewayResource:
    Type: 'AWS::ApiGateway::Resource'
    Properties:
      RestApiId: !Ref CJMAPIGateway
      ParentId: !GetAtt 
        - CJMAPIGateway
        - RootResourceId
      PathPart: "customerJourney"
    
  CJMAPIGatewayRootMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      ResourceId: !Ref CJMAPIGatewayResource
      RestApiId: !Ref CJMAPIGateway
      AuthorizationType: CUSTOM
      AuthorizerId: !Ref CJMApiAuthorizer
      HttpMethod: "POST"
      Integration:
        IntegrationHttpMethod: "POST"
        Type: "AWS"
        Uri: !Sub
          - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FetchCustomerJourneyMap.Arn}/invocations"
          - lambdaArn: !GetAtt FetchCustomerJourneyMap.Arn
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'"
              method.response.header.Access-Control-Allow-Methods: "'GET,POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates:
              application/json: ''
        PassthroughBehavior: WHEN_NO_MATCH
        RequestTemplates:
          application/json: '{"statusCode": 200}'
      MethodResponses:
        - ResponseModels:
            application/json: 'Empty'
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true            
          StatusCode: 200

  CJMAPIGatewayRootOPTIONSMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      ResourceId: !Ref CJMAPIGatewayResource
      RestApiId: !Ref CJMAPIGateway
      AuthorizationType: CUSTOM
      AuthorizerId: !Ref CJMApiAuthorizer
      HttpMethod: "OPTIONS"
      Integration:
        IntegrationResponses:
          - StatusCode: 200
            ResponseParameters:
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,X-Amz-Security-Token,Authorization,X-Api-Key,X-Requested-With,Accept,Access-Control-Allow-Methods,Access-Control-Allow-Origin,Access-Control-Allow-Headers'"
              method.response.header.Access-Control-Allow-Methods: "'GET,POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Origin: "'*'"
            ResponseTemplates:
              application/json: ''
        PassthroughBehavior: WHEN_NO_MATCH
        RequestTemplates:
          application/json: '{"statusCode": 200}'
        Type: MOCK  
      MethodResponses:
        - ResponseModels:
            application/json: 'Empty'
          ResponseParameters:
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Origin: true          
          StatusCode: 200
              
  CJMApiAuthorizer:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      Name: CJMApiDefaultAuthorizer
      RestApiId: !Ref CJMAPIGateway
      Type: REQUEST
      AuthorizerUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CJMApiGatewayAuthorizer.Arn}/invocations
      AuthorizerResultTtlInSeconds: 0
      IdentitySource: method.request.querystring.queryValue1 

  CJMAPIGatewayDeployment:
    Type: "AWS::ApiGateway::Deployment"
    DependsOn:
      - CJMAPIGatewayRootMethod
      - CJMAPIGatewayRootOPTIONSMethod
    Properties:
      RestApiId: !Ref CJMAPIGateway
      StageName: "Prod"
                      
  #### invokeWebSiteCreator #####
  invokeWebSiteCreator:
    Type: Custom::CreateWebSite
    DependsOn: 
      - CopyCfnStacksLambdaTrigger
      - CJMAPIGatewayDeployment
    Properties:  
        ServiceToken:  !GetAtt webSiteCreator.Arn
        customAction: configureWebsite
        Region: !Ref AWS::Region
        destS3Bucket: !Ref CFS3BucketForWebSite
        destS3KeyPrefix: ivrjourneymap
        apiGatewayURL: !Sub "https://${CJMAPIGateway}.execute-api.${AWS::Region}.amazonaws.com/Prod/customerJourney" 

  ####### Lex bot configuration section #####
  BotRuntimeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lexv2.amazonaws.com
            Action:
              - "sts:AssumeRole"
      Path: "/"
      Policies:
        - PolicyName: LexRuntimeRolePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - "polly:SynthesizeSpeech"
                  - "comprehend:DetectSentiment"
                Resource: "*"

  ConnectCustomerJourneyTemplateBot:
    DependsOn: BotRuntimeRole
    Type: AWS::Lex::Bot
    Properties:
      Name: !FindInMap [CFTParameters, CFLexBotName, Name]
      RoleArn: !GetAtt BotRuntimeRole.Arn
      DataPrivacy:
        ChildDirected: false
      IdleSessionTTLInSeconds: 300
      Description: "Customer journey map bot"
      AutoBuildBotLocales: true
      BotLocales:
        - LocaleId: "en_US"
          Description: "Customer journey map bot locale"
          NluConfidenceThreshold: 0.40
          VoiceSettings:
            VoiceId: "Ivy"         
          Intents:
            - Name: "Cx_Promotions"
              Description: "Intent to handle promotions requests"
              SampleUtterances:
                - Utterance: "latest promotion requests"
                - Utterance: "can i know what are the new promotions available"
                - Utterance: "early promotions available"
                - Utterance: "early promotions"
                - Utterance: "promotions"
            - Name: "Cx_MobileEnquiries"
              Description: "Intent to handle mobile enquiries "
              SampleUtterances:
                - Utterance: "mobileenquiries"
                - Utterance: "mobile enquiries"
                - Utterance: "mobile"
                - Utterance: "I would like to enquiry about my mobile"
                - Utterance: "regarding mobile enquiries"
            - Name: "Cx_BroadbandEnquiries"
              Description: "Intent to handle broadband enquiries "
              SampleUtterances:
                - Utterance: "broadbandenquiries"
                - Utterance: "broadband enquiries"
                - Utterance: "broadband"
                - Utterance: "I would like to enquiry about my home broadband"
                - Utterance: "regarding broadband enquiries"
            - Name: "Cx_BillingEnquiries"
              Description: "Intent to handle billing enquiries "
              SampleUtterances:
                - Utterance: "billingbandenquiries"
                - Utterance: "billing enquiries"
                - Utterance: "billing"
                - Utterance: "I would like to enquiry about my billing"
                - Utterance: "regarding billing enquiries"                                
            - Name: "FallbackIntent"
              Description: "Default intent when no other intent matches"
              ParentIntentSignature: "AMAZON.FallbackIntent"

  # 3. Define a bot version that depends on the DRAFT version of the Lex Bot.
  ConnectCustomerJourneyTemplateBotVersionWithCFN:
    DependsOn: ConnectCustomerJourneyTemplateBot
    Type: AWS::Lex::BotVersion
    Properties:
      BotId: !Ref ConnectCustomerJourneyTemplateBot
      BotVersionLocaleSpecification:
        - LocaleId: en_US
          BotVersionLocaleDetails:
            SourceBotVersion: DRAFT
      Description: ConnectCustomerJourneyTemplateBot Version

  # 4. Define the alias by providing the bot version created by the 
  #    AWS::Lex::BotVersion resource above.
  BotAlias:
    DependsOn: ConnectCustomerJourneyTemplateBotVersionWithCFN
    Type: AWS::Lex::BotAlias
    Properties:
      BotId: !Ref ConnectCustomerJourneyTemplateBot
      BotAliasName: "CustomerJourneyBotAlias"
      BotVersion: !GetAtt ConnectCustomerJourneyTemplateBotVersionWithCFN.BotVersion
      SentimentAnalysisSettings:
        DetectSentiment: true

  AssociateLexBotToConnectInstance:
    DependsOn: BotAlias
    Type: AWS::Connect::IntegrationAssociation
    Properties:
      InstanceId: !Ref AmazonConnectInstanceARN
      IntegrationType: LEX_BOT
      IntegrationArn: !Sub "arn:aws:lex:${AWS::Region}:${AWS::AccountId}:bot-alias/${ConnectCustomerJourneyTemplateBot}/${BotAlias.BotAliasId}"

  AssociateSaveCFAttributeDataLambdaToConnectInstance:
    DependsOn: AssociateLexBotToConnectInstance
    Type: AWS::Connect::IntegrationAssociation
    Properties:
      InstanceId: !Ref AmazonConnectInstanceARN
      IntegrationType: LAMBDA_FUNCTION
      IntegrationArn: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${SaveCFAttributeData}"

  AssociateSendSMSFromContactFlowLambdaToConnectInstance:
    DependsOn: AssociateSaveCFAttributeDataLambdaToConnectInstance
    Type: AWS::Connect::IntegrationAssociation
    Properties:
      InstanceId: !Ref AmazonConnectInstanceARN
      IntegrationType: LAMBDA_FUNCTION
      IntegrationArn: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${SendSMSFromContactFlow}"

  LambdaToCreateContactFlowPreInfo:
    DependsOn: AssociateSendSMSFromContactFlowLambdaToConnectInstance
    Type: AWS::CloudFormation::CustomResource
    Version: "1.0"
    Properties:
      ServiceToken: !GetAtt LambdaToCreateContactFlow.Arn

  LambdaToCreateContactFlow:
    Type: AWS::Lambda::Function
    #DependsOn:
      #- AdminUser
    Properties:
      Description: Function to disassociate task channel from basic queue in basic routing profile
      Environment:
        Variables:
          INSTANCE_ARN: !Ref AmazonConnectInstanceARN
          BOTALIAS_ID: !GetAtt BotAlias.Arn
          SAVE_CF_ATTRIBUTE_DATA_ARN: !GetAtt SaveCFAttributeData.Arn
          SEND_SMS_FROM_CONTACTFLOW_ARN: !GetAtt SendSMSFromContactFlow.Arn
      Code:
        ZipFile: |
          # MIT No Attribution

          # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.

          # Permission is hereby granted, free of charge, to any person obtaining a copy of this
          # software and associated documentation files (the "Software"), to deal in the Software
          # without restriction, including without limitation the rights to use, copy, modify,
          # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
          # permit persons to whom the Software is furnished to do so.

          # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
          # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
          # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
          # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
          # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
          # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

          import json
          import boto3
          import os
          import cfnresponse

          client = boto3.client('connect')
          kms = boto3.client('kms')

          InstanceArn= os.environ['INSTANCE_ARN']
          BotAliasID= os.environ['BOTALIAS_ID']
          SaveCFAttributeDataArn= os.environ['SAVE_CF_ATTRIBUTE_DATA_ARN']
          SendSMSFromContactFlowArn= os.environ['SEND_SMS_FROM_CONTACTFLOW_ARN']

          # This Lambda retrieves the details required to create a Quick Connect to trasnsfer to the Basic Queue
          def lambda_handler(event, context):
              print(event)

              if event['RequestType'] == 'Create':
                  print(event)

                  # List RoutingProfiles and Security Profiles
                  try:
                      InstanceId = InstanceArn[-36:]

                      # List Queues
                      responseListQueues = client.list_queues(
                          InstanceId=InstanceId,
                          QueueTypes=['STANDARD'],
                          MaxResults=10
                      )

                      print('>>> Returned Value responseListQueues :' + json.dumps(responseListQueues, indent=4, sort_keys=True, default=str))
            
                      QList = responseListQueues ["QueueSummaryList"]
                      
                      for x in QList:
                        if x['Name'] == "BasicQueue":
                            BasicQueueArn=x['Arn']
                            print("BasicQueueArn= " + BasicQueueArn)
                            BasicQueueId=x['Id']
                            print("BasicQueueId= " + BasicQueueId)

                      # Creating contact flows

                      cf_json = '{"Version":"2019-10-30","StartAction":"1f888acf-edd6-4d0a-a062-8e7b7c080a6c","Metadata":{"entryPointPosition":{"x":14.4,"y":212.8},"ActionMetadata":{"d89a4381-4993-4892-9e0b-b6566a448a19":{"position":{"x":3364,"y":669.6}},"4d91f9b5-4ce0-4f19-b8e1-74e11e61d08a":{"position":{"x":1961.6,"y":566.4}},"41e63342-ce36-4476-82d0-866d5f861ca9":{"position":{"x":3308.8,"y":1414.4}},"26c9bd0c-1ba8-4c28-b31e-ea44c18ea0c0":{"position":{"x":1629.6,"y":1121.6},"dynamicParams":[]},"4a4d64b4-7c0a-425d-b704-9d6083abcc44":{"position":{"x":1629.6,"y":1316},"dynamicParams":[]},"94c37a0e-aadb-460b-8def-3569b0f6a432":{"position":{"x":957.6,"y":804},"dynamicParams":[]},"591d48e9-ac46-4d6e-92f7-fc2836f7bbee":{"position":{"x":1197.6,"y":736.8},"conditionMetadata":[{"id":"d17a5aeb-483d-43c4-b380-181891722cb2","value":"1"},{"id":"bfc725fe-1b13-4895-b7ee-4d0b78b94f22","value":"2"},{"id":"50561ed3-c6fb-474b-976e-85de283af3dc","value":"0"}]},"58b8f426-916f-4667-a465-34f16e98e770":{"position":{"x":2088.8,"y":793.6},"dynamicParams":[]},"52ae21b1-702a-4046-9418-a5dffd53ceff":{"position":{"x":3777.6,"y":230.4}},"293c29b4-56eb-4ba8-a777-32e470b66a23":{"position":{"x":3073.6,"y":148}},"2fbf2d2f-f055-45f8-a25a-9490134931f6":{"position":{"x":3112.8,"y":656.8}},"743bfe4e-0727-43be-9619-22be5f277377":{"position":{"x":2470.4,"y":576}},"090b2b73-5fd0-47d5-93ef-c8e5b5c0a4a2":{"position":{"x":3792,"y":657.6}},"831aa7ff-6a97-4535-9436-32ac296beea8":{"position":{"x":2377.6,"y":799.2},"parameters":{"LambdaFunctionARN":{"displayName":"SaveCFAttributeData"}},"dynamicMetadata":{"flowType":false,"Intent":false}},"7ba265fb-2a0a-4be8-bfc0-5468d73a017d":{"position":{"x":2870.4,"y":632},"parameters":{"LambdaFunctionARN":{"displayName":"SaveCFAttributeData"}},"dynamicMetadata":{"flowType":false,"Intent":false}},"2c383d0d-dd77-4d23-941e-86cbfd8eb4b1":{"position":{"x":2797.6,"y":1241.6}},"df4014e0-7b6c-426e-9600-25d1a4f26e85":{"position":{"x":1960.8,"y":1169.6}},"47cf1897-7840-416a-a698-d1b8a4ff61d8":{"position":{"x":1974.4,"y":1389.6}},"4b0540e1-dec4-41dc-bf4a-0a7dc524f215":{"position":{"x":2328.8,"y":1140},"parameters":{"LambdaFunctionARN":{"displayName":"SaveCFAttributeData"}},"dynamicMetadata":{"flowType":false,"Intent":false}},"94f7124e-e3e4-4c3d-b5f9-4800e2c8b1d1":{"position":{"x":2796.8,"y":196.8}},"74b95be7-30d9-4e90-b280-06d547ae6cf9":{"position":{"x":2102.4,"y":74.4},"parameters":{"Attributes":{"secondNode":{"useDynamic":true}}},"dynamicParams":["secondNode"]},"be35e709-2e4a-4e39-afec-9d9f0abb8ff2":{"position":{"x":2792.8,"y":4},"parameters":{"LambdaFunctionARN":{"displayName":"SaveCFAttributeData"}},"dynamicMetadata":{"flowType":false,"Intent":false}},"20b7c81f-d690-49a5-99e0-6836b254d662":{"position":{"x":2426.4,"y":56.8},"parameters":{"LambdaFunctionARN":{"displayName":"SendSMSFromContactFlow"}},"dynamicMetadata":{"message":false}},"5246a380-b98d-4694-8417-103cb3138abd":{"position":{"x":2793.6,"y":356}},"236b2dc5-a937-4d91-ade7-0b7b736f8d72":{"position":{"x":3232,"y":900},"parameters":{"QueueId":{"displayName":"BasicQueue"}},"queue":{"text":"BasicQueue"}},"93a56a6f-17b8-43ad-9bd1-f643b44be49c":{"position":{"x":2424,"y":308},"parameters":{"LambdaFunctionARN":{"displayName":"SaveCFAttributeData"}},"dynamicMetadata":{"flowType":false,"Intent":false}},"1f888acf-edd6-4d0a-a062-8e7b7c080a6c":{"position":{"x":165.6,"y":209.6}},"cc8a12d6-46d3-4bc6-bd3d-05ddc6c1ba04":{"position":{"x":1268.8,"y":102.4},"parameters":{"Attributes":{"firstNode":{"useDynamic":true}}},"dynamicParams":["firstNode"]},"39f5b0c8-a4d4-48b4-b361-ed365738aa99":{"position":{"x":1808.8,"y":26.4}},"771276b6-f114-4d8e-83d4-bfe1e93d8d8c":{"position":{"x":2102.4,"y":280.8},"parameters":{"Attributes":{"secondNode":{"useDynamic":true}}},"dynamicParams":["secondNode"]},"400f51f0-a8f0-4b17-af21-2b81a9fb843c":{"position":{"x":1494.4,"y":208.8},"parameters":{"LexV2Bot":{"AliasArn":{"displayName":"journeyAlias","useLexBotDropdown":true,"lexV2BotName":"customer-ivr-journey-map-lexbot"}}},"useLexBotDropdown":true,"lexV2BotName":"customer-ivr-journey-map-lexbot","lexV2BotAliasName":"journeyAlias","conditionMetadata":[{"id":"82c046c3-e8ee-41b0-8918-cf1dc8dd000d","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"Cx_BillingEnquiries"},{"id":"1e4c0f36-3831-46f8-8f5e-52f4c1c80ccc","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"Cx_Promotions"}]},"9445a743-0312-408b-82b6-2d23eba91d40":{"position":{"x":1025.6,"y":384},"parameters":{"Attributes":{"firstNode":{"useDynamic":true}}},"dynamicParams":["firstNode"]},"12e968f7-dd86-410d-807e-c9c40ed2beb0":{"position":{"x":909.6,"y":624}},"ed4e8033-1b10-4a36-9543-789188e14707":{"position":{"x":1024,"y":208}},"fa887bc8-736f-4e4c-8658-9f719ecd1ca3":{"position":{"x":400.8,"y":201.6}},"30715e44-0ae7-478e-aa0b-df771925e681":{"position":{"x":681.6,"y":201.6},"parameters":{"LexV2Bot":{"AliasArn":{"displayName":"journeyAlias","useLexBotDropdown":true,"lexV2BotName":"customer-ivr-journey-map-lexbot"}}},"useLexBotDropdown":true,"lexV2BotName":"customer-ivr-journey-map-lexbot","lexV2BotAliasName":"journeyAlias","conditionMetadata":[{"id":"43372630-c38c-441e-8365-a4a5b3438564","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"Cx_MobileEnquiries"},{"id":"7836f17f-4c85-4521-863f-8ab42ba533a5","operator":{"name":"Equals","value":"Equals","shortDisplay":"="},"value":"Cx_BroadbandEnquiries"}]}},"Annotations":[]},"Actions":[{"Parameters":{},"Identifier":"d89a4381-4993-4892-9e0b-b6566a448a19","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"Text":"Got it"},"Identifier":"4d91f9b5-4ce0-4f19-b8e1-74e11e61d08a","Type":"MessageParticipant","Transitions":{"NextAction":"743bfe4e-0727-43be-9619-22be5f277377"}},{"Parameters":{},"Identifier":"41e63342-ce36-4476-82d0-866d5f861ca9","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"Attributes":{"secondNode":"Pressed 1"},"TargetContact":"Current"},"Identifier":"26c9bd0c-1ba8-4c28-b31e-ea44c18ea0c0","Type":"UpdateContactAttributes","Transitions":{"NextAction":"df4014e0-7b6c-426e-9600-25d1a4f26e85","Errors":[{"NextAction":"df4014e0-7b6c-426e-9600-25d1a4f26e85","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Attributes":{"secondNode":"Pressed 2"},"TargetContact":"Current"},"Identifier":"4a4d64b4-7c0a-425d-b704-9d6083abcc44","Type":"UpdateContactAttributes","Transitions":{"NextAction":"47cf1897-7840-416a-a698-d1b8a4ff61d8","Errors":[{"NextAction":"47cf1897-7840-416a-a698-d1b8a4ff61d8","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Attributes":{"firstNode":"Non-Lex"},"TargetContact":"Current"},"Identifier":"94c37a0e-aadb-460b-8def-3569b0f6a432","Type":"UpdateContactAttributes","Transitions":{"NextAction":"591d48e9-ac46-4d6e-92f7-fc2836f7bbee","Errors":[{"NextAction":"591d48e9-ac46-4d6e-92f7-fc2836f7bbee","ErrorType":"NoMatchingError"}]}},{"Parameters":{"StoreInput":"False","InputTimeLimitSeconds":"5","Text":"press 1 to know the TeliVision packages available \\n press 2 to cancel existing connection \\n press 0 to speak to agent"},"Identifier":"591d48e9-ac46-4d6e-92f7-fc2836f7bbee","Type":"GetParticipantInput","Transitions":{"NextAction":"58b8f426-916f-4667-a465-34f16e98e770","Conditions":[{"NextAction":"26c9bd0c-1ba8-4c28-b31e-ea44c18ea0c0","Condition":{"Operator":"Equals","Operands":["1"]}},{"NextAction":"4a4d64b4-7c0a-425d-b704-9d6083abcc44","Condition":{"Operator":"Equals","Operands":["2"]}},{"NextAction":"58b8f426-916f-4667-a465-34f16e98e770","Condition":{"Operator":"Equals","Operands":["0"]}}],"Errors":[{"NextAction":"58b8f426-916f-4667-a465-34f16e98e770","ErrorType":"InputTimeLimitExceeded"},{"NextAction":"58b8f426-916f-4667-a465-34f16e98e770","ErrorType":"NoMatchingCondition"},{"NextAction":"58b8f426-916f-4667-a465-34f16e98e770","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Attributes":{"secondNode":"Pressed 0"},"TargetContact":"Current"},"Identifier":"58b8f426-916f-4667-a465-34f16e98e770","Type":"UpdateContactAttributes","Transitions":{"NextAction":"831aa7ff-6a97-4535-9436-32ac296beea8","Errors":[{"NextAction":"831aa7ff-6a97-4535-9436-32ac296beea8","ErrorType":"NoMatchingError"}]}},{"Parameters":{},"Identifier":"52ae21b1-702a-4046-9418-a5dffd53ceff","Type":"DisconnectParticipant","Transitions":{}},{"Parameters":{"Text":"Error executing lambda while updating status of Deflect"},"Identifier":"293c29b4-56eb-4ba8-a777-32e470b66a23","Type":"MessageParticipant","Transitions":{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff"}},{"Parameters":{"Text":"Error executing lambda while inserting queue status"},"Identifier":"2fbf2d2f-f055-45f8-a25a-9490134931f6","Type":"MessageParticipant","Transitions":{"NextAction":"d89a4381-4993-4892-9e0b-b6566a448a19"}},{"Parameters":{"Text":"please hold while we transfer your call to CSO."},"Identifier":"743bfe4e-0727-43be-9619-22be5f277377","Type":"MessageParticipant","Transitions":{"NextAction":"7ba265fb-2a0a-4be8-bfc0-5468d73a017d"}},{"Parameters":{},"Identifier":"090b2b73-5fd0-47d5-93ef-c8e5b5c0a4a2","Type":"TransferContactToQueue","Transitions":{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff","Errors":[{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff","ErrorType":"QueueAtCapacity"},{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff","ErrorType":"NoMatchingError"}]}},{"Parameters":{"LambdaFunctionARN":"arn:aws:lambda:ca-central-1:201089190273:function:SaveCFAttributeData","InvocationTimeLimitSeconds":"3","LambdaInvocationAttributes":{"flowType":"Non-Lex","Intent":"$.Attributes.firstNode -> $.Attributes.secondNode"},"ResponseValidation":{"ResponseType":"STRING_MAP"}},"Identifier":"831aa7ff-6a97-4535-9436-32ac296beea8","Type":"InvokeLambdaFunction","Transitions":{"NextAction":"236b2dc5-a937-4d91-ade7-0b7b736f8d72","Errors":[{"NextAction":"236b2dc5-a937-4d91-ade7-0b7b736f8d72","ErrorType":"NoMatchingError"}]}},{"Parameters":{"LambdaFunctionARN":"arn:aws:lambda:ca-central-1:201089190273:function:SaveCFAttributeData","InvocationTimeLimitSeconds":"3","LambdaInvocationAttributes":{"flowType":"AgentQueue","Intent":"$.Attributes.firstNode"},"ResponseValidation":{"ResponseType":"STRING_MAP"}},"Identifier":"7ba265fb-2a0a-4be8-bfc0-5468d73a017d","Type":"InvokeLambdaFunction","Transitions":{"NextAction":"236b2dc5-a937-4d91-ade7-0b7b736f8d72","Errors":[{"NextAction":"2fbf2d2f-f055-45f8-a25a-9490134931f6","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Thank you\\nGood Bye"},"Identifier":"2c383d0d-dd77-4d23-941e-86cbfd8eb4b1","Type":"MessageParticipant","Transitions":{"NextAction":"41e63342-ce36-4476-82d0-866d5f861ca9"}},{"Parameters":{"Text":"Family Fun : English and Chinese entertainment plus News channels at $23.90 cents per month"},"Identifier":"df4014e0-7b6c-426e-9600-25d1a4f26e85","Type":"MessageParticipant","Transitions":{"NextAction":"4b0540e1-dec4-41dc-bf4a-0a7dc524f215"}},{"Parameters":{"Text":"Sorry for the inconvenience caused to cancel. We will cancel your subscription"},"Identifier":"47cf1897-7840-416a-a698-d1b8a4ff61d8","Type":"MessageParticipant","Transitions":{"NextAction":"4b0540e1-dec4-41dc-bf4a-0a7dc524f215"}},{"Parameters":{"LambdaFunctionARN":"arn:aws:lambda:ca-central-1:201089190273:function:SaveCFAttributeData","InvocationTimeLimitSeconds":"3","LambdaInvocationAttributes":{"flowType":"Non-Lex","Intent":"$.Attributes.firstNode -> $.Attributes.secondNode"},"ResponseValidation":{"ResponseType":"STRING_MAP"}},"Identifier":"4b0540e1-dec4-41dc-bf4a-0a7dc524f215","Type":"InvokeLambdaFunction","Transitions":{"NextAction":"2c383d0d-dd77-4d23-941e-86cbfd8eb4b1","Errors":[{"NextAction":"2c383d0d-dd77-4d23-941e-86cbfd8eb4b1","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"error executing lambda to send sms"},"Identifier":"94f7124e-e3e4-4c3d-b5f9-4800e2c8b1d1","Type":"MessageParticipant","Transitions":{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff"}},{"Parameters":{"Attributes":{"secondNode":"$.Lex.IntentName"},"TargetContact":"Current"},"Identifier":"74b95be7-30d9-4e90-b280-06d547ae6cf9","Type":"UpdateContactAttributes","Transitions":{"NextAction":"20b7c81f-d690-49a5-99e0-6836b254d662","Errors":[{"NextAction":"20b7c81f-d690-49a5-99e0-6836b254d662","ErrorType":"NoMatchingError"}]}},{"Parameters":{"LambdaFunctionARN":"arn:aws:lambda:ca-central-1:201089190273:function:SaveCFAttributeData","InvocationTimeLimitSeconds":"8","LambdaInvocationAttributes":{"flowType":"SMS-Deflect","Intent":"$.Attributes.firstNode -> $.Attributes.secondNode"},"ResponseValidation":{"ResponseType":"STRING_MAP"}},"Identifier":"be35e709-2e4a-4e39-afec-9d9f0abb8ff2","Type":"InvokeLambdaFunction","Transitions":{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff","Errors":[{"NextAction":"293c29b4-56eb-4ba8-a777-32e470b66a23","ErrorType":"NoMatchingError"}]}},{"Parameters":{"LambdaFunctionARN":"arn:aws:lambda:ca-central-1:201089190273:function:SendSMSFromContactFlow","InvocationTimeLimitSeconds":"8","LambdaInvocationAttributes":{"message":"Message from Amazon: Please use below weblink to follow FAQ''s.  https://aws.amazon.com/connect/faqs/"},"ResponseValidation":{"ResponseType":"STRING_MAP"}},"Identifier":"20b7c81f-d690-49a5-99e0-6836b254d662","Type":"InvokeLambdaFunction","Transitions":{"NextAction":"be35e709-2e4a-4e39-afec-9d9f0abb8ff2","Errors":[{"NextAction":"94f7124e-e3e4-4c3d-b5f9-4800e2c8b1d1","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"error executing lambda while adding intent to db"},"Identifier":"5246a380-b98d-4694-8417-103cb3138abd","Type":"MessageParticipant","Transitions":{"NextAction":"52ae21b1-702a-4046-9418-a5dffd53ceff"}},{"Parameters":{"QueueId":"arn:aws:connect:ca-central-1:201089190273:instance/1742c1ce-d940-43c6-bc37-87f8dffa29b1/queue/21fa9f4a-0803-41ab-bda0-f38d13143a3b"},"Identifier":"236b2dc5-a937-4d91-ade7-0b7b736f8d72","Type":"UpdateContactTargetQueue","Transitions":{"NextAction":"090b2b73-5fd0-47d5-93ef-c8e5b5c0a4a2","Errors":[{"NextAction":"090b2b73-5fd0-47d5-93ef-c8e5b5c0a4a2","ErrorType":"NoMatchingError"}]}},{"Parameters":{"LambdaFunctionARN":"arn:aws:lambda:ca-central-1:201089190273:function:SaveCFAttributeData","InvocationTimeLimitSeconds":"3","LambdaInvocationAttributes":{"flowType":"AgentQueue","Intent":"$.Attributes.firstNode -> $.Attributes.secondNode"},"ResponseValidation":{"ResponseType":"STRING_MAP"}},"Identifier":"93a56a6f-17b8-43ad-9bd1-f643b44be49c","Type":"InvokeLambdaFunction","Transitions":{"NextAction":"236b2dc5-a937-4d91-ade7-0b7b736f8d72","Errors":[{"NextAction":"5246a380-b98d-4694-8417-103cb3138abd","ErrorType":"NoMatchingError"}]}},{"Parameters":{"FlowLoggingBehavior":"Enabled"},"Identifier":"1f888acf-edd6-4d0a-a062-8e7b7c080a6c","Type":"UpdateFlowLoggingBehavior","Transitions":{"NextAction":"fa887bc8-736f-4e4c-8658-9f719ecd1ca3"}},{"Parameters":{"Attributes":{"firstNode":"$.Lex.IntentName"},"TargetContact":"Current"},"Identifier":"cc8a12d6-46d3-4bc6-bd3d-05ddc6c1ba04","Type":"UpdateContactAttributes","Transitions":{"NextAction":"400f51f0-a8f0-4b17-af21-2b81a9fb843c","Errors":[{"NextAction":"400f51f0-a8f0-4b17-af21-2b81a9fb843c","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"we are experiencing difficulties with the system right now. We are sending SMS with web link where you can chat with our CSO.\\nThank you\\nGood Bye"},"Identifier":"39f5b0c8-a4d4-48b4-b361-ed365738aa99","Type":"MessageParticipant","Transitions":{"NextAction":"74b95be7-30d9-4e90-b280-06d547ae6cf9"}},{"Parameters":{"Attributes":{"secondNode":"$.Lex.IntentName"},"TargetContact":"Current"},"Identifier":"771276b6-f114-4d8e-83d4-bfe1e93d8d8c","Type":"UpdateContactAttributes","Transitions":{"NextAction":"93a56a6f-17b8-43ad-9bd1-f643b44be49c","Errors":[{"NextAction":"93a56a6f-17b8-43ad-9bd1-f643b44be49c","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"please say promotions to know more about new iphone launch and its promotions\\nplease say billing for billing enquiries.","LexV2Bot":{"AliasArn":"arn:aws:lex:ca-central-1:201089190273:bot-alias/FHWBCFND4O/OM3Q2OGUWH"}},"Identifier":"400f51f0-a8f0-4b17-af21-2b81a9fb843c","Type":"ConnectParticipantWithLexBot","Transitions":{"NextAction":"771276b6-f114-4d8e-83d4-bfe1e93d8d8c","Conditions":[{"NextAction":"39f5b0c8-a4d4-48b4-b361-ed365738aa99","Condition":{"Operator":"Equals","Operands":["Cx_BillingEnquiries"]}},{"NextAction":"771276b6-f114-4d8e-83d4-bfe1e93d8d8c","Condition":{"Operator":"Equals","Operands":["Cx_Promotions"]}}],"Errors":[{"NextAction":"771276b6-f114-4d8e-83d4-bfe1e93d8d8c","ErrorType":"NoMatchingCondition"},{"NextAction":"771276b6-f114-4d8e-83d4-bfe1e93d8d8c","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Attributes":{"firstNode":"$.Lex.IntentName"},"TargetContact":"Current"},"Identifier":"9445a743-0312-408b-82b6-2d23eba91d40","Type":"UpdateContactAttributes","Transitions":{"NextAction":"4d91f9b5-4ce0-4f19-b8e1-74e11e61d08a","Errors":[{"NextAction":"4d91f9b5-4ce0-4f19-b8e1-74e11e61d08a","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"Switching to DTMF Flow"},"Identifier":"12e968f7-dd86-410d-807e-c9c40ed2beb0","Type":"MessageParticipant","Transitions":{"NextAction":"94c37a0e-aadb-460b-8def-3569b0f6a432"}},{"Parameters":{"Text":"Got it"},"Identifier":"ed4e8033-1b10-4a36-9543-789188e14707","Type":"MessageParticipant","Transitions":{"NextAction":"cc8a12d6-46d3-4bc6-bd3d-05ddc6c1ba04"}},{"Parameters":{"Text":"Hello, Thanks for calling Amazon Connect hot line 2"},"Identifier":"fa887bc8-736f-4e4c-8658-9f719ecd1ca3","Type":"MessageParticipant","Transitions":{"NextAction":"30715e44-0ae7-478e-aa0b-df771925e681","Errors":[{"NextAction":"30715e44-0ae7-478e-aa0b-df771925e681","ErrorType":"NoMatchingError"}]}},{"Parameters":{"Text":"How can i help you today ?\\nPlease say mobile for mobile related queries\\nBroadband for broadband related queries.","LexV2Bot":{"AliasArn":"arn:aws:lex:ca-central-1:201089190273:bot-alias/FHWBCFND4O/OM3Q2OGUWH"}},"Identifier":"30715e44-0ae7-478e-aa0b-df771925e681","Type":"ConnectParticipantWithLexBot","Transitions":{"NextAction":"12e968f7-dd86-410d-807e-c9c40ed2beb0","Conditions":[{"NextAction":"ed4e8033-1b10-4a36-9543-789188e14707","Condition":{"Operator":"Equals","Operands":["Cx_MobileEnquiries"]}},{"NextAction":"9445a743-0312-408b-82b6-2d23eba91d40","Condition":{"Operator":"Equals","Operands":["Cx_BroadbandEnquiries"]}}],"Errors":[{"NextAction":"12e968f7-dd86-410d-807e-c9c40ed2beb0","ErrorType":"NoMatchingCondition"},{"NextAction":"12e968f7-dd86-410d-807e-c9c40ed2beb0","ErrorType":"NoMatchingError"}]}}]}'
                      print ('original contact flow json : ' + cf_json);
                      print('2')
                      parsed_data = json.loads(cf_json)
                      print('3')
                      print('step1')
                      for action in parsed_data['Actions']:
                          if action['Identifier'] == '400f51f0-a8f0-4b17-af21-2b81a9fb843c':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LexV2Bot']['AliasArn'] = BotAliasID
                          if action['Identifier'] == '831aa7ff-6a97-4535-9436-32ac296beea8':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LambdaFunctionARN'] = SaveCFAttributeDataArn
                          if action['Identifier'] == '7ba265fb-2a0a-4be8-bfc0-5468d73a017d':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LambdaFunctionARN'] = SaveCFAttributeDataArn
                          if action['Identifier'] == '4b0540e1-dec4-41dc-bf4a-0a7dc524f215':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LambdaFunctionARN'] = SaveCFAttributeDataArn
                          if action['Identifier'] == 'be35e709-2e4a-4e39-afec-9d9f0abb8ff2':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LambdaFunctionARN'] = SaveCFAttributeDataArn
                          if action['Identifier'] == '20b7c81f-d690-49a5-99e0-6836b254d662':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LambdaFunctionARN'] = SendSMSFromContactFlowArn
                          if action['Identifier'] == '236b2dc5-a937-4d91-ade7-0b7b736f8d72':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['QueueId'] = BasicQueueId
                          if action['Identifier'] == '93a56a6f-17b8-43ad-9bd1-f643b44be49c':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LambdaFunctionARN'] = SaveCFAttributeDataArn
                          if action['Identifier'] == '30715e44-0ae7-478e-aa0b-df771925e681':
                              # Update the QueueId within the action's parameters
                              action['Parameters']['LexV2Bot']['AliasArn'] = BotAliasID
                      
                      print (json.dumps(parsed_data, indent=4, sort_keys=True, default=str));

                      print('step4') 
                      cf_response = client.create_contact_flow(
                        InstanceId=InstanceArn,
                        Name='Connect customer journey map',
                        Type='CONTACT_FLOW',
                        Description='Venky_CFT_Flow',
                        Content=json.dumps(parsed_data, indent=4, sort_keys=True, default=str),
                        Status='PUBLISHED'
                      )
                      print('>>> Returned Value cf_response :' + json.dumps(cf_response, indent=4, sort_keys=True, default=str))

                      responseData = {}
                      responseData['BasicQueue'] = BasicQueueId
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
                      print(responseData)
                  except Exception as a:
                      print (a)
                      return { 'message': 'Failed listing Queues and ContactFlows' }

              else:
                  responseData = {}
                  responseData['Data'] = "Delete"
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")

      Handler: index.lambda_handler
      Role: !GetAtt IAMRole.Arn
      Runtime: python3.9