diff --git a/.env.example b/.env.example index f7a3f4b..1a0d531 100644 --- a/.env.example +++ b/.env.example @@ -41,6 +41,9 @@ KILO_API_KEY= # Bytez BYTEZ_API_KEY= +# DeepInfra +DEEPINFRA_API_KEY= + # GitHub GITHUB_PAT= GITHUB_USERNAME= @@ -60,13 +63,24 @@ ADMIN_USER= ADMIN_PASSWORD= SESSION_SECRET= +# External Admin API Key +# Generate a secure key: openssl rand -hex 32 +# Format: sk_live_ for production, sk_test_ for testing +ADMIN_API_KEY= +ADMIN_API_JWT_TTL=3600 +ADMIN_API_RATE_LIMIT=1000 + # Database Configuration (Phase 1.2 & 1.3) # Set USE_JSON_DATABASE=1 to use legacy JSON files (for rollback) USE_JSON_DATABASE= DATABASE_PATH=./.data/shopify_ai.db DATABASE_ENCRYPTION_KEY= +DATABASE_KEY_FILE= DATABASE_BACKUP_ENABLED=1 DATABASE_WAL_MODE=1 +DATABASE_USE_SQLCIPHER=1 +DATABASE_CIPHER_COMPAT=4 +DATABASE_KDF_ITER=64000 # JWT Token Configuration JWT_SECRET= diff --git a/DEPLOYMENT_CHECKLIST.md b/DEPLOYMENT_CHECKLIST.md index f2c8feb..2691d51 100644 --- a/DEPLOYMENT_CHECKLIST.md +++ b/DEPLOYMENT_CHECKLIST.md @@ -35,6 +35,8 @@ Use this checklist when deploying the secure database implementation to producti ```bash # 1. Set environment variables export DATABASE_ENCRYPTION_KEY= +export DATABASE_USE_SQLCIPHER=1 +export DATABASE_KEY_FILE=/home/web/data/.data/.encryption_key export JWT_SECRET= # 2. Deploy container @@ -71,9 +73,9 @@ docker logs shopify-ai-builder | grep "JSON" # 4. Run migration inside container docker exec shopify-ai-builder node /opt/webchat/scripts/migrate-to-database.js -# 5. Verify migration results -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "SELECT COUNT(*) FROM users;" -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "SELECT COUNT(*) FROM sessions;" +# 5. Verify migration results (SQLCipher) +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; SELECT COUNT(*) FROM users;" +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; SELECT COUNT(*) FROM sessions;" # 6. Switch to database mode export USE_JSON_DATABASE=0 @@ -103,13 +105,13 @@ docker logs shopify-ai-builder | grep -v "JSON" ### 2. Smoke Tests ```bash -# Test database access -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db ".tables" +# Test database access (SQLCipher) +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; .tables" # Count records -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "SELECT COUNT(*) FROM users;" -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "SELECT COUNT(*) FROM sessions;" -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "SELECT COUNT(*) FROM audit_log;" +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; SELECT COUNT(*) FROM users;" +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; SELECT COUNT(*) FROM sessions;" +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; SELECT COUNT(*) FROM audit_log;" # Check encryption keys docker exec shopify-ai-builder test -f /home/web/data/.data/.encryption_key && echo "Encryption key exists" @@ -138,9 +140,9 @@ docker exec shopify-ai-builder ls -l /home/web/data/.data/ # Verify database is encrypted (should see binary data) docker exec shopify-ai-builder head -c 100 /home/web/data/.data/shopify_ai.db | od -c -# Check audit log -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db \ - "SELECT event_type, COUNT(*) FROM audit_log GROUP BY event_type;" +# Check audit log (SQLCipher) +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db \ + "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; SELECT event_type, COUNT(*) FROM audit_log GROUP BY event_type;" ``` **Checklist:** @@ -263,15 +265,15 @@ docker exec shopify-ai-builder cp -r /home/web/data/.data/migration_backup_*/* / ### Performance Issues ```bash -# Check WAL mode -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "PRAGMA journal_mode;" +# Check WAL mode (SQLCipher) +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; PRAGMA journal_mode;" # Check database size docker exec shopify-ai-builder du -h /home/web/data/.data/shopify_ai.db # Vacuum database if needed (offline) docker stop shopify-ai-builder -docker exec shopify-ai-builder sqlite3 /home/web/data/.data/shopify_ai.db "VACUUM;" +docker exec shopify-ai-builder sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; VACUUM;" docker start shopify-ai-builder ``` diff --git a/Dockerfile b/Dockerfile index ca8d597..0f1c44f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,6 +73,11 @@ RUN apt-get update \ tini \ libicu-dev \ libssl-dev \ + build-essential \ + python3 \ + pkg-config \ + sqlcipher \ + libsqlcipher-dev \ && rm -rf /var/lib/apt/lists/* # Install Node.js @@ -114,11 +119,23 @@ RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/healthcheck.sh /usr/loc # Copy webchat COPY chat /opt/webchat +ENV npm_config_sqlite=sqlcipher \ + npm_config_build_from_source=true RUN cd /opt/webchat && npm install --production && chmod -R 755 /opt/webchat COPY chat_v2 /opt/webchat_v2 RUN chmod -R 755 /opt/webchat_v2 +# Copy WordPress validator MCP server only (wp-cli-testing disabled for now) +RUN mkdir -p /opt/opencode/mcp-servers +COPY opencode/mcp-servers/wordpress-validator /opt/opencode/mcp-servers/wordpress-validator +RUN cd /opt/opencode/mcp-servers/wordpress-validator && npm install --production && \ + chmod -R 755 /opt/opencode/mcp-servers + +# Copy validation script +COPY scripts/validate-wordpress-plugin.sh /opt/scripts/validate-wordpress-plugin.sh +RUN chmod +x /opt/scripts/validate-wordpress-plugin.sh + # Create data directories RUN mkdir -p /home/web/data \ && mkdir -p /var/log/shopify-ai \ diff --git a/EXTERNAL_ADMIN_API.md b/EXTERNAL_ADMIN_API.md new file mode 100644 index 0000000..a320a1a --- /dev/null +++ b/EXTERNAL_ADMIN_API.md @@ -0,0 +1,677 @@ +# External Admin API Documentation + +## Overview + +The External Admin API provides programmatic access to all admin functionality via a RESTful API. This enables automation, integrations with external tools, and custom admin dashboards. + +## Table of Contents + +1. [Authentication](#authentication) +2. [Configuration](#configuration) +3. [API Endpoints](#api-endpoints) +4. [Request/Response Format](#requestresponse-format) +5. [Rate Limiting](#rate-limiting) +6. [Error Handling](#error-handling) +7. [Examples](#examples) +8. [Security Best Practices](#security-best-practices) + +--- + +## Authentication + +The External Admin API supports two authentication methods: + +### Option 1: API Key (Direct) + +Include your API key in the `Authorization` header with the `Bearer` scheme: + +```http +Authorization: Bearer sk_live_your_api_key_here +``` + +**Key Format:** +- Production keys: `sk_live_` prefix +- Test keys: `sk_test_` prefix + +### Option 2: API Key → JWT Token (Recommended) + +For better performance, exchange your API key for a short-lived JWT token: + +1. **Obtain JWT Token:** + ```http + POST /api/external/auth/validate + Authorization: Bearer sk_live_your_api_key_here + ``` + +2. **Response:** + ```json + { + "success": true, + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "tokenType": "Bearer", + "expiresIn": 3600, + "expiresAt": "2026-02-20T11:00:00.000Z" + }, + "meta": { + "timestamp": "2026-02-20T10:00:00.000Z", + "requestId": "req_abc123" + } + } + ``` + +3. **Use JWT Token:** + ```http + GET /api/external/users + Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... + ``` + +### Token Validation + +Validate your current authentication: + +```http +GET /api/external/auth/me +Authorization: Bearer +``` + +--- + +## Configuration + +### Environment Variables + +Add these to your `.env` file or Docker environment: + +| Variable | Description | Default | Required | +|----------|-------------|---------|----------| +| `ADMIN_API_KEY` | Your secret API key | - | Yes (to enable API) | +| `ADMIN_API_JWT_TTL` | JWT token lifetime in seconds | 3600 (1 hour) | No | +| `ADMIN_API_RATE_LIMIT` | Requests per hour per key | 1000 | No | +| `JWT_SECRET` | Secret for JWT signing | - | Yes | + +### Docker Compose Setup + +```yaml +# docker-compose.yml +services: + shopify-ai-builder: + environment: + - ADMIN_API_KEY=${ADMIN_API_KEY:-} + - ADMIN_API_JWT_TTL=${ADMIN_API_JWT_TTL:-3600} + - ADMIN_API_RATE_LIMIT=${ADMIN_API_RATE_LIMIT:-1000} + - JWT_SECRET=${JWT_SECRET:-} +``` + +### Generating a Secure API Key + +```bash +# Generate a 32-byte hex key +openssl rand -hex 32 + +# Example output: a1b2c3d4e5f6... (64 hex characters) + +# Set in .env: +ADMIN_API_KEY=sk_live_a1b2c3d4e5f6... +``` + +--- + +## API Endpoints + +### Base URL + +All external API endpoints are prefixed with `/api/external`. + +### Authentication + +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/auth/validate` | Exchange API key for JWT token | +| GET | `/auth/me` | Get current authentication info | + +### User Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/users` | List all users (paginated) | +| GET | `/users/:id` | Get user by ID | +| PATCH | `/users/:id/plan` | Update user's subscription plan | +| PATCH | `/users/:id/tokens` | Adjust user's token allocation | +| DELETE | `/users/:id` | Delete a user | +| GET | `/users/:id/sessions` | List user's active sessions | +| DELETE | `/users/:id/sessions/:sessionId` | Revoke a user session | + +### Model Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/models` | List all configured models | +| POST | `/models` | Create a new model configuration | +| GET | `/models/:id` | Get model by ID | +| PATCH | `/models/:id` | Update model configuration | +| DELETE | `/models/:id` | Delete a model | +| POST | `/models/reorder` | Reorder model display priority | + +### Affiliate Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/affiliates` | List all affiliates (paginated) | +| GET | `/affiliates/:id` | Get affiliate by ID | +| DELETE | `/affiliates/:id` | Delete an affiliate | + +### Withdrawal Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/withdrawals` | List withdrawal requests (paginated) | +| PUT | `/withdrawals` | Update withdrawal status | + +### Analytics & Monitoring + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/analytics/overview` | Get dashboard overview stats | +| GET | `/analytics/tracking` | Get visitor tracking stats | +| GET | `/analytics/resources` | Get resource monitoring data | +| GET | `/analytics/usage` | Get token usage statistics | + +### Content Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/blogs` | List all blogs (paginated) | +| POST | `/blogs` | Create a new blog post | +| GET | `/blogs/:id` | Get blog by ID | +| PATCH | `/blogs/:id` | Update blog post | +| DELETE | `/blogs/:id` | Delete a blog post | +| GET | `/feature-requests` | List feature requests (paginated) | +| PATCH | `/feature-requests/:id` | Update feature request status | +| GET | `/contact-messages` | List contact messages (paginated) | +| DELETE | `/contact-messages/:id` | Delete a contact message | + +### System Operations + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/system/health` | Health check (no auth required) | +| GET | `/system/tests` | Run system tests | +| POST | `/system/tests` | Run specific system tests | +| POST | `/system/cache/clear` | Clear server cache | +| GET | `/system/audit-log` | Query audit log (paginated) | + +--- + +## Request/Response Format + +### Standard Response + +```json +{ + "success": true, + "data": { + // Response data here + }, + "meta": { + "timestamp": "2026-02-20T10:00:00.000Z", + "requestId": "req_abc123" + } +} +``` + +### Paginated Response + +```json +{ + "success": true, + "data": [ + // Array of items + ], + "meta": { + "timestamp": "2026-02-20T10:00:00.000Z", + "requestId": "req_abc123" + }, + "pagination": { + "page": 1, + "perPage": 20, + "total": 150, + "totalPages": 8, + "hasNext": true, + "hasPrev": false + } +} +``` + +### Query Parameters + +| Parameter | Description | Default | Max | +|-----------|-------------|---------|-----| +| `page` | Page number | 1 | - | +| `perPage` | Items per page | 20 | 100 | +| `search` | Search query | - | - | +| `status` | Filter by status | - | - | +| `plan` | Filter by plan | - | - | + +--- + +## Rate Limiting + +All authenticated requests are rate-limited per API key. + +### Default Limits + +- **Requests per hour:** 1000 (configurable via `ADMIN_API_RATE_LIMIT`) +- **Burst:** Not applicable (smooth rate limiting) + +### Rate Limit Headers + +Every response includes rate limit information: + +```http +X-RateLimit-Limit: 1000 +X-RateLimit-Remaining: 856 +X-RateLimit-Reset: 1708407600 +``` + +### Rate Limit Exceeded Response + +```json +{ + "success": false, + "error": { + "code": "RATE_LIMIT_EXCEEDED", + "message": "Rate limit exceeded. Please retry after the reset time.", + "details": { + "resetAt": "2026-02-20T11:00:00.000Z" + } + }, + "meta": { + "timestamp": "2026-02-20T10:45:00.000Z", + "requestId": "req_xyz789" + } +} +``` + +--- + +## Error Handling + +### Error Response Format + +```json +{ + "success": false, + "error": { + "code": "ERROR_CODE", + "message": "Human-readable error message", + "details": { + // Additional context + } + }, + "meta": { + "timestamp": "2026-02-20T10:00:00.000Z", + "requestId": "req_abc123" + } +} +``` + +### Error Codes + +| Code | HTTP Status | Description | +|------|-------------|-------------| +| `MISSING_AUTH_HEADER` | 401 | No Authorization header provided | +| `INVALID_AUTH_SCHEME` | 401 | Invalid authorization scheme (use Bearer) | +| `INVALID_API_KEY` | 401 | API key is invalid | +| `TOKEN_EXPIRED` | 401 | JWT token has expired | +| `INVALID_TOKEN` | 401 | JWT token is malformed or invalid | +| `RATE_LIMIT_EXCEEDED` | 429 | Rate limit exceeded | +| `ENDPOINT_NOT_FOUND` | 404 | Endpoint does not exist | +| `METHOD_NOT_ALLOWED` | 405 | HTTP method not supported | +| `VALIDATION_ERROR` | 400 | Request validation failed | +| `NOT_FOUND` | 404 | Resource not found | +| `INTERNAL_ERROR` | 500 | Internal server error | + +--- + +## Examples + +### cURL Examples + +#### Get JWT Token + +```bash +curl -X POST https://your-domain.com/api/external/auth/validate \ + -H "Authorization: Bearer sk_live_your_api_key_here" +``` + +#### List Users + +```bash +curl -X GET "https://your-domain.com/api/external/users?page=1&perPage=10" \ + -H "Authorization: Bearer your_jwt_token_here" +``` + +#### Update User Plan + +```bash +curl -X PATCH https://your-domain.com/api/external/users/user_123/plan \ + -H "Authorization: Bearer your_jwt_token_here" \ + -H "Content-Type: application/json" \ + -d '{"plan": "professional"}' +``` + +#### Create Model + +```bash +curl -X POST https://your-domain.com/api/external/models \ + -H "Authorization: Bearer your_jwt_token_here" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "gpt-4-turbo", + "label": "GPT-4 Turbo", + "tier": "premium", + "supportsMedia": true + }' +``` + +### JavaScript/Node.js Examples + +#### Setup + +```javascript +const API_BASE = 'https://your-domain.com/api/external'; +const API_KEY = 'sk_live_your_api_key_here'; + +let jwtToken = null; +let tokenExpiresAt = null; + +async function getJwtToken() { + if (jwtToken && tokenExpiresAt && Date.now() < tokenExpiresAt) { + return jwtToken; + } + + const response = await fetch(`${API_BASE}/auth/validate`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${API_KEY}` + } + }); + + const data = await response.json(); + if (!data.success) { + throw new Error(data.error.message); + } + + jwtToken = data.data.token; + tokenExpiresAt = new Date(data.data.expiresAt).getTime(); + return jwtToken; +} + +async function apiRequest(method, path, body = null) { + const token = await getJwtToken(); + + const options = { + method, + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }; + + if (body) { + options.body = JSON.stringify(body); + } + + const response = await fetch(`${API_BASE}${path}`, options); + return response.json(); +} +``` + +#### Usage Examples + +```javascript +// List users +const users = await apiRequest('GET', '/users?page=1&perPage=20'); +console.log(users); + +// Get specific user +const user = await apiRequest('GET', '/users/user_123'); +console.log(user); + +// Update user plan +const result = await apiRequest('PATCH', '/users/user_123/plan', { + plan: 'professional' +}); +console.log(result); + +// Adjust user tokens +const tokensResult = await apiRequest('PATCH', '/users/user_123/tokens', { + tokens: 1000000, + operation: 'set' +}); +console.log(tokensResult); + +// List models +const models = await apiRequest('GET', '/models'); +console.log(models); + +// Create model +const newModel = await apiRequest('POST', '/models', { + name: 'claude-3-opus', + label: 'Claude 3 Opus', + tier: 'premium', + supportsMedia: true +}); +console.log(newModel); + +// Get analytics +const analytics = await apiRequest('GET', '/analytics/overview'); +console.log(analytics); +``` + +### Python Examples + +```python +import requests +from datetime import datetime + +API_BASE = 'https://your-domain.com/api/external' +API_KEY = 'sk_live_your_api_key_here' + +class ExternalAdminAPI: + def __init__(self, api_base, api_key): + self.api_base = api_base + self.api_key = api_key + self.jwt_token = None + self.token_expires_at = None + + def get_jwt_token(self): + if self.jwt_token and self.token_expires_at: + if datetime.now() < self.token_expires_at: + return self.jwt_token + + response = requests.post( + f'{self.api_base}/auth/validate', + headers={'Authorization': f'Bearer {self.api_key}'} + ) + data = response.json() + + if not data['success']: + raise Exception(data['error']['message']) + + self.jwt_token = data['data']['token'] + self.token_expires_at = datetime.fromisoformat( + data['data']['expiresAt'].replace('Z', '+00:00') + ) + return self.jwt_token + + def request(self, method, path, body=None): + token = self.get_jwt_token() + headers = { + 'Authorization': f'Bearer {token}', + 'Content-Type': 'application/json' + } + + response = requests.request( + method, + f'{self.api_base}{path}', + headers=headers, + json=body + ) + return response.json() + +# Usage +api = ExternalAdminAPI(API_BASE, API_KEY) + +# List users +users = api.request('GET', '/users?page=1&perPage=20') +print(users) + +# Update user plan +result = api.request('PATCH', '/users/user_123/plan', {'plan': 'professional'}) +print(result) +``` + +--- + +## Security Best Practices + +### API Key Management + +1. **Never commit API keys to version control** + - Use environment variables + - Use secrets management (Docker secrets, Kubernetes secrets, AWS Secrets Manager) + +2. **Use different keys for different environments** + - `sk_test_` prefix for development/testing + - `sk_live_` prefix for production + +3. **Rotate keys periodically** + - Generate new keys at least every 90 days + - Immediately revoke compromised keys + +4. **Limit key exposure** + - Only store keys on secure servers + - Never expose keys in client-side code + - Use IP whitelisting if possible (future feature) + +### JWT Token Security + +1. **Short token lifetime** + - Default: 1 hour + - Configure via `ADMIN_API_JWT_TTL` + +2. **Token storage** + - Store tokens in memory, not persistent storage + - Clear tokens on application shutdown + +3. **Token refresh** + - Implement token refresh before expiration + - Handle `TOKEN_EXPIRED` errors gracefully + +### Network Security + +1. **Use HTTPS** + - Never transmit API keys over HTTP + - Ensure SSL/TLS certificates are valid + +2. **IP Restrictions** (if applicable) + - Restrict API access to known IP ranges + - Use VPN or private networks + +3. **Rate Limiting** + - Respect rate limit headers + - Implement client-side throttling + - Handle 429 responses with exponential backoff + +### Audit & Monitoring + +1. **Monitor API Usage** + - Review audit logs regularly + - Set up alerts for unusual activity + +2. **Log Retention** + - Maintain audit logs for compliance + - Use `/system/audit-log` endpoint to query logs + +3. **Anomaly Detection** + - Monitor for unusual request patterns + - Alert on failed authentication attempts + +--- + +## Troubleshooting + +### Common Issues + +#### "API_KEY_NOT_CONFIGURED" + +**Cause:** `ADMIN_API_KEY` environment variable not set. + +**Solution:** Set the environment variable and restart the server: +```bash +export ADMIN_API_KEY=sk_live_your_secure_key_here +``` + +#### "INVALID_API_KEY" + +**Cause:** API key doesn't match the configured key. + +**Solution:** +1. Verify the API key is correct +2. Check for extra whitespace or encoding issues +3. Ensure the key prefix is correct (`sk_live_` or `sk_test_`) + +#### "TOKEN_EXPIRED" + +**Cause:** JWT token has exceeded its lifetime. + +**Solution:** Request a new token using the API key: +```bash +POST /api/external/auth/validate +Authorization: Bearer sk_live_your_api_key +``` + +#### "RATE_LIMIT_EXCEEDED" + +**Cause:** Too many requests in the current hour. + +**Solution:** +1. Wait until the reset time (check `X-RateLimit-Reset` header) +2. Reduce request frequency +3. Increase rate limit via `ADMIN_API_RATE_LIMIT` environment variable + +### Debugging Tips + +1. **Enable verbose logging** + - Check server logs for detailed error messages + - Look for `[ExternalAPI]` prefixed log entries + +2. **Test with health endpoint** + ```bash + curl https://your-domain.com/api/external/system/health + ``` + +3. **Verify authentication** + ```bash + curl -X GET https://your-domain.com/api/external/auth/me \ + -H "Authorization: Bearer your_token" + ``` + +--- + +## Version History + +| Version | Date | Changes | +|---------|------|---------| +| 1.0.0 | 2026-02-20 | Initial release | + +--- + +## Support + +For issues or questions: +- GitHub Issues: [project-repo]/issues +- Documentation: This file +- Server Logs: Check container logs for `[ExternalAPI]` entries diff --git a/chat/DATABASE_IMPLEMENTATION.md b/chat/DATABASE_IMPLEMENTATION.md index 098d1b2..804aff4 100644 --- a/chat/DATABASE_IMPLEMENTATION.md +++ b/chat/DATABASE_IMPLEMENTATION.md @@ -5,7 +5,7 @@ This implementation adds database encryption at rest and secure session manageme ## Features ### Phase 1.2: Database with Encryption at Rest -- ✅ SQLite database with better-sqlite3 +- ✅ SQLCipher-encrypted SQLite database with better-sqlite3 - ✅ Field-level AES-256-GCM encryption for sensitive data - ✅ PBKDF2 key derivation (100,000 iterations) - ✅ WAL mode for better concurrency @@ -24,16 +24,17 @@ This implementation adds database encryption at rest and secure session manageme ## Architecture ### Database Schema -- **users**: User accounts with encrypted email, name, 2FA secrets +- **users**: User accounts with encrypted email, name, 2FA secrets + JSON data column - **sessions**: Active sessions for revocation - **refresh_tokens**: Refresh tokens with device fingerprinting - **token_blacklist**: Immediate token revocation -- **affiliates**, **withdrawals**, **feature_requests**, **contact_messages** +- **affiliate_accounts** (current app), **affiliates** (legacy), **withdrawals**, **feature_requests**, **contact_messages** - **audit_log**: Comprehensive security event logging - **payment_sessions**: DoDo payment tracking ### Encryption -- **Algorithm**: AES-256-GCM with authenticated encryption +- **Database**: SQLCipher encryption at rest +- **Algorithm**: AES-256-GCM with authenticated encryption (field-level) - **Key Derivation**: PBKDF2 with 100,000 iterations - **Per-field**: Sensitive fields encrypted individually - **Token Storage**: PBKDF2 hashed (not encrypted) for secure comparison @@ -65,8 +66,12 @@ Optional: ```bash USE_JSON_DATABASE=1 # Use JSON files instead of database (for rollback) DATABASE_PATH=./.data/shopify_ai.db +DATABASE_KEY_FILE=./.data/.encryption_key DATABASE_BACKUP_ENABLED=1 DATABASE_WAL_MODE=1 +DATABASE_USE_SQLCIPHER=1 +DATABASE_CIPHER_COMPAT=4 +DATABASE_KDF_ITER=64000 JWT_ACCESS_TOKEN_TTL=900 # 15 minutes in seconds JWT_REFRESH_TOKEN_TTL=604800 # 7 days in seconds ``` @@ -135,14 +140,14 @@ export USE_JSON_DATABASE=1 ### Verify Database Setup ```bash -# Check database exists and tables are created -sqlite3 ./.data/shopify_ai.db ".tables" +# Check database exists and tables are created (SQLCipher) +sqlcipher ./.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; .tables" # Should output: -# affiliates payment_sessions token_blacklist -# audit_log refresh_tokens users -# contact_messages sessions withdrawals -# feature_requests +# affiliate_accounts payment_sessions token_blacklist +# affiliates refresh_tokens users +# audit_log sessions withdrawals +# contact_messages feature_requests ``` ### Test Encryption @@ -161,8 +166,8 @@ node scripts/migrate-to-database.js ### Database Health - Check file size: `ls -lh ./.data/shopify_ai.db` -- Check WAL mode: `sqlite3 ./.data/shopify_ai.db "PRAGMA journal_mode;"` -- Check tables: `sqlite3 ./.data/shopify_ai.db ".tables"` +- Check WAL mode: `sqlcipher ./.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; PRAGMA journal_mode;"` +- Check tables: `sqlcipher ./.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; .tables"` ### Audit Logs Audit logs are stored in the `audit_log` table and include: diff --git a/chat/OPENCODE_SERVER_INTEGRATION.md b/chat/OPENCODE_SERVER_INTEGRATION.md new file mode 100644 index 0000000..b57a531 --- /dev/null +++ b/chat/OPENCODE_SERVER_INTEGRATION.md @@ -0,0 +1,1275 @@ +# OpenCode Server Integration Guide + +## Overview + +This guide details how to integrate OpenCode's `serve` command with the chat server to enable a persistent single-process architecture, reducing resource consumption when multiple sessions are running concurrently. + +## Current Architecture vs. Proposed + +### Current (Per-Message Spawning) + +``` +┌─────────────┐ spawn ┌─────────────┐ +│ Chat Server │ ──────────────▶│ OpenCode │ (Process 1) +│ │ │ --session A │ +│ │ └─────────────┘ +│ │ spawn ┌─────────────┐ +│ │ ──────────────▶│ OpenCode │ (Process 2) +│ │ │ --session B │ +└─────────────┘ └─────────────┘ + +Memory: 2 processes × ~300MB = ~600MB +Startup: ~1-2s latency per message +``` + +### Proposed (Persistent Server) + +``` +┌─────────────┐ HTTP/WS ┌─────────────┐ +│ Chat Server │ ◀─────────────▶│ OpenCode │ (Single Process) +│ │ │ serve │ +│ │ │ :4096 │ +│ │ │ │ +│ Session A ─┼── POST /session/A/message +│ Session B ─┼── POST /session/B/message +└─────────────┘ └─────────────┘ + +Memory: 1 process × ~400MB = ~400MB +Startup: ~10-50ms latency per message +``` + +## OpenCode Server API Reference + +### Server Startup + +```bash +opencode serve --port 4096 --hostname 127.0.0.1 +``` + +**Options:** +- `--port`: Port number (default: 4096, auto-selects if 4096 is busy) +- `--hostname`: Bind address (default: 127.0.0.1) +- `--mdns`: Enable mDNS discovery (optional) +- `--cors`: CORS whitelist origins (optional) + +**Environment Variables:** +- `OPENCODE_SERVER_PASSWORD`: Basic auth password (optional but recommended) +- `OPENCODE_SERVER_USERNAME`: Basic auth username (default: "opencode") + +### Key Endpoints + +#### Session Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/session` | List all sessions | +| POST | `/session` | Create new session | +| GET | `/session/:sessionID` | Get session details | +| DELETE | `/session/:sessionID` | Delete session | +| PATCH | `/session/:sessionID` | Update session (title, etc.) | +| POST | `/session/:sessionID/abort` | Abort running task | + +#### Messaging + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/session/:sessionID/message` | Get all messages | +| POST | `/session/:sessionID/message` | Send prompt (streaming response) | +| POST | `/session/:sessionID/prompt_async` | Send prompt (non-blocking) | +| POST | `/session/:sessionID/command` | Send command | +| POST | `/session/:sessionID/shell` | Execute shell command | + +#### Events + +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/event` | SSE stream of all events | +| GET | `/session/status` | Get all session statuses | + +#### Project Context + +All endpoints accept either: +- Query parameter: `?directory=/path/to/project` +- Header: `x-opencode-directory: /path/to/project` + +### Request/Response Schemas + +#### Create Session + +```typescript +// POST /session +// Request body (optional) +{ + title?: string, + agent?: string, // Agent ID (e.g., "general", "code-review") + model?: { + providerID: string, + modelID: string + } +} + +// Response +{ + id: string, + title: string, + directory: string, + time: { + created: number, + updated: number + }, + // ... other fields +} +``` + +#### Send Message + +```typescript +// POST /session/:sessionID/message +// Request body +{ + prompt: { + parts: Array<{ type: "text", text: string } | { type: "image", url: string }> + }, + agent?: string, + model?: { + providerID: string, + modelID: string + } +} + +// Response (streaming) +{ + info: { + id: string, + sessionID: string, + role: "assistant", + // ... + }, + parts: Array +} +``` + +#### Event Stream (SSE) + +```typescript +// GET /event +// Events are JSON objects separated by newlines + +// Event types include: +{ + type: "server.connected", + properties: {} +} + +{ + type: "message.part", + properties: { + sessionID: string, + messageID: string, + part: { ... } + } +} + +{ + type: "message.complete", + properties: { + sessionID: string, + messageID: string + } +} + +{ + type: "session.status", + properties: { + sessionID: string, + status: "idle" | "busy" | "error" + } +} + +{ + type: "server.heartbeat", + properties: {} +} +``` + +## Implementation Plan + +### Phase 1: OpencodeProcessManager Enhancement + +**File:** `server.js` (lines 1117-1380) + +#### 1.1 Add Connection State + +```javascript +class OpencodeProcessManager { + constructor() { + this.process = null; + this.isReady = false; + this.baseUrl = 'http://127.0.0.1:4096'; + this.pendingRequests = new Map(); + this.eventSource = null; + this.eventHandlers = new Map(); // sessionId -> callback[] + this.lastActivity = Date.now(); + this.sessionWorkspaces = new Map(); // sessionId -> { directory, userId, appId } + this.authHeader = null; + this.connectionAttempts = 0; + this.maxConnectionAttempts = 3; + this.healthCheckInterval = null; + } +} +``` + +#### 1.2 Server Startup Method + +```javascript +async start() { + if (this.process && this.isReady) { + log('OpenCode server already running'); + return; + } + + log('Starting OpenCode server...'); + + const cliCommand = resolveCliCommand('opencode'); + const port = Number(process.env.OPENCODE_SERVER_PORT || 4096); + const hostname = process.env.OPENCODE_SERVER_HOST || '127.0.0.1'; + + // Set auth if password is configured + const password = process.env.OPENCODE_SERVER_PASSWORD; + if (password) { + const username = process.env.OPENCODE_SERVER_USERNAME || 'opencode'; + this.authHeader = 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'); + } + + try { + // Verify CLI exists + fsSync.accessSync(cliCommand, fsSync.constants.X_OK); + } catch (err) { + throw new Error(`OpenCode CLI not found: ${cliCommand}`); + } + + // Start the serve process + const args = ['serve', '--port', String(port), '--hostname', hostname]; + + this.process = spawn(cliCommand, args, { + stdio: ['ignore', 'pipe', 'pipe'], + env: { + ...process.env, + OPENCODE_SERVER_PASSWORD: password || '', + OPENCODE_SERVER_USERNAME: process.env.OPENCODE_SERVER_USERNAME || 'opencode' + } + }); + + // Handle process events + this.process.stdout.on('data', (data) => { + const text = data.toString(); + log('OpenCode server stdout', { text: text.slice(0, 200) }); + + // Detect ready signal + if (text.includes('listening on http://')) { + this.isReady = true; + this.connectionAttempts = 0; + log('OpenCode server ready', { port, hostname }); + + // Start event subscription + this.subscribeToEvents(); + + // Start health check + this.startHealthCheck(); + } + }); + + this.process.stderr.on('data', (data) => { + log('OpenCode server stderr', { text: data.toString().slice(0, 200) }); + }); + + this.process.on('error', (err) => { + log('OpenCode server error', { error: String(err) }); + this.isReady = false; + this.handleServerError(err); + }); + + this.process.on('exit', (code, signal) => { + log('OpenCode server exited', { code, signal }); + this.isReady = false; + this.process = null; + this.stopHealthCheck(); + this.closeEventSource(); + + // Auto-restart if not shutting down + if (!isShuttingDown && this.connectionAttempts < this.maxConnectionAttempts) { + this.connectionAttempts++; + log('Auto-restarting OpenCode server', { attempt: this.connectionAttempts }); + setTimeout(() => this.start().catch(e => + log('Failed to restart OpenCode server', { error: String(e) }) + ), 2000 * this.connectionAttempts); + } + }); + + // Wait for ready signal + await this.waitForReady(30000); + this.baseUrl = `http://${hostname}:${port}`; +} +``` + +#### 1.3 Health Check Implementation + +```javascript +startHealthCheck() { + if (this.healthCheckInterval) return; + + this.healthCheckInterval = setInterval(async () => { + if (!this.isReady || !this.process) return; + + try { + const response = await fetch(`${this.baseUrl}/session/status`, { + method: 'GET', + headers: this.getHeaders() + }); + + if (!response.ok) { + log('Health check failed', { status: response.status }); + if (response.status >= 500) { + this.isReady = false; + } + } + } catch (err) { + log('Health check error', { error: String(err) }); + this.isReady = false; + } + }, 30000); // Every 30 seconds +} + +stopHealthCheck() { + if (this.healthCheckInterval) { + clearInterval(this.healthCheckInterval); + this.healthCheckInterval = null; + } +} +``` + +#### 1.4 Event Subscription (SSE) + +```javascript +subscribeToEvents() { + if (this.eventSource) { + this.closeEventSource(); + } + + // Use native HTTP client for SSE (Node.js doesn't have EventSource by default) + const url = new URL('/event', this.baseUrl); + + const headers = { + 'Accept': 'text/event-stream', + 'Cache-Control': 'no-cache' + }; + + if (this.authHeader) { + headers['Authorization'] = this.authHeader; + } + + const req = http.request({ + hostname: url.hostname, + port: url.port, + path: url.pathname, + method: 'GET', + headers + }, (res) => { + let buffer = ''; + + res.on('data', (chunk) => { + buffer += chunk.toString(); + + // Process complete SSE messages + const lines = buffer.split('\n\n'); + buffer = lines.pop() || ''; // Keep incomplete message in buffer + + for (const line of lines) { + if (line.startsWith('data: ')) { + try { + const event = JSON.parse(line.slice(6)); + this.handleServerEvent(event); + } catch (e) { + log('Failed to parse SSE event', { line: line.slice(0, 100) }); + } + } + } + }); + + res.on('end', () => { + log('SSE connection closed'); + this.eventSource = null; + + // Reconnect if server is still running + if (this.isReady && !isShuttingDown) { + setTimeout(() => this.subscribeToEvents(), 1000); + } + }); + }); + + req.on('error', (err) => { + log('SSE connection error', { error: String(err) }); + this.eventSource = null; + + if (this.isReady && !isShuttingDown) { + setTimeout(() => this.subscribeToEvents(), 2000); + } + }); + + req.end(); + this.eventSource = req; +} + +closeEventSource() { + if (this.eventSource) { + this.eventSource.destroy(); + this.eventSource = null; + } +} + +handleServerEvent(event) { + // Route events to appropriate handlers + const { type, properties } = event; + + switch (type) { + case 'server.connected': + log('SSE connected to OpenCode server'); + break; + + case 'server.heartbeat': + // No action needed, just keeps connection alive + break; + + case 'message.part': + this.handleMessagePart(properties); + break; + + case 'message.complete': + this.handleMessageComplete(properties); + break; + + case 'session.status': + this.handleSessionStatus(properties); + break; + + case 'tool.call': + this.handleToolCall(properties); + break; + + case 'tool.result': + this.handleToolResult(properties); + break; + + case 'instance.disposed': + log('OpenCode instance disposed'); + break; + + default: + log('Unhandled server event', { type, properties }); + } +} +``` + +#### 1.5 HTTP Request Helper + +```javascript +getHeaders(directory = null) { + const headers = { + 'Content-Type': 'application/json' + }; + + if (this.authHeader) { + headers['Authorization'] = this.authHeader; + } + + if (directory) { + headers['x-opencode-directory'] = directory; + } + + return headers; +} + +async request(method, path, body = null, directory = null) { + if (!this.isReady) { + throw new Error('OpenCode server not ready'); + } + + const url = new URL(path, this.baseUrl); + + if (directory) { + url.searchParams.set('directory', directory); + } + + const options = { + method, + headers: this.getHeaders(directory) + }; + + if (body) { + options.body = JSON.stringify(body); + } + + const response = await fetch(url.toString(), options); + + if (!response.ok) { + const errorText = await response.text(); + const error = new Error(`OpenCode API error: ${response.status} ${errorText}`); + error.status = response.status; + throw error; + } + + // Handle streaming responses + const contentType = response.headers.get('content-type'); + if (contentType?.includes('application/json')) { + return response.json(); + } + + return response; +} +``` + +#### 1.6 Session Management + +```javascript +async createSession(workspaceDir, options = {}) { + const response = await this.request('POST', '/session', { + title: options.title || `Session ${Date.now()}`, + agent: options.agent, + model: options.model + }, workspaceDir); + + const sessionId = response.id; + + // Track session + this.sessionWorkspaces.set(sessionId, { + directory: workspaceDir, + userId: options.userId, + appId: options.appId, + createdAt: Date.now() + }); + + return response; +} + +async getSession(sessionId, directory) { + return this.request('GET', `/session/${sessionId}`, null, directory); +} + +async listSessions(directory) { + return this.request('GET', '/session', null, directory); +} + +async deleteSession(sessionId, directory) { + const result = await this.request('DELETE', `/session/${sessionId}`, null, directory); + this.sessionWorkspaces.delete(sessionId); + return result; +} +``` + +#### 1.7 Message Sending + +```javascript +async sendMessage(sessionId, workspaceDir, message, options = {}) { + const body = { + prompt: { + parts: [{ type: 'text', text: message.content }] + } + }; + + // Add images if present + if (message.attachments?.length) { + for (const attachment of message.attachments) { + if (attachment.type?.startsWith('image/')) { + body.prompt.parts.push({ + type: 'image', + url: attachment.url // Must be a data URL or accessible URL + }); + } + } + } + + // Add agent/model if specified + if (options.agent) body.agent = options.agent; + if (options.model) body.model = options.model; + + return this.request('POST', `/session/${sessionId}/message`, body, workspaceDir); +} + +async sendMessageAsync(sessionId, workspaceDir, message, options = {}) { + // Non-blocking version - returns immediately, events come via SSE + const body = { + prompt: { + parts: [{ type: 'text', text: message.content }] + } + }; + + if (options.agent) body.agent = options.agent; + if (options.model) body.model = options.model; + + await this.request('POST', `/session/${sessionId}/prompt_async`, body, workspaceDir); +} + +async abortSession(sessionId, directory) { + return this.request('POST', `/session/${sessionId}/abort`, null, directory); +} +``` + +#### 1.8 Event Handlers for Streaming + +```javascript +handleMessagePart(properties) { + const { sessionID, messageID, part } = properties; + + // Find the pending request for this session + const pending = this.pendingRequests.get(messageID); + if (pending?.streamCallback) { + pending.streamCallback({ + type: 'part', + part + }); + } + + // Also notify session-specific handlers + const handlers = this.eventHandlers.get(sessionID); + if (handlers) { + for (const handler of handlers) { + handler({ type: 'part', messageID, part }); + } + } +} + +handleMessageComplete(properties) { + const { sessionID, messageID } = properties; + + // Resolve the pending request + const pending = this.pendingRequests.get(messageID); + if (pending) { + pending.resolve({ sessionID, messageID }); + this.pendingRequests.delete(messageID); + } + + // Notify session handlers + const handlers = this.eventHandlers.get(sessionID); + if (handlers) { + for (const handler of handlers) { + handler({ type: 'complete', messageID }); + } + } +} + +handleSessionStatus(properties) { + const { sessionID, status } = properties; + + const workspace = this.sessionWorkspaces.get(sessionID); + if (workspace) { + workspace.status = status; + workspace.lastStatusUpdate = Date.now(); + } + + log('Session status update', { sessionID, status }); +} + +handleToolCall(properties) { + const { sessionID, messageID, tool, input } = properties; + + // Forward to stream callback for real-time display + const pending = this.pendingRequests.get(messageID); + if (pending?.streamCallback) { + pending.streamCallback({ + type: 'tool_call', + tool, + input + }); + } +} + +handleToolResult(properties) { + const { sessionID, messageID, tool, output } = properties; + + const pending = this.pendingRequests.get(messageID); + if (pending?.streamCallback) { + pending.streamCallback({ + type: 'tool_result', + tool, + output + }); + } +} + +// Register/unregister event handlers for a session +registerEventHandler(sessionId, handler) { + if (!this.eventHandlers.has(sessionId)) { + this.eventHandlers.set(sessionId, new Set()); + } + this.eventHandlers.get(sessionId).add(handler); + + return () => { + this.eventHandlers.get(sessionId)?.delete(handler); + if (this.eventHandlers.get(sessionId)?.size === 0) { + this.eventHandlers.delete(sessionId); + } + }; +} +``` + +#### 1.9 Cleanup and Shutdown + +```javascript +async stop() { + log('Stopping OpenCode server...'); + + this.isReady = false; + this.stopHealthCheck(); + this.closeEventSource(); + + // Reject all pending requests + for (const [messageId, pending] of this.pendingRequests) { + pending.reject(new Error('OpenCode server shutting down')); + } + this.pendingRequests.clear(); + this.eventHandlers.clear(); + this.sessionWorkspaces.clear(); + + if (this.process) { + // Graceful shutdown + this.process.kill('SIGTERM'); + + await new Promise((resolve) => { + const timeout = setTimeout(() => { + if (this.process) { + log('Force killing OpenCode server'); + this.process.kill('SIGKILL'); + } + resolve(); + }, 5000); + + if (this.process) { + this.process.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + } else { + clearTimeout(timeout); + resolve(); + } + }); + + this.process = null; + } + + log('OpenCode server stopped'); +} + +getStats() { + return { + isRunning: !!this.process, + isReady: this.isReady, + baseUrl: this.baseUrl, + activeSessions: this.sessionWorkspaces.size, + pendingRequests: this.pendingRequests.size, + eventHandlers: this.eventHandlers.size, + lastActivity: this.lastActivity, + idleTime: Date.now() - this.lastActivity + }; +} +``` + +### Phase 2: Integration with Chat Server + +#### 2.1 Modify sendToOpencode Function + +**File:** `server.js` (around line 9519) + +```javascript +async function sendToOpencode({ session, model, content, message, cli, streamCallback, opencodeSessionId }) { + const messageKey = message?.id || 'unknown'; + + // Check if server mode is available + if (opencodeManager.isReady) { + log('Using OpenCode server mode', { sessionId: session.id, messageId: messageKey }); + + try { + // Ensure session exists in OpenCode + let ocSessionId = opencodeSessionId || session.opencodeSessionId; + + if (!ocSessionId) { + // Create new session in OpenCode server + const newSession = await opencodeManager.createSession(session.workspaceDir, { + title: session.title || `Chat ${session.id}`, + userId: session.userId, + appId: session.appId + }); + ocSessionId = newSession.id; + session.opencodeSessionId = ocSessionId; + session.initialOpencodeSessionId = ocSessionId; + await persistState(); + log('Created new OpenCode session', { ocSessionId }); + } + + // Register stream callback + let streamUnregister = null; + if (streamCallback) { + streamUnregister = opencodeManager.registerEventHandler(ocSessionId, (event) => { + streamCallback(event); + }); + } + + // Track this request + const requestId = messageKey; + opencodeManager.pendingRequests.set(requestId, { + resolve: () => {}, + reject: () => {}, + streamCallback, + started: Date.now() + }); + + // Send message + const response = await opencodeManager.sendMessageAsync(ocSessionId, session.workspaceDir, { + content, + attachments: message?.attachments + }, { + model: model ? { providerID: 'openrouter', modelID: model } : undefined + }); + + // Messages will come via SSE, handled by event handlers + // Return a promise that resolves when complete + return new Promise((resolve, reject) => { + const pending = opencodeManager.pendingRequests.get(requestId); + if (pending) { + pending.resolve = (result) => { + if (streamUnregister) streamUnregister(); + opencodeManager.pendingRequests.delete(requestId); + resolve(result); + }; + pending.reject = (error) => { + if (streamUnregister) streamUnregister(); + opencodeManager.pendingRequests.delete(requestId); + reject(error); + }; + } + + // Timeout safety + setTimeout(() => { + if (opencodeManager.pendingRequests.has(requestId)) { + if (streamUnregister) streamUnregister(); + opencodeManager.pendingRequests.delete(requestId); + reject(new Error('OpenCode request timed out')); + } + }, 600000); // 10 minutes + }); + + } catch (error) { + log('OpenCode server mode failed, falling back to spawn', { error: String(error) }); + // Fall through to spawn-based execution + } + } + + // Fallback: Original spawn-based implementation + return sendToOpencodeViaSpawn({ session, model, content, message, cli, streamCallback, opencodeSessionId }); +} +``` + +#### 2.2 Initialize Server on Startup + +**File:** `server.js` (in the startup section, around where server starts) + +```javascript +async function initializeOpencodeServer() { + const useServerMode = process.env.OPENCODE_SERVER_MODE !== 'false'; + + if (useServerMode) { + try { + log('Initializing OpenCode server mode...'); + await opencodeManager.start(); + log('OpenCode server mode initialized successfully'); + } catch (error) { + log('Failed to initialize OpenCode server mode, using spawn fallback', { + error: String(error) + }); + } + } else { + log('OpenCode server mode disabled, using spawn mode'); + } +} + +// Call during server startup +async function main() { + // ... existing initialization ... + + await initializeOpencodeServer(); + + // ... start HTTP server ... +} +``` + +#### 2.3 Cleanup on Shutdown + +**File:** `server.js` (in gracefulShutdown function) + +```javascript +async function gracefulShutdown(signal) { + if (isShuttingDown) return; + isShuttingDown = true; + + log(`Received ${signal}, starting graceful shutdown...`); + + // Stop periodic tasks + stopAutoSave(); + stopMemoryCleanup(); + + // Stop OpenCode server + log('Stopping OpenCode server...'); + await opencodeManager.stop(); + + // ... rest of existing shutdown logic ... +} +``` + +### Phase 3: Configuration Options + +#### Environment Variables + +```bash +# Enable/disable server mode (default: enabled) +OPENCODE_SERVER_MODE=true + +# Server port (default: 4096) +OPENCODE_SERVER_PORT=4096 + +# Server hostname (default: 127.0.0.1) +OPENCODE_SERVER_HOST=127.0.0.1 + +# Basic auth password (recommended for production) +OPENCODE_SERVER_PASSWORD=your-secure-password + +# Basic auth username (default: opencode) +OPENCODE_SERVER_USERNAME=opencode + +# Auto-restart on failure (default: true) +OPENCODE_SERVER_AUTO_RESTART=true + +# Maximum connection attempts before fallback (default: 3) +OPENCODE_SERVER_MAX_RETRIES=3 +``` + +#### Feature Flag Integration + +```javascript +// In configuration section +const OPENCODE_SERVER_MODE = process.env.OPENCODE_SERVER_MODE !== 'false'; +const OPENCODE_SERVER_PORT = Number(process.env.OPENCODE_SERVER_PORT || 4096); +const OPENCODE_SERVER_HOST = process.env.OPENCODE_SERVER_HOST || '127.0.0.1'; +const OPENCODE_SERVER_PASSWORD = process.env.OPENCODE_SERVER_PASSWORD || ''; +const OPENCODE_SERVER_USERNAME = process.env.OPENCODE_SERVER_USERNAME || 'opencode'; +const OPENCODE_SERVER_AUTO_RESTART = process.env.OPENCODE_SERVER_AUTO_RESTART !== 'false'; +const OPENCODE_SERVER_MAX_RETRIES = Number(process.env.OPENCODE_SERVER_MAX_RETRIES || 3); +``` + +### Phase 4: Error Handling and Fallback + +#### 4.1 Automatic Fallback Strategy + +```javascript +class OpencodeProcessManager { + // ... existing code ... + + async executeWithFallback(sessionId, workspaceDir, operation, fallbackFn) { + // Try server mode first + if (this.isReady) { + try { + return await operation(); + } catch (error) { + // Check if error is recoverable + if (this.isRecoverableError(error)) { + log('Recoverable error in server mode, retrying', { error: String(error) }); + await this.restart(); + return await operation(); + } + + // Fall back to spawn mode + log('Server mode failed, falling back to spawn', { error: String(error) }); + return await fallbackFn(); + } + } + + // Server not ready, use spawn mode + return await fallbackFn(); + } + + isRecoverableError(error) { + // Connection errors, timeouts, and 5xx errors are recoverable + if (error.code === 'ECONNREFUSED') return true; + if (error.code === 'ECONNRESET') return true; + if (error.code === 'ETIMEDOUT') return true; + if (error.status >= 500 && error.status < 600) return true; + return false; + } + + async restart() { + log('Restarting OpenCode server...'); + await this.stop(); + await new Promise(resolve => setTimeout(resolve, 1000)); + await this.start(); + } +} +``` + +#### 4.2 Graceful Degradation + +```javascript +// In sendToOpencodeWithFallback +async function sendToOpencodeWithFallback({ session, model, content, message, cli, streamCallback, opencodeSessionId, plan }) { + const useServerMode = opencodeManager.isReady; + + if (useServerMode) { + try { + return await sendToOpencode({ session, model, content, message, cli, streamCallback, opencodeSessionId }); + } catch (error) { + log('Server mode failed after retries, using spawn fallback', { + error: String(error), + sessionId: session.id + }); + } + } + + // Original spawn-based implementation + return sendToOpencodeViaSpawn({ session, model, content, message, cli, streamCallback, opencodeSessionId, plan }); +} +``` + +### Phase 5: Monitoring and Observability + +#### 5.1 Health Endpoint + +```javascript +// Add to HTTP server routes +app.get('/api/opencode/status', async (req, res) => { + const stats = opencodeManager.getStats(); + res.json({ + serverMode: { + enabled: OPENCODE_SERVER_MODE, + ...stats + }, + fallbackMode: !opencodeManager.isReady + }); +}); + +app.post('/api/opencode/restart', async (req, res) => { + try { + await opencodeManager.restart(); + res.json({ success: true, stats: opencodeManager.getStats() }); + } catch (error) { + res.status(500).json({ error: String(error) }); + } +}); +``` + +#### 5.2 Metrics Logging + +```javascript +class OpencodeProcessManager { + // ... existing code ... + + logMetrics() { + const stats = this.getStats(); + const memUsage = process.memoryUsage(); + + log('OpenCode server metrics', { + ...stats, + memory: { + heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB', + heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB', + rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB' + } + }); + } +} + +// Log metrics periodically +setInterval(() => { + if (opencodeManager.isReady) { + opencodeManager.logMetrics(); + } +}, 60000); // Every minute +``` + +## Testing Strategy + +### Unit Tests + +```javascript +// test/opencode-server.test.js + +describe('OpencodeProcessManager', () => { + let manager; + + beforeEach(() => { + manager = new OpencodeProcessManager(); + }); + + afterEach(async () => { + await manager.stop(); + }); + + test('should start server successfully', async () => { + await manager.start(); + expect(manager.isReady).toBe(true); + expect(manager.process).toBeDefined(); + }); + + test('should handle server crash and restart', async () => { + await manager.start(); + manager.process.kill('SIGKILL'); + await new Promise(r => setTimeout(r, 5000)); + expect(manager.isReady).toBe(true); + }); + + test('should create and track sessions', async () => { + await manager.start(); + const session = await manager.createSession('/tmp/test'); + expect(session.id).toBeDefined(); + expect(manager.sessionWorkspaces.has(session.id)).toBe(true); + }); + + test('should handle concurrent requests', async () => { + await manager.start(); + const session = await manager.createSession('/tmp/test'); + + const requests = Array(10).fill(null).map(() => + manager.sendMessage(session.id, '/tmp/test', { content: 'test' }) + ); + + const results = await Promise.all(requests); + expect(results).toHaveLength(10); + }); +}); +``` + +### Integration Tests + +```javascript +describe('OpenCode Server Integration', () => { + test('should fallback to spawn mode on server failure', async () => { + // Force server failure + await opencodeManager.stop(); + opencodeManager.isReady = false; + + // Send message - should use spawn fallback + const result = await sendToOpencodeWithFallback({ + session: { id: 'test', workspaceDir: '/tmp' }, + model: 'test-model', + content: 'Hello' + }); + + expect(result).toBeDefined(); + }); + + test('should handle SSE event streaming', async () => { + await opencodeManager.start(); + + const events = []; + const unregister = opencodeManager.registerEventHandler('test-session', (event) => { + events.push(event); + }); + + // Trigger an event + opencodeManager.handleServerEvent({ + type: 'message.part', + properties: { sessionID: 'test-session', part: { text: 'test' } } + }); + + expect(events).toHaveLength(1); + unregister(); + }); +}); +``` + +## Migration Path + +### Step 1: Add Feature Flag (Low Risk) + +1. Add environment variable `OPENCODE_SERVER_MODE=false` (default disabled) +2. Add OpencodeProcessManager class enhancements +3. Test in development + +### Step 2: Enable for Testing (Medium Risk) + +1. Set `OPENCODE_SERVER_MODE=true` in staging +2. Monitor metrics and error rates +3. Compare resource usage with spawn mode + +### Step 3: Gradual Rollout (Low Risk) + +1. Enable for subset of users/sessions +2. Use canary deployment pattern +3. Monitor for issues + +### Step 4: Full Rollout (Production) + +1. Enable for all traffic +2. Keep fallback mode available +3. Remove spawn mode code in future version (optional) + +## Performance Comparison + +### Expected Improvements + +| Metric | Spawn Mode | Server Mode | Improvement | +|--------|------------|-------------|-------------| +| Memory per session | ~300MB | Shared ~400MB | 33% reduction (2 sessions) | +| Startup latency | 1-2s | 10-50ms | 95% reduction | +| CPU overhead | High (process spawn) | Low (HTTP calls) | Significant | +| Concurrent sessions | Limited by memory | Higher capacity | Better scalability | + +### Benchmarking Script + +```bash +#!/bin/bash +# benchmark-opencode.sh + +echo "Benchmarking OpenCode modes..." + +# Spawn mode +OPENCODE_SERVER_MODE=false node server.js & +PID1=$! +sleep 5 +ab -n 100 -c 10 http://localhost:4000/api/test +kill $PID1 + +# Server mode +OPENCODE_SERVER_MODE=true node server.js & +PID2=$! +sleep 5 +ab -n 100 -c 10 http://localhost:4000/api/test +kill $PID2 +``` + +## Troubleshooting + +### Common Issues + +1. **Port already in use**: Change `OPENCODE_SERVER_PORT` or stop conflicting service +2. **Connection refused**: Check if OpenCode CLI is properly installed +3. **Auth failures**: Verify `OPENCODE_SERVER_PASSWORD` matches +4. **Memory issues**: Monitor with `/api/opencode/status` endpoint + +### Debug Mode + +```bash +# Enable verbose logging +DEBUG=opencode:* OPENCODE_SERVER_MODE=true node server.js + +# Check server status +curl http://localhost:4000/api/opencode/status + +# Manual restart +curl -X POST http://localhost:4000/api/opencode/restart +``` + +## Future Enhancements + +1. **Connection pooling**: Reuse HTTP connections for better performance +2. **Request batching**: Combine multiple operations into single API calls +3. **Multi-server support**: Load balance across multiple OpenCode instances +4. **Circuit breaker**: Automatic mode switching based on error rates +5. **Prometheus metrics**: Export metrics for monitoring systems diff --git a/chat/TESTING_GUIDE.md b/chat/TESTING_GUIDE.md index ec3b76a..5475814 100644 --- a/chat/TESTING_GUIDE.md +++ b/chat/TESTING_GUIDE.md @@ -235,15 +235,15 @@ docker exec shopify-ai-test node /opt/webchat/scripts/migrate-to-database.js ### 7. Verify Tables ```bash -docker exec shopify-ai-test sqlite3 /home/web/data/.data/shopify_ai.db ".tables" +docker exec shopify-ai-test sqlcipher /home/web/data/.data/shopify_ai.db "PRAGMA key = '$DATABASE_ENCRYPTION_KEY'; .tables" ``` Expected output: ``` -affiliates payment_sessions token_blacklist -audit_log refresh_tokens users -contact_messages sessions withdrawals -feature_requests +affiliate_accounts payment_sessions token_blacklist +affiliates refresh_tokens users +audit_log sessions withdrawals +contact_messages feature_requests ``` ### 8. Check Encryption Keys Persisted diff --git a/chat/blog-system.js b/chat/blog-system.js index b57eb2e..8d1f008 100644 --- a/chat/blog-system.js +++ b/chat/blog-system.js @@ -9,7 +9,18 @@ const fsSync = require('fs'); const path = require('path'); // Blog configuration -const BLOGS_REPO_DIR = path.join(process.cwd(), 'blogs'); +// Support Docker/Portainer deployment where: +// - BLOGS_DIR can be explicitly set +// - CHAT_APP_DIR points to /opt/webchat in Docker +// - CHAT_REPO_ROOT points to workspace directory +// - Fall back to cwd/blogs for local development +const BLOGS_REPO_DIR = process.env.BLOGS_DIR + ? process.env.BLOGS_DIR + : process.env.CHAT_APP_DIR + ? path.join(process.env.CHAT_APP_DIR, 'blogs') + : process.env.CHAT_REPO_ROOT + ? path.join(process.env.CHAT_REPO_ROOT, 'blogs') + : path.join(process.cwd(), 'blogs'); const BLOGS_DB_FILE = path.join(process.cwd(), '.data', '.opencode-chat', 'blogs.db.json'); const BLOGS_UPLOAD_DIR = path.join(process.cwd(), 'public', 'blogs', 'images'); diff --git a/chat/blogs/seo/choosing-wordpress-plugins-expert-guide.json b/chat/blogs/seo/choosing-wordpress-plugins-expert-guide.json index 7c40150..b66d9de 100644 --- a/chat/blogs/seo/choosing-wordpress-plugins-expert-guide.json +++ b/chat/blogs/seo/choosing-wordpress-plugins-expert-guide.json @@ -3,7 +3,7 @@ "slug": "choosing-wordpress-plugins-expert-guide", "type": "seo", "title": "Choosing the Right WordPress Plugins: An Expert Guide to Building Your Perfect Stack", - "excerpt": "Master the art of selecting WordPress plugins. Learn evaluation criteria, avoid common pitfalls, and discover how Plugin Compass AI can build custom plugins tailored to your exact needs.", + "excerpt": "Master the art of selecting WordPress plugins. Learn evaluation criteria, avoid common pitfalls, and discover how to build the perfect plugin stack for your website.", "content": { "blocks": [ { @@ -202,10 +202,11 @@ "data": { "style": "unordered", "items": [ - "Wordfence: Comprehensive with firewall and scanning", - "Sucuri: Great for sites needing WAF protection", - "iThemes Security: Feature-rich with strong login protection", - "Solid Security: Formerly iThemes, enterprise-focused" + "Firewall protection to block malicious traffic", + "Real-time malware scanning", + "Login security with two-factor authentication", + "File integrity monitoring", + "Security activity logging" ] } }, @@ -227,10 +228,11 @@ "data": { "style": "unordered", "items": [ - "WP Rocket: Premium, easiest to configure, best results", - "WP Super Cache: Free, simple, maintained by Automattic", - "W3 Total Cache: Advanced users, many configuration options", - "LiteSpeed Cache: Best if your host uses LiteSpeed" + "Page caching for static content delivery", + "Browser caching to reduce repeat visits", + "Database caching for faster queries", + "GZIP compression to reduce file sizes", + "CDN integration for global delivery" ] } }, @@ -246,10 +248,11 @@ "data": { "style": "unordered", "items": [ - "Yoast SEO: Most popular, comprehensive features", - "Rank Math: Growing rapidly, more features in free version", - "SEOPress: Lightweight alternative with strong features", - "The SEO Framework: Minimalist, performance-focused" + "XML sitemaps for search engine discovery", + "Meta tag management for title and descriptions", + "Schema markup for rich search results", + "Canonical URL handling to prevent duplicate content", + "Open Graph and Twitter Card support" ] } }, @@ -265,10 +268,12 @@ "data": { "style": "unordered", "items": [ - "Gravity Forms: Premium, most powerful, developer-friendly", - "WPForms: User-friendly, great for beginners", - "Fluent Forms: Excellent free version, fast performance", - "Formidable Forms: Advanced calculations and data views" + "Drag-and-drop form builder", + "Conditional logic for dynamic forms", + "File upload capability", + "Email notifications and auto-responders", + "Database storage and entry management", + "Spam protection integration" ] } }, @@ -284,10 +289,12 @@ "data": { "style": "unordered", "items": [ - "WooCommerce: The standard, most extensions available", - "Easy Digital Downloads: Perfect for digital products", - "WP EasyCart: Lightweight alternative for small stores", - "BigCommerce for WordPress: Headless commerce solution" + "Multiple payment gateway options", + "Inventory management system", + "Shipping label integration", + "Tax calculation capability", + "Customer account management", + "Order tracking and notifications" ] } }, @@ -303,10 +310,12 @@ "data": { "style": "unordered", "items": [ - "Elementor: Most popular, extensive template library", - "Beaver Builder: Developer-friendly, clean code output", - "Divi: Theme and builder combination, visual editing", - "Bricks: Rising star, excellent performance" + "Visual drag-and-drop interface", + "Pre-built template library", + "Responsive design controls", + "Theme builder capabilities", + "Custom CSS support", + "Performance-optimized code output" ] } }, @@ -529,7 +538,7 @@ { "type": "paragraph", "data": { - "text": "Plugin Compass represents this future—AI-generated custom plugins that perfectly match modern WordPress architecture while maintaining the platform's famous flexibility." + "text": "This evolution opens doors for businesses with unique requirements to explore custom solutions." } }, { @@ -548,7 +557,7 @@ { "type": "paragraph", "data": { - "text": "When existing plugins don't meet your exact needs, remember that custom solutions are more accessible than ever. Plugin Compass empowers you to build tailored plugins that perfectly fit your business requirements, without the cost and complexity of traditional development. Ready to build your perfect WordPress solution? Start with Plugin Compass today." + "text": "When existing plugins don't meet your exact needs, custom plugin development offers a powerful alternative. Tools like Plugin Compass can help you build tailored plugins that perfectly fit your specific requirements." } } ] diff --git a/chat/blogs/seo/optimize-wordpress-performance-without-coding.json b/chat/blogs/seo/optimize-wordpress-performance-without-coding.json index 2f347a6..32b0e10 100644 --- a/chat/blogs/seo/optimize-wordpress-performance-without-coding.json +++ b/chat/blogs/seo/optimize-wordpress-performance-without-coding.json @@ -87,11 +87,11 @@ "data": { "style": "unordered", "items": [ - "GeneratePress: Under 10KB page size, modular approach to features", - "Astra: Optimized for speed with extensive customization options", - "Kadence: Lightweight with advanced header/footer builder", - "Neve: Mobile-first design with AMP compatibility", - "Blocksy: Built for Gutenberg with minimal bloat" + "Minimal page size (under 50KB is ideal)", + "Modular feature approach - only load what you need", + "Regular updates and active development", + "Responsive design built-in", + "Clean code with no unnecessary bloat" ] } }, @@ -126,10 +126,11 @@ "data": { "style": "unordered", "items": [ - "WP Rocket (Premium): Best-in-class with easy configuration", - "WP Super Cache (Free): Simple and effective, maintained by Automattic", - "W3 Total Cache (Free): Feature-rich with advanced options", - "LiteSpeed Cache (Free): Excellent if your host uses LiteSpeed servers" + "Page caching to serve static versions of your pages", + "Browser caching headers for returning visitors", + "Object caching for database queries", + "GZIP compression to reduce file sizes", + "Minification to reduce CSS and JavaScript file sizes" ] } }, @@ -166,7 +167,7 @@ "items": [ "Use the right format: JPEG for photographs, PNG for graphics with transparency, WebP when possible", "Resize before uploading: Don't upload 4000px images for 800px display areas", - "Compress images: Use tools like TinyPNG, ShortPixel, or Imagify", + "Compress images: Use tools like TinyPNG, ShortPixel, or Imagify before uploading, or use plugins that handle this automatically", "Enable lazy loading: Images load only when users scroll to them", "Use responsive images: Serve different sizes for different devices" ] @@ -175,7 +176,7 @@ { "type": "paragraph", "data": { - "text": "Recommended image optimization plugins:" + "text": "Look for image optimization plugins that offer:" } }, { @@ -183,10 +184,11 @@ "data": { "style": "unordered", "items": [ - "ShortPixel: Excellent compression with WebP conversion", - "Imagify: Made by WP Rocket team, great integration", - "Smush: Popular free option with lazy loading", - "Optimole: Cloud-based optimization with CDN delivery" + "Automatic compression on upload", + "WebP conversion for modern browsers", + "Lazy loading integration", + "Bulk optimization for existing images", + "CDN integration for faster delivery" ] } }, @@ -225,7 +227,7 @@ { "type": "paragraph", "data": { - "text": "This is where Plugin Compass offers a unique advantage. Instead of installing multiple plugins to achieve your goals, you can use our AI to build a single, optimized custom plugin that does exactly what you need—nothing more, nothing less." + "text": "Sometimes, combining multiple plugins can cause conflicts or performance issues. For unique requirements, custom plugin development can provide a more efficient solution." } }, { @@ -252,10 +254,11 @@ "data": { "style": "unordered", "items": [ - "Cloudflare (Free tier available): Includes security features and optimization", - "Bunny CDN: Affordable with excellent performance", - "KeyCDN: Pay-as-you-go pricing", - "StackPath: Formerly MaxCDN, reliable enterprise option" + "Global server network for faster delivery worldwide", + "Static file caching (images, CSS, JavaScript)", + "DDoS protection and security features", + "Easy WordPress integration", + "Free tier availability for testing" ] } }, @@ -283,9 +286,11 @@ "data": { "style": "unordered", "items": [ - "WP-Optimize: Cleans database, compresses images, and caches", - "Advanced Database Cleaner: Detailed control over what to clean", - "WP Sweep: Simple, safe database cleaning" + "Post revision cleanup", + "Spam comment removal", + "Transient option cleanup", + "Database table optimization", + "Scheduled automatic cleaning" ] } }, @@ -330,20 +335,20 @@ { "type": "header", "data": { - "text": "Building Custom Performance Solutions with Plugin Compass", + "text": "Custom Solutions for Unique Performance Needs", "level": 2 } }, { "type": "paragraph", "data": { - "text": "While the techniques above will significantly improve your site's performance, sometimes you need custom solutions for specific performance challenges. Plugin Compass empowers you to build tailored performance plugins without writing code." + "text": "While the techniques above will significantly improve your site's performance, sometimes you need custom solutions for specific performance challenges. Custom plugin development can address unique requirements that generic plugins cannot." } }, { "type": "paragraph", "data": { - "text": "Here are custom performance solutions you can build with Plugin Compass:" + "text": "Here are custom performance solutions that can help:" } }, { @@ -363,7 +368,7 @@ { "type": "paragraph", "data": { - "text": "For example, if you run a WooCommerce store with thousands of products, you might need custom database indexing and query optimization that generic plugins can't provide. With Plugin Compass, simply describe your needs in plain English, and our AI will generate an optimized plugin specifically for your store's requirements." + "text": "For example, if you run a WooCommerce store with thousands of products, you might need custom database indexing and query optimization that generic plugins can't provide. Custom plugin development can address these specific needs." } }, { @@ -435,7 +440,7 @@ { "type": "paragraph", "data": { - "text": "For unique performance challenges or specific business requirements, Plugin Compass offers the perfect solution. Build custom performance plugins tailored exactly to your needs, saving money on premium plugins while achieving better results. Ready to take your WordPress performance to the next level? Start building with Plugin Compass today." + "text": "For unique performance challenges or specific business requirements, custom plugin development offers a powerful alternative to generic solutions." } } ] diff --git a/chat/blogs/seo/wordpress-backup-strategies-guide.json b/chat/blogs/seo/wordpress-backup-strategies-guide.json new file mode 100644 index 0000000..3f794b6 --- /dev/null +++ b/chat/blogs/seo/wordpress-backup-strategies-guide.json @@ -0,0 +1,476 @@ +{ + "id": "wordpress-backup-strategies-guide", + "slug": "wordpress-backup-strategies-guide", + "type": "seo", + "title": "WordPress Backup Strategies: A Practical Guide to Protecting Your Website Data", + "excerpt": "Learn essential backup strategies for WordPress. Understand what to backup, how often, storage options, and testing procedures to ensure you can recover from any disaster.", + "content": { + "blocks": [ + { + "type": "paragraph", + "data": { + "text": "Imagine waking up one morning to find your website hacked, corrupted by a failed update, or accidentally deleted by a team member. Without a proper backup strategy, years of content, customer data, and business operations could vanish instantly. This guide covers essential backup strategies—from understanding what to back up to implementing a reliable recovery plan." + } + }, + { + "type": "header", + "data": { + "text": "Why Backups Are Non-Negotiable", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Website disasters happen more often than you might think. Consider these statistics:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "60% of companies that lose their data shut down within 6 months", + "29% of data loss is caused by human error", + "30,000 websites are hacked daily", + "94% of companies with a disaster recovery plan survive major data loss" + ] + } + }, + { + "type": "paragraph", + "data": { + "text": "A robust backup strategy is your insurance policy against these threats. It's not a question of if you'll need a backup—it's when." + } + }, + { + "type": "header", + "data": { + "text": "Understanding What Needs to Be Backed Up", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "A complete WordPress backup consists of two components that must both be preserved:" + } + }, + { + "type": "header", + "data": { + "text": "1. Files", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Your WordPress files include:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "WordPress core files", + "Theme files (including customizations)", + "Plugin files", + "Uploaded media (images, videos, documents)", + "Configuration files (wp-config.php)", + ".htaccess file and other server configs" + ] + } + }, + { + "type": "header", + "data": { + "text": "2. Database", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Your MySQL database contains all your dynamic content:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "All posts, pages, and custom post types", + "User information and roles", + "Comments and discussions", + "Plugin and theme settings", + "WooCommerce orders and products", + "Custom field data", + "SEO metadata", + "Widget and menu configurations" + ] + } + }, + { + "type": "header", + "data": { + "text": "The 3-2-1 Backup Rule", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "The gold standard for data protection follows the 3-2-1 rule:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "3 copies of your data (1 primary, 2 backups)", + "2 different storage media or services", + "1 offsite backup (physically separate location)" + ] + } + }, + { + "type": "quote", + "data": { + "text": "Following the 3-2-1 rule ensures that even if your primary server fails and your local backup is corrupted, you still have a third copy safely stored elsewhere.", + "caption": "Data Protection Best Practices" + } + }, + { + "type": "header", + "data": { + "text": "Backup Frequency: How Often Should You Backup?", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Your backup frequency depends on how often your site changes and how much data you can afford to lose:" + } + }, + { + "type": "table", + "data": { + "content": [ + ["Site Type", "Recommended Frequency", "Retention"], + ["Static brochure site", "Weekly", "4 weeks"], + ["Active blog (1-2 posts/week)", "Daily", "30 days"], + ["E-commerce store", "Real-time or hourly", "90 days"], + ["Membership site", "Daily minimum", "30 days"], + ["News site with frequent updates", "Multiple times daily", "14 days"], + ["Multi-site network", "Daily with real-time for active subsites", "30 days"] + ] + } + }, + { + "type": "paragraph", + "data": { + "text": "The key question is: How much data can you afford to lose? If you publish 10 articles per day, daily backups mean potentially losing a full day's work. Hourly backups limit that loss to at most one hour." + } + }, + { + "type": "header", + "data": { + "text": "Types of Backups", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "Full Backups", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "A complete backup includes all files and the database. This is the most comprehensive but takes more storage space and time to create and restore." + } + }, + { + "type": "header", + "data": { + "text": "Incremental Backups", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "After an initial full backup, incremental backups only save changes since the last backup. This saves storage space and time but requires all incremental backups plus the original full backup to restore." + } + }, + { + "type": "header", + "data": { + "text": "Differential Backups", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Differential backups save changes since the last full backup. They're a middle ground between full and incremental—faster than full but require less reconstruction than incremental." + } + }, + { + "type": "header", + "data": { + "text": "Database-Only vs File-Only Backups", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Some situations call for partial backups:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Database-only: Quick backups for content-heavy sites", + "File-only: When files haven't changed but database has", + "Selective: Specific tables or directories when needed" + ] + } + }, + { + "type": "header", + "data": { + "text": "Backup Storage Options", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Never store backups only on your web server. If the server fails, you lose both your site and backups. Consider these options:" + } + }, + { + "type": "header", + "data": { + "text": "Cloud Storage Services", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "General-purpose cloud storage offers flexibility and often includes free tiers:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Google Drive: 15GB free, integrates with many backup plugins", + "Dropbox: 2GB free, excellent sync client", + "Amazon S3: Pay-as-you-go, highly reliable, industry standard", + "Microsoft OneDrive: 5GB free, good Office integration", + "Wasabi: Affordable S3-compatible storage" + ] + } + }, + { + "type": "header", + "data": { + "text": "Specialized Backup Storage", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Some services are designed specifically for backups:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Backblaze B2: Very affordable cloud storage", + "pCloud: European-based with client-side encryption", + "iDrive: Multiple device backup plus WordPress support" + ] + } + }, + { + "type": "header", + "data": { + "text": "What to Look for in Backup Solutions", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "When evaluating backup tools, prioritize these features:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Automated scheduling (set it and forget it)", + "Multiple storage destination options", + "Easy one-click restoration", + "Incremental backup support", + "Compression and encryption options", + "Email notifications for backup status", + "Staging site creation for safe testing" + ] + } + }, + { + "type": "header", + "data": { + "text": "Testing Your Backups", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "A backup you can't restore is worthless. Regular testing is crucial:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Quarterly restoration tests: Restore a backup to a staging site", + "Verify file integrity: Check that all files are present", + "Test database restoration: Ensure data is complete", + "Document the process: Keep step-by-step restoration instructions", + "Test different backup ages: Verify older backups still work" + ] + } + }, + { + "type": "header", + "data": { + "text": "Creating a Disaster Recovery Plan", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Beyond having backups, you need a plan for when disaster strikes:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Identify who is responsible for recovery", + "Document the restoration process step by step", + "Determine acceptable downtime for your business", + "Establish communication procedures", + "Know your hosting provider's capabilities and limits", + "Keep emergency contacts handy" + ] + } + }, + { + "type": "header", + "data": { + "text": "What to Do When Disaster Strikes", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "When problems occur, follow this process:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Stay calm: Panic leads to mistakes", + "Assess the damage: Determine what happened and when", + "Choose the right backup: Select a clean backup from before the incident", + "Restore to staging first: Test the restoration before affecting your live site", + "Verify functionality: Check that everything works properly", + "Document everything: Record what happened and how you fixed it", + "Implement preventive measures: Add monitoring or security to prevent recurrence" + ] + } + }, + { + "type": "header", + "data": { + "text": "Best Practices Summary", + "level": 2 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Automate everything: Manual backups are forgotten backups", + "Use multiple storage locations: Never rely on a single backup destination", + "Encrypt sensitive backups: Protect customer data with encryption", + "Monitor backup success: Set up alerts for failed backups", + "Keep backup history: Maintain 30-90 days of backups", + "Test regularly: A backup you can't restore is useless", + "Document everything: Keep restoration procedures updated", + "Secure your backups: Use strong passwords and two-factor authentication", + "Consider compliance: Ensure backups meet GDPR, HIPAA, or other requirements", + "Have a disaster recovery plan: Know exactly what to do when disaster strikes" + ] + } + }, + { + "type": "header", + "data": { + "text": "Conclusion", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "A comprehensive backup strategy is essential for any WordPress site. By following the 3-2-1 rule, choosing the right backup frequency, using reliable storage locations, and regularly testing your backups, you can ensure your website is protected against any disaster." + } + }, + { + "type": "paragraph", + "data": { + "text": "Remember: The best backup strategy is one you'll actually use. Set up automation, test regularly, and have a clear recovery plan. Your future self will thank you." + } + } + ] + }, + "author": "Plugin Compass Team", + "status": "published", + "featured_image": "", + "meta_title": "WordPress Backup Strategies: Practical Guide | Plugin Compass", + "meta_description": "Learn essential WordPress backup strategies. Understand what to backup, storage options, testing procedures, and disaster recovery planning.", + "category": "wordpress", + "tags": ["wordpress", "backup", "security", "disaster recovery", "data protection"], + "published_at": "2026-01-10T10:00:00Z", + "updated_at": "2026-02-20T10:00:00Z" +} diff --git a/chat/blogs/seo/wordpress-maintenance-checklist-2026.json b/chat/blogs/seo/wordpress-maintenance-checklist-2026.json new file mode 100644 index 0000000..7c5a7a4 --- /dev/null +++ b/chat/blogs/seo/wordpress-maintenance-checklist-2026.json @@ -0,0 +1,429 @@ +{ + "id": "wordpress-maintenance-checklist-2026", + "slug": "wordpress-maintenance-checklist-2026", + "type": "seo", + "title": "WordPress Maintenance Checklist for 2026: Keep Your Site Running Smoothly", + "excerpt": "A comprehensive maintenance checklist to keep your WordPress site secure, fast, and reliable. Learn the essential tasks for daily, weekly, and monthly maintenance routines.", + "content": { + "blocks": [ + { + "type": "paragraph", + "data": { + "text": "Running a WordPress site requires ongoing maintenance to ensure it remains secure, fast, and reliable. Without regular care, sites can become vulnerable to security threats, suffer performance degradation, and eventually break. This comprehensive checklist outlines the essential maintenance tasks you should perform to keep your WordPress site in top condition throughout 2026." + } + }, + { + "type": "header", + "data": { + "text": "Why Maintenance Matters", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Regular maintenance isn't just about fixing problems—it's about preventing them. A well-maintained site offers:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Better security against threats and vulnerabilities", + "Faster loading times and improved user experience", + "Fewer technical issues and downtime", + "Better search engine rankings", + "Lower long-term costs by catching issues early" + ] + } + }, + { + "type": "header", + "data": { + "text": "Daily Maintenance Tasks", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "These quick checks take just a few minutes each day:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Check if your site is accessible (use a monitoring service)", + "Review any automated error notifications", + "Check for pending comment moderation (if comments are enabled)", + "Review form submissions if you collect leads or orders" + ] + } + }, + { + "type": "header", + "data": { + "text": "Weekly Maintenance Tasks", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Set aside 30-60 minutes each week for these tasks:" + } + }, + { + "type": "header", + "data": { + "text": "Updates and Backups", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Verify backups completed successfully", + "Check for WordPress core updates", + "Check for theme and plugin updates", + "Review any security alerts from your hosting provider" + ] + } + }, + { + "type": "header", + "data": { + "text": "Performance Check", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Test page load times on key pages", + "Check server resource usage (CPU, memory)", + "Review error logs for recurring issues", + "Clear any unnecessary cache if needed" + ] + } + }, + { + "type": "header", + "data": { + "text": "Content Review", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Review and approve pending comments", + "Check for broken links on important pages", + "Review user registrations if applicable", + "Check product inventory if running an e-commerce site" + ] + } + }, + { + "type": "header", + "data": { + "text": "Monthly Maintenance Tasks", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Deeper maintenance tasks that require more time:" + } + }, + { + "type": "header", + "data": { + "text": "Security Audit", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Review user accounts and remove inactive ones", + "Check for unused themes and plugins (delete them)", + "Verify file permissions are correct", + "Review security plugin logs", + "Check SSL certificate expiration", + "Update strong passwords for admin accounts" + ] + } + }, + { + "type": "header", + "data": { + "text": "Database Maintenance", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Optimize database tables", + "Clean up post revisions (configure limits)", + "Remove spam comments", + "Clean expired transients", + "Remove unused media files" + ] + } + }, + { + "type": "header", + "data": { + "text": "Performance Optimization", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Analyze performance with Google PageSpeed Insights", + "Optimize images that have been added", + "Review and clean up CSS and JavaScript", + "Check database query performance", + "Review caching configuration" + ] + } + }, + { + "type": "header", + "data": { + "text": "Content Audit", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Identify underperforming content", + "Update outdated information", + "Refresh internal links", + "Review and update meta descriptions", + "Check for broken external links" + ] + } + }, + { + "type": "header", + "data": { + "text": "Quarterly Maintenance Tasks", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Every 3 months, perform these deeper tasks:" + } + }, + { + "type": "header", + "data": { + "text": "Testing and Verification", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Test complete backup restoration on a staging site", + "Verify all forms are working correctly", + "Test checkout process if e-commerce", + "Verify email notifications are working", + "Test on different browsers and devices" + ] + } + }, + { + "type": "header", + "data": { + "text": "Review and Planning", + "level": 3 + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Review hosting needs and consider upgrades", + "Evaluate if current plugins are still needed", + "Review SEO performance and adjust strategy", + "Check domain registration and SSL renewal dates", + "Review analytics for trends and insights" + ] + } + }, + { + "type": "header", + "data": { + "text": "Annual Maintenance Tasks", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Once a year, tackle these important items:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Renew domain registrations", + "Renew SSL certificates", + "Review and update privacy policy", + "ReviewTerms of Service if applicable", + "Check GDPR and compliance requirements", + "Archive or update old content", + "Review and update disaster recovery plan", + "Consider a site redesign or major update" + ] + } + }, + { + "type": "header", + "data": { + "text": "Automating Maintenance", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Many maintenance tasks can be automated to save time:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Set up automated backups (daily minimum)", + "Configure automatic updates for minor releases", + "Use uptime monitoring services", + "Set up automated security scanning", + "Configure error monitoring and alerts", + "Use image optimization plugins" + ] + } + }, + { + "type": "header", + "data": { + "text": "Creating a Maintenance Schedule", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Consistency is key. Here's how to build a sustainable routine:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Schedule maintenance tasks on your calendar", + "Set up reminders for recurring tasks", + "Document your procedures for consistency", + "Track issues to identify patterns", + "Adjust frequency based on site complexity" + ] + } + }, + { + "type": "quote", + "data": { + "text": "Spend one hour per week on maintenance to avoid spending days fixing problems later.", + "caption": "WordPress Maintenance Best Practices" + } + }, + { + "type": "header", + "data": { + "text": "When to Seek Professional Help", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Some tasks require expert assistance:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Major WordPress upgrades", + "Server configuration changes", + "Security incident response", + "Performance optimization for complex sites", + "Custom development needs", + "Migration to new hosting" + ] + } + }, + { + "type": "header", + "data": { + "text": "Conclusion", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Regular maintenance is essential for keeping your WordPress site secure, fast, and reliable. By following this checklist and establishing a consistent routine, you can prevent most problems before they occur and ensure your site continues to serve your visitors effectively." + } + }, + { + "type": "paragraph", + "data": { + "text": "Start with the daily and weekly tasks, then gradually build up to the monthly and quarterly routines. Your site—and your visitors—will thank you." + } + } + ] + }, + "author": "Plugin Compass Team", + "status": "published", + "featured_image": "", + "meta_title": "WordPress Maintenance Checklist 2026 | Plugin Compass", + "meta_description": "Essential WordPress maintenance checklist for 2026. Daily, weekly, monthly tasks to keep your site secure, fast, and reliable.", + "category": "wordpress", + "tags": ["wordpress", "maintenance", "security", "performance", "checklist"], + "published_at": "2026-02-20T10:00:00Z", + "updated_at": "2026-02-20T10:00:00Z" +} diff --git a/chat/blogs/seo/wordpress-security-essential-guide.json b/chat/blogs/seo/wordpress-security-essential-guide.json new file mode 100644 index 0000000..735d019 --- /dev/null +++ b/chat/blogs/seo/wordpress-security-essential-guide.json @@ -0,0 +1,393 @@ +{ + "id": "wordpress-security-essential-guide", + "slug": "wordpress-security-essential-guide", + "type": "seo", + "title": "WordPress Security Essentials: A Practical Guide to Protecting Your Website", + "excerpt": "Learn essential WordPress security practices to protect your website from threats. Understand firewalls, malware prevention, login security, and how to build a defense-in-depth strategy.", + "content": { + "blocks": [ + { + "type": "paragraph", + "data": { + "text": "WordPress powers over 43% of all websites on the internet, making it a prime target for hackers and malicious attacks. In 2026, website security has never been more critical, with cyber threats becoming increasingly sophisticated. While WordPress core is secure, the vast ecosystem of themes and plugins can introduce vulnerabilities. This guide covers essential security concepts and practical steps every WordPress site owner should understand." + } + }, + { + "type": "header", + "data": { + "text": "Why WordPress Security Matters", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Understanding why security matters helps prioritize protection efforts. A compromised website can lead to:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Data breaches exposing customer information", + "Blacklisting by search engines (damaging your SEO)", + "Malware distribution to your visitors", + "Loss of revenue and customer trust", + "Legal liabilities and compliance issues" + ] + } + }, + { + "type": "paragraph", + "data": { + "text": "Investing in robust security measures is not just about protection—it's about maintaining your business reputation and ensuring uninterrupted service to your customers." + } + }, + { + "type": "header", + "data": { + "text": "Understanding the Threat Landscape", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Before implementing security measures, it's important to understand the types of threats your site faces:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Brute Force Attacks: Automated attempts to guess passwords", + "SQL Injection: Malicious database queries", + "Cross-Site Scripting (XSS): Injecting malicious scripts", + "Distributed Denial of Service (DDoS): Overwhelming your server", + "Phishing: Trick users into revealing credentials", + "Malware: Viruses, ransomware, and spyware" + ] + } + }, + { + "type": "header", + "data": { + "text": "Essential Security Measures", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "1. Web Application Firewall (WAF)", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "A WAF acts as a shield between your website and incoming traffic, blocking malicious requests before they reach your server. When evaluating security solutions, look for:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Real-time threat detection and blocking", + "Protection against OWASP Top 10 vulnerabilities", + "DDoS mitigation capabilities", + "Regular rule updates to address new threats", + "Minimal false positives to avoid blocking legitimate traffic" + ] + } + }, + { + "type": "header", + "data": { + "text": "2. Malware Scanning and Removal", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Malware scanning helps detect malicious code before it causes damage. Effective scanning should include:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Core file integrity monitoring", + "Theme and plugin scanning", + "Database malware detection", + "Scheduled automatic scans", + "Notification system for detected threats" + ] + } + }, + { + "type": "header", + "data": { + "text": "3. Login Security", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "The login page is often the first target for attackers. Strengthen login security by implementing:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Two-factor authentication (2FA)", + "Strong password requirements", + "Login attempt limits", + "CAPTCHA to prevent automated attacks", + "Unique login URLs (not /wp-admin)", + "Email-based login notifications" + ] + } + }, + { + "type": "header", + "data": { + "text": "4. User Access Management", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Not all users need full access. Implement the principle of least privilege:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Create role-specific user accounts", + "Use WordPress built-in roles (Admin, Editor, Author, Contributor, Subscriber)", + "Regularly audit user accounts and remove inactive ones", + "Consider using multi-site for managing multiple properties", + "Monitor user activity logs" + ] + } + }, + { + "type": "header", + "data": { + "text": "5. File System Security", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Protect your files from unauthorized access and modification:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Disable file editing in the WordPress admin", + "Set proper file permissions (directories 755, files 644)", + "Protect configuration files (wp-config.php, .htaccess)", + "Disable PHP execution in upload directories", + "Use secure FTP (SFTP) for file transfers" + ] + } + }, + { + "type": "header", + "data": { + "text": "6. Database Security", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Your database contains all your content and user data. Protect it by:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Using strong database passwords", + "Changing the database table prefix from wp_", + "Limiting database user privileges", + "Enabling SSL connections to the database", + "Regular backups (see our backup guide)" + ] + } + }, + { + "type": "header", + "data": { + "text": "7. SSL/TLS Certificates", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "HTTPS encrypts data between your server and visitors, protecting sensitive information:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Install an SSL certificate (often free through Let's Encrypt)", + "Force HTTPS for admin areas", + "Implement HTTP to HTTPS redirects", + "Use HSTS headers for enhanced security", + "Monitor certificate expiration dates" + ] + } + }, + { + "type": "header", + "data": { + "text": "8. Monitoring and Logging", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "You can't protect what you can't see. Implement monitoring to detect issues early:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Failed login attempt logging", + "File change detection", + "404 error monitoring (can indicate scanning)", + "Resource usage monitoring", + "Uptime monitoring", + "Regular security audit reports" + ] + } + }, + { + "type": "header", + "data": { + "text": "Defense in Depth: Layered Security", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "No single security measure is foolproof. The most effective approach uses multiple layers of protection:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "At the network level: Firewall, CDN, DDoS protection", + "At the server level: Proper configurations, monitoring", + "At the application level: Security plugins, updates", + "At the user level: Strong passwords, 2FA, access controls", + "At the data level: Backups, encryption, access logs" + ] + } + }, + { + "type": "header", + "data": { + "text": "Choosing Security Solutions", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "When evaluating security tools, consider these factors:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Coverage: Does it address the threats relevant to your site?", + "Performance: Will it slow down your website?", + "Updates: Is it regularly updated for new threats?", + "Support: Is help available when needed?", + "Reviews: What do other users report?", + "Cost: Does it fit your budget? Are there hidden fees?" + ] + } + }, + { + "type": "paragraph", + "data": { + "text": "Important: Never run multiple security plugins simultaneously—they'll conflict and can actually create vulnerabilities." + } + }, + { + "type": "header", + "data": { + "text": "When Standard Solutions Aren't Enough", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "While standard security plugins work well for most sites, businesses with unique requirements may need custom solutions. This is where custom plugin development can help address specific security needs that off-the-shelf products don't cover." + } + }, + { + "type": "header", + "data": { + "text": "Conclusion", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "WordPress security is not a set-it-and-forget-it task. It requires ongoing attention, regular updates, and a multi-layered approach. By understanding the threat landscape and implementing defense-in-depth strategies, you can significantly reduce your risk." + } + }, + { + "type": "paragraph", + "data": { + "text": "Remember: No single plugin or measure can guarantee 100% security. Combine good security practices—regular backups, strong passwords, keeping everything updated—with appropriate security tools. Stay vigilant, monitor for threats, and have a response plan ready." + } + } + ] + }, + "author": "Plugin Compass Team", + "status": "published", + "featured_image": "", + "meta_title": "WordPress Security Essentials: Practical Guide | Plugin Compass", + "meta_description": "Learn essential WordPress security practices. Understand firewalls, malware prevention, login security, and defense-in-depth strategies.", + "category": "wordpress", + "tags": ["wordpress", "security", "website protection", "malware", "firewall", "defense"], + "published_at": "2026-01-15T10:00:00Z", + "updated_at": "2026-02-20T10:00:00Z" +} diff --git a/chat/blogs/seo/wordpress-seo-best-practices-2026.json b/chat/blogs/seo/wordpress-seo-best-practices-2026.json new file mode 100644 index 0000000..180519e --- /dev/null +++ b/chat/blogs/seo/wordpress-seo-best-practices-2026.json @@ -0,0 +1,546 @@ +{ + "id": "wordpress-seo-best-practices-2026", + "slug": "wordpress-seo-best-practices-2026", + "type": "seo", + "title": "WordPress SEO Best Practices for 2026: A Complete Guide", + "excerpt": "Master WordPress SEO with this comprehensive guide. Learn on-page optimization, technical SEO, content strategies, and how to improve your search rankings.", + "content": { + "blocks": [ + { + "type": "paragraph", + "data": { + "text": "Search engine optimization (SEO) remains one of the most effective ways to drive organic traffic to your WordPress site. With search algorithms evolving constantly, staying up-to-date with best practices is essential. This guide covers the most important SEO strategies for WordPress sites in 2026, from technical optimization to content creation." + } + }, + { + "type": "header", + "data": { + "text": "Understanding SEO Fundamentals", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Before diving into specific techniques, it's important to understand what search engines are looking for:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Relevant, high-quality content that matches user intent", + "Proper HTML structure and semantic markup", + "Fast page loading times", + "Mobile-friendly design", + "Secure website (HTTPS)", + "Good user experience and low bounce rates", + "Authoritative backlinks from reputable sources" + ] + } + }, + { + "type": "header", + "data": { + "text": "Technical SEO for WordPress", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "1. Site Speed Optimization", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Page speed is a confirmed ranking factor. Here's how to optimize your WordPress site:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Use a quality hosting provider appropriate for your traffic", + "Implement caching (page, browser, and object caching)", + "Optimize images (compress, use next-gen formats, lazy load)", + "Minify CSS, JavaScript, and HTML", + "Reduce server response time", + "Use a Content Delivery Network (CDN)", + "Eliminate render-blocking resources" + ] + } + }, + { + "type": "header", + "data": { + "text": "2. Mobile Optimization", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "With mobile-first indexing, your site must perform well on mobile devices:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Use a responsive theme that adapts to all screen sizes", + "Ensure tap targets are appropriately sized", + "Test with Google's Mobile-Friendly Test", + "Avoid interstitials that block content", + "Optimize for Core Web Vitals" + ] + } + }, + { + "type": "header", + "data": { + "text": "3. XML Sitemaps", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Sitemaps help search engines discover and index your content:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Generate an XML sitemap automatically", + "Include all important content pages", + "Exclude admin pages, login, and duplicate content", + "Submit to Google Search Console", + "Update sitemap when adding new content" + ] + } + }, + { + "type": "header", + "data": { + "text": "4. SSL and Security", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Security is a ranking factor and builds user trust:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Install an SSL certificate (often free via Let's Encrypt)", + "Redirect HTTP to HTTPS", + "Implement HSTS headers", + "Keep WordPress, themes, and plugins updated", + "Use security plugins for additional protection" + ] + } + }, + { + "type": "header", + "data": { + "text": "5. URL Structure", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Clean, descriptive URLs help both users and search engines:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Use readable, keyword-rich slugs", + "Keep URLs short and descriptive", + "Use hyphens to separate words", + "Avoid parameters when possible", + "Implement canonical URLs to avoid duplicate content", + "Maintain a consistent URL structure" + ] + } + }, + { + "type": "header", + "data": { + "text": "On-Page SEO", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "1. Title Tags and Meta Descriptions", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "These elements appear in search results and influence click-through rates:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Include primary keyword near the beginning", + "Keep titles under 60 characters", + "Write unique titles for each page", + "Meta descriptions under 155 characters", + "Include a clear call-to-action", + "Make each description unique and compelling" + ] + } + }, + { + "type": "header", + "data": { + "text": "2. Heading Structure", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Proper heading hierarchy helps search engines understand content structure:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Use one H1 per page (usually the page title)", + "Use H2 for major sections", + "Use H3 for subsections", + "Include keywords in headings naturally", + "Don't skip heading levels", + "Make headings descriptive and clear" + ] + } + }, + { + "type": "header", + "data": { + "text": "3. Content Optimization", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Quality content is the foundation of SEO:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Write comprehensive, in-depth content", + "Use keywords naturally throughout content", + "Include related keywords and synonyms", + "Add images with optimized alt text", + "Use internal links to related content", + "Update and improve existing content" + ] + } + }, + { + "type": "header", + "data": { + "text": "4. Image Optimization", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Images can drive significant traffic but need proper optimization:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Use descriptive filenames with keywords", + "Write detailed alt text for each image", + "Compress images to reduce file size", + "Use WebP or other modern formats", + "Implement lazy loading", + "Specify dimensions to prevent layout shifts" + ] + } + }, + { + "type": "header", + "data": { + "text": "5. Internal Linking", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Internal links help search engines understand site structure:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Link to related content within your site", + "Use descriptive anchor text", + "Create a logical hierarchy", + "Link from high-authority pages to important ones", + "Don't over-optimize anchor text", + "Regularly audit for broken internal links" + ] + } + }, + { + "type": "header", + "data": { + "text": "Schema Markup", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Schema markup helps search engines understand your content better and can lead to rich snippets:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Article schema for blog posts", + "Organization schema for business info", + "LocalBusiness schema for local SEO", + "Product schema for e-commerce", + "FAQ schema for question content", + "Review schema for testimonials" + ] + } + }, + { + "type": "header", + "data": { + "text": "Content Strategy", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "1. Keyword Research", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Understanding what people search for is fundamental:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Identify primary keywords for each page", + "Find related long-tail keywords", + "Understand search intent (informational, navigational, transactional)", + "Analyze keyword difficulty and competition", + "Look for questions people are asking", + "Monitor ranking positions over time" + ] + } + }, + { + "type": "header", + "data": { + "text": "2. Content Quality", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Search engines prioritize content that provides value:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Answer questions comprehensively", + "Provide unique insights and perspectives", + "Use data and statistics to support points", + "Include expert quotes or research", + "Update content regularly", + "Aim for comprehensive coverage of topics" + ] + } + }, + { + "type": "header", + "data": { + "text": "3. Content Types", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Diversify your content to reach different audiences:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "How-to guides and tutorials", + "Industry news and updates", + "Case studies and success stories", + "Infographics and visual content", + "Videos and podcasts", + "FAQ pages addressing common questions" + ] + } + }, + { + "type": "header", + "data": { + "text": "User Experience and SEO", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "User signals influence rankings significantly:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Reduce bounce rate with engaging content", + "Increase time on page with quality content", + "Improve click-through rate with compelling titles", + "Ensure easy navigation and clear structure", + "Make content scannable with formatting", + "Optimize for Core Web Vitals" + ] + } + }, + { + "type": "header", + "data": { + "text": "Monitoring and Analytics", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "You can't improve what you don't measure:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Set up Google Search Console", + "Install Google Analytics 4", + "Track keyword rankings", + "Monitor organic traffic trends", + "Analyze user behavior metrics", + "Identify and fix crawl errors" + ] + } + }, + { + "type": "header", + "data": { + "text": "Common SEO Mistakes to Avoid", + "level": 2 + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Keyword stuffing and over-optimization", + "Duplicate content issues", + "Ignoring mobile optimization", + "Neglecting technical SEO", + "Buying links or link schemes", + "Not updating content regularly", + "Ignoring user experience signals" + ] + } + }, + { + "type": "header", + "data": { + "text": "Conclusion", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "SEO for WordPress requires attention to both technical elements and quality content. By focusing on site speed, mobile optimization, proper markup, and valuable content, you can improve your search rankings and drive more organic traffic." + } + }, + { + "type": "paragraph", + "data": { + "text": "Remember that SEO is a long-term strategy. Results don't happen overnight, but consistent effort in following these best practices will pay off over time. Stay updated with algorithm changes, monitor your performance, and continue improving your site." + } + } + ] + }, + "author": "Plugin Compass Team", + "status": "published", + "featured_image": "", + "meta_title": "WordPress SEO Best Practices 2026 | Plugin Compass", + "meta_description": "Complete WordPress SEO guide for 2026. Learn on-page optimization, technical SEO, content strategies, and how to improve search rankings.", + "category": "wordpress", + "tags": ["wordpress", "seo", "search engine optimization", "rankings", "traffic"], + "published_at": "2026-02-20T10:00:00Z", + "updated_at": "2026-02-20T10:00:00Z" +} diff --git a/chat/blogs/seo/wordpress-theme-selection-guide.json b/chat/blogs/seo/wordpress-theme-selection-guide.json new file mode 100644 index 0000000..c51a2d6 --- /dev/null +++ b/chat/blogs/seo/wordpress-theme-selection-guide.json @@ -0,0 +1,467 @@ +{ + "id": "wordpress-theme-selection-guide", + "slug": "wordpress-theme-selection-guide", + "type": "seo", + "title": "How to Choose the Right WordPress Theme: A Complete Guide", + "excerpt": "Learn how to select the perfect WordPress theme for your website. Understand the key factors to consider including performance, customization, support, and long-term viability.", + "content": { + "blocks": [ + { + "type": "paragraph", + "data": { + "text": "Your WordPress theme forms the foundation of your website's design and functionality. Choosing the right theme is one of the most important decisions you'll make, as it affects everything from user experience to search engine rankings. With thousands of themes available, making the right choice can feel overwhelming. This guide walks you through the key factors to consider when selecting a WordPress theme." + } + }, + { + "type": "header", + "data": { + "text": "Why Theme Selection Matters", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Your theme impacts far more than just aesthetics:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Performance: Poorly coded themes slow down your site", + "Security: Outdated themes can have vulnerabilities", + "Functionality: Some themes limit what you can do", + "Mobile experience: Not all themes work well on mobile", + "SEO: Theme code affects search rankings", + "Maintenance: Difficult themes create ongoing problems", + "Scalability: Some themes can't grow with your business" + ] + } + }, + { + "type": "header", + "data": { + "text": "Key Factors to Consider", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "1. Performance", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Site speed is crucial for user experience and SEO. When evaluating theme performance:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Look for lightweight themes (page size under 100KB is ideal)", + "Check if the theme uses unnecessary libraries", + "Consider themes with modular features (load only what you need)", + "Test demo sites with PageSpeed Insights", + "Avoid themes with excessive animations and effects", + "Check if the theme includes built-in caching" + ] + } + }, + { + "type": "quote", + "data": { + "text": "A fast theme is the foundation of a fast website. No amount of optimization can fix a poorly coded theme.", + "caption": "WordPress Performance Expert" + } + }, + { + "type": "header", + "data": { + "text": "2. Responsiveness and Mobile-First", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "With mobile-first indexing, your theme must work perfectly on all devices:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Verify the theme is fully responsive", + "Test on multiple devices and screen sizes", + "Check that mobile navigation works well", + "Ensure tap targets are appropriately sized", + "Look for themes that prioritize mobile performance", + "Verify Core Web Vitals pass on mobile" + ] + } + }, + { + "type": "header", + "data": { + "text": "3. Customization Options", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Consider how much you'll need to customize the theme:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Does the theme include a customizer for easy changes?", + "Are there built-in layout options?", + "Can you change colors and fonts easily?", + "Does it support the page builder you want to use?", + "Are there header and footer builder options?", + "Does it include pre-built templates or require coding?" + ] + } + }, + { + "type": "header", + "data": { + "text": "4. Plugin Compatibility", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Your theme should work well with essential plugins:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Test with popular SEO plugins", + "Verify compatibility with caching plugins", + "Check that form plugins display correctly", + "Ensure e-commerce themes work with WooCommerce", + "Look for themes that don't conflict with security plugins", + "Test with any specific plugins you need" + ] + } + }, + { + "type": "header", + "data": { + "text": "5. Support and Documentation", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Quality support can save you hours of frustration:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Check support response times and quality", + "Look for comprehensive documentation", + "Check if there's a knowledge base or tutorials", + "Look for active community forums", + "Consider premium themes with dedicated support", + "Check developer track record and reputation" + ] + } + }, + { + "type": "header", + "data": { + "text": "6. Update Frequency", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "Themes that aren't regularly updated can become a security risk:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Check when the theme was last updated", + "Look for themes updated within the last 3-6 months", + "Check changelog for update history", + "Verify WordPress version compatibility", + "Check PHP version requirements", + "Consider themes with active development" + ] + } + }, + { + "type": "header", + "data": { + "text": "Types of WordPress Themes", + "level": 2 + } + }, + { + "type": "header", + "data": { + "text": "Multi-Purpose vs. Niche Themes", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Multi-purpose themes offer flexibility but may include unnecessary features:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Multi-purpose: Work for any type of site, more features, potentially larger", + "Niche: Designed for specific industries, leaner, tailored features", + "Consider your current and future needs" + ] + } + }, + { + "type": "header", + "data": { + "text": "Page Builder Compatibility", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Themes work differently with page builders:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Full-site editing themes (Gutenberg-native)", + "Themes with built-in page builders", + "Themes designed for Elementor, Beaver Builder, etc.", + "Neutral themes that work with any builder" + ] + } + }, + { + "type": "header", + "data": { + "text": "Free vs. Premium Themes", + "level": 3 + } + }, + { + "type": "paragraph", + "data": { + "text": "Both free and premium options have their place:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Free themes: Good for simple sites, budget constraints", + "Premium themes: More features, better support, regular updates", + "Consider total cost including necessary add-ons" + ] + } + }, + { + "type": "header", + "data": { + "text": "Where to Find Quality Themes", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Source themes from reputable marketplaces:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "WordPress.org Theme Directory (reviewed and tested)", + "ThemeForest (popular marketplace)", + "Elegant Themes (Divi)", + "GeneratePress (lightweight option)", + "Astra (popular multi-purpose)", + "StudioPress (Genesis framework)" + ] + } + }, + { + "type": "header", + "data": { + "text": "Red Flags to Avoid", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Watch out for these warning signs:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "No updates in over a year", + "Poor ratings or negative reviews", + "Limited or no support", + "Excessive bundled plugins", + "Confusing or cluttered admin interface", + "Not responsive on mobile", + "Poor documentation" + ] + } + }, + { + "type": "header", + "data": { + "text": "Making Your Final Decision", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Follow this decision process:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "List your requirements (must-haves and nice-to-haves)", + "Set a budget (including premium plugins you might need)", + "Research and shortlist 3-5 themes", + "Test demos thoroughly on multiple devices", + "Check reviews and support quality", + "Verify update history and compatibility", + "Test with essential plugins before committing" + ] + } + }, + { + "type": "header", + "data": { + "text": "Theme Trial and Testing", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Before committing to a theme, test it thoroughly:" + } + }, + { + "type": "list", + "data": { + "style": "ordered", + "items": [ + "Install on a staging site", + "Build out key page templates", + "Test with real content", + "Check page load times", + "Verify plugin compatibility", + "Test on multiple devices", + "Evaluate the customizer options" + ] + } + }, + { + "type": "header", + "data": { + "text": "Long-Term Considerations", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Think about the future when choosing a theme:" + } + }, + { + "type": "list", + "data": { + "style": "unordered", + "items": [ + "Will the theme support your growth?", + "Is the developer likely to continue updates?", + "Can you migrate to another theme later if needed?", + "Does the theme follow WordPress best practices?", + "Is there a roadmap for future development?", + "Will it work with future WordPress versions?" + ] + } + }, + { + "type": "header", + "data": { + "text": "Conclusion", + "level": 2 + } + }, + { + "type": "paragraph", + "data": { + "text": "Choosing the right WordPress theme is a critical decision that affects your entire online presence. Take your time, do your research, and don't rush the decision. Consider your current needs as well as future growth, and prioritize performance and quality over flashy features." + } + }, + { + "type": "paragraph", + "data": { + "text": "Remember that you can always change your theme later, but it's much easier to choose well from the start. Use the criteria in this guide to evaluate your options, and you'll be well on your way to finding the perfect theme for your WordPress site." + } + } + ] + }, + "author": "Plugin Compass Team", + "status": "published", + "featured_image": "", + "meta_title": "How to Choose the Right WordPress Theme | Plugin Compass", + "meta_description": "Complete guide to choosing the right WordPress theme. Learn about performance, customization, support, and how to avoid common mistakes.", + "category": "wordpress", + "tags": ["wordpress", "theme", "design", "website", "selection"], + "published_at": "2026-02-20T10:00:00Z", + "updated_at": "2026-02-20T10:00:00Z" +} diff --git a/chat/package-lock.json b/chat/package-lock.json index 6159371..e17ef16 100644 --- a/chat/package-lock.json +++ b/chat/package-lock.json @@ -15,7 +15,6 @@ "better-sqlite3": "^11.8.1", "jsonwebtoken": "^9.0.2", "multer": "^2.0.2", - "nodemailer": "^7.0.7", "pdfkit": "^0.17.2", "sharp": "^0.33.5" } @@ -1263,15 +1262,6 @@ "node-gyp-build-test": "build-test.js" } }, - "node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", - "license": "MIT-0", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/chat/public/admin-accounts.html b/chat/public/admin-accounts.html index f058ab6..d90dac5 100644 --- a/chat/public/admin-accounts.html +++ b/chat/public/admin-accounts.html @@ -33,6 +33,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -94,4 +95,4 @@ - \ No newline at end of file + diff --git a/chat/public/admin-affiliates.html b/chat/public/admin-affiliates.html index 9209830..d0feff0 100644 --- a/chat/public/admin-affiliates.html +++ b/chat/public/admin-affiliates.html @@ -33,6 +33,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -94,3 +95,4 @@ + diff --git a/chat/public/admin-blogs.html b/chat/public/admin-blogs.html index 1f94f59..27715b3 100644 --- a/chat/public/admin-blogs.html +++ b/chat/public/admin-blogs.html @@ -33,6 +33,89 @@ .ce-toolbar__content { max-width: 100% !important; } + + /* EditorJS Toolbar & Popup Fixes */ + .ce-toolbar__plus, + .ce-toolbar__actions { + background: var(--bg-1, #fff) !important; + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + } + .ce-inline-toolbar { + background: var(--bg-1, #fff) !important; + border: 1px solid var(--border, #e5e7eb) !important; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; + } + .ce-inline-tool, + .ce-inline-toolbar__dropdown, + .ce-toolbar__plus { + background: transparent !important; + } + .ce-inline-tool:hover, + .ce-inline-toolbar__dropdown:hover { + background: rgba(0,0,0,0.05) !important; + } + .ce-inline-tool--active { + background: var(--accent, #006B3D) !important; + color: #fff !important; + } + .ce-popover { + background: var(--bg-1, #fff) !important; + border: 1px solid var(--border, #e5e7eb) !important; + border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.15) !important; + } + .ce-popover__item { + background: transparent !important; + } + .ce-popover__item:hover { + background: rgba(0,0,0,0.05) !important; + } + .ce-popover__item--active, + .ce-popover__item--focused { + background: rgba(0, 107, 61, 0.1) !important; + } + .ce-conversion-toolbar { + background: var(--bg-1, #fff) !important; + border: 1px solid var(--border, #e5e7eb) !important; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; + } + .ce-conversion-tool { + background: transparent !important; + } + .ce-conversion-tool:hover { + background: rgba(0,0,0,0.05) !important; + } + .ce-settings { + background: var(--bg-1, #fff) !important; + border: 1px solid var(--border, #e5e7eb) !important; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important; + } + .ce-settings__button { + background: transparent !important; + } + .ce-settings__button:hover { + background: rgba(0,0,0,0.05) !important; + } + .cdx-settings-button { + background: transparent !important; + } + .cdx-settings-button:hover { + background: rgba(0,0,0,0.05) !important; + } + .ce-toolbox__button { + background: transparent !important; + } + .ce-toolbox__button:hover { + background: rgba(0,0,0,0.05) !important; + } + .ce-toolbox__button--active { + background: var(--accent, #006B3D) !important; + color: #fff !important; + } .blog-list { display: grid; gap: 12px; @@ -245,6 +328,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -427,3 +511,4 @@ + diff --git a/chat/public/admin-contact-messages.html b/chat/public/admin-contact-messages.html index 435cfbd..079f38f 100644 --- a/chat/public/admin-contact-messages.html +++ b/chat/public/admin-contact-messages.html @@ -138,6 +138,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -307,3 +308,4 @@ + diff --git a/chat/public/admin-external-testing.html b/chat/public/admin-external-testing.html index 2c2d6f8..e630a2a 100644 --- a/chat/public/admin-external-testing.html +++ b/chat/public/admin-external-testing.html @@ -31,6 +31,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -80,3 +81,4 @@ + diff --git a/chat/public/admin-feature-requests.html b/chat/public/admin-feature-requests.html index 1913d2e..f7c80ac 100644 --- a/chat/public/admin-feature-requests.html +++ b/chat/public/admin-feature-requests.html @@ -226,6 +226,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -535,3 +536,4 @@ + diff --git a/chat/public/admin-plan.html b/chat/public/admin-plan.html index b9ef621..49bd744 100644 --- a/chat/public/admin-plan.html +++ b/chat/public/admin-plan.html @@ -31,6 +31,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -46,7 +47,7 @@
Admin
Planning Control
-
Fallback-ready planning across OpenRouter, Mistral, Google, Groq, and NVIDIA.
+
Fallback-ready planning across OpenRouter, Mistral, Google, Groq, NVIDIA, and DeepInfra.
@@ -82,6 +83,7 @@ + @@ -145,3 +147,4 @@ + diff --git a/chat/public/admin-plans.html b/chat/public/admin-plans.html index e3b5090..c2795f5 100644 --- a/chat/public/admin-plans.html +++ b/chat/public/admin-plans.html @@ -31,6 +31,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -97,4 +98,4 @@
- \ No newline at end of file + diff --git a/chat/public/admin-resources.html b/chat/public/admin-resources.html index 23b6c1e..67f99cc 100644 --- a/chat/public/admin-resources.html +++ b/chat/public/admin-resources.html @@ -224,9 +224,10 @@ Withdrawals Tracking Resources - External Testing - Contact Messages - Feature Requests + System Tests + External Testing + Contact Messages + Feature Requests Login @@ -962,3 +963,4 @@ + diff --git a/chat/public/admin-system-tests.html b/chat/public/admin-system-tests.html new file mode 100644 index 0000000..c3a67b2 --- /dev/null +++ b/chat/public/admin-system-tests.html @@ -0,0 +1,95 @@ + + + + + + Admin Panel - System Tests + + + + + + + +
+ +
+
+
+ +
+
Admin
+
System Tests
+
Run end-to-end checks for database, accounts, and payments.
+
+
+ + +
+
+ +
+
+

Full system self-test

+
Diagnostics
+
+

+ Runs database/encryption checks, creates a temporary test account, and verifies payment configuration. + If Dodo is configured, it will create a test checkout session (no charge is completed). +

+
+ +
+
+
+
+ +
+
+

WordPress Validator MCP Test

+
MCP Server
+
+

+ Tests the WordPress Validator MCP server end-to-end: creates a minimal test plugin, + runs the validator MCP tool, and verifies the response format. +

+
+ +
+
+
+
+
+
+
+ + + diff --git a/chat/public/admin-tracking.html b/chat/public/admin-tracking.html index b7ead99..0e3aac8 100644 --- a/chat/public/admin-tracking.html +++ b/chat/public/admin-tracking.html @@ -184,6 +184,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -990,3 +991,4 @@ + diff --git a/chat/public/admin-withdrawals.html b/chat/public/admin-withdrawals.html index 1c087c1..a16a177 100644 --- a/chat/public/admin-withdrawals.html +++ b/chat/public/admin-withdrawals.html @@ -33,6 +33,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -93,3 +94,4 @@ + diff --git a/chat/public/admin.html b/chat/public/admin.html index c4ff33c..52141aa 100644 --- a/chat/public/admin.html +++ b/chat/public/admin.html @@ -43,6 +43,7 @@ Withdrawals Tracking Resources + System Tests External Testing Contact Messages Feature Requests @@ -120,10 +121,6 @@

These models are displayed to users in the builder dropdown for selection. This is separate from the OpenCode fallback chain.

-