campaign_connector/docs/instruct.md

1093 lines
34 KiB
Markdown

# Development Instructions for AI Assistant
## User Instructions
The user likes to develop using plain javascript, hmtl, and css for most of their applications. They are getting used to python and are comfortable with it, but prefer javascript for frontend work. They use Ubuntu as their main development environment and like to use Docker for containerization. They also have an interest in Android development, particularly using Termux for lightweight solutions.
They like to do production driven development - using docker containers to quickly push updates. They use newer docker compose commands.
No file should be more then 1000 lines and if they approach that length lets make sure to implement a refactoring and breaking up of the file.
--- Everything past this line is instructions from past LLM's. If you want to pass along updated development instructions, please only update past this line ---
## SMS Campaign Manager + Android-Homelab Integration Project
**📁 Project Structure:** This project is now organized into logical directories. See `../PROJECT_STRUCTURE.md` for the complete file layout.
**🚀 Quick Start:** Use `../run.sh start` from project root to launch the application.
### Developer Profile & Preferences
#### Primary Technology Stack
- **Frontend**: JavaScript (ES6+), HTML5, CSS3
- Prefer vanilla JS or lightweight frameworks
- Responsive design with mobile-first approach
- Clean, semantic HTML structure
- Modern CSS features (Grid, Flexbox, Custom Properties)
- **Backend**: Python (comfortable, secondary to JS)
- Flask for web services and APIs
- SQLite for development, PostgreSQL for production
- pip for package management
- Virtual environments for isolation
- Request/Response pattern with proper error handling
- **Development Environment**: Ubuntu Linux
- bash shell scripting
- Docker for containerization and deployment
- VS Code or similar editors (with Remote SSH support)
- Git for version control
- SSH-based remote development on Android
#### Android/Mobile Development
- **Lightweight solutions preferred** - Battery and performance conscious
- **Termux environment** - Linux tools on Android with SSH access
- **Minimal dependencies** - Essential packages only
- **Background service efficiency** - Proper power management
- **Remote development** - SSH-based coding from Ubuntu homelab
- **Dual connection patterns** - Primary/fallback reliability strategies
### Development Methodology
#### Test-Driven Development (TDD)
- **Write tests first** - Define expected behavior before implementation
- **Red-Green-Refactor cycle** - Fail, pass, optimize
- **Unit tests** for individual functions and modules
- **Integration tests** for API endpoints and workflows
- **Connection testing** - Verify dual SMS connection reliability
- **End-to-end validation** - Complete workflow testing including Android integration
- **End-to-end tests** for complete user journeys
#### Testing Frameworks
- **JavaScript**: Jest, Mocha, or native browser testing
- **Python**: pytest, unittest (built-in)
- **API Testing**: Postman/Newman, curl scripts
- **Docker Testing**: Container health checks, multi-stage builds
### Code Quality Standards
#### JavaScript Guidelines
```javascript
// Use const/let, avoid var
const apiEndpoint = '/api/campaign/status';
let currentCampaign = null;
// Async/await over promises when possible
async function getCampaignStatus() {
try {
const response = await fetch(apiEndpoint);
return await response.json();
} catch (error) {
console.error('Campaign status fetch failed:', error);
throw error;
}
}
// Modular structure with clear separation of concerns
class CampaignManager {
constructor(apiClient) {
this.api = apiClient;
this.status = 'idle';
}
async startCampaign(campaignId) {
// Implementation with error handling
}
}
```
#### Python Guidelines
```python
# Type hints and docstrings
from typing import Dict, List, Optional
import pytest
def send_sms(phone: str, message: str, name: Optional[str] = None) -> Dict:
"""Send SMS message with optional name substitution.
Args:
phone: Target phone number
message: Message text with possible {name} placeholder
name: Optional name for substitution
Returns:
Dict with status, timestamp, and any errors
Raises:
ConnectionError: If phone connection fails
"""
# Implementation
# Test-driven development
def test_sms_sending():
"""Test SMS sending with mock phone connection."""
# Arrange
phone = "7802921731"
message = "Hello {name}!"
name = "Reed"
# Act
result = send_sms(phone, message, name)
# Assert
assert result['status'] == 'success'
assert result['sent_message'] == "Hello Reed!"
```
#### Architecture Preferences
#### Frontend Architecture
- **Progressive Enhancement** - Works without JavaScript, enhanced with it
- **Component-based thinking** - Reusable, self-contained modules
- **State management** - Clear data flow, avoid global state
- **API-first design** - Frontend consumes backend APIs
- **Real-time updates** - Polling for live connection status (as seen in dashboard)
#### Backend Architecture
- **RESTful APIs** - Clear, predictable endpoints
- **Dependency injection** - Testable, modular code
- **Configuration externalization** - Environment variables, config files
- **Error handling** - Comprehensive, user-friendly error responses
- **Connection management** - Health monitoring and automatic failover
- **Dual service patterns** - Primary/fallback reliability (Termux API + ADB)
### Docker Development Patterns
#### Ubuntu Host Environment
```dockerfile
# Multi-stage builds for efficiency
FROM python:3.11-slim as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
FROM python:3.11-slim as runtime
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
WORKDIR /app
COPY . .
# Network host mode for ADB and SSH connectivity
CMD ["python", "app.py"]
```
#### Development vs Production
- **Development**: Volume mounts, hot reload, debug logging, SSH tunnels
- **Production**: Minimal images, health checks, proper logging, automated restarts
- **Testing**: Test-specific containers, isolated databases, mock services
- **Remote Integration**: SSH connections to Android devices, API tunneling
### Android/Termux Constraints
#### Resource Management
- **Minimal memory footprint** - Avoid heavy frameworks
- **Battery optimization** - Efficient polling, proper sleep modes
- **Storage awareness** - Clean up temporary files, log rotation
- **Network efficiency** - Compress data, batch operations
- **SSH optimization** - Persistent connections, multiplexing
#### Service Architecture
```python
# Lightweight Flask server for Termux with SSH integration
from flask import Flask, jsonify, request
import subprocess
import logging
import os
import json
import time
# Minimal logging configuration
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24) # Random key for security
@app.route('/health')
def health_check():
"""Lightweight health check endpoint."""
return jsonify({
'status': 'healthy',
'timestamp': time.time(),
'uptime_seconds': get_uptime(),
'ssh_active': check_ssh_connection()
})
@app.route('/api/sms/send', methods=['POST'])
def send_sms():
"""Native SMS sending via Termux API."""
data = request.get_json()
phone = data.get('phone')
message = data.get('message', '')
name = data.get('name')
# Substitute name if provided
if name:
message = message.replace('{name}', name)
try:
# Use termux-sms-send for native SMS
result = subprocess.run([
'termux-sms-send',
'-n', phone,
message
], capture_output=True, text=True, timeout=30)
if result.returncode == 0:
return jsonify({
'success': True,
'message': 'SMS sent successfully',
'phone': phone,
'sent_message': message,
'timestamp': time.time()
})
else:
return jsonify({
'success': False,
'error': result.stderr or 'SMS send failed',
'phone': phone
}), 500
except Exception as e:
return jsonify({
'success': False,
'error': str(e),
'phone': phone
}), 500
@app.route('/api/device/battery')
def get_battery():
"""Get device battery status."""
try:
result = subprocess.run(['termux-battery-status'],
capture_output=True, text=True)
battery_data = json.loads(result.stdout)
return jsonify({'success': True, 'battery': battery_data})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
if __name__ == '__main__':
# Bind to all interfaces to allow SSH tunneled connections
app.run(host='0.0.0.0', port=5001, debug=False)
```
### Specific Project Guidelines
#### SMS Campaign Manager Integration
- **Dual connection support** - Termux API primary, ADB fallback
- **Graceful failover** - Automatic switching between connection types
- **Real-time status** - WebSocket or polling for live updates
- **User experience** - Clear feedback, error recovery
- **Remote development** - SSH-based coding from Ubuntu homelab
- **Connection monitoring** - Health checks and performance metrics
#### Testing Strategy
- **Mock phone connections** - Unit tests without hardware
- **Docker test environments** - Isolated, reproducible tests
- **API contract testing** - Ensure frontend/backend compatibility
- **End-to-end automation** - Full workflow validation including Android device
- **SSH connection testing** - Verify remote development environment
- **Dual connection validation** - Test failover scenarios
#### Security Considerations
- **Input validation** - Sanitize all user inputs
- **Authentication** - HMAC signatures for API requests
- **Command whitelisting** - Prevent dangerous operations
- **Error information** - Avoid exposing system details
- **SSH security** - Key-based authentication, proper permissions
- **Network isolation** - Secure tunneling for API communications
### Communication Style
#### Code Documentation
- **Clear variable names** - Self-documenting code
- **Function docstrings** - Purpose, parameters, return values
- **Inline comments** - For complex logic only
- **README files** - Setup, usage, troubleshooting
- **Integration guides** - Detailed setup for Android/SSH components
#### Problem-Solving Approach
- **Break down complexity** - Divide large tasks into smaller steps
- **Iterative development** - Working increments, continuous testing
- **Error-first thinking** - What can go wrong? How to handle it?
- **Performance awareness** - Resource usage, optimization opportunities
- **Connection reliability** - Always plan for network failures and recoveries
### Development Workflow
#### Git Practices
- **Feature branches** - One feature per branch
- **Descriptive commits** - Clear, actionable commit messages
- **Pull requests** - Code review and discussion
- **Tag releases** - Version management
- **Integration testing** - Verify Android components before merging
#### Docker Development
```bash
# Development workflow with Android integration
docker-compose -f docker-compose.dev.yml up --build
docker-compose exec app pytest tests/
docker-compose logs -f app
# Test Android connectivity
ssh android-dev "curl http://localhost:5001/health"
# Production deployment
docker-compose up -d
docker-compose exec app python -m pytest
```
#### Remote Android Development
```bash
# SSH into Android device for development
ssh android-dev
# Start development server on Android
cd ~/sms-campaign-manager
python termux-sms-api-server.py
# Test from Ubuntu homelab
curl -X GET http://10.0.0.193:5001/health
curl -X GET http://10.0.0.193:5001/api/device/battery
```
### Expected Deliverables
#### Code Quality
- **Test coverage >80%** - Comprehensive test suites including Android integration
- **Working Docker setup** - Easy deployment and development
- **Clear documentation** - Setup, API reference, troubleshooting, Android guides
- **Error handling** - Graceful failure recovery and connection failover
#### User Experience
- **Responsive interface** - Works on desktop and mobile
- **Real-time feedback** - Status updates and progress indicators including connection health
- **Intuitive workflows** - Minimal learning curve
- **Accessibility** - WCAG guidelines compliance
- **Connection transparency** - Clear indication of which SMS method is active
#### Performance
- **Fast page loads** - Optimized assets, efficient queries
- **Minimal resource usage** - Especially on Android side
- **Scalable architecture** - Handle increasing message volumes
- **Monitoring integration** - Health checks and metrics for both Ubuntu and Android components
- **Connection reliability** - Sub-second failover between Termux API and ADB
### Anti-Patterns to Avoid
#### Technical
- **Heavy frameworks** on Android - Keep it lightweight for battery life
- **Synchronous operations** - Use async/await patterns
- **Hardcoded values** - Use configuration and environment variables
- **Ignore error handling** - Always plan for failure scenarios
- **Single connection dependency** - Always have fallback methods
- **Blocking SSH operations** - Use timeouts and proper connection management
#### Development Process
- **Big-bang releases** - Prefer incremental updates
- **Testing as afterthought** - Test-driven development
- **Monolithic architecture** - Modular, testable components
- **Poor documentation** - Code should be self-explanatory with good docs
- **Ignore Android constraints** - Battery, memory, and storage limitations
- **Network assumptions** - Always plan for connectivity issues
### Current Project Status Integration Notes
#### Completed Components
-**SSH Development Environment**: Full remote coding setup from Ubuntu to Android
-**Dual SMS Connections**: Termux API primary with ADB fallback
-**Flask Enhancement**: Connection manager with health monitoring
-**Docker Integration**: Network host mode for ADB and SSH connectivity
-**Documentation**: Comprehensive guides covering 472+ pages of setup procedures
#### Development Environment Ready
- **SSH Access**: `ssh android-dev` (10.0.0.193:8022)
- **API Server**: Running on Android at http://10.0.0.193:5001
- **Web Interface**: Ubuntu homelab at http://localhost:5000
- **Connection Monitoring**: Real-time status via `/api/connections/status`
This instruction set ensures consistent, high-quality development aligned with your preferences for JavaScript, HTML, CSS frontend work, comfortable Python backend development, Ubuntu environments, Docker usage, and lightweight Android solutions with reliable remote development capabilities.
## Database Schema & API Endpoints Reference
### Database Architecture
The SMS Campaign Manager uses **SQLite** with **TRUNCATE journal mode** to avoid WAL file locking issues in Docker environments. The database is located at `./data/campaign.db` with proper permissions and backup strategies.
#### Core Tables Schema
```sql
-- Campaigns table - Main campaign tracking
CREATE TABLE campaigns (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, -- Campaign display name
template TEXT NOT NULL, -- Message template with {name} placeholders
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP, -- When campaign execution began
completed_at TIMESTAMP, -- When campaign finished
status TEXT DEFAULT 'draft', -- 'draft', 'pending', 'running', 'completed', 'paused'
total_recipients INTEGER DEFAULT 0, -- Expected recipient count
total_sent INTEGER DEFAULT 0 -- Actually sent message count
);
-- Recipients table - Individual campaign targets
CREATE TABLE recipients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
campaign_id INTEGER, -- Foreign key to campaigns
phone TEXT NOT NULL, -- Target phone number (e.g., '7801234567')
name TEXT, -- Contact name for {name} substitution
status TEXT DEFAULT 'pending', -- 'pending', 'sent', 'failed'
sent_at TIMESTAMP, -- When message was delivered
error_message TEXT, -- Error details if failed
FOREIGN KEY (campaign_id) REFERENCES campaigns (id)
);
-- Messages table - SMS tracking and conversation history
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone TEXT NOT NULL, -- Phone number
message TEXT NOT NULL, -- Actual message content sent
direction TEXT DEFAULT 'outbound', -- 'outbound' (sent) or 'inbound' (received)
status TEXT DEFAULT 'pending', -- 'pending', 'sent', 'delivered', 'failed'
campaign_id INTEGER, -- Optional: link to campaign
name TEXT, -- Contact name
timestamp REAL, -- Unix timestamp from phone
sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_read INTEGER DEFAULT 0, -- Read status for conversations
conversation_id TEXT, -- Conversation grouping identifier
FOREIGN KEY (campaign_id) REFERENCES campaigns (id)
);
-- Contact lists table - Reusable contact groups
CREATE TABLE contact_lists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, -- List display name
filename TEXT, -- Original CSV filename
contacts TEXT, -- JSON array of contact objects
contact_count INTEGER DEFAULT 0, -- Cached count for performance
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
#### Database Connection Best Practices
```python
# Avoid WAL mode issues in Docker - use TRUNCATE mode
def get_db_connection(db_path):
"""Get database connection with proper settings to avoid WAL issues"""
conn = sqlite3.connect(db_path, timeout=30.0, isolation_level='DEFERRED')
# Use TRUNCATE journal mode instead of WAL to avoid file locking issues
conn.execute("PRAGMA journal_mode=TRUNCATE")
conn.execute("PRAGMA synchronous=NORMAL")
conn.execute("PRAGMA temp_store=MEMORY")
conn.execute("PRAGMA busy_timeout=30000")
return conn
# Retry logic for database operations
def execute_with_retry(operation, max_retries=3):
"""Execute database operation with retry logic"""
for attempt in range(max_retries):
try:
return operation()
except sqlite3.OperationalError as e:
if "disk I/O error" in str(e) and attempt < max_retries - 1:
logger.warning(f"Database I/O error, retrying... (attempt {attempt + 1}/{max_retries})")
time.sleep(1)
continue
else:
raise
```
### API Endpoints Documentation
#### Campaign Management
**POST /api/campaign/upload**
```javascript
// Upload CSV file with contact preview
const formData = new FormData();
formData.append('file', csvFile);
const response = await fetch('/api/campaign/upload', {
method: 'POST',
body: formData
});
// Response format:
{
"success": true,
"total_contacts": 4,
"contacts": [
{"name": "John Doe", "phone": "7801234567"},
{"name": "Jane Smith", "phone": "7801234568"}
],
"preview": [/* First 10 contacts for UI display */],
"message": "Successfully loaded 4 contacts"
}
```
**POST /api/campaign/create**
```javascript
// Create new campaign with recipients
const campaignData = {
name: "Weekend Volunteer Outreach",
message: "Hi {name}! Hope all is well. Are you available this weekend?",
csv_data: [
{"name": "John Doe", "phone": "7801234567"},
{"name": "Jane Smith", "phone": "7801234568"}
]
};
const response = await fetch('/api/campaign/create', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(campaignData)
});
// Response with contact preview:
{
"success": true,
"campaign_id": 24,
"campaign_name": "Weekend Volunteer Outreach",
"total_recipients": 2,
"contacts_preview": [
{
"name": "John Doe",
"phone": "7801234567",
"preview_message": "Hi John Doe! Hope all is well. Are you available this weekend?"
}
],
"message": "Campaign created with 2 recipients"
}
```
**POST /api/campaign/start**
```javascript
// Start campaign execution (fetches recipients from database)
const startData = {
campaign_id: 24
};
const response = await fetch('/api/campaign/start', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(startData)
});
// Response:
{
"success": true,
"status": "started",
"total": 2
}
```
**GET /api/campaigns/recent**
```javascript
// Get recent campaign list with status
const response = await fetch('/api/campaigns/recent');
const campaigns = await response.json();
// Response format:
[
{
"id": 24,
"name": "Weekend Volunteer Outreach",
"total_recipients": 2,
"sent_count": 2, // Actual messages sent
"status": "completed",
"created_at": "2025-08-25 16:17:00.364348"
}
]
```
#### Connection & Status Monitoring
**GET /api/phone/status**
```javascript
// Check dual SMS connection status
const response = await fetch('/api/phone/status');
const status = await response.json();
// Response format:
{
"termux_connected": true, // Termux API health check
"adb_connected": true, // ADB connection status
"connected": true, // Either connection available
"ip": "10.0.0.193",
"port": "5555",
"prefer_termux": true // Primary connection preference
}
```
**GET /api/connections/status**
```javascript
// Detailed connection diagnostics
const response = await fetch('/api/connections/status');
const connections = await response.json();
// Response format:
{
"connections": {
"termux_api": {
"available": true,
"url": "http://10.0.0.193:5001",
"type": "primary",
"last_check": "2025-08-25T16:20:00Z"
},
"adb": {
"available": true,
"target": "10.0.0.193:5555",
"type": "fallback"
}
},
"optimal_connection": "termux_api"
}
```
#### Contact List Management
**GET /api/lists**
```javascript
// Get saved contact lists
const response = await fetch('/api/lists');
const lists = await response.json();
// Response format:
[
{
"id": 1,
"name": "volunteers_2025.csv_20250825_120000",
"contact_count": 15,
"created_at": "2025-08-25T12:00:00Z"
}
]
```
**GET /api/lists/:id**
```javascript
// Load specific contact list with full contact data
const response = await fetch(`/api/lists/${listId}`);
const list = await response.json();
// Response format:
{
"id": 1,
"name": "volunteers_2025.csv",
"contacts": [
{"name": "John Doe", "phone": "7801234567"},
{"name": "Jane Smith", "phone": "7801234568"}
],
"contact_count": 2
}
```
#### Templates Management
**GET /api/templates**
```javascript
// Get all message templates
const response = await fetch('/api/templates');
const templates = await response.json();
// Response format:
[
{
"id": 1,
"name": "Volunteer Check-In",
"content": "Hi {name}! Hope all is well. Are you available this weekend?",
"description": "Check availability for volunteer events",
"category": "volunteer",
"is_favorite": 0,
"times_used": 0,
"created_at": "2025-08-25 16:39:21",
"updated_at": "2025-08-25 16:39:21"
}
]
```
**POST /api/templates**
```javascript
// Create new template
const templateData = {
name: "Follow-up Message",
content: "Hi {name}! Following up on our conversation...",
description: "Follow up template",
category: "followup",
is_favorite: 0
};
const response = await fetch('/api/templates', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(templateData)
});
// Response:
{
"success": true,
"template_id": 6,
"message": "Template created successfully"
}
```
**GET /api/templates/:id**
```javascript
// Get specific template
const response = await fetch('/api/templates/1');
const data = await response.json();
// Response:
{
"success": true,
"template": {
"id": 1,
"name": "Volunteer Check-In",
"template": "Hi {name}! Hope all is well. Are you available this weekend?",
"description": "Check availability for volunteer events",
"category": "volunteer",
"usage_count": 3
}
}
```
**PUT /api/templates/:id**
```javascript
// Update template
const updateData = {
name: "Updated Template Name",
content: "Updated message content",
is_favorite: 1
};
const response = await fetch('/api/templates/1', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(updateData)
});
```
**DELETE /api/templates/:id**
```javascript
// Delete template
const response = await fetch('/api/templates/1', { method: 'DELETE' });
const result = await response.json();
// Response:
{
"success": true,
"message": "Template deleted successfully"
}
```
**POST /api/templates/:id/use**
```javascript
// Mark template as used (increments usage_count)
await fetch('/api/templates/1/use', { method: 'POST' });
```
**GET /api/analytics**
```javascript
// Campaign performance metrics
const response = await fetch('/api/analytics');
const analytics = await response.json();
// Response format:
{
"total_sent": 14,
"total_responded": 3,
"total_opted_out": 0,
"follow_ups": 2,
"response_types": [
{"type": "positive", "count": 2},
{"type": "neutral", "count": 1}
],
"recent_campaigns": [/* Latest 5 campaigns */]
}
```
### Frontend Integration Patterns
#### Alpine.js Data Structure
```javascript
function campaignApp() {
return {
// Connection monitoring
phoneStatus: {
termux_connected: false,
adb_connected: false,
connected: false,
last_check: null
},
// Campaign creation
campaignName: '',
messageTemplate: '',
contactsPreview: [], // First 10 contacts for preview
totalContacts: 0, // Full contact count
uploadedContacts: [], // All uploaded contacts
campaignReady: false, // Upload validation flag
// Recent campaigns (auto-refreshed)
recentCampaigns: [],
// Auto-refresh intervals
async init() {
await this.checkConnectionStatus();
await this.loadRecentCampaigns();
// Periodic updates
setInterval(() => this.checkConnectionStatus(), 10000);
setInterval(() => this.loadRecentCampaigns(), 15000);
},
// File upload with preview
async handleFileUpload(event) {
const file = event.target.files[0];
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/campaign/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
this.contactsPreview = data.preview;
this.totalContacts = data.total_contacts;
this.uploadedContacts = data.contacts;
this.campaignReady = true;
// Store for campaign creation
window.campaignContacts = data.contacts;
}
}
}
}
```
#### Error Handling Patterns
```javascript
// Consistent error handling across API calls
async function apiCall(endpoint, options = {}) {
try {
const response = await fetch(endpoint, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || `HTTP ${response.status}`);
}
return data;
} catch (error) {
console.error(`API call failed: ${endpoint}`, error);
// User-friendly error display
if (error.message.includes('Failed to fetch')) {
throw new Error('Connection lost. Please check your internet connection.');
}
throw error;
}
}
// Usage example
async function startCampaign() {
try {
const result = await apiCall('/api/campaign/create', {
method: 'POST',
body: JSON.stringify({
name: this.campaignName,
message: this.messageTemplate,
csv_data: this.uploadedContacts
})
});
if (result.success) {
alert(`Campaign "${result.campaign_name}" created with ${result.total_recipients} recipients!`);
// Start the campaign
const startResult = await apiCall('/api/campaign/start', {
method: 'POST',
body: JSON.stringify({campaign_id: result.campaign_id})
});
if (startResult.success) {
this.campaignState.status = 'running';
this.campaignState.total = startResult.total;
}
}
} catch (error) {
alert(`Failed to start campaign: ${error.message}`);
}
}
```
### Database Maintenance & Troubleshooting
#### Common Issues & Solutions
**WAL File Locking (Fixed)**
```bash
# Database cleanup script (./fix-database.sh)
#!/bin/bash
echo "🔧 Fixing database and permissions..."
# Stop container
docker compose down
# Remove WAL and SHM files
rm -f data/campaign.db-wal data/campaign.db-shm
# Convert to TRUNCATE mode
sqlite3 data/campaign.db << EOF
PRAGMA journal_mode=TRUNCATE;
VACUUM;
.exit
EOF
# Fix permissions
chmod 777 data/
chmod 666 data/campaign.db
# Restart
docker compose up -d
```
**Campaign Data Integrity**
```sql
-- Verify campaign consistency
SELECT
c.id,
c.name,
c.total_recipients as expected,
COUNT(r.id) as actual_recipients,
c.total_sent,
COUNT(CASE WHEN r.status = 'sent' THEN 1 END) as actual_sent
FROM campaigns c
LEFT JOIN recipients r ON c.id = r.campaign_id
GROUP BY c.id
HAVING expected != actual_recipients;
-- Fix recipient counts
UPDATE campaigns
SET total_recipients = (
SELECT COUNT(*) FROM recipients WHERE campaign_id = campaigns.id
);
```
#### Performance Optimization
**Database Indexing**
```sql
-- Add indexes for common queries
CREATE INDEX IF NOT EXISTS idx_recipients_campaign_status
ON recipients(campaign_id, status);
CREATE INDEX IF NOT EXISTS idx_messages_phone_timestamp
ON messages(phone, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_campaigns_status_created
ON campaigns(status, created_at DESC);
```
**Connection Pooling**
```python
# SQLite connection management
import sqlite3
from contextlib import contextmanager
@contextmanager
def get_db_transaction():
"""Context manager for database transactions with proper cleanup"""
conn = None
try:
conn = get_db_connection(app.config['DATABASE'])
conn.execute("BEGIN")
yield conn
conn.commit()
except Exception as e:
if conn:
conn.rollback()
raise
finally:
if conn:
conn.close()
# Usage
async def create_campaign_with_recipients(campaign_data, recipients):
with get_db_transaction() as conn:
cursor = conn.cursor()
# Create campaign
cursor.execute(
"INSERT INTO campaigns (name, template, total_recipients, status) VALUES (?, ?, ?, ?)",
(campaign_data['name'], campaign_data['message'], len(recipients), 'pending')
)
campaign_id = cursor.lastrowid
# Batch insert recipients
cursor.executemany(
"INSERT INTO recipients (campaign_id, phone, name, status) VALUES (?, ?, ?, ?)",
[(campaign_id, r.get('phone'), r.get('name'), 'pending') for r in recipients]
)
return campaign_id
```
### Testing Database Operations
```python
# Test campaign creation end-to-end
def test_campaign_workflow():
"""Test complete campaign creation and execution workflow"""
# Setup
test_contacts = [
{"name": "Test User 1", "phone": "7801234567"},
{"name": "Test User 2", "phone": "7801234568"}
]
# Test campaign creation
response = client.post('/api/campaign/create', json={
'name': 'Test Campaign',
'message': 'Hello {name}!',
'csv_data': test_contacts
})
assert response.json['success'] == True
campaign_id = response.json['campaign_id']
# Verify database state
with get_db_connection() as conn:
cursor = conn.cursor()
# Check campaign record
cursor.execute("SELECT * FROM campaigns WHERE id = ?", (campaign_id,))
campaign = cursor.fetchone()
assert campaign['total_recipients'] == 2
# Check recipient records
cursor.execute("SELECT * FROM recipients WHERE campaign_id = ?", (campaign_id,))
recipients = cursor.fetchall()
assert len(recipients) == 2
assert all(r['status'] == 'pending' for r in recipients)
# Test campaign start
response = client.post('/api/campaign/start', json={
'campaign_id': campaign_id
})
assert response.json['success'] == True
assert response.json['total'] == 2
```
This comprehensive database and API reference ensures consistent implementation patterns, proper error handling, and maintainable code architecture aligned with the project's lightweight, Docker-based approach.