Plugin packaging and docs
This commit is contained in:
@@ -1,218 +0,0 @@
|
||||
# Revert and Fix Summary
|
||||
|
||||
**Date:** January 9, 2026
|
||||
**Status:** ✅ COMPLETED
|
||||
|
||||
---
|
||||
|
||||
## What Was Done
|
||||
|
||||
### 1. ✅ Reverted WordPress Plugin Changes
|
||||
|
||||
**Removed all unplanned WordPress plugin files:**
|
||||
- Deleted entire `/plugins/wordpress/source/igny8-wp-bridge/` directory (20 files, ~4,600 lines)
|
||||
- Deleted `/scripts/build-wp-plugin.sh` build script
|
||||
|
||||
**Result:** WordPress plugin directory completely removed from git staging area.
|
||||
|
||||
### 2. ✅ Fixed Plugin Download Issue
|
||||
|
||||
**Problem:** Frontend was trying to download plugin from GitHub instead of using the backend API.
|
||||
|
||||
**Fix Applied:**
|
||||
```typescript
|
||||
// File: frontend/src/components/sites/WordPressIntegrationForm.tsx
|
||||
// Line: ~173
|
||||
|
||||
// BEFORE:
|
||||
const handleDownloadPlugin = () => {
|
||||
const pluginUrl = `https://github.com/igny8/igny8-wp-bridge/releases/latest/download/igny8-wp-bridge.zip`;
|
||||
window.open(pluginUrl, '_blank');
|
||||
toast.success('Plugin download started');
|
||||
};
|
||||
|
||||
// AFTER:
|
||||
const handleDownloadPlugin = () => {
|
||||
// Use the backend API endpoint for plugin download
|
||||
const pluginUrl = `/api/plugins/igny8-wp-bridge/download/`;
|
||||
window.open(pluginUrl, '_blank');
|
||||
toast.success('Plugin download started');
|
||||
};
|
||||
```
|
||||
|
||||
**Result:** Download button now points to correct API endpoint: `/api/plugins/igny8-wp-bridge/download/`
|
||||
|
||||
### 3. ⚠️ Plugin File Missing
|
||||
|
||||
**Issue Discovered:** The plugin ZIP file doesn't exist on the server.
|
||||
|
||||
**Evidence:**
|
||||
```bash
|
||||
# API endpoint works but returns 404:
|
||||
curl http://localhost:8011/api/plugins/wordpress/latest/
|
||||
# Returns: download_url: "http://localhost:8011/api/plugins/igny8-wp-bridge/download/"
|
||||
|
||||
# But file doesn't exist:
|
||||
docker exec igny8_backend python manage.py shell -c "..."
|
||||
# Output: File exists: False
|
||||
```
|
||||
|
||||
**Database shows:**
|
||||
- Plugin: "IGNY8 WordPress Bridge" (version 1.1.1)
|
||||
- File path: `igny8-wp-bridge-v1.1.1.zip`
|
||||
- File size: 98706 bytes
|
||||
- **BUT:** Physical file doesn't exist at `/app/igny8-wp-bridge-v1.1.1.zip`
|
||||
|
||||
---
|
||||
|
||||
## What Needs to Be Done
|
||||
|
||||
### Step 1: Upload Plugin ZIP File
|
||||
|
||||
The plugin file needs to be manually uploaded to the backend.
|
||||
|
||||
**Option A: Upload via Django Admin** (Recommended)
|
||||
1. Go to Django Admin: https://api.igny8.com/admin/
|
||||
2. Navigate to: Plugins → Plugin Versions
|
||||
3. Find version 1.1.1
|
||||
4. Click edit
|
||||
5. Upload the ZIP file in the "File" field
|
||||
6. Save
|
||||
|
||||
**Option B: Upload via Command Line**
|
||||
```bash
|
||||
# If you have the plugin ZIP file locally, copy it to the container:
|
||||
docker cp /path/to/igny8-wp-bridge-v1.1.1.zip igny8_backend:/app/igny8-wp-bridge-v1.1.1.zip
|
||||
|
||||
# Then update the database if needed
|
||||
```
|
||||
|
||||
### Step 2: Verify Plugin Path Configuration
|
||||
|
||||
Check that the Plugin Distribution System is configured to store files in the right location.
|
||||
|
||||
**Check settings.py for MEDIA_ROOT:**
|
||||
```python
|
||||
# backend/igny8_core/settings.py
|
||||
# Should have something like:
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = '/media/'
|
||||
```
|
||||
|
||||
**Currently:** MEDIA_ROOT is not configured in settings.py, which is why the file path is relative (`igny8-wp-bridge-v1.1.1.zip`) instead of absolute (`/app/media/plugins/igny8-wp-bridge-v1.1.1.zip`).
|
||||
|
||||
### Step 3: Fix Media Configuration (Recommended)
|
||||
|
||||
Add proper media configuration to settings.py:
|
||||
|
||||
```python
|
||||
# backend/igny8_core/settings.py
|
||||
# Add after STATIC configuration:
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
MEDIA_URL = '/media/'
|
||||
```
|
||||
|
||||
Then update the plugin file path in the database to use the media directory.
|
||||
|
||||
---
|
||||
|
||||
## Current Git Status
|
||||
|
||||
```
|
||||
Changes not staged for commit:
|
||||
modified: backend/igny8_core/settings.py
|
||||
modified: backend/igny8_core/urls.py
|
||||
modified: docs/plans/FINAL-PRELAUNCH-PENDING.md
|
||||
modified: docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md
|
||||
modified: frontend/src/components/sites/WordPressIntegrationForm.tsx
|
||||
|
||||
Untracked files:
|
||||
backend/igny8_core/plugins/
|
||||
```
|
||||
|
||||
**What changed:**
|
||||
1. ✅ `frontend/src/components/sites/WordPressIntegrationForm.tsx` - Fixed download URL
|
||||
2. ✅ `backend/igny8_core/plugins/` - Plugin Distribution System (new module)
|
||||
3. ✅ `backend/igny8_core/settings.py` - Added Plugin Management to admin navigation
|
||||
4. ✅ `backend/igny8_core/urls.py` - Added plugin distribution URLs
|
||||
5. ✅ Documentation updates
|
||||
|
||||
**What was reverted:**
|
||||
1. ✅ All WordPress plugin source files (20 files removed)
|
||||
2. ✅ Build script removed
|
||||
|
||||
---
|
||||
|
||||
## Testing After Fix
|
||||
|
||||
### Test 1: API Endpoint
|
||||
```bash
|
||||
curl http://localhost:8011/api/plugins/wordpress/latest/
|
||||
```
|
||||
**Expected:** Returns plugin info with download URL
|
||||
**Status:** ✅ WORKS
|
||||
|
||||
### Test 2: Download Endpoint (After uploading file)
|
||||
```bash
|
||||
curl -I http://localhost:8011/api/plugins/igny8-wp-bridge/download/
|
||||
```
|
||||
**Expected:** Returns 200 OK and starts download
|
||||
**Status:** ⚠️ Will work after file is uploaded
|
||||
|
||||
### Test 3: Frontend Download Button (After uploading file)
|
||||
1. Go to: https://app.igny8.com/sites/{id}/settings?tab=integrations
|
||||
2. Generate API key
|
||||
3. Click "Download Plugin" button
|
||||
**Expected:** Plugin downloads
|
||||
**Status:** ⚠️ Will work after file is uploaded
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### ✅ Completed
|
||||
- Reverted all unplanned WordPress plugin changes
|
||||
- Fixed frontend download button to use correct API endpoint
|
||||
- Backend plugin distribution system is working
|
||||
- Admin navigation includes plugin management
|
||||
|
||||
### ⚠️ Pending
|
||||
- Upload actual plugin ZIP file to backend
|
||||
- Optionally: Add MEDIA_ROOT configuration to settings.py
|
||||
|
||||
### ❌ Removed
|
||||
- 20 WordPress plugin template/sync files (~4,600 lines)
|
||||
- Build script for plugin packaging
|
||||
|
||||
---
|
||||
|
||||
## Quick Fix Steps
|
||||
|
||||
**To make plugin download work immediately:**
|
||||
|
||||
1. Get the plugin ZIP file (if you have it)
|
||||
2. Upload via Django Admin:
|
||||
- URL: https://api.igny8.com/admin/plugins/pluginversion/
|
||||
- Find version 1.1.1
|
||||
- Upload ZIP file
|
||||
- Save
|
||||
|
||||
**OR** if you need to create the plugin ZIP:
|
||||
1. The actual WordPress plugin source should be in a separate repository
|
||||
2. Create ZIP with proper structure
|
||||
3. Upload to Django admin
|
||||
|
||||
---
|
||||
|
||||
## Files Modified in This Session
|
||||
|
||||
1. `/data/app/igny8/frontend/src/components/sites/WordPressIntegrationForm.tsx`
|
||||
- Changed download URL from GitHub to `/api/plugins/igny8-wp-bridge/download/`
|
||||
|
||||
2. **DELETED**: `/data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/` (entire directory)
|
||||
3. **DELETED**: `/data/app/igny8/scripts/build-wp-plugin.sh`
|
||||
|
||||
---
|
||||
|
||||
**End of Summary**
|
||||
@@ -12,4 +12,5 @@ class PluginsConfig(AppConfig):
|
||||
|
||||
def ready(self):
|
||||
"""Import signal handlers when app is ready."""
|
||||
pass
|
||||
# Import signals to register handlers
|
||||
from . import signals # noqa: F401
|
||||
|
||||
80
backend/igny8_core/plugins/signals.py
Normal file
80
backend/igny8_core/plugins/signals.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Plugin Distribution System Signals
|
||||
|
||||
Handles automatic ZIP creation when plugin versions are released.
|
||||
"""
|
||||
import logging
|
||||
from django.db.models.signals import pre_save, post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import PluginVersion
|
||||
from .utils import create_plugin_zip
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=PluginVersion)
|
||||
def auto_build_plugin_on_release(sender, instance, **kwargs):
|
||||
"""
|
||||
Automatically build ZIP package when a version is marked as released or update_ready.
|
||||
|
||||
This ensures:
|
||||
1. ZIP file is always up-to-date with source code
|
||||
2. File size and checksum are auto-calculated
|
||||
3. No manual intervention needed for releases
|
||||
"""
|
||||
# Skip if this is a new instance (no pk yet)
|
||||
if not instance.pk:
|
||||
return
|
||||
|
||||
try:
|
||||
# Get the old instance to check for status change
|
||||
old_instance = PluginVersion.objects.get(pk=instance.pk)
|
||||
except PluginVersion.DoesNotExist:
|
||||
return
|
||||
|
||||
# Check if status is changing TO 'released' or 'update_ready'
|
||||
release_statuses = ['released', 'update_ready']
|
||||
old_status = old_instance.status
|
||||
new_status = instance.status
|
||||
|
||||
# Only trigger build if:
|
||||
# 1. Status is changing
|
||||
# 2. New status is a release status
|
||||
# 3. Old status was not a release status (avoid rebuilding on every save)
|
||||
if old_status == new_status:
|
||||
return
|
||||
|
||||
if new_status not in release_statuses:
|
||||
return
|
||||
|
||||
# If moving from one release status to another, don't rebuild
|
||||
if old_status in release_statuses:
|
||||
logger.info(f"Plugin {instance.plugin.slug} v{instance.version}: Status changing from {old_status} to {new_status}, no rebuild needed")
|
||||
return
|
||||
|
||||
logger.info(f"Building plugin ZIP for {instance.plugin.slug} v{instance.version} (status: {old_status} -> {new_status})")
|
||||
|
||||
# Build the ZIP
|
||||
file_path, checksum, file_size = create_plugin_zip(
|
||||
platform=instance.plugin.platform,
|
||||
plugin_slug=instance.plugin.slug,
|
||||
version=instance.version,
|
||||
update_version=True
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
logger.error(f"Failed to build ZIP for {instance.plugin.slug} v{instance.version}")
|
||||
return
|
||||
|
||||
# Update the instance with new file info
|
||||
instance.file_path = file_path
|
||||
instance.checksum = checksum
|
||||
instance.file_size = file_size
|
||||
|
||||
# Set released_at if moving to released status and not already set
|
||||
if new_status in release_statuses and not instance.released_at:
|
||||
instance.released_at = timezone.now()
|
||||
|
||||
logger.info(f"Built plugin ZIP: {file_path} ({file_size} bytes, checksum: {checksum[:16]}...)")
|
||||
37
docs/60-PLUGINS/INDEX.md
Normal file
37
docs/60-PLUGINS/INDEX.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Plugin Management Documentation
|
||||
|
||||
This section covers plugin distribution, management, and integration from the IGNY8 app perspective.
|
||||
|
||||
## Contents
|
||||
|
||||
1. [WORDPRESS-INTEGRATION.md](WORDPRESS-INTEGRATION.md) - Complete guide for WordPress integration management
|
||||
2. [PLUGIN-UPDATE-WORKFLOW.md](PLUGIN-UPDATE-WORKFLOW.md) - How plugin updates work and post-update checklist
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### API Endpoints (Production)
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/download/` | Download latest plugin ZIP |
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/` | Check for updates (called by WP plugin) |
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/info/` | Get plugin metadata |
|
||||
|
||||
### Key Directories
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `/data/app/igny8/plugins/wordpress/source/` | Plugin source code |
|
||||
| `/data/app/igny8/plugins/wordpress/dist/` | Distribution ZIP files |
|
||||
| `/data/app/igny8/backend/igny8_core/plugins/` | Django plugin management app |
|
||||
|
||||
### Database Models
|
||||
|
||||
- `Plugin` - Plugin registry (name, slug, platform)
|
||||
- `PluginVersion` - Version tracking with file info
|
||||
- `PluginInstallation` - Track installations per site
|
||||
- `PluginDownload` - Download analytics
|
||||
|
||||
---
|
||||
|
||||
**Note**: For internal plugin details (PHP functions, hooks, code structure), see the documentation inside the plugin directory at `/plugins/wordpress/source/igny8-wp-bridge/docs/`.
|
||||
424
docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md
Normal file
424
docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# Plugin Update Workflow
|
||||
|
||||
**Last Updated:** January 9, 2026
|
||||
**Status:** Production
|
||||
**Scope:** How to release plugin updates and what happens automatically vs manually
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Update Workflow Overview](#1-update-workflow-overview)
|
||||
2. [What Happens Automatically](#2-what-happens-automatically)
|
||||
3. [What Requires Manual Action](#3-what-requires-manual-action)
|
||||
4. [Step-by-Step: Releasing a New Version](#4-step-by-step-releasing-a-new-version)
|
||||
5. [Post-Update Verification Checklist](#5-post-update-verification-checklist)
|
||||
6. [Version Numbering](#6-version-numbering)
|
||||
7. [Rollback Procedure](#7-rollback-procedure)
|
||||
8. [Emergency Updates](#8-emergency-updates)
|
||||
|
||||
---
|
||||
|
||||
## 1. Update Workflow Overview
|
||||
|
||||
### The Big Picture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Plugin Update Lifecycle │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 1. DEVELOP │ Make changes to plugin source code │
|
||||
│ │ ↓ │ Location: /plugins/wordpress/source/igny8-wp-bridge/ │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 2. VERSION │ Update version in PHP header + constant │
|
||||
│ │ ↓ │ File: igny8-bridge.php │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 3. RELEASE │ Create PluginVersion in Django admin │
|
||||
│ │ ↓ │ Set status = "released" or "update_ready" │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 4. AUTO-BUILD│ ✅ AUTOMATIC: ZIP created, checksums calculated │
|
||||
│ │ ↓ │ Signal handler builds package on status change │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 5. VERIFY │ Test download, check contents, verify endpoints │
|
||||
│ │ ↓ │ See verification checklist below │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 6. AVAILABLE │ WordPress sites can now see and install update │
|
||||
│ │ │ Users update via WP admin or auto-update │
|
||||
│ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. What Happens Automatically
|
||||
|
||||
When you change a `PluginVersion` status to `released` or `update_ready`, the following happens **automatically** via Django signals:
|
||||
|
||||
### ✅ Automatic Actions
|
||||
|
||||
| Action | Details |
|
||||
|--------|---------|
|
||||
| **ZIP Creation** | Source files packaged into distribution ZIP |
|
||||
| **Version Update** | Version number updated in PHP files inside ZIP |
|
||||
| **File Cleanup** | Tests, .git, __pycache__, .bak files removed from ZIP |
|
||||
| **Checksum Calculation** | SHA256 hash generated |
|
||||
| **File Size Recording** | Byte count stored in database |
|
||||
| **File Path Update** | `file_path` field populated |
|
||||
| **Released Date** | `released_at` timestamp set |
|
||||
| **Symlink Update** | `*-latest.zip` symlink updated |
|
||||
|
||||
### How It Works (Signal Handler)
|
||||
|
||||
```python
|
||||
# backend/igny8_core/plugins/signals.py
|
||||
|
||||
@receiver(pre_save, sender=PluginVersion)
|
||||
def auto_build_plugin_on_release(sender, instance, **kwargs):
|
||||
"""
|
||||
Triggered when PluginVersion.status changes to:
|
||||
- 'released'
|
||||
- 'update_ready'
|
||||
|
||||
Actions:
|
||||
1. Calls create_plugin_zip() utility
|
||||
2. Updates instance.file_path
|
||||
3. Updates instance.file_size
|
||||
4. Updates instance.checksum
|
||||
5. Sets instance.released_at
|
||||
"""
|
||||
```
|
||||
|
||||
### What WordPress Sites See
|
||||
|
||||
Once released, the `check-update` API returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"update_available": true,
|
||||
"latest_version": "1.2.0",
|
||||
"download_url": "https://api.igny8.com/api/plugins/igny8-wp-bridge/download/",
|
||||
"changelog": "Your changelog here"
|
||||
}
|
||||
```
|
||||
|
||||
WordPress will show "Update Available" in:
|
||||
- Plugins page
|
||||
- Dashboard → Updates
|
||||
- Admin bar (if enabled)
|
||||
|
||||
---
|
||||
|
||||
## 3. What Requires Manual Action
|
||||
|
||||
### ❌ Manual Steps Required
|
||||
|
||||
| Action | When | How |
|
||||
|--------|------|-----|
|
||||
| **Update Source Version** | Before release | Edit `igny8-bridge.php` header |
|
||||
| **Create PluginVersion Record** | Each release | Django admin or management command |
|
||||
| **Write Changelog** | Each release | Enter in PluginVersion.changelog |
|
||||
| **Verify After Release** | After status change | Run verification checklist |
|
||||
| **Mark Old Version Deprecated** | After successful release | Change old version status |
|
||||
|
||||
### Source Version Update Locations
|
||||
|
||||
When releasing a new version, update these in the source:
|
||||
|
||||
**File:** `/plugins/wordpress/source/igny8-wp-bridge/igny8-bridge.php`
|
||||
|
||||
```php
|
||||
/**
|
||||
* Plugin Name: IGNY8 WordPress Bridge
|
||||
* Version: 1.2.0 ← UPDATE THIS
|
||||
*/
|
||||
|
||||
define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← AND THIS
|
||||
```
|
||||
|
||||
**Note:** The ZIP build process also updates these, but it's good practice to keep source in sync.
|
||||
|
||||
---
|
||||
|
||||
## 4. Step-by-Step: Releasing a New Version
|
||||
|
||||
### Step 1: Make Code Changes
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/
|
||||
|
||||
# Make your changes to PHP files
|
||||
# Test locally if possible
|
||||
```
|
||||
|
||||
### Step 2: Update Version Number
|
||||
|
||||
Edit `igny8-bridge.php`:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Plugin Name: IGNY8 WordPress Bridge
|
||||
* Version: 1.2.0 ← New version
|
||||
*/
|
||||
|
||||
define('IGNY8_BRIDGE_VERSION', '1.2.0'); ← Match here
|
||||
```
|
||||
|
||||
### Step 3: Create PluginVersion in Django Admin
|
||||
|
||||
1. Go to: `https://api.igny8.com/backend/`
|
||||
2. Navigate to: **Plugin Distribution** → **Plugin Versions**
|
||||
3. Click **Add Plugin Version**
|
||||
4. Fill in:
|
||||
- **Plugin:** IGNY8 WordPress Bridge
|
||||
- **Version:** 1.2.0
|
||||
- **Status:** draft (initially)
|
||||
- **Changelog:** Describe changes
|
||||
- **Min PHP Version:** 7.4 (or higher if needed)
|
||||
5. Click **Save**
|
||||
|
||||
### Step 4: Release the Version
|
||||
|
||||
1. Edit the PluginVersion you just created
|
||||
2. Change **Status** to `released`
|
||||
3. Click **Save**
|
||||
|
||||
**What happens:** Signal triggers auto-build → ZIP created → database updated
|
||||
|
||||
### Step 5: Verify Release
|
||||
|
||||
Run the [verification checklist](#5-post-update-verification-checklist) below.
|
||||
|
||||
### Step 6: Deprecate Old Version (Optional)
|
||||
|
||||
1. Find the previous version in Django admin
|
||||
2. Change **Status** to `deprecated`
|
||||
3. Save
|
||||
|
||||
---
|
||||
|
||||
## 5. Post-Update Verification Checklist
|
||||
|
||||
### Immediate Checks (Do Every Release)
|
||||
|
||||
```bash
|
||||
# 1. Verify ZIP exists
|
||||
ls -lh /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip
|
||||
|
||||
# 2. Verify symlink updated
|
||||
ls -la /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-latest.zip
|
||||
|
||||
# 3. Test download endpoint
|
||||
curl -s -o /dev/null -w "%{http_code} - %{size_download} bytes\n" \
|
||||
https://api.igny8.com/api/plugins/igny8-wp-bridge/download/
|
||||
|
||||
# 4. Test check-update endpoint
|
||||
curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0" | python3 -m json.tool
|
||||
|
||||
# 5. Test info endpoint
|
||||
curl -s https://api.igny8.com/api/plugins/igny8-wp-bridge/info/ | python3 -m json.tool
|
||||
|
||||
# 6. Verify version in ZIP
|
||||
unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip \
|
||||
igny8-wp-bridge/igny8-bridge.php | grep -E "Version:|IGNY8_BRIDGE_VERSION"
|
||||
|
||||
# 7. Verify API URL in ZIP (must be api.igny8.com)
|
||||
unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.2.0.zip \
|
||||
igny8-wp-bridge/igny8-bridge.php | grep "api.igny8.com"
|
||||
```
|
||||
|
||||
### Database Verification
|
||||
|
||||
```bash
|
||||
# Check database has correct values
|
||||
docker exec igny8_backend python manage.py shell -c "
|
||||
from igny8_core.plugins.models import Plugin, PluginVersion
|
||||
p = Plugin.objects.get(slug='igny8-wp-bridge')
|
||||
v = p.get_latest_version()
|
||||
print(f'Version: {v.version}')
|
||||
print(f'Status: {v.status}')
|
||||
print(f'File path: {v.file_path}')
|
||||
print(f'File size: {v.file_size}')
|
||||
print(f'Checksum: {v.checksum[:32]}...')
|
||||
print(f'Released at: {v.released_at}')
|
||||
"
|
||||
```
|
||||
|
||||
### Expected Results
|
||||
|
||||
| Check | Expected |
|
||||
|-------|----------|
|
||||
| Download returns | 200 status, ~150-200KB |
|
||||
| check-update shows | `"latest_version": "1.2.0"` |
|
||||
| info shows | `"version": "1.2.0"` |
|
||||
| ZIP version header | `Version: 1.2.0` |
|
||||
| API URL | `api.igny8.com` (NOT app.igny8.com) |
|
||||
|
||||
### WordPress Verification (If Possible)
|
||||
|
||||
1. Go to a test WordPress site with plugin installed
|
||||
2. Navigate to **Dashboard** → **Updates** → **Check Again**
|
||||
3. Verify "IGNY8 WordPress Bridge" shows update available
|
||||
4. Click "View version details" → verify changelog appears
|
||||
5. Click "Update Now" → verify update completes successfully
|
||||
|
||||
---
|
||||
|
||||
## 6. Version Numbering
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
Format: `MAJOR.MINOR.PATCH` (e.g., 1.2.3)
|
||||
|
||||
| Part | When to Increment |
|
||||
|------|-------------------|
|
||||
| **MAJOR** | Breaking changes, incompatible API changes |
|
||||
| **MINOR** | New features, backwards compatible |
|
||||
| **PATCH** | Bug fixes, minor improvements |
|
||||
|
||||
### Version Code Calculation
|
||||
|
||||
Used for numeric comparison in database:
|
||||
|
||||
```
|
||||
1.0.0 → 10000
|
||||
1.0.1 → 10001
|
||||
1.2.0 → 10200
|
||||
1.2.3 → 10203
|
||||
2.0.0 → 20000
|
||||
```
|
||||
|
||||
Formula: `(MAJOR * 10000) + (MINOR * 100) + PATCH`
|
||||
|
||||
---
|
||||
|
||||
## 7. Rollback Procedure
|
||||
|
||||
### If Update Causes Issues
|
||||
|
||||
**Option 1: Quick Rollback via Database**
|
||||
|
||||
```bash
|
||||
# Make old version the "latest" by changing status
|
||||
docker exec igny8_backend python manage.py shell -c "
|
||||
from igny8_core.plugins.models import PluginVersion
|
||||
|
||||
# Demote new version
|
||||
new = PluginVersion.objects.get(plugin__slug='igny8-wp-bridge', version='1.2.0')
|
||||
new.status = 'deprecated'
|
||||
new.save()
|
||||
|
||||
# Promote old version back
|
||||
old = PluginVersion.objects.get(plugin__slug='igny8-wp-bridge', version='1.1.1')
|
||||
old.status = 'released'
|
||||
old.save()
|
||||
|
||||
print('Rollback complete')
|
||||
"
|
||||
```
|
||||
|
||||
**Option 2: Keep Old ZIP Files**
|
||||
|
||||
Old ZIP files are preserved in `dist/`. To serve an old version:
|
||||
|
||||
```bash
|
||||
# Update symlink to point to old version
|
||||
cd /data/app/igny8/plugins/wordpress/dist/
|
||||
ln -sf igny8-wp-bridge-v1.1.1.zip igny8-wp-bridge-latest.zip
|
||||
```
|
||||
|
||||
### Retention Policy
|
||||
|
||||
Keep at least 3 previous version ZIPs for emergency rollback:
|
||||
- Current release
|
||||
- Previous release
|
||||
- One before that
|
||||
|
||||
---
|
||||
|
||||
## 8. Emergency Updates
|
||||
|
||||
### For Critical Security Fixes
|
||||
|
||||
1. **Set `force_update = True`** on the new PluginVersion
|
||||
2. This flag signals WordPress sites that update is mandatory
|
||||
3. Sites with auto-updates enabled will update immediately
|
||||
|
||||
### Emergency Release Checklist
|
||||
|
||||
```bash
|
||||
# 1. Make the fix in source
|
||||
cd /data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/
|
||||
# ... make changes ...
|
||||
|
||||
# 2. Update version (use PATCH increment)
|
||||
# Edit igny8-bridge.php
|
||||
|
||||
# 3. Create version in Django admin with:
|
||||
# - Status: update_ready
|
||||
# - Force Update: ✓ (checked)
|
||||
# - Changelog: "SECURITY: [description]"
|
||||
|
||||
# 4. Verify immediately
|
||||
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0
|
||||
|
||||
# Response should include:
|
||||
# "force_update": true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
### Release New Version
|
||||
|
||||
```
|
||||
1. Edit source code
|
||||
2. Update version in igny8-bridge.php (header + constant)
|
||||
3. Create PluginVersion in Django admin (status: draft)
|
||||
4. Change status to "released" → Auto-build triggers
|
||||
5. Run verification checklist
|
||||
6. Deprecate old version
|
||||
```
|
||||
|
||||
### Verification Commands
|
||||
|
||||
```bash
|
||||
# Test all endpoints
|
||||
curl -I https://api.igny8.com/api/plugins/igny8-wp-bridge/download/
|
||||
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0
|
||||
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/info/
|
||||
|
||||
# Check ZIP contents
|
||||
unzip -l /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v*.zip | head -20
|
||||
```
|
||||
|
||||
### Emergency Rollback
|
||||
|
||||
```bash
|
||||
# Swap versions in database
|
||||
docker exec igny8_backend python manage.py shell -c "
|
||||
from igny8_core.plugins.models import PluginVersion
|
||||
PluginVersion.objects.filter(version='NEW').update(status='deprecated')
|
||||
PluginVersion.objects.filter(version='OLD').update(status='released')
|
||||
"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [WORDPRESS-INTEGRATION.md](WORDPRESS-INTEGRATION.md) - Full integration guide
|
||||
- [docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md](/docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md) - Original system design
|
||||
598
docs/60-PLUGINS/WORDPRESS-INTEGRATION.md
Normal file
598
docs/60-PLUGINS/WORDPRESS-INTEGRATION.md
Normal file
@@ -0,0 +1,598 @@
|
||||
# WordPress Integration Management
|
||||
|
||||
**Last Updated:** January 9, 2026
|
||||
**Status:** Production
|
||||
**Scope:** App-side management of WordPress plugin distribution and site integrations
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#1-overview)
|
||||
2. [Architecture](#2-architecture)
|
||||
3. [Plugin Distribution System](#3-plugin-distribution-system)
|
||||
4. [Database Models](#4-database-models)
|
||||
5. [API Endpoints](#5-api-endpoints)
|
||||
6. [Django Admin Management](#6-django-admin-management)
|
||||
7. [Frontend Integration](#7-frontend-integration)
|
||||
8. [Site Integration Flow](#8-site-integration-flow)
|
||||
9. [Troubleshooting](#9-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### What This Document Covers
|
||||
|
||||
This document covers the **app-side** management of WordPress integration:
|
||||
|
||||
- How the IGNY8 app distributes the WordPress plugin
|
||||
- How site integrations are tracked in the database
|
||||
- API endpoints that WordPress plugins call
|
||||
- Admin interface for managing plugins and versions
|
||||
- Frontend components for plugin download
|
||||
|
||||
### What This Document Does NOT Cover
|
||||
|
||||
- Internal plugin PHP code and functions (see `/plugins/wordpress/source/igny8-wp-bridge/docs/`)
|
||||
- WordPress-side setup and configuration
|
||||
- Plugin installation on WordPress sites
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture
|
||||
|
||||
### System Components
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ IGNY8 App Server │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Django Backend │ │ PostgreSQL │ │ Plugin Files │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ - API Views │◄──►│ - Plugin │ │ /plugins/ │ │
|
||||
│ │ - Admin │ │ - PluginVersion │ │ └─wordpress/ │ │
|
||||
│ │ - Signals │ │ - Installation │ │ ├─source/ │ │
|
||||
│ │ │ │ - Downloads │ │ └─dist/ │ │
|
||||
│ └────────┬─────────┘ └──────────────────┘ └────────┬─────────┘ │
|
||||
│ │ │ │
|
||||
│ │ API Routes: api.igny8.com │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ API Endpoints │ │
|
||||
│ │ │ │
|
||||
│ │ /api/plugins/{slug}/download/ → Serve ZIP file │ │
|
||||
│ │ /api/plugins/{slug}/check-update/ → Return update info │ │
|
||||
│ │ /api/plugins/{slug}/info/ → Return plugin metadata │ │
|
||||
│ │ /api/plugins/{slug}/register/ → Register installation │ │
|
||||
│ │ /api/plugins/{slug}/health-check/ → Report plugin status │ │
|
||||
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTPS
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ WordPress Sites │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Site A │ │ Site B │ │ Site C │ │ Site N │ │
|
||||
│ │ v1.1.1 │ │ v1.1.0 │ │ v1.1.1 │ │ v1.0.0 │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### URL Routing
|
||||
|
||||
| Domain | Service | Purpose |
|
||||
|--------|---------|---------|
|
||||
| `app.igny8.com` | Frontend (Vite) | User interface |
|
||||
| `api.igny8.com` | Backend (Django) | API endpoints |
|
||||
|
||||
**Important**: WordPress plugins must call `api.igny8.com`, not `app.igny8.com`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Plugin Distribution System
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
/data/app/igny8/
|
||||
├── plugins/
|
||||
│ └── wordpress/
|
||||
│ ├── source/ # Development source
|
||||
│ │ └── igny8-wp-bridge/
|
||||
│ │ ├── igny8-bridge.php # Main plugin file
|
||||
│ │ ├── includes/ # PHP classes
|
||||
│ │ ├── admin/ # Admin interface
|
||||
│ │ ├── sync/ # Sync functionality
|
||||
│ │ ├── templates/ # Frontend templates
|
||||
│ │ └── docs/ # Plugin-internal docs
|
||||
│ └── dist/ # Distribution files
|
||||
│ ├── igny8-wp-bridge-v1.1.1.zip # Version ZIP
|
||||
│ ├── igny8-wp-bridge-v1.1.1.sha256
|
||||
│ └── igny8-wp-bridge-latest.zip # Symlink to latest
|
||||
│
|
||||
└── backend/
|
||||
└── igny8_core/
|
||||
└── plugins/ # Django app
|
||||
├── models.py # Database models
|
||||
├── views.py # API endpoints
|
||||
├── signals.py # Auto-build triggers
|
||||
├── utils.py # ZIP creation utilities
|
||||
├── admin.py # Django admin config
|
||||
└── urls.py # URL routing
|
||||
```
|
||||
|
||||
### How ZIP Files Are Created
|
||||
|
||||
#### Automatic Build (Recommended)
|
||||
|
||||
When a `PluginVersion` status changes to `released` or `update_ready`, the system **automatically**:
|
||||
|
||||
1. Reads source files from `/plugins/wordpress/source/igny8-wp-bridge/`
|
||||
2. Updates version numbers in PHP files
|
||||
3. Removes development files (tests, .git, __pycache__)
|
||||
4. Creates ZIP in `/plugins/wordpress/dist/`
|
||||
5. Calculates SHA256 checksum
|
||||
6. Updates database with `file_path`, `file_size`, `checksum`
|
||||
7. Updates `*-latest.zip` symlink
|
||||
|
||||
This is handled by `signals.py`:
|
||||
|
||||
```python
|
||||
# backend/igny8_core/plugins/signals.py
|
||||
|
||||
@receiver(pre_save, sender=PluginVersion)
|
||||
def auto_build_plugin_on_release(sender, instance, **kwargs):
|
||||
"""Automatically build ZIP when version is released."""
|
||||
# Triggered on status change to 'released' or 'update_ready'
|
||||
```
|
||||
|
||||
#### Manual Build (If Needed)
|
||||
|
||||
```bash
|
||||
# From project root
|
||||
cd /data/app/igny8/plugins/wordpress/source
|
||||
|
||||
# Create ZIP manually
|
||||
zip -r ../dist/igny8-wp-bridge-v1.2.0.zip igny8-wp-bridge/ \
|
||||
-x "*.git*" -x "*__pycache__*" -x "*.DS_Store" -x "*tester*"
|
||||
|
||||
# Update checksum
|
||||
sha256sum ../dist/igny8-wp-bridge-v1.2.0.zip > ../dist/igny8-wp-bridge-v1.2.0.sha256
|
||||
|
||||
# Update symlink
|
||||
ln -sf igny8-wp-bridge-v1.2.0.zip ../dist/igny8-wp-bridge-latest.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Database Models
|
||||
|
||||
### Plugin
|
||||
|
||||
Represents a plugin type (WordPress, Shopify, etc.).
|
||||
|
||||
```python
|
||||
class Plugin(models.Model):
|
||||
name = models.CharField(max_length=100) # "IGNY8 WordPress Bridge"
|
||||
slug = models.SlugField(unique=True) # "igny8-wp-bridge"
|
||||
platform = models.CharField(...) # "wordpress", "shopify", "custom"
|
||||
description = models.TextField()
|
||||
homepage_url = models.URLField()
|
||||
is_active = models.BooleanField(default=True)
|
||||
```
|
||||
|
||||
**Current Records:**
|
||||
|
||||
| ID | Name | Slug | Platform |
|
||||
|----|------|------|----------|
|
||||
| 1 | IGNY8 WordPress Bridge | igny8-wp-bridge | wordpress |
|
||||
|
||||
### PluginVersion
|
||||
|
||||
Tracks each version of a plugin.
|
||||
|
||||
```python
|
||||
class PluginVersion(models.Model):
|
||||
plugin = models.ForeignKey(Plugin, ...)
|
||||
version = models.CharField(max_length=20) # "1.1.1"
|
||||
version_code = models.IntegerField() # 10101 (for comparison)
|
||||
status = models.CharField(...) # "draft", "released", "update_ready"
|
||||
|
||||
# File info (auto-populated on release)
|
||||
file_path = models.CharField(...) # "igny8-wp-bridge-v1.1.1.zip"
|
||||
file_size = models.IntegerField() # 167589 (bytes)
|
||||
checksum = models.CharField(max_length=64) # SHA256
|
||||
|
||||
# Release info
|
||||
changelog = models.TextField()
|
||||
min_php_version = models.CharField(default='7.4')
|
||||
released_at = models.DateTimeField(null=True)
|
||||
force_update = models.BooleanField(default=False)
|
||||
```
|
||||
|
||||
**Version Status Flow:**
|
||||
|
||||
```
|
||||
draft → testing → staged → released → update_ready → deprecated
|
||||
│ │
|
||||
│ └─ Notifies WP sites
|
||||
└─ Available for download
|
||||
```
|
||||
|
||||
### PluginInstallation
|
||||
|
||||
Tracks where plugins are installed.
|
||||
|
||||
```python
|
||||
class PluginInstallation(models.Model):
|
||||
site = models.ForeignKey('Site', ...)
|
||||
plugin = models.ForeignKey(Plugin, ...)
|
||||
current_version = models.ForeignKey(PluginVersion, ...)
|
||||
is_active = models.BooleanField(default=True)
|
||||
last_health_check = models.DateTimeField(null=True)
|
||||
health_status = models.CharField(...) # "healthy", "outdated", "error"
|
||||
```
|
||||
|
||||
### PluginDownload
|
||||
|
||||
Tracks download analytics.
|
||||
|
||||
```python
|
||||
class PluginDownload(models.Model):
|
||||
plugin = models.ForeignKey(Plugin, ...)
|
||||
version = models.ForeignKey(PluginVersion, ...)
|
||||
ip_address = models.GenericIPAddressField()
|
||||
user_agent = models.CharField(max_length=500)
|
||||
download_type = models.CharField(...) # "manual", "auto_update"
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API Endpoints
|
||||
|
||||
### Public Endpoints (No Auth Required)
|
||||
|
||||
#### Download Plugin
|
||||
|
||||
```
|
||||
GET /api/plugins/{slug}/download/
|
||||
```
|
||||
|
||||
Returns the latest released ZIP file.
|
||||
|
||||
**Response:** Binary ZIP file download
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
curl -O https://api.igny8.com/api/plugins/igny8-wp-bridge/download/
|
||||
```
|
||||
|
||||
#### Check for Updates
|
||||
|
||||
```
|
||||
GET /api/plugins/{slug}/check-update/?current_version=1.0.0
|
||||
```
|
||||
|
||||
Called by installed WordPress plugins to check for updates.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"update_available": true,
|
||||
"current_version": "1.0.0",
|
||||
"latest_version": "1.1.1",
|
||||
"latest_version_code": 10101,
|
||||
"download_url": "https://api.igny8.com/api/plugins/igny8-wp-bridge/download/",
|
||||
"changelog": "Bug fixes and improvements",
|
||||
"info_url": "https://api.igny8.com/api/plugins/igny8-wp-bridge/info/",
|
||||
"force_update": false,
|
||||
"checksum": "6b9e231c07434df1dcfe81596b57f3571c30b6c2..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Plugin Info
|
||||
|
||||
```
|
||||
GET /api/plugins/{slug}/info/
|
||||
```
|
||||
|
||||
Returns plugin metadata for WordPress update modal.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"name": "IGNY8 WordPress Bridge",
|
||||
"slug": "igny8-wp-bridge",
|
||||
"version": "1.1.1",
|
||||
"author": "IGNY8",
|
||||
"homepage": "https://igny8.com",
|
||||
"description": "Lightweight bridge plugin...",
|
||||
"changelog": "Bug fixes and improvements",
|
||||
"download_url": "https://api.igny8.com/api/plugins/igny8-wp-bridge/download/",
|
||||
"file_size": 167589,
|
||||
"requires_php": "7.4",
|
||||
"tested_wp": "6.7"
|
||||
}
|
||||
```
|
||||
|
||||
### Authenticated Endpoints
|
||||
|
||||
#### Register Installation
|
||||
|
||||
```
|
||||
POST /api/plugins/{slug}/register/
|
||||
Headers: Authorization: Api-Key {site_api_key}
|
||||
```
|
||||
|
||||
Called when plugin is activated on a WordPress site.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"site_id": 123,
|
||||
"version": "1.1.1",
|
||||
"wp_version": "6.7",
|
||||
"php_version": "8.2"
|
||||
}
|
||||
```
|
||||
|
||||
#### Health Check
|
||||
|
||||
```
|
||||
POST /api/plugins/{slug}/health-check/
|
||||
Headers: Authorization: Api-Key {site_api_key}
|
||||
```
|
||||
|
||||
Periodic status report from installed plugins.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"site_id": 123,
|
||||
"version": "1.1.1",
|
||||
"status": "active",
|
||||
"last_sync": "2026-01-09T12:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Django Admin Management
|
||||
|
||||
### Accessing Plugin Admin
|
||||
|
||||
1. Go to: `https://api.igny8.com/backend/`
|
||||
2. Login with admin credentials
|
||||
3. Navigate to **Plugin Distribution** section
|
||||
|
||||
### Admin Sections
|
||||
|
||||
#### Plugins
|
||||
|
||||
View and manage plugin registry.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Name | Display name |
|
||||
| Slug | URL identifier (cannot change after creation) |
|
||||
| Platform | wordpress, shopify, custom |
|
||||
| Is Active | Whether available for download |
|
||||
|
||||
#### Plugin Versions
|
||||
|
||||
Manage versions and trigger releases.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Plugin | Parent plugin |
|
||||
| Version | Semantic version (e.g., 1.1.1) |
|
||||
| Status | draft, testing, staged, released, update_ready, deprecated |
|
||||
| File Path | Auto-set on release |
|
||||
| File Size | Auto-calculated on release |
|
||||
| Checksum | Auto-calculated on release |
|
||||
| Changelog | What's new (shown to users) |
|
||||
| Force Update | Set true for critical security fixes |
|
||||
|
||||
**To Release a New Version:**
|
||||
|
||||
1. Create new PluginVersion record with status `draft`
|
||||
2. Enter version number, changelog
|
||||
3. Change status to `released` or `update_ready`
|
||||
4. Save → ZIP is automatically built
|
||||
|
||||
#### Plugin Installations
|
||||
|
||||
View where plugins are installed.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Site | IGNY8 site record |
|
||||
| Plugin | Which plugin |
|
||||
| Current Version | Installed version |
|
||||
| Health Status | healthy, outdated, error |
|
||||
| Last Health Check | Timestamp |
|
||||
|
||||
#### Plugin Downloads
|
||||
|
||||
View download analytics.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| Plugin | Which plugin |
|
||||
| Version | Which version |
|
||||
| Download Type | manual, auto_update |
|
||||
| IP Address | Client IP |
|
||||
| User Agent | Browser/client info |
|
||||
| Created At | Timestamp |
|
||||
|
||||
---
|
||||
|
||||
## 7. Frontend Integration
|
||||
|
||||
### Download Button Location
|
||||
|
||||
**Page:** Site Settings → Integrations → WordPress
|
||||
|
||||
**Component:** `frontend/src/components/sites/WordPressIntegrationForm.tsx`
|
||||
|
||||
### How Download Works
|
||||
|
||||
```typescript
|
||||
// WordPressIntegrationForm.tsx
|
||||
|
||||
import { API_BASE_URL } from '../../services/api';
|
||||
|
||||
const handleDownloadPlugin = () => {
|
||||
// Uses API_BASE_URL which resolves to https://api.igny8.com/api
|
||||
const pluginUrl = `${API_BASE_URL}/plugins/igny8-wp-bridge/download/`;
|
||||
window.open(pluginUrl, '_blank');
|
||||
toast.success('Plugin download started');
|
||||
};
|
||||
```
|
||||
|
||||
### Plugin Information Display
|
||||
|
||||
The frontend shows:
|
||||
|
||||
- Plugin name and version
|
||||
- File size
|
||||
- PHP requirements
|
||||
- Download button
|
||||
- Changelog (expandable)
|
||||
|
||||
---
|
||||
|
||||
## 8. Site Integration Flow
|
||||
|
||||
### Integration Creation Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ WordPress Site Setup │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. User downloads plugin from IGNY8 app │
|
||||
│ └─ GET /api/plugins/igny8-wp-bridge/download/ │
|
||||
│ │
|
||||
│ 2. User installs and activates plugin in WordPress │
|
||||
│ │
|
||||
│ 3. User enters Site ID and API Key in WP plugin settings │
|
||||
│ └─ These are generated in IGNY8 app │
|
||||
│ │
|
||||
│ 4. WP Plugin calls IGNY8 to register │
|
||||
│ └─ POST /api/plugins/igny8-wp-bridge/register/ │
|
||||
│ │
|
||||
│ 5. IGNY8 creates SiteIntegration record │
|
||||
│ └─ Links WordPress site to IGNY8 site │
|
||||
│ │
|
||||
│ 6. Integration is now active │
|
||||
│ └─ Content can be published from IGNY8 to WordPress │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Integration Data Model
|
||||
|
||||
```python
|
||||
# SiteIntegration model (in auth/models.py)
|
||||
|
||||
class SiteIntegration(models.Model):
|
||||
site = models.ForeignKey(Site, ...) # IGNY8 site
|
||||
platform = models.CharField(...) # "wordpress"
|
||||
external_site_url = models.URLField() # WordPress URL
|
||||
api_key = models.CharField(...) # Encrypted key
|
||||
sync_enabled = models.BooleanField()
|
||||
last_sync = models.DateTimeField()
|
||||
connection_status = models.CharField(...) # "connected", "error"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "404 Not Found" on Download
|
||||
|
||||
**Cause:** Using wrong domain (app.igny8.com instead of api.igny8.com)
|
||||
|
||||
**Solution:** Ensure URL is `https://api.igny8.com/api/plugins/igny8-wp-bridge/download/`
|
||||
|
||||
#### "Plugin file not found" Error
|
||||
|
||||
**Cause:** ZIP file doesn't exist in dist/ directory
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check if file exists
|
||||
ls -la /data/app/igny8/plugins/wordpress/dist/
|
||||
|
||||
# If missing, rebuild manually or change version status to trigger auto-build
|
||||
```
|
||||
|
||||
#### Updates Not Showing in WordPress
|
||||
|
||||
**Cause 1:** WordPress caches plugin update checks
|
||||
|
||||
**Solution:** In WordPress admin, go to Dashboard → Updates → Check Again
|
||||
|
||||
**Cause 2:** Version status is not `released` or `update_ready`
|
||||
|
||||
**Solution:** Check PluginVersion status in Django admin
|
||||
|
||||
#### Wrong File Size/Checksum
|
||||
|
||||
**Cause:** Database values don't match actual file
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Get actual values
|
||||
stat -c %s /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.1.1.zip
|
||||
sha256sum /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.1.1.zip
|
||||
|
||||
# Update in Django admin or shell
|
||||
```
|
||||
|
||||
### Verification Commands
|
||||
|
||||
```bash
|
||||
# Test download endpoint
|
||||
curl -I https://api.igny8.com/api/plugins/igny8-wp-bridge/download/
|
||||
|
||||
# Test check-update endpoint
|
||||
curl "https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0"
|
||||
|
||||
# Test info endpoint
|
||||
curl https://api.igny8.com/api/plugins/igny8-wp-bridge/info/
|
||||
|
||||
# Check ZIP contents
|
||||
unzip -l /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.1.1.zip
|
||||
|
||||
# Verify API URL in plugin
|
||||
unzip -p /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-v1.1.1.zip \
|
||||
igny8-wp-bridge/igny8-bridge.php | grep "api.igny8.com"
|
||||
```
|
||||
|
||||
### Log Locations
|
||||
|
||||
| Log | Location |
|
||||
|-----|----------|
|
||||
| Django logs | `/data/app/logs/django.log` |
|
||||
| Plugin build logs | Check Django log for `"Created plugin ZIP"` messages |
|
||||
| Download tracking | `PluginDownload` model in database |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [PLUGIN-UPDATE-WORKFLOW.md](PLUGIN-UPDATE-WORKFLOW.md) - Post-update checklist
|
||||
- `/plugins/wordpress/source/igny8-wp-bridge/docs/` - Plugin internal documentation
|
||||
- [docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md](/docs/plans/PLUGIN-DISTRIBUTION-SYSTEM.md) - Original implementation plan
|
||||
@@ -19,6 +19,8 @@
|
||||
| **Read design guide** | [30-FRONTEND/DESIGN-GUIDE.md](30-FRONTEND/DESIGN-GUIDE.md) |
|
||||
| Understand frontend structure | [30-FRONTEND/PAGES.md](30-FRONTEND/PAGES.md) |
|
||||
| Trace a workflow end-to-end | [40-WORKFLOWS/](#workflows) |
|
||||
| **Manage WordPress plugin** | [60-PLUGINS/WORDPRESS-INTEGRATION.md](60-PLUGINS/WORDPRESS-INTEGRATION.md) |
|
||||
| **Release plugin update** | [60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md](60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md) |
|
||||
| Look up model fields | [90-REFERENCE/MODELS.md](90-REFERENCE/MODELS.md) |
|
||||
| **Payment system (Stripe/PayPal/Bank)** | [90-REFERENCE/PAYMENT-SYSTEM.md](90-REFERENCE/PAYMENT-SYSTEM.md) |
|
||||
| See prelaunch checklist | [plans/FINAL-PRELAUNCH.md](plans/FINAL-PRELAUNCH.md) |
|
||||
@@ -133,6 +135,22 @@
|
||||
|
||||
---
|
||||
|
||||
## 60-PLUGINS - Plugin Distribution & Management
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [INDEX.md](60-PLUGINS/INDEX.md) | Plugin management overview |
|
||||
| [WORDPRESS-INTEGRATION.md](60-PLUGINS/WORDPRESS-INTEGRATION.md) | Complete WordPress integration guide (app-side) |
|
||||
| [PLUGIN-UPDATE-WORKFLOW.md](60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md) | How to release updates, verification checklist |
|
||||
|
||||
**Key Points:**
|
||||
- Plugin source: `/plugins/wordpress/source/igny8-wp-bridge/`
|
||||
- Distribution: `/plugins/wordpress/dist/`
|
||||
- Django app: `/backend/igny8_core/plugins/`
|
||||
- API domain: `api.igny8.com` (not app.igny8.com)
|
||||
|
||||
---
|
||||
|
||||
## 90-REFERENCE - Quick Lookup
|
||||
|
||||
| Document | Purpose |
|
||||
|
||||
@@ -12,7 +12,7 @@ import Checkbox from '../form/input/Checkbox';
|
||||
import Switch from '../form/switch/Switch';
|
||||
import { useToast } from '../ui/toast/ToastContainer';
|
||||
import { integrationApi, SiteIntegration } from '../../services/integration.api';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { fetchAPI, API_BASE_URL } from '../../services/api';
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
AlertIcon,
|
||||
@@ -171,8 +171,8 @@ export default function WordPressIntegrationForm({
|
||||
};
|
||||
|
||||
const handleDownloadPlugin = () => {
|
||||
// Use the backend API endpoint for plugin download
|
||||
const pluginUrl = `/api/plugins/igny8-wp-bridge/download/`;
|
||||
// Use the backend API endpoint for plugin download (must use full API URL, not relative)
|
||||
const pluginUrl = `${API_BASE_URL}/plugins/igny8-wp-bridge/download/`;
|
||||
window.open(pluginUrl, '_blank');
|
||||
toast.success('Plugin download started');
|
||||
};
|
||||
|
||||
Binary file not shown.
@@ -1 +0,0 @@
|
||||
asdasd
|
||||
@@ -85,6 +85,7 @@ class Igny8Bridge {
|
||||
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'includes/class-igny8-api.php';
|
||||
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'includes/class-igny8-site.php';
|
||||
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'includes/class-igny8-rest-api.php';
|
||||
require_once IGNY8_BRIDGE_PLUGIN_DIR . 'includes/class-igny8-updater.php';
|
||||
// Webhooks removed - using API key authentication only
|
||||
|
||||
// Webhook logs (used in admin and frontend)
|
||||
@@ -135,6 +136,15 @@ class Igny8Bridge {
|
||||
if (is_admin()) {
|
||||
Igny8Admin::get_instance();
|
||||
}
|
||||
|
||||
// Initialize auto-updater
|
||||
if (class_exists('Igny8_Updater')) {
|
||||
new Igny8_Updater(
|
||||
IGNY8_BRIDGE_PLUGIN_FILE,
|
||||
IGNY8_BRIDGE_VERSION,
|
||||
'https://api.igny8.com/api/plugins/'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Updater
|
||||
*
|
||||
* Handles automatic updates from IGNY8 API
|
||||
*
|
||||
* @package Igny8Bridge
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class Igny8_Updater {
|
||||
|
||||
/**
|
||||
* Plugin slug
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_slug = 'igny8-wp-bridge';
|
||||
|
||||
/**
|
||||
* Plugin file
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $plugin_file;
|
||||
|
||||
/**
|
||||
* Current version
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* API URL
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $api_url;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $plugin_file Plugin file path
|
||||
* @param string $version Current version
|
||||
* @param string $api_url API URL
|
||||
*/
|
||||
public function __construct($plugin_file, $version, $api_url) {
|
||||
$this->plugin_file = $plugin_file;
|
||||
$this->version = $version;
|
||||
$this->api_url = trailingslashit($api_url);
|
||||
|
||||
// Hook into WordPress update system
|
||||
add_filter('pre_set_site_transient_update_plugins', array($this, 'check_for_updates'));
|
||||
add_filter('plugins_api', array($this, 'plugin_info'), 10, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for plugin updates
|
||||
*
|
||||
* @param object $transient Update transient
|
||||
* @return object Modified transient
|
||||
*/
|
||||
public function check_for_updates($transient) {
|
||||
if (empty($transient->checked)) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
// Get update info from IGNY8 API
|
||||
$update_info = $this->get_update_info();
|
||||
|
||||
if (!$update_info || !isset($update_info['update_available'])) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ($update_info['update_available'] === true) {
|
||||
$plugin_basename = plugin_basename($this->plugin_file);
|
||||
|
||||
$transient->response[$plugin_basename] = (object) array(
|
||||
'slug' => $this->plugin_slug,
|
||||
'new_version' => $update_info['latest_version'],
|
||||
'package' => $update_info['download_url'],
|
||||
'url' => $update_info['info_url'] ?? '',
|
||||
'tested' => $update_info['tested'] ?? '',
|
||||
'requires_php' => $update_info['requires_php'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide plugin information for update screen
|
||||
*
|
||||
* @param false|object|array $result The result object or array
|
||||
* @param string $action The type of information being requested
|
||||
* @param object $args Plugin API arguments
|
||||
* @return false|object Modified result
|
||||
*/
|
||||
public function plugin_info($result, $action, $args) {
|
||||
if ($action !== 'plugin_information') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!isset($args->slug) || $args->slug !== $this->plugin_slug) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Get plugin info from IGNY8 API
|
||||
$info = $this->get_plugin_info();
|
||||
|
||||
if (!$info) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'name' => $info['name'] ?? 'IGNY8 WordPress Bridge',
|
||||
'slug' => $this->plugin_slug,
|
||||
'version' => $info['version'] ?? $this->version,
|
||||
'author' => 'IGNY8',
|
||||
'author_profile' => 'https://igny8.com',
|
||||
'homepage' => 'https://igny8.com',
|
||||
'sections' => array(
|
||||
'description' => $info['description'] ?? '',
|
||||
'changelog' => $info['changelog'] ?? '',
|
||||
),
|
||||
'download_link' => $info['download_url'] ?? '',
|
||||
'tested' => $info['tested'] ?? '',
|
||||
'requires_php' => $info['requires_php'] ?? '7.4',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get update information from IGNY8 API
|
||||
*
|
||||
* @return array|false Update info or false on failure
|
||||
*/
|
||||
private function get_update_info() {
|
||||
$url = $this->api_url . $this->plugin_slug . '/check-update/';
|
||||
|
||||
$response = wp_remote_get($url, array(
|
||||
'timeout' => 10,
|
||||
'headers' => array(
|
||||
'X-IGNY8-Site-ID' => get_option('igny8_site_id'),
|
||||
'X-IGNY8-API-Key' => get_option('igny8_api_key'),
|
||||
),
|
||||
'body' => array(
|
||||
'current_version' => $this->version,
|
||||
),
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
if ($code !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$data = json_decode($body, true);
|
||||
|
||||
return $data ?: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin information from IGNY8 API
|
||||
*
|
||||
* @return array|false Plugin info or false on failure
|
||||
*/
|
||||
private function get_plugin_info() {
|
||||
$url = $this->api_url . $this->plugin_slug . '/info/';
|
||||
|
||||
$response = wp_remote_get($url, array(
|
||||
'timeout' => 10,
|
||||
));
|
||||
|
||||
if (is_wp_error($response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code($response);
|
||||
if ($code !== 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body($response);
|
||||
$data = json_decode($body, true);
|
||||
|
||||
return $data ?: false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user