From a7cd37dda8730409f4745428ae732aa702bf3503 Mon Sep 17 00:00:00 2001
From: Benjamin Aronov
The purpose of the quickstart guide is to provide simple examples focused on one goal. For example, sending an SMS, making a Text to Speech call, or sending an image in WhatsApp.
## Configure with Your Vonage API Keys
-
+
To use this sample you will first need a [Vonage account](https://dashboard.nexmo.com/sign-up). Once you have your own API credentials, rename the `.env.example` file to `.env` and set the values as required.
For some of the examples you will need to [buy a number](https://dashboard.nexmo.com/buy-numbers).
## Setup
-
+
```
git clone git@github.com:Vonage-Community/sample-messages-ruby_on_rails-quickstart.git
cd sample-messages-ruby_on_rails-quickstart
@@ -18,11 +18,9 @@ bundle install
rails db:create db:migrate
rails s
```
-
## Tutorials & Sample Code
-
### SMS
-
+
Tutorial | Code Sample
--------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------
[How to Send SMS Messages with Ruby on Rails](https://developer.vonage.com/en/blog/send-sms-ruby-on-rails-dr) | [outbound_sms_controller.rb](app/controllers/outbound_sms_controller.rb)
@@ -30,7 +28,7 @@ Tutorial
[How to Receive SMS Messages with Ruby on Rails](https://developer.vonage.com/en/blog/receive-sms-messages-ruby-on-rails-dr) | [inbound_sms_controller.rb](app/controllers/inbound_sms_controller.rb)
### Voice
-
+
Tutorial | Code Sample
------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------
[How to Make an Outbound Text-to-Speech Phone Call with Rails](#) | [outbound_calls_controller.rb](app/controllers/outbound_calls_controller.rb)
@@ -38,7 +36,7 @@ Tutorial
[How to Handle Inbound Phone Calls with Ruby on Rails](#) | [inbound_calls_controller.rb](app/controllers/inbound_calls_controller.rb)
### RCS
-
+
Tutorial | Code Sample
--------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------
[How to Send RCS Suggested Reply Messages with Ruby on Rails](#) | [outbound_rcs_controller.rb](app/controllers/outbound_rcs_controller.rb)
@@ -46,18 +44,18 @@ Tutorial
[How to Receive RCS Reply Messages with Ruby on Rails](#) | [inbound_rcs_controller.rb](app/controllers/inbound_rcs_controller.rb)
### WhatsApp
-
+
Tutorial | Code Sample
--------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------
[How to Send WhatsApp Messages with Ruby on Rails](#) | [outbound_whatsapp_controller.rb](app/controllers/outbound_whatsapp_controller.rb)
-[How to Receive WhatsApp Messages with Ruby on Rails](#) | [#](#)
+[How to Receive WhatsApp Messages with Ruby on Rails](#) | [inbound_whatsapp_controller.rb](app/controllers/inbound_whatsapp_controller.rb)
## Request More Examples
-
+
For help with the code or to request an example not listed here, please join the [Vonage Community Slack](https://developer.vonage.com/en/community/slack). Feedback and requests are highly appreciated!
## Licenses
-
+
- The code samples in this repo is under [MIT](LICENSE)
- The tutorials contents are under Creative Commons, [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/legalcode)
diff --git a/WHATSAPP_SETUP.md b/WHATSAPP_SETUP.md
deleted file mode 100644
index 612ebf6..0000000
--- a/WHATSAPP_SETUP.md
+++ /dev/null
@@ -1,127 +0,0 @@
-# WhatsApp Integration Setup Guide
-
-This guide will help you set up and use the WhatsApp messaging functionality that has been added to your Rails application.
-
-## What Was Added
-
-The following components have been integrated into your existing Rails app:
-
-### 1. Database
-- **Migration**: `db/migrate/20251015112429_create_whatsapp_messages.rb`
-- **Model**: `app/models/whatsapp_message.rb`
-- Stores WhatsApp message records with fields: `to`, `from`, `text`, `status`, `message_uuid`, `is_inbound`
-
-### 2. Controller
-- **File**: `app/controllers/outbound_whatsapp_controller.rb`
-- **Actions**:
- - `new` - Display the form for sending WhatsApp messages
- - `create` - Send a text WhatsApp message
- - `interactive` - Send an interactive WhatsApp message with reply buttons
-
-### 3. View
-- **File**: `app/views/outbound_whatsapp/new.html.erb`
-- Contains forms for both text and interactive WhatsApp messages
-
-### 4. Routes
-Added to `config/routes.rb`:
-```ruby
-get '/outbound_whatsapp/new', to: 'outbound_whatsapp#new', as: :new_outbound_whatsapp
-post '/outbound_whatsapp', to: 'outbound_whatsapp#create', as: :outbound_whatsapp
-post '/outbound_whatsapp/interactive', to: 'outbound_whatsapp#interactive', as: :interactive_whatsapp
-```
-
-### 5. Environment Configuration
-Added `VONAGE_WHATSAPP_NUMBER` to `.env.example`
-
-## Setup Instructions
-
-### Step 1: Run the Migration
-```bash
-rails db:migrate
-```
-
-### Step 2: Configure Your Vonage WhatsApp Application
-
-1. Log in to your [Vonage Dashboard](https://dashboard.nexmo.com/)
-2. Create a new application (or use an existing one) and enable the **Messages** capability
-3. Add webhook URLs for:
- - **Inbound**: `https://your-domain.com/inbound` (or use ngrok for testing)
- - **Status**: `https://your-domain.com/status`
-4. Click **Generate public and private key**
-5. Move the downloaded `private.key` file to the root of your Rails app
-6. Note your **Application ID**
-7. Click **Save**
-8. Under the **Link external accounts** tab, link your WhatsApp number
-
-### Step 3: Update Your .env File
-
-Add the following to your `.env` file (create one if it doesn't exist):
-
-```bash
-VONAGE_APPLICATION_ID=your_application_id_here
-VONAGE_PRIVATE_KEY=./private.key
-VONAGE_WHATSAPP_NUMBER=14157386102 # Your WhatsApp Business number
-```
-
-### Step 4: Test the Integration
-
-1. Start your Rails server:
- ```bash
- rails s
- ```
-
-2. Navigate to: `http://localhost:3000/outbound_whatsapp/new`
-
-3. You should see two forms:
- - **Send a WhatsApp Message** - For sending text messages
- - **Try an Interactive Message** - For sending messages with reply buttons
-
-## Features
-
-### Text Messages
-Send simple text messages to any WhatsApp number. The form includes:
-- **From**: Your WhatsApp Business number (pre-filled from ENV)
-- **To**: Recipient's WhatsApp number (format: +14155551234)
-- **Message**: The text content to send
-
-### Interactive Messages
-Send messages with reply buttons that users can tap to respond. The example includes:
-- A header ("Delivery time")
-- Body text with a question
-- Footer text with additional info
-- Three reply buttons with different time slots
-
-You can customize the interactive message structure in the `interactive` action of `OutboundWhatsappController`.
-
-## How It Works
-
-1. **User fills out the form** and submits
-2. **Controller receives the data** and creates a `WhatsappMessage` record
-3. **Vonage client is initialized** with your credentials
-4. **Message is sent** via the Vonage Messages API
-5. **Response is checked** - if successful (HTTP 202), the `message_uuid` is saved
-6. **User is redirected** back to the form with a success notice
-
-## Next Steps
-
-As mentioned in the tutorial, this implementation covers sending messages. To build a complete WhatsApp integration, you'll want to add:
-
-1. **Inbound message handling** - Receive messages from users
-2. **Status webhooks** - Track message delivery and read status
-3. **Interactive response handling** - Process which button the user clicked
-
-These features would require additional controllers and webhook endpoints, similar to the existing `inbound_sms` and `sms_message_status` controllers in your app.
-
-## Troubleshooting
-
-- **Make sure your WhatsApp number is linked** to your Vonage application
-- **Verify your .env file** has the correct credentials
-- **Check that private.key** is in the root directory
-- **Ensure the recipient number** is in E.164 format (e.g., +14155551234)
-- **For testing**, you can use the [Vonage Sandbox for WhatsApp](https://developer.vonage.com/en/messages/concepts/messages-api-sandbox)
-
-## Resources
-
-- [Vonage Messages API Documentation](https://developer.vonage.com/en/messages/overview)
-- [WhatsApp Business API Guide](https://developer.vonage.com/en/messages/concepts/whatsapp)
-- [Vonage Ruby SDK](https://github.com/Vonage/vonage-ruby-sdk)
diff --git a/app/assets/stylesheets/home.css b/app/assets/stylesheets/home.css
new file mode 100644
index 0000000..d3718dc
--- /dev/null
+++ b/app/assets/stylesheets/home.css
@@ -0,0 +1,141 @@
+/* README container */
+.readme-container {
+ max-width: 1000px;
+ margin: 0 auto;
+ padding: 40px 20px;
+ background-color: #ffffff;
+ min-height: 100vh;
+}
+
+.readme-content {
+ background: white;
+ padding: 40px;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* Markdown styling */
+.markdown-body {
+ font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ font-size: 16px;
+ line-height: 1.6;
+ color: #333;
+}
+
+.markdown-body h1 {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 32px;
+ font-weight: 600;
+ color: #000;
+ margin: 0 0 24px 0;
+ padding-bottom: 16px;
+ border-bottom: 2px solid #871FFF;
+}
+
+.markdown-body h2 {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 24px;
+ font-weight: 600;
+ color: #333;
+ margin: 32px 0 16px 0;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+.markdown-body h3 {
+ font-family: 'Montserrat', sans-serif;
+ font-size: 20px;
+ font-weight: 600;
+ color: #333;
+ margin: 24px 0 12px 0;
+}
+
+.markdown-body p {
+ margin: 0 0 16px 0;
+ line-height: 1.7;
+}
+
+.markdown-body a {
+ color: #871FFF;
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.2s;
+}
+
+.markdown-body a:hover {
+ color: #6a19cc;
+ text-decoration: underline;
+}
+
+.markdown-body code {
+ background-color: #f5f5f5;
+ padding: 2px 6px;
+ border-radius: 3px;
+ font-family: 'Roboto Mono', monospace;
+ font-size: 14px;
+ color: #871FFF;
+}
+
+.markdown-body pre {
+ background-color: #f5f5f5;
+ padding: 16px;
+ border-radius: 6px;
+ overflow-x: auto;
+ margin: 16px 0;
+ border: 1px solid #e5e5e5;
+}
+
+.markdown-body pre code {
+ background-color: transparent;
+ padding: 0;
+ color: #333;
+ font-size: 13px;
+ line-height: 1.5;
+}
+
+.markdown-body ul,
+.markdown-body ol {
+ margin: 0 0 16px 0;
+ padding-left: 24px;
+}
+
+.markdown-body li {
+ margin: 8px 0;
+ line-height: 1.6;
+}
+
+.markdown-body table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 16px 0;
+ font-size: 14px;
+}
+
+.markdown-body table th {
+ background: linear-gradient(135deg, #871FFF 0%, #FFA68C 100%);
+ color: white;
+ padding: 12px;
+ text-align: left;
+ font-weight: 600;
+ font-family: 'Montserrat', sans-serif;
+}
+
+.markdown-body table td {
+ padding: 12px;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+.markdown-body table tr:hover {
+ background-color: #fafafa;
+}
+
+.markdown-body strong {
+ font-weight: 600;
+ color: #000;
+}
+
+.markdown-body hr {
+ border: none;
+ border-top: 1px solid #e5e5e5;
+ margin: 32px 0;
+}
diff --git a/app/assets/stylesheets/inbound_whatsapp.css b/app/assets/stylesheets/inbound_whatsapp.css
new file mode 100644
index 0000000..533afe2
--- /dev/null
+++ b/app/assets/stylesheets/inbound_whatsapp.css
@@ -0,0 +1,245 @@
+/* Base styles */
+body {
+ margin: 0;
+ padding: 0;
+ background-color: #f5f5f5;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+}
+
+/* Messages container */
+.messages-container {
+ max-width: 900px;
+ margin: 40px auto;
+ padding: 0 20px;
+}
+
+.messages-card {
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+/* Header with Vonage brand gradient */
+.messages-header {
+ background: linear-gradient(135deg, #871FFF 0%, #FFA68C 100%);
+ padding: 20px 24px;
+ color: white;
+}
+
+.messages-header h1 {
+ margin: 0 0 8px 0;
+ font-size: 24px;
+ font-family: 'Montserrat', sans-serif;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.messages-header h1 svg {
+ width: 32px;
+ height: 32px;
+ flex-shrink: 0;
+}
+
+.messages-header p {
+ margin: 0;
+ font-size: 14px;
+ opacity: 0.9;
+}
+
+/* Message items */
+.message-item {
+ padding: 20px 24px;
+ border-bottom: 1px solid #e5e5e5;
+ transition: background-color 0.2s;
+}
+
+.message-item:hover {
+ background-color: #fafafa;
+}
+
+.message-item:last-child {
+ border-bottom: none;
+}
+
+/* Message content layout */
+.message-content {
+ display: flex;
+ gap: 16px;
+}
+
+/* Avatar with Vonage gradient */
+.message-avatar {
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #871FFF 0%, #FFA68C 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-weight: 600;
+ flex-shrink: 0;
+}
+
+/* Message body */
+.message-body {
+ flex: 1;
+ min-width: 0;
+}
+
+.message-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 8px;
+ gap: 16px;
+}
+
+.message-sender {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+.message-sender-name {
+ font-weight: 500;
+ color: #333;
+ font-size: 15px;
+}
+
+.message-sender-phone {
+ color: #666;
+ font-size: 13px;
+}
+
+.message-time {
+ color: #999;
+ font-size: 12px;
+ white-space: nowrap;
+}
+
+/* Message text */
+.message-text {
+ color: #333;
+ font-size: 14px;
+ line-height: 1.5;
+ margin-bottom: 8px;
+}
+
+/* Button reply styling */
+.message-reply {
+ display: inline-flex;
+ padding: 10px 14px;
+ background-color: #F5EBFF;
+ border: 1px solid #871FFF;
+ border-radius: 6px;
+ margin-bottom: 8px;
+}
+
+.message-reply-content {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.message-reply-label {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #871FFF;
+ text-transform: uppercase;
+}
+
+.message-reply-text {
+ font-weight: 600;
+ color: #333;
+ font-size: 14px;
+}
+
+.message-reply-id {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ color: #666;
+}
+
+/* Other message types */
+.message-other {
+ display: inline-flex;
+ flex-direction: column;
+ gap: 4px;
+ padding: 10px 14px;
+ background-color: #f5f5f5;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ margin-bottom: 8px;
+}
+
+.message-other-label {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #666;
+ text-transform: uppercase;
+}
+
+.message-other-text {
+ color: #333;
+ font-size: 14px;
+}
+
+/* Message metadata */
+.message-metadata {
+ display: flex;
+ gap: 16px;
+ font-size: 11px;
+ color: #999;
+ font-family: 'Roboto Mono', monospace;
+}
+
+/* Empty state */
+.messages-empty {
+ padding: 60px 24px;
+ text-align: center;
+ color: #666;
+}
+
+.messages-empty h3 {
+ margin: 0 0 8px 0;
+ font-size: 18px;
+ font-weight: 500;
+ color: #333;
+}
+
+.messages-empty p {
+ margin: 0;
+ font-size: 14px;
+}
+
+/* Footer */
+.messages-footer {
+ padding: 16px 24px;
+ background-color: #fafafa;
+ border-top: 1px solid #e5e5e5;
+ text-align: center;
+}
+
+.messages-footer p {
+ margin: 0;
+ font-size: 13px;
+ color: #666;
+}
+
+.messages-footer a {
+ color: #871FFF;
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.messages-footer a:hover {
+ text-decoration: underline;
+}
diff --git a/app/assets/stylesheets/outbound_whatsapp.css b/app/assets/stylesheets/outbound_whatsapp.css
new file mode 100644
index 0000000..57eee67
--- /dev/null
+++ b/app/assets/stylesheets/outbound_whatsapp.css
@@ -0,0 +1,313 @@
+/*
+ * This is a manifest file that'll be compiled into application.css.
+ *
+ * With Propshaft, assets are served efficiently without preprocessing steps. You can still include
+ * application-wide styles in this file, but keep in mind that CSS precedence will follow the standard
+ * cascading order, meaning styles declared later in the document or manifest will override earlier ones,
+ * depending on specificity.
+ *
+ * Consider organizing styles into separate files for maintainability.
+ */
+
+/* Base styles */
+body {
+ margin: 0;
+ padding: 0;
+ background-color: #f5f5f5;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+}
+
+/* Messages container */
+.messages-container {
+ max-width: 900px;
+ margin: 40px auto;
+ padding: 0 20px;
+}
+
+.messages-card {
+ background-color: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+/* Header */
+.messages-header {
+ background: linear-gradient(135deg, #871FFF 0%, #FFA68C 100%);
+ padding: 20px 24px;
+ color: white;
+}
+
+.messages-header h1 {
+ margin: 0;
+ font-size: 24px;
+ font-family: 'Montserrat', sans-serif;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+}
+
+.messages-header h1 svg {
+ width: 32px;
+ height: 32px;
+ margin-right: 12px;
+}
+
+.messages-header p {
+ margin: 4px 0 0 0;
+ font-size: 14px;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+ opacity: 0.9;
+}
+
+/* Message list */
+.messages-list {
+ border-top: 1px solid #e5e5e5;
+}
+
+.message-item {
+ padding: 20px 24px;
+ border-bottom: 1px solid #e5e5e5;
+ transition: background-color 0.2s;
+}
+
+.message-item:hover {
+ background-color: #fafafa;
+}
+
+.message-item:last-child {
+ border-bottom: none;
+}
+
+.message-content {
+ display: flex;
+ gap: 16px;
+}
+
+/* Avatar */
+.message-avatar {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #871FFF 0%, #FFA68C 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ font-weight: 600;
+ font-size: 16px;
+}
+
+/* Message body */
+.message-body {
+ flex: 1;
+ min-width: 0;
+}
+
+.message-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+}
+
+.message-sender {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.message-sender-name {
+ font-family: 'Roboto', sans-serif;
+ font-weight: 500;
+ color: #000;
+ font-size: 14px;
+}
+
+.message-sender-phone {
+ font-family: 'Roboto Mono', monospace;
+ font-weight: 300;
+ color: #666;
+ font-size: 12px;
+}
+
+.message-time {
+ font-family: 'Roboto Mono', monospace;
+ font-weight: 300;
+ color: #999;
+ font-size: 12px;
+}
+
+/* Message text */
+.message-text {
+ margin: 12px 0;
+ color: #333;
+ font-family: 'Roboto', sans-serif;
+ font-weight: 300;
+ font-size: 14px;
+ line-height: 1.5;
+ white-space: pre-wrap;
+}
+
+/* Button reply styling */
+.message-reply {
+ display: inline-flex;
+ align-items: center;
+ padding: 10px 14px;
+ background-color: #F5EBFF;
+ border: 1px solid #871FFF;
+ border-radius: 6px;
+ gap: 8px;
+}
+
+.message-reply svg {
+ width: 16px;
+ height: 16px;
+ color: #871FFF;
+ flex-shrink: 0;
+}
+
+.message-reply-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.message-reply-label {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #871FFF;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.message-reply-text {
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ color: #000;
+ font-weight: 500;
+}
+
+.message-reply-id {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ font-weight: 300;
+ color: #666;
+ margin-top: 2px;
+}
+
+/* Other message type */
+.message-other {
+ display: inline-flex;
+ align-items: center;
+ padding: 10px 14px;
+ background-color: #f5f5f5;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ gap: 8px;
+}
+
+.message-other svg {
+ width: 16px;
+ height: 16px;
+ color: #666;
+ flex-shrink: 0;
+}
+
+.message-other-label {
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #666;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.message-other-text {
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ font-weight: 300;
+ color: #333;
+}
+
+/* Metadata */
+.message-metadata {
+ display: flex;
+ gap: 16px;
+ margin-top: 8px;
+ font-family: 'Roboto Mono', monospace;
+ font-size: 11px;
+ font-weight: 300;
+ color: #999;
+}
+
+.message-metadata-item {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+}
+
+.message-metadata-item svg {
+ width: 12px;
+ height: 12px;
+}
+
+/* Empty state */
+.messages-empty {
+ padding: 60px 24px;
+ text-align: center;
+}
+
+.messages-empty svg {
+ width: 48px;
+ height: 48px;
+ color: #ccc;
+ margin: 0 auto 12px;
+}
+
+.messages-empty h3 {
+ margin: 0 0 8px 0;
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ font-weight: 500;
+ color: #333;
+}
+
+.messages-empty p {
+ margin: 0;
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ font-weight: 300;
+ color: #999;
+}
+
+/* Footer */
+.messages-footer {
+ background-color: #fafafa;
+ padding: 12px 24px;
+ text-align: center;
+ border-top: 1px solid #e5e5e5;
+}
+
+.messages-footer p {
+ margin: 0;
+ font-family: 'Roboto Mono', monospace;
+ font-size: 12px;
+ font-weight: 300;
+ color: #666;
+}
+
+.messages-footer a {
+ color: #871FFF;
+ text-decoration: none;
+ font-family: 'Roboto Mono', monospace;
+ font-weight: 300;
+}
+
+.messages-footer a:hover {
+ text-decoration: underline;
+}
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
new file mode 100644
index 0000000..297ca5c
--- /dev/null
+++ b/app/controllers/home_controller.rb
@@ -0,0 +1,6 @@
+class HomeController < ApplicationController
+ def index
+ readme_path = Rails.root.join('README.md')
+ @readme_content = File.read(readme_path) if File.exist?(readme_path)
+ end
+end
diff --git a/app/controllers/inbound_whatsapp_controller.rb b/app/controllers/inbound_whatsapp_controller.rb
new file mode 100644
index 0000000..1137d53
--- /dev/null
+++ b/app/controllers/inbound_whatsapp_controller.rb
@@ -0,0 +1,64 @@
+class InboundWhatsappController < ApplicationController
+ skip_before_action :verify_authenticity_token
+
+ def index
+ @messages = WhatsappMessage.where(is_inbound: true).order(created_at: :desc)
+ end
+
+ def inbound
+ payload = JSON.parse(request.body.read)
+ Rails.logger.info("📥 Inbound message: #{payload}")
+
+ from = payload["from"]
+ to = payload["to"]
+ message_uuid = payload["message_uuid"]
+ message_type = payload["message_type"]
+ profile_name = payload.dig("profile", "name")
+
+ # Parse message based on type
+ if message_type == "reply"
+ # Interactive button reply
+ reply_id = payload.dig("reply", "id")
+ reply_title = payload.dig("reply", "title")
+ text = "Selected: #{reply_title}"
+ reply_data = payload["reply"].to_json
+ elsif message_type == "text"
+ # Regular text message
+ text = payload["text"]
+ reply_data = nil
+ else
+ # Fallback for other message types
+ text = payload["text"] || "Unsupported message type: #{message_type}"
+ reply_data = nil
+ end
+
+ WhatsappMessage.create!(
+ from: from,
+ to: to,
+ text: text,
+ message_uuid: message_uuid,
+ message_type: message_type,
+ profile_name: profile_name,
+ reply_data: reply_data,
+ is_inbound: true
+ )
+
+ head :ok
+ end
+
+ # Handle delivery and read status updates
+ def status
+ payload = JSON.parse(request.body.read)
+ Rails.logger.info("📡 Status update: #{payload}")
+
+ message_uuid = payload.dig("message_uuid")
+ status = payload.dig("status")
+
+ if message_uuid && status
+ message = WhatsappMessage.find_by(message_uuid: message_uuid)
+ message&.update(status: status)
+ end
+
+ head :ok
+ end
+end
\ No newline at end of file
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
new file mode 100644
index 0000000..c2909ed
--- /dev/null
+++ b/app/helpers/home_helper.rb
@@ -0,0 +1,35 @@
+module HomeHelper
+ def markdown_to_html(text)
+ return "" if text.blank?
+
+ html = text.dup
+
+ # Convert headers
+ html.gsub!(/^### (.+)$/, '\1
')
+ html.gsub!(/^## (.+)$/, '\1
')
+ html.gsub!(/^# (.+)$/, '\1
')
+
+ # Convert links [text](url)
+ html.gsub!(/\[([^\]]+)\]\(([^)]+)\)/, '\1')
+
+ # Convert code blocks
+ html.gsub!(/```([^`]+)```/m, '
')
+
+ # Convert inline code `code`
+ html.gsub!(/`([^`]+)`/, '\1\1')
+
+ # Convert bold **text**
+ html.gsub!(/\*\*([^*]+)\*\*/, '\1')
+
+ # Convert paragraphs (double newlines)
+ html.gsub!(/\n\n/, '
') + html = "
#{html}
" + + # Clean up empty paragraphs + html.gsub!(/\s*<\/p>/, '') + html.gsub!(/
\s*
README.md not found.
+ <% end %> +Find me in app/views/inbound_whatsapp/inbound.html.erb
diff --git a/app/views/inbound_whatsapp/index.html.erb b/app/views/inbound_whatsapp/index.html.erb new file mode 100644 index 0000000..33b5f6d --- /dev/null +++ b/app/views/inbound_whatsapp/index.html.erb @@ -0,0 +1,36 @@ + diff --git a/app/views/inbound_whatsapp/status.html.erb b/app/views/inbound_whatsapp/status.html.erb new file mode 100644 index 0000000..4239da4 --- /dev/null +++ b/app/views/inbound_whatsapp/status.html.erb @@ -0,0 +1,2 @@ +Find me in app/views/inbound_whatsapp/status.html.erb
diff --git a/config/routes.rb b/config/routes.rb index d8c62a0..51de0b1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,6 @@ Rails.application.routes.draw do - get "inbound_rcs/create" + # Root path - displays README + root 'home#index' # For RcsMessageStatus controller, create post '/rcs_message_status', to: 'rcs_message_status#create', as: :rcs_message_status @@ -34,4 +35,9 @@ get '/outbound_whatsapp/new', to: 'outbound_whatsapp#new', as: :new_outbound_whatsapp post '/outbound_whatsapp', to: 'outbound_whatsapp#create', as: :outbound_whatsapp post '/outbound_whatsapp/interactive', to: 'outbound_whatsapp#interactive', as: :interactive_whatsapp + + # Routes for InboundWhatsapp controller, inbound and status webhooks and displaying the index + get '/messages', to: 'inbound_whatsapp#index', as: :messages + post '/inbound_whatsapp/inbound', to: 'inbound_whatsapp#inbound' + post '/inbound_whatsapp/status', to: 'inbound_whatsapp#status' end diff --git a/db/migrate/20251021131458_add_missing_fields_to_whatsapp_messages.rb b/db/migrate/20251021131458_add_missing_fields_to_whatsapp_messages.rb new file mode 100644 index 0000000..1293a9c --- /dev/null +++ b/db/migrate/20251021131458_add_missing_fields_to_whatsapp_messages.rb @@ -0,0 +1,7 @@ +class AddMissingFieldsToWhatsappMessages < ActiveRecord::Migration[8.0] + def change + add_column :whatsapp_messages, :message_type, :string + add_column :whatsapp_messages, :profile_name, :string + add_column :whatsapp_messages, :reply_data, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index c6b0d68..ed87e84 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_10_15_112429) do +ActiveRecord::Schema[8.0].define(version: 2025_10_21_131458) do create_table "calls", force: :cascade do |t| t.string "to" t.string "from" @@ -56,5 +56,8 @@ t.boolean "is_inbound" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "message_type" + t.string "profile_name" + t.text "reply_data" end end diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb new file mode 100644 index 0000000..eac0e33 --- /dev/null +++ b/test/controllers/home_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class HomeControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/controllers/inbound_whatsapp_controller_test.rb b/test/controllers/inbound_whatsapp_controller_test.rb new file mode 100644 index 0000000..9549f1c --- /dev/null +++ b/test/controllers/inbound_whatsapp_controller_test.rb @@ -0,0 +1,18 @@ +require "test_helper" + +class InboundWhatsappControllerTest < ActionDispatch::IntegrationTest + test "should get index" do + get inbound_whatsapp_index_url + assert_response :success + end + + test "should get inbound" do + get inbound_whatsapp_inbound_url + assert_response :success + end + + test "should get status" do + get inbound_whatsapp_status_url + assert_response :success + end +end