# 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 ` 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