completed SMS gateway project

This commit is contained in:
Min Zeya Phyo
2025-10-22 17:22:17 +08:00
commit c883fa7128
190 changed files with 16294 additions and 0 deletions

430
CLAUDE.md Normal file
View File

@@ -0,0 +1,430 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
MySMSAPio is an SMS Gateway Backend API - a Rails 8.0 REST API and WebSocket server for managing SMS messaging through Android gateway devices. It provides programmatic SMS capabilities with OTP management, webhooks, and real-time WebSocket communication.
The project includes a web-based admin interface for managing API keys, monitoring SMS logs, and viewing gateway status. See [ADMIN_INTERFACE.md](ADMIN_INTERFACE.md) for detailed documentation.
## Tech Stack
- **Framework**: Rails 8.0.3 (API-only mode)
- **Ruby**: 3.4.7
- **Database**: PostgreSQL 14+
- **Cache/Queue**: Redis 7+
- **Background Jobs**: Sidekiq 7 with sidekiq-cron
- **WebSocket**: Action Cable (Redis adapter)
- **Authentication**: API Keys with SHA256 hashing
- **Server**: Puma with Thruster for production
- **Deployment**: Kamal (Docker-based deployment)
- **Code Quality**: RuboCop with rails-omakase configuration, Brakeman for security
## SMS Gateway Dependencies
- **redis**: WebSocket, caching, background jobs
- **sidekiq**: Background job processing
- **sidekiq-cron**: Scheduled jobs (health checks, cleanup)
- **jwt**: API authentication tokens
- **rack-cors**: Cross-origin resource sharing
- **phonelib**: Phone number validation
- **rotp**: OTP generation
- **httparty**: Webhook HTTP requests
- **pagy**: Pagination
## Development Commands
### Initial Setup
```bash
bin/setup
```
This will:
- Install dependencies
- Prepare the database
- Clear logs and temp files
- Start the development server
To skip auto-starting the server:
```bash
bin/setup --skip-server
```
### Running the Application
```bash
bin/dev
# or
bin/rails server
```
### Database
**Create and migrate databases:**
```bash
bin/rails db:create
bin/rails db:migrate
```
**Prepare database (create + migrate + seed):**
```bash
bin/rails db:prepare
```
**Reset database:**
```bash
bin/rails db:reset
```
**Database console:**
```bash
bin/rails dbconsole
# or via Kamal:
bin/kamal dbc
```
### Testing
**Run all tests:**
```bash
bin/rails test
```
**Run specific test file:**
```bash
bin/rails test test/models/your_model_test.rb
```
**Run specific test by line number:**
```bash
bin/rails test test/models/your_model_test.rb:42
```
**System tests:**
```bash
bin/rails test:system
```
Tests run in parallel using all processor cores by default.
### Code Quality
**Run RuboCop:**
```bash
bin/rubocop
```
**Auto-correct RuboCop violations:**
```bash
bin/rubocop -a
```
**Run Brakeman security scanner:**
```bash
bin/brakeman
```
### Rails Console
```bash
bin/rails console
# or via Kamal:
bin/kamal console
```
### Background Jobs (Sidekiq)
**Start Sidekiq:**
```bash
bundle exec sidekiq
```
**View scheduled jobs:**
```bash
bundle exec sidekiq-cron
```
**Scheduled jobs run automatically**:
- `CheckGatewayHealthJob`: Every minute
- `CleanupExpiredOtpsJob`: Every 15 minutes
- `ResetDailyCountersJob`: Daily at midnight
### Asset Management
**Precompile assets:**
```bash
bin/rails assets:precompile
```
**Clear compiled assets:**
```bash
bin/rails assets:clobber
```
### Deployment (Kamal)
**Deploy application:**
```bash
bin/kamal deploy
```
**View logs:**
```bash
bin/kamal logs
```
**Access remote shell:**
```bash
bin/kamal shell
```
**View configuration:**
```bash
bin/kamal config
```
The deployment configuration is in `config/deploy.yml`. Production uses Docker containers with SSL enabled via Let's Encrypt.
## Database Architecture
### Development/Test
Single PostgreSQL database per environment:
- Development: `my_smsa_pio_development`
- Test: `my_smsa_pio_test`
### Production
Multi-database setup for performance isolation:
- **Primary**: Main application data
- **Cache**: Database-backed cache via Solid Cache (migrations in `db/cache_migrate/`)
- **Queue**: Job queue via Solid Queue (migrations in `db/queue_migrate/`)
- **Cable**: WebSocket connections via Solid Cable (migrations in `db/cable_migrate/`)
Each database shares connection pooling configuration but maintains separate migration paths.
## Application Structure
The application follows standard Rails 8 conventions with the modern "Omakase" stack:
- **app/**: Standard Rails application code (models, controllers, views, jobs, mailers, helpers)
- **config/**: Configuration files including multi-database setup
- **db/**: Database schemas with separate migration directories for each database role
- **lib/**: Custom library code (autoloaded via `config.autoload_lib`)
- **test/**: Minitest-based test suite with system tests using Capybara + Selenium
## Key Configuration Details
### Module Name
The Rails application module is `MySmsaPio` (defined in `config/application.rb`).
### Environment Variables
**Required**:
- `DATABASE_URL`: PostgreSQL connection string
- `REDIS_URL`: Redis connection string (used for Action Cable, Sidekiq, caching)
- `SECRET_KEY_BASE`: Rails secret key
- `RAILS_ENV`: Environment (development, test, production)
**Optional**:
- `ALLOWED_ORIGINS`: CORS allowed origins (default: `*`)
- `DEFAULT_COUNTRY_CODE`: Default country for phone validation (default: `US`)
- `RAILS_LOG_LEVEL`: Logging level
### Docker & Production
The application is containerized using a multi-stage Dockerfile optimized for production:
- Base Ruby 3.4.7 slim image
- Uses Thruster as the web server (listens on port 80)
- Non-root user (rails:rails, uid/gid 1000)
- Entrypoint handles database preparation via `bin/docker-entrypoint`
- Assets precompiled during build
### Code Style
Follow RuboCop Rails Omakase conventions. Configuration is minimal, inheriting from `rubocop-rails-omakase` gem.
## Health Checks
The application includes a health check endpoint:
```
GET /up
```
Returns 200 if app boots successfully, 500 otherwise. Used by load balancers and monitoring.
---
## SMS Gateway Specific Information
### Database Models
**Gateway** (`app/models/gateway.rb`):
- Represents Android SMS gateway devices
- Tracks connection status, heartbeat, message counts
- Generates and stores API keys (hashed with SHA256)
**SmsMessage** (`app/models/sms_message.rb`):
- Stores all SMS messages (inbound and outbound)
- Auto-generates unique message IDs
- Validates phone numbers using Phonelib
- Triggers SendSmsJob on creation for outbound messages
**OtpCode** (`app/models/otp_code.rb`):
- Generates 6-digit OTP codes
- Enforces rate limiting (3 per phone per hour)
- Auto-expires after 5 minutes
- Tracks verification attempts (max 3)
**WebhookConfig** (`app/models/webhook_config.rb`):
- Configures webhooks for SMS events
- Signs payloads with HMAC-SHA256
- Supports retry logic
**ApiKey** (`app/models/api_key.rb`):
- Client API keys for application access
- Permissions-based access control
- Tracks usage and expiration
### API Controllers
**Gateway APIs** (`app/controllers/api/v1/gateway/`):
- `RegistrationsController`: Register new gateway devices
- `HeartbeatsController`: Keep-alive from gateways
- `SmsController`: Report received SMS and delivery status
**Client APIs** (`app/controllers/api/v1/`):
- `SmsController`: Send/receive SMS, check status
- `OtpController`: Generate and verify OTP codes
- `Admin::GatewaysController`: Manage gateway devices
- `Admin::StatsController`: System statistics
### WebSocket Communication
**GatewayChannel** (`app/channels/gateway_channel.rb`):
- Real-time bidirectional communication with gateway devices
- Authenticated via API key digest
- Handles:
- Gateway connection/disconnection
- Heartbeat messages
- Delivery reports
- Inbound SMS notifications
- Outbound SMS commands
**Connection** (`app/channels/application_cable/connection.rb`):
- Authenticates WebSocket connections
- Verifies gateway API keys
### Background Jobs
**Processing Jobs**:
- `SendSmsJob`: Routes outbound SMS to available gateways via WebSocket
- `ProcessInboundSmsJob`: Triggers webhooks for received SMS
- `RetryFailedSmsJob`: Retries failed messages with exponential backoff
- `TriggerWebhookJob`: Executes webhook HTTP requests
**Scheduled Jobs** (config/sidekiq_cron.yml):
- `CheckGatewayHealthJob`: Marks offline gateways (every minute)
- `CleanupExpiredOtpsJob`: Deletes expired OTP codes (every 15 minutes)
- `ResetDailyCountersJob`: Resets daily message counters (daily at midnight)
### Admin Web Interface
**Admin Authentication** (`app/models/admin.rb`, `app/controllers/admin/`):
- Session-based authentication with bcrypt password hashing
- Access URL: `/admin/login`
- Default credentials (development): admin@example.com / password123
**Admin Features**:
1. **Dashboard** (`/admin/dashboard`): Real-time statistics and recent activity
2. **API Keys Management** (`/admin/api_keys`): Create, view, and revoke API keys
3. **SMS Logs** (`/admin/logs`): Monitor messages with advanced filtering
4. **Gateway Management** (`/admin/gateways`): View and manage gateway devices
**Admin Controllers**:
- `Admin::BaseController`: Base controller with authentication
- `Admin::SessionsController`: Login/logout
- `Admin::DashboardController`: Dashboard with stats
- `Admin::ApiKeysController`: API key CRUD operations
- `Admin::LogsController`: SMS logs with filtering
- `Admin::GatewaysController`: Gateway management
See [ADMIN_INTERFACE.md](ADMIN_INTERFACE.md) for complete documentation.
### Authentication & Security
**API Key Types**:
1. **Gateway Keys** (`gw_live_...`): For Android gateway devices
2. **Client Keys** (`api_live_...`): For application APIs
3. **Admin Access**: Session-based web authentication
**Authentication Flow**:
- API keys passed in `Authorization: Bearer <key>` header
- Keys are hashed with SHA256 before storage
- Admin passwords hashed with bcrypt
- Concerns: `ApiAuthenticatable`, `RateLimitable`
**Rate Limiting**:
- Implemented via Redis caching
- OTP: Max 3 per phone per hour
- SMS Send: 100 per minute per API key
- Customizable per endpoint
### Key Files
**Models**: `app/models/{gateway,sms_message,otp_code,webhook_config,api_key}.rb`
**Controllers**: `app/controllers/api/v1/**/*_controller.rb`
**Jobs**: `app/jobs/{send_sms_job,process_inbound_sms_job,retry_failed_sms_job,trigger_webhook_job,check_gateway_health_job,cleanup_expired_otps_job,reset_daily_counters_job}.rb`
**Channels**: `app/channels/{gateway_channel,application_cable/connection}.rb`
**Concerns**: `app/controllers/concerns/{api_authenticatable,rate_limitable}.rb`, `app/models/concerns/metrics.rb`
**Config**:
- `config/routes.rb`: API routes
- `config/cable.yml`: Action Cable (Redis)
- `config/sidekiq_cron.yml`: Scheduled jobs
- `config/initializers/{cors,sidekiq,phonelib,pagy}.rb`
### Common Development Tasks
**Generate new API key**:
```ruby
# In Rails console
result = ApiKey.generate!(name: "My App", permissions: { send_sms: true, receive_sms: true })
puts result[:raw_key] # Save this immediately!
```
**Register gateway manually**:
```ruby
# In Rails console
gateway = Gateway.new(device_id: "my-device-001", name: "My Gateway")
api_key = gateway.generate_api_key!
puts api_key # Save this immediately!
```
**Check gateway status**:
```ruby
# In Rails console
Gateway.online.each { |g| puts "#{g.name}: #{g.status}" }
```
**View pending messages**:
```ruby
# In Rails console
SmsMessage.pending.count
SmsMessage.failed.each { |msg| puts "#{msg.message_id}: #{msg.error_message}" }
```
**Test WebSocket connection**:
```bash
# Use wscat or similar WebSocket client
wscat -c "ws://localhost:3000/cable?api_key=gw_live_your_key_here"
```
### Important Notes
- **Redis is required** for Action Cable, Sidekiq, and caching to work
- All phone numbers are validated and normalized using Phonelib
- Gateway devices must send heartbeats every 2 minutes or they'll be marked offline
- Outbound SMS messages are queued and sent asynchronously via Sidekiq
- Failed messages retry automatically up to 3 times with exponential backoff
- OTP codes expire after 5 minutes and allow max 3 verification attempts
- All API keys are SHA256 hashed - raw keys are only shown once during creation