431 lines
12 KiB
Markdown
431 lines
12 KiB
Markdown
# 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
|