# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

AWSTemplateFormatVersion: 2010-09-09
Description: >
  Amazon Connect instance CloudFormation template.
  Creates an Amazon Connect Instance, Hours of Operation, Contact Flow.
  Creates an Agent user for calls to be directed to.
  Creates an Admin user to manage Amazon Connect.
  Passwords for the Agent and Admin are stored as Secure Strings in AWS Systems Manager Parameter Store.

Parameters:
  Environment:
    Type: String
    Default: dev
    Description: Environment to deploy into
    AllowedValues: [dev, uat, prod]

Resources:
  AmazonConnectInstance:
    Type: AWS::Connect::Instance
    Properties:
      Attributes:
        ContactflowLogs: true
        InboundCalls: true
        OutboundCalls: false
      IdentityManagementType: CONNECT_MANAGED
      InstanceAlias: !Sub
        - ${prefix}-${randomString}-${environment}
        - prefix: summit-demo
          randomString: !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
          environment: !Ref Environment

  HoursOfOperation:
    Type: AWS::Connect::HoursOfOperation
    Properties:
      Name: New Hours
      Description: New Hours
      InstanceArn: !GetAtt AmazonConnectInstance.Arn
      TimeZone: Asia/Singapore
      Config:
        - Day: MONDAY
          EndTime:
            Hours: 18
            Minutes: 0
          StartTime:
            Hours: 8
            Minutes: 30
        - Day: TUESDAY
          EndTime:
            Hours: 18
            Minutes: 0
          StartTime:
            Hours: 8
            Minutes: 30
        - Day: WEDNESDAY
          EndTime:
            Hours: 18
            Minutes: 0
          StartTime:
            Hours: 8
            Minutes: 30
        - Day: THURSDAY
          EndTime:
            Hours: 18
            Minutes: 0
          StartTime:
            Hours: 8
            Minutes: 30
        - Day: FRIDAY
          EndTime:
            Hours: 18
            Minutes: 0
          StartTime:
            Hours: 8
            Minutes: 30

  CustomResourceGetSecurityProfileAgent:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionGetSecurityProfile.Arn
      SecurityProfileName: Agent

  LambdaFunctionGetSecurityProfile:
    Type: AWS::Lambda::Function
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: Permission to write to CloudWatch logs is part of the managed policy AWSLambdaBasicExecutionRole
          - id: W89
            reason: Lambda function does not interact with resources in the VPC and does not handle sensitive data
          - id: W92
            reason: This lambda function does not require ReservedConcurrentExecutions
    Properties:
      Handler: index.lambda_handler
      Description: >
        Get Security Profile ID, ARN
      Role: !GetAtt GetSecurityProfileRole.Arn
      Runtime: python3.10
      MemorySize: 128
      Timeout: 30
      Environment:
        Variables:
          LOG_LEVEL: INFO
          INSTANCE_ID: !Ref AmazonConnectInstance
      Code:
        ZipFile: |
          import boto3
          import logging
          import os
          import cfnresponse

          client = boto3.client('connect')

          LOG_LEVEL = os.getenv('LOG_LEVEL')
          INSTANCE_ID = os.getenv('INSTANCE_ID')

          def lambda_handler(event, context):
            global log_level
            log_level = str(LOG_LEVEL).upper()
            if log_level not in { 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' }:
              log_level = 'ERROR'
            logging.getLogger().setLevel(log_level)

            logging.info(f'Event: {event}')

            request_type = event['RequestType']

            if request_type == 'Delete':
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              return

            if request_type in {'Create', 'Update'}:
              try:
                security_profile_name = event['ResourceProperties']['SecurityProfileName']

                marker = None

                while True:
                  paginator = client.get_paginator('list_security_profiles')
                  response_iterator = paginator.paginate(
                    InstanceId=INSTANCE_ID,
                    PaginationConfig={
                      'PageSize': 10,
                      'StartingToken': marker
                    }
                  )
                  for page in response_iterator:
                    security_profiles = page['SecurityProfileSummaryList']
                    for security_profile in security_profiles:
                      if security_profile['Name'] == security_profile_name:
                        response_data = {
                          'SecurityProfileId': security_profile['Id'],
                          'SecurityProfileArn': security_profile['Arn'],
                        }
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                        return
                  try:
                    marker = response_iterator['Marker']
                  except Exception as e:
                    logging.error(e)
                    cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                    break
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

  GetSecurityProfileRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: listConnectSecurityProfiles
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - connect:ListSecurityProfiles
              Resource: !Sub arn:${AWS::Partition}:connect:${AWS::Region}:${AWS::AccountId}:instance*
              Condition:
                StringEquals:
                  connect:InstanceId: !GetAtt AmazonConnectInstance.Id

  CustomResourceGetRoutingProfileBasic:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionGetRoutingProfile.Arn
      RoutingProfileName: Basic Routing Profile

  LambdaFunctionGetRoutingProfile:
    Type: AWS::Lambda::Function
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: Permission to write to CloudWatch logs is part of the managed policy AWSLambdaBasicExecutionRole
          - id: W89
            reason: Lambda function does not interact with resources in the VPC and does not handle sensitive data
          - id: W92
            reason: This lambda function does not require ReservedConcurrentExecutions
    Properties:
      Handler: index.lambda_handler
      Description: >
        Get Routing Profile ID, ARN
      Role: !GetAtt GetRoutingProfileRole.Arn
      Runtime: python3.10
      MemorySize: 128
      Timeout: 30
      Environment:
        Variables:
          LOG_LEVEL: INFO
          INSTANCE_ID: !Ref AmazonConnectInstance
      Code:
        ZipFile: |
          import boto3
          import logging
          import os
          import cfnresponse

          client = boto3.client('connect')

          LOG_LEVEL = os.getenv('LOG_LEVEL')
          INSTANCE_ID = os.getenv('INSTANCE_ID')

          def lambda_handler(event, context):
            global log_level
            log_level = str(LOG_LEVEL).upper()
            if log_level not in { 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' }:
              log_level = 'ERROR'
            logging.getLogger().setLevel(log_level)

            logging.info(f'Event: {event}')

            request_type = event['RequestType']

            if request_type == 'Delete':
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              return

            if request_type in {'Create', 'Update'}:
              try:
                routing_profile_name = event['ResourceProperties']['RoutingProfileName']

                marker = None

                while True:
                  paginator = client.get_paginator('list_routing_profiles')
                  response_iterator = paginator.paginate(
                    InstanceId=INSTANCE_ID,
                    PaginationConfig={
                      'PageSize': 10,
                      'StartingToken': marker
                    }
                  )
                  for page in response_iterator:
                    routing_profiles = page['RoutingProfileSummaryList']
                    for routing_profile in routing_profiles:
                      if routing_profile['Name'] == routing_profile_name:
                        response_data = {
                          'RoutingProfileId': routing_profile['Id'],
                          'RoutingProfileArn': routing_profile['Arn'],
                        }
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                        return
                  try:
                    marker = response_iterator['Marker']
                  except Exception as e:
                    logging.error(e)
                    cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                    break
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

  GetRoutingProfileRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: listConnectRoutingProfiles
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - connect:ListRoutingProfiles
              Resource: !Sub arn:${AWS::Partition}:connect:${AWS::Region}:${AWS::AccountId}:instance*
              Condition:
                StringEquals:
                  connect:InstanceId: !GetAtt AmazonConnectInstance.Id

  CustomResourceGenerateRandomStringAgent:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionGenerateRandomString.Arn
      StringLength: 15 # Specify minimum 4, max 60
      SecurityProfileName: Agent

  LambdaFunctionGenerateRandomString:
    Type: AWS::Lambda::Function
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: Permission to write to CloudWatch logs is part of the managed policy AWSLambdaBasicExecutionRole
          - id: W89
            reason: Lambda function does not interact with resources in the VPC and does not handle sensitive data
          - id: W92
            reason: This lambda function does not require ReservedConcurrentExecutions
    Properties:
      Handler: index.lambda_handler
      Description: >
        Generate random string
      Role: !GetAtt GenerateRandomStringRole.Arn
      Runtime: python3.10
      MemorySize: 128
      Timeout: 3
      Environment:
        Variables:
          LOG_LEVEL: INFO
      Code:
        ZipFile: |
          import boto3
          import botocore
          import random
          import logging
          import os
          import cfnresponse
          import string

          client = boto3.client('ssm')

          LOG_LEVEL = os.getenv('LOG_LEVEL')

          def lambda_handler(event, context):
            global log_level
            log_level = str(LOG_LEVEL).upper()
            if log_level not in { 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' }:
              log_level = 'ERROR'
            logging.getLogger().setLevel(log_level)

            logging.info(f'Event: {event}')

            request_type = event['RequestType']

            if request_type == 'Delete':
              security_profile_name = event['ResourceProperties']['SecurityProfileName']
              try:
                response = client.delete_parameter(
                  Name=f'amazon-connect-temp-{security_profile_name}-password'
                )

                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                return
              except botocore.exceptions.ClientError as err:
                if err.response['Error']['Code'] == 'ParameterNotFound':
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                  return
                else:
                  logging.error(err)
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {err}'})
                  return
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

            if request_type in {'Create', 'Update'}:
              try:
                string_length = event['ResourceProperties']['StringLength']
                security_profile_name = event['ResourceProperties']['SecurityProfileName']

                valid_characters = string.ascii_letters + string.digits + "!@#$%^&*_=-"
                # First 4 specifies a mix of from each combination
                random_string = random.SystemRandom().choice(string.ascii_lowercase)
                random_string += random.SystemRandom().choice(string.ascii_uppercase)
                random_string += random.SystemRandom().choice(string.digits)
                random_string += random.SystemRandom().choice('!@#$%^&*_=-')

                for i in range(int(string_length)-4):
                  random_string += random.SystemRandom().choice(valid_characters)

                response = client.put_parameter(
                  Name=f'amazon-connect-temp-{security_profile_name}-password',
                  Description=f'SSM Parameter to store the temporary Amazon Connect {security_profile_name} Password',
                  Value=random_string,
                  Type='SecureString',
                  Overwrite=True,
                  Tier='Standard'
                )

                response_data = {
                  'RandomString': random_string
                }
                cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

  GenerateRandomStringRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: putAndDeleteSSMParameters
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - ssm:DeleteParameter
              - ssm:PutParameter
              Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter*

  ConnectUserAgent:
    Type: AWS::Connect::User
    Properties:
      IdentityInfo:
        FirstName: demo
        LastName: user
      PhoneConfig:
        PhoneType: DESK_PHONE
        AutoAccept: true
        DeskPhoneNumber: '+12345678902'
        AfterContactWorkTimeLimit: 10
      Username: demouser
      InstanceArn: !GetAtt AmazonConnectInstance.Arn
      RoutingProfileArn: !GetAtt CustomResourceGetRoutingProfileBasic.RoutingProfileArn
      SecurityProfileArns: [!GetAtt CustomResourceGetSecurityProfileAgent.SecurityProfileArn]
      Password: !GetAtt CustomResourceGenerateRandomStringAgent.RandomString

  PhoneNumber:
    Type: AWS::Connect::PhoneNumber
    Properties:
      TargetArn: !GetAtt AmazonConnectInstance.Arn
      Description: AnyCompany Phone Number
      Type: DID
      CountryCode: US
      Tags:
        - Key: Name
          Value: basicPhoneNumber

  CustomResourceAssociatePhoneNumberToContactFlow:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionAssociatePhoneNumberToContactFlow.Arn

  LambdaFunctionAssociatePhoneNumberToContactFlow:
    Type: AWS::Lambda::Function
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: Permission to write to CloudWatch logs is part of the managed policy AWSLambdaBasicExecutionRole
          - id: W89
            reason: Lambda function does not interact with resources in the VPC and does not handle sensitive data
          - id: W92
            reason: This lambda function does not require ReservedConcurrentExecutions
    Properties:
      Handler: index.lambda_handler
      Description: >
        Associate phone number to contact flow
      Role: !GetAtt AssociatePhoneNumberToContactFlowRole.Arn
      Runtime: python3.10
      MemorySize: 128
      Timeout: 30
      Environment:
        Variables:
          LOG_LEVEL: INFO
          INSTANCE_ID: !GetAtt AmazonConnectInstance.Id
          PHONE_NUMBER_ID: !Select [1, !Split ['/', !Ref PhoneNumber]]
          CONTACT_FLOW_ID: !Select [3, !Split ['/', !Ref Flow]]
      Code:
        ZipFile: |
          import boto3
          import logging
          import os
          import cfnresponse

          client = boto3.client('connect')

          LOG_LEVEL = os.getenv('LOG_LEVEL')
          INSTANCE_ID = os.getenv('INSTANCE_ID')
          PHONE_NUMBER_ID = os.getenv('PHONE_NUMBER_ID')
          CONTACT_FLOW_ID = os.getenv('CONTACT_FLOW_ID')

          def lambda_handler(event, context):
            global log_level
            log_level = str(LOG_LEVEL).upper()
            if log_level not in { 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' }:
              log_level = 'ERROR'
            logging.getLogger().setLevel(log_level)

            logging.info(f'Event: {event}')

            request_type = event['RequestType']

            if request_type == 'Delete':
              try:
                response = client.disassociate_phone_number_contact_flow(
                  InstanceId=INSTANCE_ID,
                  PhoneNumberId=PHONE_NUMBER_ID
                )
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                return
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

            if request_type in {'Create', 'Update'}:
              try:
                response = client.associate_phone_number_contact_flow(
                  InstanceId=INSTANCE_ID,
                  PhoneNumberId=PHONE_NUMBER_ID,
                  ContactFlowId=CONTACT_FLOW_ID
                )
                cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                return
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

  AssociatePhoneNumberToContactFlowRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: associatePhoneNumberContactFlow
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - connect:AssociatePhoneNumberContactFlow
              Resource:
              - !Sub arn:${AWS::Partition}:connect:${AWS::Region}:${AWS::AccountId}:instance/${AmazonConnectInstance.Id}/contact-flow*
              - !Sub arn:${AWS::Partition}:connect:${AWS::Region}:${AWS::AccountId}:phone-number*
              Condition:
                StringEquals:
                  connect:InstanceId: !GetAtt AmazonConnectInstance.Id
            - Effect: Allow
              Action:
              - connect:DisassociatePhoneNumberContactFlow
              Resource:
              - !Sub arn:${AWS::Partition}:connect:${AWS::Region}:${AWS::AccountId}:phone-number*
              Condition:
                StringEquals:
                  connect:InstanceId: !GetAtt AmazonConnectInstance.Id

  CustomResourceGetContactFlowCustomerQueue:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionGetContactFlow.Arn
      ContactFlowName: Default customer queue
      ContactFlowTypes: [CUSTOMER_QUEUE]

  LambdaFunctionGetContactFlow:
    Type: AWS::Lambda::Function
    Metadata:
      cfn_nag:
        rules_to_suppress:
          - id: W58
            reason: Permission to write to CloudWatch logs is part of the managed policy AWSLambdaBasicExecutionRole
          - id: W89
            reason: Lambda function does not interact with resources in the VPC and does not handle sensitive data
          - id: W92
            reason: This lambda function does not require ReservedConcurrentExecutions
    Properties:
      Handler: index.lambda_handler
      Description: >
        Get Contact Flow ID, ARN
      Role: !GetAtt GetContactFlowRole.Arn
      Runtime: python3.10
      MemorySize: 128
      Timeout: 30
      Environment:
        Variables:
          LOG_LEVEL: INFO
          INSTANCE_ID: !Ref AmazonConnectInstance
      Code:
        ZipFile: |
          import boto3
          import logging
          import os
          import cfnresponse

          client = boto3.client('connect')

          LOG_LEVEL = os.getenv('LOG_LEVEL')
          INSTANCE_ID = os.getenv('INSTANCE_ID')

          def lambda_handler(event, context):
            global log_level
            log_level = str(LOG_LEVEL).upper()
            if log_level not in { 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL' }:
              log_level = 'ERROR'
            logging.getLogger().setLevel(log_level)

            logging.info(f'Event: {event}')

            request_type = event['RequestType']

            if request_type == 'Delete':
              cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
              return

            if request_type in {'Create', 'Update'}:
              try:
                contact_flow_name = event['ResourceProperties']['ContactFlowName']
                contact_flow_types = event['ResourceProperties']['ContactFlowTypes']
                
                marker = None

                while True:
                  paginator = client.get_paginator('list_contact_flows')
                  response_iterator = paginator.paginate(
                    InstanceId=INSTANCE_ID,
                    ContactFlowTypes=contact_flow_types,
                    PaginationConfig={
                      'PageSize': 10,
                      'StartingToken': marker
                    }
                  )
                  for page in response_iterator:
                    contact_flows = page['ContactFlowSummaryList']
                    for contact_flow in contact_flows:
                      if contact_flow['Name'] == contact_flow_name:
                        response_data = {
                          'ContactFlowId': contact_flow['Id'],
                          'ContactFlowArn': contact_flow['Arn'],
                        }
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                        return
                  try:
                    marker = response_iterator['Marker']
                  except Exception as e:
                    logging.error(e)
                    cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                    break
              except Exception as e:
                logging.error(e)
                cfnresponse.send(event, context, cfnresponse.FAILED, {'message': f'ERROR: {e}'})
                return

  GetContactFlowRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: listConnectContactFlows
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
              - connect:ListContactFlows
              Resource: !Sub arn:${AWS::Partition}:connect:${AWS::Region}:${AWS::AccountId}:instance*
              Condition:
                StringEquals:
                  connect:InstanceId: !GetAtt AmazonConnectInstance.Id
                  
  Flow:
    Type: AWS::Connect::ContactFlow
    Properties:
      Name: AnyCompany Main Flow
      Description: flow created using cfn
      InstanceArn: !GetAtt AmazonConnectInstance.Arn
      Type: CONTACT_FLOW
      Content: !Sub |
        {
          "Version": "2019-10-30",
          "StartAction": "8a1b1f06-0b60-40a7-9cee-77b1b8a93634",
          "Metadata": {
            "entryPointPosition": {
              "x": -408,
              "y": 171.2
            },
            "ActionMetadata": {
              "6737f241-656c-4ab9-b5ce-781ca4ea7d58": {
                "position": {
                  "x": -23.2,
                  "y": 548
                }
              },
              "8a1b1f06-0b60-40a7-9cee-77b1b8a93634": {
                "position": {
                  "x": -285.6,
                  "y": 179.2
                },
                "parameters": {
                  "HoursOfOperationId": {
                    "displayName": "HoursOfOperation"
                  }
                },
                "Hours": {
                  "id": "${HoursOfOperation.HoursOfOperationArn}",
                  "text": "HoursOfOperation"
                }
              },
              "eb8dce1a-15ee-4c37-a795-d812ca64e70c": {
                "position": {
                  "x": 944,
                  "y": 336
                }
              },
              "5ea8ece2-ad30-43c7-a758-64ae2bd33dfc": {
                "position": {
                  "x": 8.8,
                  "y": 194.4
                }
              },
              "9bf359a2-72d5-4d9c-a26b-c58e97e1620d": {
                "position": {
                  "x": 251.2,
                  "y": 156
                },
                "conditionMetadata": [
                  {
                    "id": "34f977e0-ed97-420d-a8b1-06fa3f77cf4a",
                    "value": "0"
                  }
                ]
              },
              "52bf144d-828c-4cd1-8140-e1c5a8049d1c": {
                "position": {
                  "x": 505.6,
                  "y": 446.4
                }
              },
              "06d0e1d7-5c1b-41ae-b401-052ed7283f97": {
                "position": {
                  "x": 738.4,
                  "y": 271.2
                }
              },
              "6d7fd93b-430b-4406-9cfc-ec9e329ec8ae": {
                "position": {
                  "x": 500,
                  "y": 189.6
                },
                "parameters": {
                  "AgentId": {
                    "displayName": "demouser (demo user)"
                  }
                },
                "queue": {
                  "text": "demouser (demo user)"
                }
              },
              "8f95c6f4-4ef8-4503-a332-a012d9ae0b0b": {
                "position": {
                  "x": 733.6,
                  "y": 38.4
                },
                "parameters": {
                  "EventHooks": {
                    "CustomerQueue": {
                      "displayName": "Default customer queue"
                    }
                  }
                },
                "contactFlow": {
                  "text": "Default customer queue",
                  "id": "${CustomResourceGetContactFlowCustomerQueue.ContactFlowArn}"
                },
                "customerOrAgent": true
              }
            }
          },
          "Actions": [
            {
              "Parameters": {
                "Text": "Sorry, we are currently closed. Please call back during our operating hours. Our new operating hours are from 8.30am to 6pm from Mondays to Fridays."
              },
              "Identifier": "6737f241-656c-4ab9-b5ce-781ca4ea7d58",
              "Type": "MessageParticipant",
              "Transitions": {
                "NextAction": "eb8dce1a-15ee-4c37-a795-d812ca64e70c",
                "Errors": [
                  {
                    "NextAction": "eb8dce1a-15ee-4c37-a795-d812ca64e70c",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {
                "HoursOfOperationId": "${HoursOfOperation.HoursOfOperationArn}"
              },
              "Identifier": "8a1b1f06-0b60-40a7-9cee-77b1b8a93634",
              "Type": "CheckHoursOfOperation",
              "Transitions": {
                "NextAction": "6737f241-656c-4ab9-b5ce-781ca4ea7d58",
                "Conditions": [
                  {
                    "NextAction": "5ea8ece2-ad30-43c7-a758-64ae2bd33dfc",
                    "Condition": {
                      "Operator": "Equals",
                      "Operands": [
                        "True"
                      ]
                    }
                  },
                  {
                    "NextAction": "6737f241-656c-4ab9-b5ce-781ca4ea7d58",
                    "Condition": {
                      "Operator": "Equals",
                      "Operands": [
                        "False"
                      ]
                    }
                  }
                ],
                "Errors": [
                  {
                    "NextAction": "6737f241-656c-4ab9-b5ce-781ca4ea7d58",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {},
              "Identifier": "eb8dce1a-15ee-4c37-a795-d812ca64e70c",
              "Type": "DisconnectParticipant",
              "Transitions": {}
            },
            {
              "Parameters": {
                "Text": "Welcome to AnyCompany hotline."
              },
              "Identifier": "5ea8ece2-ad30-43c7-a758-64ae2bd33dfc",
              "Type": "MessageParticipant",
              "Transitions": {
                "NextAction": "9bf359a2-72d5-4d9c-a26b-c58e97e1620d",
                "Errors": [
                  {
                    "NextAction": "9bf359a2-72d5-4d9c-a26b-c58e97e1620d",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {
                "StoreInput": "False",
                "InputTimeLimitSeconds": "5",
                "Text": "Our new operating hours are from 8.30am to 6pm from Mondays to Fridays.\n\nTo speak to a customer service officer, Press 0."
              },
              "Identifier": "9bf359a2-72d5-4d9c-a26b-c58e97e1620d",
              "Type": "GetParticipantInput",
              "Transitions": {
                "NextAction": "52bf144d-828c-4cd1-8140-e1c5a8049d1c",
                "Conditions": [
                  {
                    "NextAction": "6d7fd93b-430b-4406-9cfc-ec9e329ec8ae",
                    "Condition": {
                      "Operator": "Equals",
                      "Operands": [
                        "0"
                      ]
                    }
                  }
                ],
                "Errors": [
                  {
                    "NextAction": "52bf144d-828c-4cd1-8140-e1c5a8049d1c",
                    "ErrorType": "InputTimeLimitExceeded"
                  },
                  {
                    "NextAction": "52bf144d-828c-4cd1-8140-e1c5a8049d1c",
                    "ErrorType": "NoMatchingCondition"
                  },
                  {
                    "NextAction": "52bf144d-828c-4cd1-8140-e1c5a8049d1c",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {
                "Text": "Sorry, I did not receive a response."
              },
              "Identifier": "52bf144d-828c-4cd1-8140-e1c5a8049d1c",
              "Type": "MessageParticipant",
              "Transitions": {
                "NextAction": "9bf359a2-72d5-4d9c-a26b-c58e97e1620d",
                "Errors": [
                  {
                    "NextAction": "9bf359a2-72d5-4d9c-a26b-c58e97e1620d",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {},
              "Identifier": "06d0e1d7-5c1b-41ae-b401-052ed7283f97",
              "Type": "TransferContactToQueue",
              "Transitions": {
                "NextAction": "eb8dce1a-15ee-4c37-a795-d812ca64e70c",
                "Errors": [
                  {
                    "NextAction": "eb8dce1a-15ee-4c37-a795-d812ca64e70c",
                    "ErrorType": "QueueAtCapacity"
                  },
                  {
                    "NextAction": "eb8dce1a-15ee-4c37-a795-d812ca64e70c",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {
                "AgentId": "${ConnectUserAgent.UserArn}"
              },
              "Identifier": "6d7fd93b-430b-4406-9cfc-ec9e329ec8ae",
              "Type": "UpdateContactTargetQueue",
              "Transitions": {
                "NextAction": "8f95c6f4-4ef8-4503-a332-a012d9ae0b0b",
                "Errors": [
                  {
                    "NextAction": "8f95c6f4-4ef8-4503-a332-a012d9ae0b0b",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            },
            {
              "Parameters": {
                "EventHooks": {
                  "CustomerQueue": "${CustomResourceGetContactFlowCustomerQueue.ContactFlowArn}"
                }
              },
              "Identifier": "8f95c6f4-4ef8-4503-a332-a012d9ae0b0b",
              "Type": "UpdateContactEventHooks",
              "Transitions": {
                "NextAction": "06d0e1d7-5c1b-41ae-b401-052ed7283f97",
                "Errors": [
                  {
                    "NextAction": "06d0e1d7-5c1b-41ae-b401-052ed7283f97",
                    "ErrorType": "NoMatchingError"
                  }
                ]
              }
            }
          ]
        }

  ConnectUserAdmin:
    Type: AWS::Connect::User
    Properties:
      IdentityInfo:
        FirstName: admin
        LastName: user
      PhoneConfig:
        PhoneType: 'DESK_PHONE'
        AutoAccept: true
        DeskPhoneNumber: '+12345678902'
        AfterContactWorkTimeLimit: 10
      Username: adminuser
      InstanceArn: !GetAtt AmazonConnectInstance.Arn
      RoutingProfileArn: !GetAtt CustomResourceGetRoutingProfileBasic.RoutingProfileArn
      SecurityProfileArns: [!GetAtt CustomResourceGetSecurityProfileAdmin.SecurityProfileArn]
      Password: !GetAtt CustomResourceGenerateRandomStringAdmin.RandomString

  CustomResourceGenerateRandomStringAdmin:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionGenerateRandomString.Arn
      StringLength: 15
      SecurityProfileName: Admin

  CustomResourceGetSecurityProfileAdmin:
    Type: Custom::S3CustomResource
    Properties:
      ServiceToken: !GetAtt LambdaFunctionGetSecurityProfile.Arn
      SecurityProfileName: Admin