Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
853cf3d
Initial commit codebase
OdyAsh Sep 28, 2025
fe603bd
Add WA migration plan's files
OdyAsh Sep 28, 2025
40f7bf7
Add tests to WA Phase 1
OdyAsh Sep 28, 2025
858e6a7
Add integration tests for ansari-whatsapp service webhooks
OdyAsh Sep 30, 2025
801f1b4
Refactor: Use get_settings() in tests
OdyAsh Oct 1, 2025
578ee28
Enhance logging configuration for tests
OdyAsh Oct 1, 2025
02d9bbd
Refactor WA settings, logging control
OdyAsh Oct 4, 2025
0202adf
Refactor error handling in WhatsAppPresenter and AnsariClient; remove…
OdyAsh Oct 4, 2025
a206451
feat: Implement mock and real API clients for Ansari and Meta WhatsAp…
OdyAsh Oct 4, 2025
5486ad8
feat: Remove user location handling from WhatsApp service and update …
OdyAsh Oct 14, 2025
6be2853
refactor: split "presenter" file's logic into WhatsApp Conversation M…
OdyAsh Oct 14, 2025
d536d0f
refactor: enhance logger configuration and remove module-specific log…
OdyAsh Oct 17, 2025
467cdec
Refactor: Remove Unnecessary Files
OdyAsh Oct 17, 2025
ee1da89
Refactor: Rename to app_logger.py
OdyAsh Oct 17, 2025
3a6102a
chore: Update upload-artifact action to v4 in GitHub Actions workflow
OdyAsh Oct 17, 2025
a32a0f2
refactor: update files to import app_logger.py
OdyAsh Oct 17, 2025
6ded255
refactor: update environment variables and install dependencies in ed…
OdyAsh Oct 17, 2025
a088ac1
refactor: endpoints from v1 to v2, remove unncessary masking in tests…
OdyAsh Oct 18, 2025
ff9885a
refactor: enhance testing documentation and backend availability chec…
OdyAsh Oct 18, 2025
0644ece
feat: Add AWS & gh Actions setup docs
OdyAsh Oct 19, 2025
0073e23
refactor: enhance deployment workflows and add default Zrok value
OdyAsh Oct 19, 2025
a8b47d7
refactor: Rename pytests-staging.yml to perform-tests.yml and add mis…
OdyAsh Oct 19, 2025
1f5a10b
Refactor WhatsApp migration plan and update application settings
OdyAsh Oct 19, 2025
e3b1233
Added TOC to migration_plan.md
OdyAsh Oct 25, 2025
f2c34f0
Refactor documentation
OdyAsh Oct 25, 2025
58a4370
refactor: enhance README documentation for newcomers and deployment
OdyAsh Oct 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Where ansari-whatsapp is deployed
# (local (i.e., local), development, staging, production)
# NOTE: local type will add localhost to ORIGINS, etc.
DEPLOYMENT_TYPE="local"

# ansari-backend's API URL
# if local: "http://localhost:8000"
# if staging: "https://staging-api.ansari.chat/api/v2"
# if production: "https://api.ansari.chat/api/v2"
BACKEND_SERVER_URL="http://localhost:8000"


# CORS settings - URLs that are allowed to access ansari-whatsapp
ORIGINS="https://api.ansari.chat,https://ansari-api.au.ngrok.io,https://web.whatsapp.com"
# Explained in WhatsApp comments below
META_WEBHOOK_ZROK_SHARE_TOKEN="<<THE-ZROK-SHARE-TOKEN-CURRENTLY-USED-IN-META'S-CALLBACK-URL>>"

# Related to WhatsApp Business and Meta

# Source 0: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started
# Source 1: https://www.youtube.com/watch?v=KP6_BUw3i0U
# Watch Until 32:25, while quickly skimming through the non-python code parts
# Source 2 (mentioned in video above): https://glitch.com/edit/#!/insidious-tartan-alvarezsaurus
# (the `verification_webhook` endpoint in `main.py` is inspired by the above URL)
# Source 3 (optional): https://developers.facebook.com/blog/post/2022/10/24/sending-messages-with-whatsapp-in-your-python-applications/#u_0_39_8q

# Moreover, if want to test whatsapp's webhook locally, you can use zrok on a reserved URL with a zrok "share token"
# obtained by contacting its current holder: https://github.com/OdyAsh (source 1, 2 below)
# Alternatively, you can change the webhook url all together (source 3, 4 below)
# Check these sources for more details:
# Source 1: https://dev.to/odyash/quickly-share-your-app-with-zrok-4ihp
# Source 2: https://openziti.discourse.group/t/how-do-i-use-a-reserved-share-on-different-devices/2379/2
# Source 3: https://youtu.be/KP6_BUw3i0U?t=1294
# (@21:33 and 25:30, however they use glitch instead of zrok, so the video here is just to give you an idea how to setup a webhook)
# Source 4 (where you can change callback url, given that your facebook account gets access by the app's admins):
# https://developers.facebook.com/apps/871020755148175/whatsapp-business/wa-settings/
# NOTE 1: Obviously, that `871020755148175` in the above URL is the testing app's public id, so if this link still doesn't work even after you gain access,
# then the admins most probably created a new test app instance
# NOTE 2: When you see the `Callback URL`, it will be something like "https://META_WEBHOOK_ZROK_SHARE_TOKEN.share.zrok.io/whatsapp/v2"
# (The `/whatsapp/v2` endpoint can be found in `main.py`'s endpoints, that's why it's in the url above)
# Moreover, this zrok callback URL is only used for `Ansari Test` app on developers.facebook.com,
# so we should only use it while locally developing this repo, not in staging/prod/etc. environments
# NOTE 3: If an unexpected 3rd party discovers the META_WEBHOOK_ZROK_SHARE_TOKEN,
# a new one will have to be generated, then added to Meta's callback URL of the *testing* app
# (Noting that the *production* app's callback URL will be different anyway, so the 3rd party won't be able to access that app)
# (but we still don't want random calls to be made to our testing app, so that's why we'll still have to change an exposed token :])
# Side note: the current zrok instance (i.e., "https://META_WEBHOOK_ZROK_SHARE_TOKEN.share.zrok.io")
# currently proxies (i.e., maps to) this local address: http://127.0.0.1:8001
# Therefore, if for some reason, you want to change that address,
# then you'll need to re-establish the mapping by doing these 2 commands:
# `zrok release <<META_WEBHOOK_ZROK_SHARE_TOKEN>>`
# `zrok reserve public XXXX --unique-name "<<META_WEBHOOK_ZROK_SHARE_TOKEN>>"`
# where XXXX is your new port number (which correspond to the PORT variable found in `.env` file)

META_API_VERSION="v22.0"
# Phone number ID (from Meta Dashboard)
META_BUSINESS_PHONE_NUMBER_ID="your_phone_number_id_here"
# System User Access Token (from Meta Dashboard)
META_ACCESS_TOKEN_FROM_SYS_USER="your_access_token_here"
# Token for webhook verification (from Meta Dashboard)
META_WEBHOOK_VERIFY_TOKEN="your_webhook_verify_token"

# Chat settings
WHATSAPP_UNDER_MAINTENANCE="False"
# Hours to retain chat history
WHATSAPP_CHAT_RETENTION_HOURS="3"
# Maximum age in seconds for incoming WhatsApp messages to be processed (24 hours = 86400 seconds)
# Messages older than this threshold will be rejected to avoid processing outdated messages
WHATSAPP_MESSAGE_AGE_THRESHOLD_SECONDS="86400"

# Test/Development settings
# Phone number used for testing webhooks (not a real phone number ID)
# Should be replaced with a real phone number ID when running pytest scripts
# NOTE: Should be in international format without '+' or leading zeros
WHATSAPP_DEV_PHONE_NUM="201234567899"
# WhatsApp message ID for testing typing indicators/read receipts
# This must be a real message ID obtained from a real WhatsApp webhook request
# To obtain: Send a real message from WHATSAPP_DEV_PHONE_NUM, log the webhook payload,
# and copy the message ID from the "id" field in the "messages" array
# Format: "wamid." followed by 72 characters (base64-encoded)
# If not set correctly, you'll get: HTTP 400 error with "#131009 Parameter value is not valid"
WHATSAPP_DEV_MESSAGE_ID="wamid.seventy_two_char_hash"

# Meta webhook response behavior
# Meta's webhooks require HTTP 200 responses to acknowledge receipt.
# When True (production/staging/development/local): Always return HTTP 200 to Meta, regardless of internal errors
# When False (testing only): Return proper HTTP status codes (400, 500, etc.) for test assertions
# NOTE: This should ALWAYS be True in production. Only set to False in automated tests.
ALWAYS_RETURN_OK_TO_META="True"


# Logging settings
LOGGING_LEVEL="DEBUG"
# If True, only log messages from test files (i.e., files in "tests" folder or files starting with "test_")
# Otherwise, log messages coming from any file
# (Mainly used when running test scripts)
LOG_TEST_FILES_ONLY="False"

# Service Provider settings
# If True, use mock Ansari client instead of making real HTTP calls to the backend
# Mock mode is useful for:
# - Development without requiring the backend service to be running
# - Unit testing with simulated responses and realistic latency/errors
# - Offline development and testing
# - Cost savings (no AI API calls during development)
# When False (default), the real client makes actual HTTP requests to BACKEND_SERVER_URL
MOCK_ANSARI_CLIENT="False"

# If True, use mock Meta API service instead of making real HTTP calls to Meta WhatsApp API
# Mock mode is useful for:
# - Development when Meta API credentials are not configured or invalid
# - Testing webhook flow without sending actual WhatsApp messages
# - Avoiding Meta API quota consumption during development
# - Debugging issues without affecting real WhatsApp users
# When False (default), real HTTP calls are made to Meta WhatsApp Cloud API
MOCK_META_API="False"
100 changes: 100 additions & 0 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Production Deployment (AWS App Runner)

on:
# Trigger only after tests pass
workflow_run:
workflows: ["Ansari WhatsApp Pytests"]
types:
- completed
branches:
- main
# Allow manual triggering
workflow_dispatch:

jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment: production-aws
# Only run if tests passed or if manually triggered
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Configure AWS credentials
id: aws-credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ansari-whatsapp
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

- name: Deploy to App Runner
id: deploy-apprunner
uses: awslabs/amazon-app-runner-deploy@main
env:
# Backend Integration
BACKEND_SERVER_URL: ${{ format('{0}{1}', vars.SSM_ROOT, 'backend-server-url') }}
DEPLOYMENT_TYPE: ${{ format('{0}{1}', vars.SSM_ROOT, 'deployment-type') }}

# Meta/WhatsApp Credentials
META_ACCESS_TOKEN_FROM_SYS_USER: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-access-token-from-sys-user') }}
META_BUSINESS_PHONE_NUMBER_ID: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-business-phone-number-id') }}
META_WEBHOOK_VERIFY_TOKEN: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-webhook-verify-token') }}
META_API_VERSION: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-api-version') }}

# Application Settings
WHATSAPP_CHAT_RETENTION_HOURS: ${{ format('{0}{1}', vars.SSM_ROOT, 'whatsapp-chat-retention-hours') }}
WHATSAPP_MESSAGE_AGE_THRESHOLD_SECONDS: ${{ format('{0}{1}', vars.SSM_ROOT, 'whatsapp-message-age-threshold-seconds') }}
WHATSAPP_UNDER_MAINTENANCE: ${{ format('{0}{1}', vars.SSM_ROOT, 'whatsapp-under-maintenance') }}

# Operational Settings
ALWAYS_RETURN_OK_TO_META: ${{ format('{0}{1}', vars.SSM_ROOT, 'always-return-ok-to-meta') }}
LOGGING_LEVEL: ${{ format('{0}{1}', vars.SSM_ROOT, 'logging-level') }}
ORIGINS: ${{ format('{0}{1}', vars.SSM_ROOT, 'origins') }}

with:
service: ansari-whatsapp-production
image: ${{ steps.build-image.outputs.image }}
access-role-arn: ${{ secrets.SERVICE_ROLE_ARN }}
region: ${{ secrets.AWS_REGION }}
cpu: 1
memory: 2
port: 8001
wait-for-service-stability-seconds: 1200
copy-env-vars: |
DEPLOYMENT_TYPE
copy-secret-env-vars: |
BACKEND_SERVER_URL
META_ACCESS_TOKEN_FROM_SYS_USER
META_BUSINESS_PHONE_NUMBER_ID
META_WEBHOOK_VERIFY_TOKEN
META_API_VERSION
WHATSAPP_CHAT_RETENTION_HOURS
WHATSAPP_MESSAGE_AGE_THRESHOLD_SECONDS
WHATSAPP_UNDER_MAINTENANCE
ALWAYS_RETURN_OK_TO_META
LOGGING_LEVEL
ORIGINS
instance-role-arn: ${{ secrets.INSTANCE_ROLE_ARN }}

- name: App Runner URL
run: echo "App runner URL ${{ steps.deploy-apprunner.outputs.service-url }}"
99 changes: 99 additions & 0 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Staging Deployment (AWS App Runner)

on:
# Trigger only after tests pass
workflow_run:
workflows: ["Ansari WhatsApp Pytests"]
types:
- completed
branches:
- develop
# Allow manual triggering
workflow_dispatch:

jobs:
build-and-deploy:
runs-on: ubuntu-latest
environment: staging-aws
# Only run if tests passed or if manually triggered
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}

steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Configure AWS credentials
id: aws-credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2

- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ansari-whatsapp
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

- name: Deploy to App Runner
id: deploy-apprunner
uses: awslabs/amazon-app-runner-deploy@main
env:
# Backend Integration
BACKEND_SERVER_URL: ${{ format('{0}{1}', vars.SSM_ROOT, 'backend-server-url') }}
DEPLOYMENT_TYPE: ${{ format('{0}{1}', vars.SSM_ROOT, 'deployment-type') }}

# Meta/WhatsApp Credentials
META_ACCESS_TOKEN_FROM_SYS_USER: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-access-token-from-sys-user') }}
META_BUSINESS_PHONE_NUMBER_ID: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-business-phone-number-id') }}
META_WEBHOOK_VERIFY_TOKEN: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-webhook-verify-token') }}
META_API_VERSION: ${{ format('{0}{1}', vars.SSM_ROOT, 'meta-api-version') }}

# Application Settings
WHATSAPP_CHAT_RETENTION_HOURS: ${{ format('{0}{1}', vars.SSM_ROOT, 'whatsapp-chat-retention-hours') }}
WHATSAPP_MESSAGE_AGE_THRESHOLD_SECONDS: ${{ format('{0}{1}', vars.SSM_ROOT, 'whatsapp-message-age-threshold-seconds') }}
WHATSAPP_UNDER_MAINTENANCE: ${{ format('{0}{1}', vars.SSM_ROOT, 'whatsapp-under-maintenance') }}

# Operational Settings
LOGGING_LEVEL: ${{ format('{0}{1}', vars.SSM_ROOT, 'logging-level') }}
ORIGINS: ${{ format('{0}{1}', vars.SSM_ROOT, 'origins') }}

with:
service: ansari-whatsapp-staging
image: ${{ steps.build-image.outputs.image }}
access-role-arn: ${{ secrets.SERVICE_ROLE_ARN }}
region: ${{ secrets.AWS_REGION }}
cpu: 1
memory: 2
port: 8001
wait-for-service-stability-seconds: 1200
copy-env-vars: |
DEPLOYMENT_TYPE
copy-secret-env-vars: |
BACKEND_SERVER_URL
META_ACCESS_TOKEN_FROM_SYS_USER
META_BUSINESS_PHONE_NUMBER_ID
META_WEBHOOK_VERIFY_TOKEN
META_API_VERSION
WHATSAPP_CHAT_RETENTION_HOURS
WHATSAPP_MESSAGE_AGE_THRESHOLD_SECONDS
WHATSAPP_UNDER_MAINTENANCE
ALWAYS_RETURN_OK_TO_META
LOGGING_LEVEL
ORIGINS
instance-role-arn: ${{ secrets.INSTANCE_ROLE_ARN }}

- name: App Runner URL
run: echo "App runner URL ${{ steps.deploy-apprunner.outputs.service-url }}"
Loading