Skip to content

Commit db869c4

Browse files
authored
Merge pull request #19 from ashwin47/webhooks
Move to Webhooks
2 parents bf45dfd + 816a567 commit db869c4

20 files changed

Lines changed: 499 additions & 200 deletions

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
DOCKER_REGISTRY_TOKEN=xxxxx
22
SMTP_USERNAME=xxxxx
33
SMTP_PASSWORD=xxxxx
4-
RESEND_API_KEY=xxxxx
4+
RESEND_API_KEY=xxxxx
5+
TELEGRAM_WEBHOOK_URL=https://f9eaad429bdf.ngrok-free.app

CLAUDE.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
## Commands
66

77
### Development Server
8-
- `bin/dev` - Start the development server using foreman/overmind (runs Rails server + Vite + Telegram polling)
8+
- `bin/dev` - Start the development server using foreman/overmind (runs Rails server + Vite)
99
- `bin/rails s` - Start Rails server only
1010
- `bin/vite dev` - Start Vite development server only
1111

@@ -24,7 +24,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
2424
- `bin/rails db:reset` - Drop, create, migrate and seed database
2525

2626
### Code Quality
27-
- `bundle exec rubocop` - Run Ruby linter (configured for Rails omakase)
27+
- `bin/rubocop` - Run Ruby linter (configured for Rails omakase)
2828
- `bundle exec brakeman` - Run security scanner
2929

3030
### Email Testing
@@ -33,11 +33,15 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
3333

3434
### Admin Tasks
3535
- `bin/rails "invitations:cleanup"` - Clean up expired invitations
36-
- `bin/rails "reminders:send_daily"` - Send daily payment reminders
36+
- `bin/rails "reminders:process"` - Send daily payment reminders
3737

3838
### Telegram Bot
39-
- `ruby lib/telegram_polling.rb` - Start Telegram bot polling (runs automatically with `bin/dev`)
40-
- Bot responds to commands: `/start`, `/help`, `/status`, `/payments`, `/pay`, `/settings`
39+
- `bin/rails telegram:setup_webhook` - Configure webhook for development/production
40+
- `bin/rails telegram:webhook_info` - Check webhook status and configuration
41+
- `bin/rails telegram:remove_webhook` - Remove webhooks
42+
- `bin/rails telegram:reset_webhook` - Reset webhook (remove and setup)
43+
- `bin/rails telegram:test_webhook` - Test webhook endpoint locally
44+
- Bot responds to commands: `/start`, `/help`, `/status`, `/payments`, `/pay`, `/settings`, `/unlink`
4145

4246
## Application Architecture
4347

@@ -86,11 +90,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
8690
- **Bot Framework**: telegram-bot-ruby gem for Telegram API integration
8791
- **Account Linking**: Users can link Telegram accounts via verification tokens in profile settings
8892
- **Notifications**: Payment reminders, billing cycle alerts, and payment confirmations via Telegram
89-
- **Bot Commands**: Interactive commands for payment management and status checking
90-
- **Services**: `TelegramBotService` for API interactions, `TelegramNotificationService` for message formatting
93+
- **Bot Commands**: Interactive commands including `/start`, `/help`, `/status`, `/payments`, `/pay`, `/settings`, `/unlink`
94+
- **Services**: `TelegramBotService` for webhook processing, `TelegramNotificationService` for message formatting
9195
- **Jobs**: `TelegramNotificationJob` for asynchronous delivery
9296
- **Models**: `TelegramMessage` for delivery tracking and status
93-
- **Polling**: Continuous webhook processing via `lib/telegram_polling.rb`
97+
- **Webhooks**: Primary integration method via `TelegramController` at `/telegram/webhook`
98+
- **Routes**: Profile integration routes for token generation, status checking, unlinking, and notification preferences
99+
- **Testing**: Full webhook endpoint testing with mocked service calls
94100

95101
### Background Jobs
96102
- **Queue**: SolidQueue for job processing
@@ -120,9 +126,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
120126

121127
### Configuration & Environment
122128
- **Credentials**: Telegram bot token stored in Rails credentials (`telegram_bot_token`)
123-
- **Routes**: Telegram-specific routes in `config/routes.rb` for profile integration
124-
- **Initializers**: Telegram configuration in `config/initializers/telegram.rb`
125-
- **Development**: Telegram polling runs automatically with `bin/dev` via Procfile.dev
129+
- **Webhook Secret**: Optional webhook secret token in Rails credentials (`telegram_webhook_secret`)
130+
- **Routes**: Telegram webhook endpoint and profile integration routes in `config/routes.rb`
131+
- **Initializers**: Telegram webhook configuration in `config/initializers/telegram.rb`
132+
- **Development**: Webhook-based integration requiring ngrok for local testing
133+
- **Environment Variables**:
134+
- `TELEGRAM_WEBHOOK_URL` for development webhook setup
135+
- `TELEGRAM_USE_WEBHOOKS=true` to enable webhooks in development
126136

127137
### Development Tools
128138
- **Letter Opener**: Email preview in development
@@ -134,8 +144,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
134144
- **Minitest**: Rails default testing framework
135145
- **Fixtures**: Test data in `test/fixtures/`
136146
- **Integration Tests**: Full workflow testing in `test/integration/`
147+
- **Controller Tests**: Including TelegramController webhook endpoint testing
148+
- **Service Tests**: TelegramBotService and TelegramNotificationService testing
137149
- **Authentication Helpers**: `sign_in_as(user)` and `admin_authenticate` helpers
138150

139151
### Documentation
140152
- **Invitation Flow**: Complete documentation in `docs/INVITATION_FLOW_DOCUMENTATION.md`
141-
- **Authentication Flow**: Complete documentation in `docs/AUTHENTICATION_FLOW_DOCUMENTATION.md`
153+
- **Authentication Flow**: Complete documentation in `docs/AUTHENTICATION_FLOW_DOCUMENTATION.md`
154+
- **Telegram Webhooks**: Complete webhook setup and troubleshooting in `docs/TELEGRAM_WEBHOOKS.md`

Gemfile.lock

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ GEM
8383
bindex (0.8.1)
8484
bootsnap (1.18.6)
8585
msgpack (~> 1.2)
86-
brakeman (7.0.2)
86+
brakeman (7.1.0)
8787
racc
8888
builder (3.3.0)
8989
childprocess (5.1.0)
@@ -125,11 +125,11 @@ GEM
125125
dry-logic (~> 1.4)
126126
zeitwerk (~> 2.6)
127127
ed25519 (1.4.0)
128-
erb (5.0.1)
128+
erb (5.0.2)
129129
erubi (1.13.1)
130130
et-orbi (1.2.11)
131131
tzinfo
132-
faraday (2.13.2)
132+
faraday (2.13.3)
133133
faraday-net_http (>= 2.0, < 3.5)
134134
json
135135
logger
@@ -162,7 +162,7 @@ GEM
162162
ruby-vips (>= 2.0.17, < 3)
163163
inertia_rails (3.9.0)
164164
railties (>= 6)
165-
io-console (0.8.0)
165+
io-console (0.8.1)
166166
irb (1.15.2)
167167
pp (>= 0.6.0)
168168
rdoc (>= 4.0.0)
@@ -173,7 +173,7 @@ GEM
173173
js-routes (2.3.5)
174174
railties (>= 5)
175175
sorbet-runtime
176-
json (2.12.2)
176+
json (2.13.0)
177177
kamal (2.7.0)
178178
activesupport (>= 7.0)
179179
base64 (~> 0.2)
@@ -246,23 +246,23 @@ GEM
246246
net-protocol
247247
net-ssh (7.3.0)
248248
nio4r (2.7.4)
249-
nokogiri (1.18.8-aarch64-linux-gnu)
249+
nokogiri (1.18.9-aarch64-linux-gnu)
250250
racc (~> 1.4)
251-
nokogiri (1.18.8-aarch64-linux-musl)
251+
nokogiri (1.18.9-aarch64-linux-musl)
252252
racc (~> 1.4)
253-
nokogiri (1.18.8-arm-linux-gnu)
253+
nokogiri (1.18.9-arm-linux-gnu)
254254
racc (~> 1.4)
255-
nokogiri (1.18.8-arm-linux-musl)
255+
nokogiri (1.18.9-arm-linux-musl)
256256
racc (~> 1.4)
257-
nokogiri (1.18.8-arm64-darwin)
257+
nokogiri (1.18.9-arm64-darwin)
258258
racc (~> 1.4)
259-
nokogiri (1.18.8-x86_64-darwin)
259+
nokogiri (1.18.9-x86_64-darwin)
260260
racc (~> 1.4)
261-
nokogiri (1.18.8-x86_64-linux-gnu)
261+
nokogiri (1.18.9-x86_64-linux-gnu)
262262
racc (~> 1.4)
263-
nokogiri (1.18.8-x86_64-linux-musl)
263+
nokogiri (1.18.9-x86_64-linux-musl)
264264
racc (~> 1.4)
265-
ostruct (0.6.2)
265+
ostruct (0.6.3)
266266
parallel (1.27.0)
267267
parser (3.3.8.0)
268268
ast (~> 2.4.1)
@@ -326,7 +326,7 @@ GEM
326326
erb
327327
psych (>= 4.0.0)
328328
regexp_parser (2.10.0)
329-
reline (0.6.1)
329+
reline (0.6.2)
330330
io-console (~> 0.5)
331331
resend (0.22.0)
332332
httparty (>= 0.21.0)
@@ -342,7 +342,7 @@ GEM
342342
rubocop-ast (>= 1.45.1, < 2.0)
343343
ruby-progressbar (~> 1.7)
344344
unicode-display_width (>= 2.4.0, < 4.0)
345-
rubocop-ast (1.45.1)
345+
rubocop-ast (1.46.0)
346346
parser (>= 3.3.7.2)
347347
prism (~> 1.4)
348348
rubocop-performance (1.25.0)
@@ -386,15 +386,15 @@ GEM
386386
fugit (~> 1.11.0)
387387
railties (>= 7.1)
388388
thor (~> 1.3.1)
389-
sorbet-runtime (0.5.12222)
390-
sqlite3 (2.7.2-aarch64-linux-gnu)
391-
sqlite3 (2.7.2-aarch64-linux-musl)
392-
sqlite3 (2.7.2-arm-linux-gnu)
393-
sqlite3 (2.7.2-arm-linux-musl)
394-
sqlite3 (2.7.2-arm64-darwin)
395-
sqlite3 (2.7.2-x86_64-darwin)
396-
sqlite3 (2.7.2-x86_64-linux-gnu)
397-
sqlite3 (2.7.2-x86_64-linux-musl)
389+
sorbet-runtime (0.5.12356)
390+
sqlite3 (2.7.3-aarch64-linux-gnu)
391+
sqlite3 (2.7.3-aarch64-linux-musl)
392+
sqlite3 (2.7.3-arm-linux-gnu)
393+
sqlite3 (2.7.3-arm-linux-musl)
394+
sqlite3 (2.7.3-arm64-darwin)
395+
sqlite3 (2.7.3-x86_64-darwin)
396+
sqlite3 (2.7.3-x86_64-linux-gnu)
397+
sqlite3 (2.7.3-x86_64-linux-musl)
398398
sshkit (1.24.0)
399399
base64
400400
logger

Procfile.dev

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11

22
vite: bin/vite dev
3-
web: bin/rails s
4-
telegram: ruby lib/telegram_polling.rb
3+
web: bin/rails s -p 3000

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ SplitMySub is a comprehensive subscription cost-sharing platform that eliminates
5555

5656
### 🔔 **Smart Reminders**
5757
- Automated email reminders before payment due dates
58-
- Telegram notifications for users with linked accounts
58+
- Telegram notifications via webhook-based bot integration
5959
- Configurable reminder timing (default: 7 days before)
6060
- Escalating reminder sequence (gentle → follow-up → final notice)
6161
- Customizable reminder templates

app/controllers/telegram_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
class TelegramController < ApplicationController
22
skip_before_action :verify_authenticity_token
3+
allow_unauthenticated_access
34

45
def webhook
56
begin

app/services/telegram_bot_service.rb

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,6 @@ def process_webhook(update)
9292
end
9393
end
9494

95-
def process_message(message)
96-
Rails.logger.info "Processing polling message: #{message.inspect}"
97-
98-
# Convert polling message to webhook format for compatibility with existing code
99-
if message.text
100-
update = {
101-
"message" => {
102-
"chat" => { "id" => message.chat.id },
103-
"from" => {
104-
"id" => message.from.id,
105-
"username" => message.from.username
106-
},
107-
"text" => message.text
108-
}
109-
}
110-
process_text_message(update)
111-
else
112-
Rails.logger.info "Received non-text message type from polling: #{message.inspect}"
113-
end
114-
end
115-
11695
private
11796

11897
def process_text_message(update)
@@ -147,18 +126,37 @@ def handle_start_command(chat_id, text, user_info)
147126
user = User.find_by(telegram_verification_token: token)
148127

149128
if user && user.telegram_verification_token_expires_at > Time.current
150-
# Link the account
151-
user.update!(
152-
telegram_user_id: chat_id.to_s,
153-
telegram_username: user_info["username"],
154-
telegram_verification_token: nil,
155-
telegram_verification_token_expires_at: nil
156-
)
157-
158-
send_message(
159-
chat_id: chat_id,
160-
text: "🎉 Account linked successfully! Welcome to SplitMySub, #{user.first_name}!\n\nYou can now receive payment reminders and manage your subscriptions through this bot.\n\nType /help to see available commands."
161-
)
129+
# Check if this Telegram account is already linked to someone else
130+
existing_linked_user = User.find_by(telegram_user_id: chat_id.to_s)
131+
132+
if existing_linked_user && existing_linked_user.id != user.id
133+
send_message(
134+
chat_id: chat_id,
135+
text: "⚠️ This Telegram account is already linked to another SplitMySub account.\n\nIf you want to link it to a different account, please first unlink it from your current account in the profile settings, then try again."
136+
)
137+
return
138+
end
139+
140+
# Link the account (or update if already linked to same user)
141+
begin
142+
user.update!(
143+
telegram_user_id: chat_id.to_s,
144+
telegram_username: user_info["username"],
145+
telegram_verification_token: nil,
146+
telegram_verification_token_expires_at: nil
147+
)
148+
149+
send_message(
150+
chat_id: chat_id,
151+
text: "🎉 Account linked successfully! Welcome to SplitMySub, #{user.first_name}!\n\nYou can now receive payment reminders and manage your subscriptions through this bot.\n\nType /help to see available commands."
152+
)
153+
rescue ActiveRecord::RecordInvalid => e
154+
Rails.logger.error "Failed to link Telegram account: #{e.message}"
155+
send_message(
156+
chat_id: chat_id,
157+
text: "❌ Unable to link account due to a validation error. Please try generating a new verification token from your profile settings."
158+
)
159+
end
162160
else
163161
send_message(
164162
chat_id: chat_id,
@@ -192,6 +190,8 @@ def handle_authenticated_command(user, text, chat_id)
192190
handle_payments_command(user, chat_id)
193191
when "/settings"
194192
handle_settings_command(user, chat_id)
193+
when "/unlink"
194+
handle_unlink_command(user, chat_id)
195195
when /^\/pay/
196196
handle_pay_command(user, text, chat_id)
197197
else
@@ -210,12 +210,13 @@ def handle_help_command(chat_id)
210210
/payments - List all pending payments
211211
/pay [project] - Mark a payment as completed
212212
/settings - Manage notification preferences
213+
/unlink - Unlink your Telegram account from SplitMySub
213214
/help - Show this help message
214215
215216
💡 You can also receive automatic payment reminders and updates through this bot!
216217
TEXT
217218

218-
send_message(chat_id: chat_id, text: help_text)
219+
send_message(chat_id: chat_id, text: help_text, parse_mode: "HTML")
219220
end
220221

221222
def handle_status_command(user, chat_id)
@@ -350,6 +351,22 @@ def handle_pay_command(user, text, chat_id)
350351
Rails.logger.info "Payment confirmed via Telegram by user #{user.id} for project #{project.id}"
351352
end
352353

354+
def handle_unlink_command(user, chat_id)
355+
# Store user name for goodbye message
356+
user_name = user.first_name
357+
358+
# Unlink the account directly
359+
user.unlink_telegram_account!
360+
361+
send_message(
362+
chat_id: chat_id,
363+
text: "🔓 <b>Account Unlinked</b>\n\nGoodbye #{user_name}! Your Telegram account has been successfully unlinked from SplitMySub.\n\n📱 To re-link in the future:\n1. Go to your SplitMySub profile settings\n2. Generate a new verification token\n3. Send /start [token] to this bot\n\nThank you for using SplitMySub! 👋",
364+
parse_mode: "HTML"
365+
)
366+
367+
Rails.logger.info "Telegram account unlinked via bot by user #{user.id}"
368+
end
369+
353370
private
354371

355372
def bot

config/deploy.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ env:
3838
- RESEND_API_KEY
3939
- ADMIN_PASSWORD
4040
- DOCKER_REGISTRY_TOKEN
41+
- TELEGRAM_BOT_TOKEN
4142
clear:
4243
# Rails environment
4344
RAILS_ENV: production
@@ -62,6 +63,9 @@ env:
6263
APP_HOST: splitmysubscription.xyz
6364
APP_PROTOCOL: https
6465

66+
# Telegram webhook configuration
67+
TELEGRAM_USE_WEBHOOKS: true
68+
6569
# Vite configuration for production
6670
VITE_RUBY_HOST: 0.0.0.0
6771
VITE_RUBY_PORT: 3036
@@ -139,7 +143,7 @@ logging:
139143
# telegram:
140144
# image: ashwinmk2016/splitmysub
141145
# host: 146.190.118.95
142-
# cmd: ruby lib/telegram_polling.rb
146+
# cmd: echo "Telegram webhooks are now handled by main app - no separate service needed"
143147
# env:
144148
# secret:
145149
# - RAILS_MASTER_KEY

0 commit comments

Comments
 (0)