whatsapp_stack.py
  1  """CDK Stack for WhatsApp + Instagram integration via API Gateway + Meta Cloud API.
  2  
  3  Reads AgentCore Runtime ARN from SSM Parameter Store (deployed by 00-agent-agentcore).
  4  Supports dual-channel: WhatsApp Business and Instagram Direct Messages.
  5  """
  6  
  7  from aws_cdk import (
  8      Stack,
  9      CfnOutput,
 10      RemovalPolicy,
 11      aws_s3 as s3,
 12      aws_iam as iam,
 13      aws_secretsmanager as secretsmanager,
 14      aws_ssm as ssm,
 15      SecretValue,
 16  )
 17  from constructs import Construct
 18  
 19  from get_param import get_string_param
 20  from databases.databases import MessageDatabase, UserIdentityDatabase
 21  from layers.project_layers import ProjectLayers
 22  from lambdas.project_lambdas import ProjectLambdas
 23  from apis.webhooks import WebhookApi
 24  
 25  # Read AgentCore config from SSM (set by 00-agent-agentcore stack)
 26  AGENT_RUNTIME_ARN = get_string_param("/agentcore/agent_runtime_arn")
 27  RUNTIME_ROLE_ARN = get_string_param("/agentcore/runtime_role_arn")
 28  
 29  
 30  class WhatsAppApiGatewayStack(Stack):
 31      """WhatsApp + Instagram via Meta Cloud API -> API Gateway -> Lambda pipeline -> AgentCore."""
 32  
 33      def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
 34          super().__init__(scope, construct_id, **kwargs)
 35  
 36          # --- Secrets Manager for WhatsApp credentials ---
 37          secrets = secretsmanager.Secret(
 38              self,
 39              "WhatsAppSecrets",
 40              secret_object_value={
 41                  "WHATS_VERIFICATION_TOKEN": SecretValue.unsafe_plain_text(
 42                      "CHANGE_ME_VERIFICATION_TOKEN"
 43                  ),
 44                  "WHATS_TOKEN": SecretValue.unsafe_plain_text(
 45                      "CHANGE_ME_TOKEN"
 46                  ),
 47                  "DISPLAY_PHONE_NUMBER": SecretValue.unsafe_plain_text(
 48                      "CHANGE_ME_PHONE_NUMBER"
 49                  ),
 50              },
 51          )
 52  
 53          # --- Secrets Manager for Instagram credentials ---
 54          ig_secrets = secretsmanager.Secret(
 55              self,
 56              "InstagramSecrets",
 57              secret_object_value={
 58                  "IG_TOKEN": SecretValue.unsafe_plain_text(
 59                      "CHANGE_ME_IG_TOKEN"
 60                  ),
 61                  "IG_ACCOUNT_ID": SecretValue.unsafe_plain_text(
 62                      "CHANGE_ME_IG_ACCOUNT_ID"
 63                  ),
 64                  "IG_VERIFICATION_TOKEN": SecretValue.unsafe_plain_text(
 65                      "CHANGE_ME_IG_VERIFICATION_TOKEN"
 66                  ),
 67              },
 68          )
 69  
 70          # --- S3 Bucket for media and transcriptions ---
 71          bucket = s3.Bucket(
 72              self,
 73              "MediaBucket",
 74              versioned=True,
 75              removal_policy=RemovalPolicy.DESTROY,
 76              auto_delete_objects=True,
 77              block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
 78              enforce_ssl=True,
 79          )
 80  
 81          # --- DynamoDB ---
 82          db = MessageDatabase(self, "Database")
 83          users_db = UserIdentityDatabase(self, "UsersDatabase")
 84  
 85          # --- Grant AgentCore runtime role access to media bucket + users table ---
 86          agentcore_role = iam.Role.from_role_arn(
 87              self, "AgentCoreRuntimeRole", RUNTIME_ROLE_ARN
 88          )
 89          bucket.grant_read(agentcore_role)
 90          users_db.table.grant_read_write_data(agentcore_role)
 91  
 92          # --- Lambda Layers ---
 93          layers = ProjectLayers(self, "Layers")
 94  
 95          # --- Lambda Functions ---
 96          lambdas = ProjectLambdas(
 97              self,
 98              "Lambdas",
 99              table=db.table,
100              users_table=users_db.table,
101              bucket=bucket,
102              common_layer=layers.common_layer,
103              secret_arn=secrets.secret_arn,
104              ig_secret_arn=ig_secrets.secret_arn,
105              agent_runtime_arn=AGENT_RUNTIME_ARN,
106          )
107  
108          # --- API Gateway ---
109          api = WebhookApi(self, "API", whatsapp_in_fn=lambdas.webhook_receiver)
110  
111          # --- Export unified_users table name to SSM for AgentCore tool ---
112          ssm.StringParameter(
113              self,
114              "UnifiedUsersTableParam",
115              parameter_name="/agentcore/unified_users_table_name",
116              string_value=users_db.table.table_name,
117          )
118  
119          # --- Outputs ---
120          CfnOutput(self, "AgentRuntimeArn", value=AGENT_RUNTIME_ARN)
121          CfnOutput(self, "MessagesTableName", value=db.table.table_name)
122          CfnOutput(self, "S3BucketName", value=bucket.bucket_name)
123          CfnOutput(self, "UnifiedUsersTableName", value=users_db.table.table_name)
124          CfnOutput(self, "SecretArn", value=secrets.secret_arn)
125          CfnOutput(self, "IGSecretArn", value=ig_secrets.secret_arn)
126          CfnOutput(self, "WebhookReceiverName", value=lambdas.webhook_receiver.function_name)
127          CfnOutput(self, "ProcessorName", value=lambdas.message_processor.function_name)