reorg docs

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-10 02:42:31 +00:00
parent 346d3f0531
commit ce66dadc00
5 changed files with 196 additions and 220 deletions

View File

@@ -0,0 +1,692 @@
# IGNY8 Plugin Distribution System
**Created:** January 9, 2026
**Version:** 1.0
**Status:** ✅ Phase 1 Implemented
**Scope:** WordPress, Shopify, Custom Site Integration Plugins
---
## Overview
This document outlines the architecture for distributing and auto-updating IGNY8 integration plugins across multiple platforms (WordPress, Shopify, Custom Sites).
---
## 1. Directory Structure
### 1.1 - Server Location
```
/data/app/igny8/
├── backend/
│ └── igny8_core/
│ └── plugins/ # NEW: Plugin management
│ ├── __init__.py
│ ├── models.py # Plugin version tracking
│ ├── views.py # Download & update API
│ ├── serializers.py
│ ├── urls.py
│ └── utils.py # ZIP compression, versioning
├── plugins/ # NEW: Plugin source & distributions
│ ├── wordpress/
│ │ ├── source/ # Development source files
│ │ │ └── igny8-wp-bridge/
│ │ │ ├── igny8-wp-bridge.php
│ │ │ ├── includes/
│ │ │ ├── assets/
│ │ │ └── readme.txt
│ │ └── dist/ # Compiled/zipped releases
│ │ ├── igny8-wp-bridge-v1.0.0.zip
│ │ ├── igny8-wp-bridge-v1.0.1.zip
│ │ └── igny8-wp-bridge-latest.zip # Symlink to latest
│ ├── shopify/ # FUTURE
│ │ ├── source/
│ │ └── dist/
│ └── custom-site/ # FUTURE
│ ├── source/
│ └── dist/
└── docs/
└── plans/
└── PLUGIN-DISTRIBUTION-SYSTEM.md
```
### 1.2 - Why This Structure?
| Location | Purpose |
|----------|---------|
| `/plugins/` | Separate from app code, easy to manage |
| `/plugins/{platform}/source/` | Development files, version controlled |
| `/plugins/{platform}/dist/` | Ready-to-download ZIP files |
| `/backend/igny8_core/plugins/` | Django app for API & version management |
---
## 2. Database Models
### 2.1 - Plugin Model
```python
# backend/igny8_core/plugins/models.py
class Plugin(models.Model):
"""Represents a plugin type (WordPress, Shopify, etc.)"""
PLATFORM_CHOICES = [
('wordpress', 'WordPress'),
('shopify', 'Shopify'),
('custom', 'Custom Site'),
]
name = models.CharField(max_length=100) # "IGNY8 WP Bridge"
slug = models.SlugField(unique=True) # "igny8-wp-bridge"
platform = models.CharField(max_length=20, choices=PLATFORM_CHOICES)
description = models.TextField()
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'plugins'
class PluginVersion(models.Model):
"""Tracks each version of a plugin"""
STATUS_CHOICES = [
('draft', 'Draft'), # In development
('testing', 'Testing'), # Internal testing
('staged', 'Staged'), # Ready, not yet pushed
('released', 'Released'), # Available for download
('update_ready', 'Update Ready'), # Push to installed sites
('deprecated', 'Deprecated'), # Old version
]
plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE, related_name='versions')
version = models.CharField(max_length=20) # "1.0.0", "1.0.1"
version_code = models.IntegerField() # 100, 101 (for comparison)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
# File info
file_path = models.CharField(max_length=500) # Relative path in dist/
file_size = models.IntegerField(default=0) # Bytes
checksum = models.CharField(max_length=64) # SHA256 for integrity
# Release info
changelog = models.TextField(blank=True)
min_api_version = models.CharField(max_length=20, default='1.0')
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
released_at = models.DateTimeField(null=True, blank=True)
# Auto-update control
force_update = models.BooleanField(default=False) # Critical security fix
class Meta:
db_table = 'plugin_versions'
unique_together = ['plugin', 'version']
ordering = ['-version_code']
class PluginInstallation(models.Model):
"""Tracks where plugins are installed (per site)"""
site = models.ForeignKey('Site', on_delete=models.CASCADE, related_name='plugin_installations')
plugin = models.ForeignKey(Plugin, on_delete=models.CASCADE)
current_version = models.ForeignKey(PluginVersion, on_delete=models.SET_NULL, null=True)
# Installation status
is_active = models.BooleanField(default=True)
last_health_check = models.DateTimeField(null=True)
# Update tracking
pending_update = models.ForeignKey(
PluginVersion,
on_delete=models.SET_NULL,
null=True,
related_name='pending_installations'
)
update_notified_at = models.DateTimeField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'plugin_installations'
unique_together = ['site', 'plugin']
```
---
## 3. API Endpoints
### 3.1 - Public Endpoints (No Auth Required)
```python
# Download latest plugin (for initial installation)
GET /api/plugins/{slug}/download/
# Returns: ZIP file download
# Check for updates (called by installed plugins)
GET /api/plugins/{slug}/check-update/?current_version=1.0.0
# Returns: { "update_available": true, "latest_version": "1.0.1", "download_url": "...", "changelog": "..." }
```
### 3.2 - Authenticated Endpoints
```python
# Get download URL for user (tracks downloads)
GET /api/plugins/{slug}/download-url/
# Returns: { "url": "...", "expires_at": "..." }
# Report installation (called after WP plugin is activated)
POST /api/plugins/{slug}/register-installation/
# Body: { "site_id": 123, "version": "1.0.0" }
# Update installation status (health check)
POST /api/plugins/{slug}/health-check/
# Body: { "site_id": 123, "version": "1.0.0", "status": "active" }
```
### 3.3 - Admin Endpoints
```python
# List all plugin versions
GET /api/admin/plugins/
# Create new version
POST /api/admin/plugins/{slug}/versions/
# Body: { "version": "1.0.1", "changelog": "...", "status": "draft" }
# Upload plugin ZIP
POST /api/admin/plugins/{slug}/versions/{version}/upload/
# Body: multipart/form-data with ZIP file
# Release version (make available for download)
POST /api/admin/plugins/{slug}/versions/{version}/release/
# Push update to installed sites
POST /api/admin/plugins/{slug}/versions/{version}/push-update/
# This sets status to 'update_ready' and notifies all installations
```
---
## 4. WordPress Plugin Update Mechanism
### 4.1 - How WordPress Auto-Updates Work
WordPress checks for plugin updates by calling a filter. Our plugin hooks into this:
```php
// In igny8-wp-bridge.php
class IGNY8_WP_Bridge {
private $plugin_slug = 'igny8-wp-bridge';
private $version = '1.0.0';
private $api_url = 'https://app.igny8.com/api/plugins/';
public function __construct() {
// Check for updates
add_filter('pre_set_site_transient_update_plugins', [$this, 'check_for_updates']);
add_filter('plugins_api', [$this, 'plugin_info'], 10, 3);
}
public function check_for_updates($transient) {
if (empty($transient->checked)) {
return $transient;
}
// Call IGNY8 API to check for updates
$response = wp_remote_get($this->api_url . $this->plugin_slug . '/check-update/', [
'timeout' => 10,
'headers' => [
'X-IGNY8-Site-ID' => get_option('igny8_site_id'),
'X-IGNY8-API-Key' => get_option('igny8_api_key'),
],
'body' => [
'current_version' => $this->version,
]
]);
if (is_wp_error($response)) {
return $transient;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if (!empty($data['update_available'])) {
$plugin_file = $this->plugin_slug . '/' . $this->plugin_slug . '.php';
$transient->response[$plugin_file] = (object) [
'slug' => $this->plugin_slug,
'new_version' => $data['latest_version'],
'package' => $data['download_url'],
'url' => $data['info_url'] ?? '',
];
}
return $transient;
}
public function plugin_info($result, $action, $args) {
if ($action !== 'plugin_information' || $args->slug !== $this->plugin_slug) {
return $result;
}
// Fetch plugin info from IGNY8 API
$response = wp_remote_get($this->api_url . $this->plugin_slug . '/info/');
if (is_wp_error($response)) {
return $result;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
return (object) [
'name' => $data['name'],
'slug' => $this->plugin_slug,
'version' => $data['version'],
'author' => 'IGNY8',
'homepage' => 'https://igny8.com',
'sections' => [
'description' => $data['description'],
'changelog' => $data['changelog'],
],
'download_link' => $data['download_url'],
];
}
}
```
### 4.2 - Update Flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ Plugin Update Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Admin marks version as "Update Ready" in IGNY8 Dashboard │
│ │ │
│ ▼ │
│ 2. IGNY8 API returns update_available=true for check-update calls │
│ │ │
│ ▼ │
│ 3. WordPress cron checks for updates (or user clicks "Check Now") │
│ │ │
│ ▼ │
│ 4. WP Plugin calls IGNY8 API → Gets new version info │
│ │ │
│ ▼ │
│ 5. WordPress shows "Update Available" in Plugins page │
│ │ │
│ ▼ │
│ 6. User clicks Update (or auto-update if enabled) │
│ │ │
│ ▼ │
│ 7. WordPress downloads ZIP from IGNY8 and installs │
│ │ │
│ ▼ │
│ 8. Plugin activation hook reports new version to IGNY8 │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 5. Initial Download (ZIP Generation)
### 5.1 - Build Script
```bash
#!/bin/bash
# scripts/build-wp-plugin.sh
VERSION=$1
PLUGIN_NAME="igny8-wp-bridge"
SOURCE_DIR="/data/app/igny8/plugins/wordpress/source/${PLUGIN_NAME}"
DIST_DIR="/data/app/igny8/plugins/wordpress/dist"
if [ -z "$VERSION" ]; then
echo "Usage: ./build-wp-plugin.sh <version>"
exit 1
fi
# Create dist directory if not exists
mkdir -p "$DIST_DIR"
# Create temp directory for packaging
TEMP_DIR=$(mktemp -d)
PACKAGE_DIR="${TEMP_DIR}/${PLUGIN_NAME}"
# Copy source files
cp -r "$SOURCE_DIR" "$PACKAGE_DIR"
# Update version in main plugin file
sed -i "s/Version: .*/Version: ${VERSION}/" "${PACKAGE_DIR}/${PLUGIN_NAME}.php"
sed -i "s/\$version = '.*'/\$version = '${VERSION}'/" "${PACKAGE_DIR}/${PLUGIN_NAME}.php"
# Create ZIP
cd "$TEMP_DIR"
zip -r "${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.zip" "$PLUGIN_NAME"
# Update latest symlink
ln -sf "${PLUGIN_NAME}-v${VERSION}.zip" "${DIST_DIR}/${PLUGIN_NAME}-latest.zip"
# Calculate checksum
sha256sum "${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.zip" > "${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.sha256"
# Cleanup
rm -rf "$TEMP_DIR"
echo "Built: ${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.zip"
echo "Checksum: $(cat ${DIST_DIR}/${PLUGIN_NAME}-v${VERSION}.sha256)"
```
### 5.2 - Django Management Command
```python
# backend/igny8_core/plugins/management/commands/build_plugin.py
from django.core.management.base import BaseCommand
import subprocess
import hashlib
from plugins.models import Plugin, PluginVersion
class Command(BaseCommand):
help = 'Build and register a new plugin version'
def add_arguments(self, parser):
parser.add_argument('--plugin', type=str, required=True)
parser.add_argument('--version', type=str, required=True)
parser.add_argument('--changelog', type=str, default='')
parser.add_argument('--release', action='store_true')
def handle(self, *args, **options):
plugin = Plugin.objects.get(slug=options['plugin'])
version = options['version']
# Run build script
result = subprocess.run(
['./scripts/build-wp-plugin.sh', version],
capture_output=True,
text=True
)
if result.returncode != 0:
self.stderr.write(f"Build failed: {result.stderr}")
return
# Calculate version code (1.0.1 -> 101)
parts = version.split('.')
version_code = int(parts[0]) * 10000 + int(parts[1]) * 100 + int(parts[2])
# Get file info
file_path = f"{plugin.slug}-v{version}.zip"
full_path = f"/data/app/igny8/plugins/wordpress/dist/{file_path}"
with open(full_path, 'rb') as f:
checksum = hashlib.sha256(f.read()).hexdigest()
import os
file_size = os.path.getsize(full_path)
# Create version record
plugin_version, created = PluginVersion.objects.update_or_create(
plugin=plugin,
version=version,
defaults={
'version_code': version_code,
'file_path': file_path,
'file_size': file_size,
'checksum': checksum,
'changelog': options['changelog'],
'status': 'released' if options['release'] else 'draft',
}
)
action = 'Created' if created else 'Updated'
self.stdout.write(f"{action} version {version} for {plugin.name}")
```
---
## 6. Frontend Integration
### 6.1 - Download Button Component
```tsx
// frontend/src/components/sites/PluginDownloadSection.tsx
import { Button } from '@/components/ui';
import { useQuery } from '@tanstack/react-query';
import { api } from '@/services/api';
interface PluginInfo {
name: string;
slug: string;
version: string;
download_url: string;
file_size: number;
platform: string;
}
export function PluginDownloadSection({ platform = 'wordpress' }: { platform: string }) {
const { data: plugin, isLoading } = useQuery<PluginInfo>({
queryKey: ['plugin', platform],
queryFn: () => api.get(`/plugins/${platform}/latest/`).then(r => r.data),
});
const handleDownload = async () => {
if (!plugin) return;
// Track download
await api.post(`/plugins/${plugin.slug}/track-download/`);
// Trigger download
window.location.href = plugin.download_url;
};
if (isLoading) return <div>Loading...</div>;
if (!plugin) return null;
return (
<div className="bg-white rounded-xl border border-gray-200 p-6">
<div className="flex items-start gap-4">
<div className="p-3 bg-green-50 rounded-lg">
<DownloadIcon className="w-6 h-6 text-green-600" />
</div>
<div className="flex-1">
<h3 className="font-semibold text-gray-900">{plugin.name}</h3>
<p className="text-sm text-gray-500 mt-1">
Download and install the plugin on your {platform === 'wordpress' ? 'WordPress' : platform} site
</p>
<div className="flex items-center gap-4 mt-2 text-xs text-gray-400">
<span>Version {plugin.version}</span>
<span>{formatFileSize(plugin.file_size)}</span>
</div>
</div>
<Button onClick={handleDownload} variant="outline">
<DownloadIcon className="w-4 h-4 mr-2" />
Download Plugin
</Button>
</div>
</div>
);
}
```
### 6.2 - Admin Plugin Management Page (Future)
```
/admin/plugins # List all plugins
/admin/plugins/{slug} # Plugin details + versions
/admin/plugins/{slug}/versions # Version history
/admin/plugins/{slug}/upload # Upload new version
```
---
## 7. Security Considerations
### 7.1 - Download Security
| Measure | Implementation |
|---------|----------------|
| Signed URLs | Time-limited download URLs (15 min expiry) |
| Checksum | SHA256 verification in WP plugin |
| API Key Required | Downloads require valid site API key |
| Rate Limiting | Max 10 downloads/hour per site |
### 7.2 - Update Security
| Measure | Implementation |
|---------|----------------|
| HTTPS Only | All API calls over TLS |
| Version Signing | Checksum comparison before install |
| Rollback | Keep last 3 versions for emergency rollback |
| Staged Rollout | Option to release to % of installs first |
---
## 8. Implementation Phases
### Phase 1: Basic Infrastructure (Week 1) ✅ COMPLETED
- [x] Create `/plugins/` directory structure
- [x] Create Django `plugins` app with models
- [x] Migrate existing WP plugin to `/plugins/wordpress/source/`
- [x] Create build script for ZIP generation
- [x] Implement download API endpoint
### Phase 2: Frontend Integration (Week 1) ✅ COMPLETED
- [x] Update Site Settings > Integrations download button
- [x] Create plugin info API endpoint
- [x] Add version display to download section
### Phase 3: Update System (Week 2) ✅ COMPLETED
- [x] Implement check-update API
- [x] Add update hooks to WP plugin
- [x] Create PluginInstallation tracking
- [x] Build admin version management UI
### Phase 4: Advanced Features (Week 3)
- [ ] Signed download URLs
- [ ] Version rollback support
- [ ] Update notifications in IGNY8 dashboard
- [ ] Staged rollout percentage
### Phase 5: Additional Platforms (Future)
- [ ] Shopify plugin structure
- [ ] Custom site SDK/package
- [ ] Platform-specific update mechanisms
---
## 9. File Serving Configuration
### 9.1 - Nginx Configuration
```nginx
# Add to nginx config for igny8
# Serve plugin downloads
location /plugins/download/ {
alias /data/app/igny8/plugins/;
# Only allow ZIP files
location ~ \.zip$ {
add_header Content-Disposition 'attachment';
add_header X-Content-Type-Options 'nosniff';
}
# Deny access to source files
location ~ /source/ {
deny all;
}
}
```
### 9.2 - Django URL (Proxied Downloads)
```python
# Better approach: Django handles download with auth
# backend/igny8_core/plugins/views.py
from django.http import FileResponse
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
@api_view(['GET'])
@permission_classes([AllowAny])
def download_plugin(request, slug):
plugin = get_object_or_404(Plugin, slug=slug, is_active=True)
latest = plugin.versions.filter(status__in=['released', 'update_ready']).first()
if not latest:
return Response({'error': 'No version available'}, status=404)
file_path = f'/data/app/igny8/plugins/{plugin.platform}/dist/{latest.file_path}'
response = FileResponse(
open(file_path, 'rb'),
content_type='application/zip'
)
response['Content-Disposition'] = f'attachment; filename="{latest.file_path}"'
return response
```
---
## 10. Monitoring & Analytics
### 10.1 - Metrics to Track
| Metric | Purpose |
|--------|---------|
| Downloads per version | Adoption rate |
| Active installations | Current reach |
| Update success rate | Quality indicator |
| Version distribution | Upgrade urgency |
| Health check failures | Support needs |
### 10.2 - Admin Dashboard Widgets
- **Plugin Downloads** - Daily/weekly download counts
- **Version Distribution** - Pie chart of installed versions
- **Update Adoption** - Time to 80% adoption after release
- **Installation Health** - Sites with outdated/failing plugins
---
## Quick Reference
### Commands
```bash
# Build new version
python manage.py build_plugin --plugin=igny8-wp-bridge --version=1.0.1 --changelog="Bug fixes"
# Release version (make downloadable)
python manage.py release_plugin --plugin=igny8-wp-bridge --version=1.0.1
# Push update to sites
python manage.py push_plugin_update --plugin=igny8-wp-bridge --version=1.0.1
```
### API Quick Reference
```
GET /api/plugins/{slug}/download/ # Download latest ZIP
GET /api/plugins/{slug}/check-update/ # Check for updates
GET /api/plugins/{slug}/info/ # Plugin metadata
POST /api/plugins/{slug}/register/ # Register installation
POST /api/plugins/{slug}/health-check/ # Report status
```
---
**Document Owner:** IGNY8 Team
**Next Review:** After Phase 1 implementation

View File

@@ -0,0 +1,896 @@
# Implementation Plan: Phases 1, 5, and 6
**Created:** January 9, 2026
**Target:** Safe, verified execution of cleanup, UX improvements, and data backup
---
## Table of Contents
1. [Phase 1: Code Cleanup & Technical Debt](#phase-1-code-cleanup--technical-debt)
2. [Phase 5: UX Improvements](#phase-5-ux-improvements)
3. [Phase 6: Data Backup & Cleanup](#phase-6-data-backup--cleanup)
4. [Execution Checklist](#execution-checklist)
---
# Phase 1: Code Cleanup & Technical Debt
## 1.1 Pre-Cleanup Verification
### 1.1.1 Create Safety Branch
```bash
# BEFORE ANY CHANGES - Create a safety branch
cd /data/app/igny8
git checkout -b cleanup/phase-1-$(date +%Y%m%d)
git push origin cleanup/phase-1-$(date +%Y%m%d)
```
### 1.1.2 Run Full Test Suite (Baseline)
```bash
# Backend tests
cd /data/app/igny8/backend
python manage.py test --verbosity=2 > /tmp/test-baseline-backend.log 2>&1
echo "Exit code: $?"
# Frontend build check
cd /data/app/igny8/frontend
npm run build > /tmp/test-baseline-frontend.log 2>&1
echo "Exit code: $?"
# Frontend lint check
npm run lint > /tmp/test-baseline-lint.log 2>&1
```
**✅ CHECKPOINT:** All tests must pass before proceeding.
---
## 1.2 Frontend Cleanup - Empty/Unused Folders
### Files to Delete (Verified Empty/Unused)
| Path | Reason | Verified |
|------|--------|----------|
| `frontend/src/pages/Admin/` | Empty folder | ⬜ |
| `frontend/src/pages/admin/` | Empty folder (duplicate lowercase) | ⬜ |
| `frontend/src/pages/settings/` | Empty folder (lowercase duplicate) | ⬜ |
| `frontend/src/components/debug/` | Empty debug folder | ⬜ |
| `frontend/src/components/widgets/` | Empty folder | ⬜ |
| `frontend/src/components/metrics/` | Empty folder | ⬜ |
### Execution Steps:
```bash
# Step 1: Verify folders are empty
cd /data/app/igny8/frontend/src
# Check each folder before deletion
ls -la pages/Admin/ 2>/dev/null || echo "Admin/ doesn't exist or empty"
ls -la pages/admin/ 2>/dev/null || echo "admin/ doesn't exist or empty"
ls -la pages/settings/ 2>/dev/null || echo "settings/ doesn't exist or empty"
ls -la components/debug/ 2>/dev/null || echo "debug/ doesn't exist or empty"
ls -la components/widgets/ 2>/dev/null || echo "widgets/ doesn't exist or empty"
ls -la components/metrics/ 2>/dev/null || echo "metrics/ doesn't exist or empty"
# Step 2: Remove empty folders (only if confirmed empty)
rmdir pages/Admin 2>/dev/null
rmdir pages/admin 2>/dev/null
rmdir pages/settings 2>/dev/null
rmdir components/debug 2>/dev/null
rmdir components/widgets 2>/dev/null
rmdir components/metrics 2>/dev/null
```
**✅ CHECKPOINT:** Run `npm run build` - must succeed.
---
## 1.3 Frontend Cleanup - Template/Sample Components
### Components to Delete (Never Used - Template/Demo Code)
| Path | File Count | Reason |
|------|------------|--------|
| `frontend/src/components/ecommerce/` | 7 files | E-commerce template components - not used in app |
| `frontend/src/components/sample-componeents/` | 2 files | Sample HTML files with typo in folder name |
| `frontend/src/components/charts/bar/` | - | Unused bar chart template |
| `frontend/src/components/charts/line/` | - | Unused line chart template |
| `frontend/src/components/tables/BasicTables/` | - | Unused basic table template |
### Pre-Delete Verification:
```bash
# Search for any imports of these components
cd /data/app/igny8/frontend
# Check ecommerce imports
grep -r "ecommerce" src/ --include="*.ts" --include="*.tsx" | grep -v "node_modules"
# Check sample imports
grep -r "sample-componeents" src/ --include="*.ts" --include="*.tsx"
# Check charts/bar imports
grep -r "charts/bar" src/ --include="*.ts" --include="*.tsx"
# Check charts/line imports
grep -r "charts/line" src/ --include="*.ts" --include="*.tsx"
# Check BasicTables imports
grep -r "BasicTables" src/ --include="*.ts" --include="*.tsx"
```
### Execution (Only if no imports found):
```bash
cd /data/app/igny8/frontend/src
# Delete unused template folders
rm -rf components/ecommerce/
rm -rf components/sample-componeents/
rm -rf components/charts/bar/
rm -rf components/charts/line/
rm -rf components/tables/BasicTables/
# If charts folder is now empty, remove it
rmdir components/charts 2>/dev/null || true
# If tables folder is now empty, remove it
rmdir components/tables 2>/dev/null || true
```
**✅ CHECKPOINT:** Run `npm run build` - must succeed.
---
## 1.4 Frontend Cleanup - Deprecated Files
### Individual Files to Delete
| File | Reason |
|------|--------|
| `frontend/src/components/Automation/CurrentProcessingCard.old.tsx` | Old deprecated version |
### Execution:
```bash
# Verify no imports exist
cd /data/app/igny8/frontend
grep -r "CurrentProcessingCard.old" src/
# If no results, safe to delete
rm src/components/Automation/CurrentProcessingCard.old.tsx
```
**✅ CHECKPOINT:** Run `npm run build` - must succeed.
---
## 1.5 Console.log Cleanup
### Files with console.log statements to review:
| File | Line(s) | Action |
|------|---------|--------|
| `src/services/api.ts` | 2010, 2015 | Review - may need for debugging |
| `src/components/UserProfile/UserMetaCard.tsx` | 11 | Remove |
| `src/components/UserProfile/UserAddressCard.tsx` | 11 | Remove |
| `src/components/UserProfile/UserInfoCard.tsx` | 11 | Remove |
| `src/components/Automation/ConfigModal.tsx` | 42 | Remove |
| `src/components/common/ImageQueueModal.tsx` | 227, 229, 239, 242, 247, 251, 259, 262 | Remove all |
| `src/components/common/ImageGenerationCard.tsx` | 107, 125, 129, 141, 142, 151, 178 | Remove all |
### Execution Strategy:
**Option A: Manual removal (safer)**
Edit each file and remove console.log statements manually.
**Option B: Automated with review**
```bash
cd /data/app/igny8/frontend
# Find all console.log in src (excluding node_modules)
grep -rn "console.log" src/ --include="*.ts" --include="*.tsx" > /tmp/console-logs.txt
# Review the file before any automated removal
cat /tmp/console-logs.txt
```
### Per-File Actions:
```typescript
// In UserMetaCard.tsx, UserAddressCard.tsx, UserInfoCard.tsx - REMOVE:
console.log("Saving changes...");
// In ConfigModal.tsx - REMOVE:
console.log('Saving config with delays:', dataToSave);
// In ImageQueueModal.tsx - REMOVE ALL console.log statements
// In ImageGenerationCard.tsx - REMOVE ALL console.log statements
// In api.ts - KEEP or convert to proper logging (these may be useful)
```
**✅ CHECKPOINT:** Run `npm run build && npm run lint` - must succeed.
---
## 1.6 ESLint Verification
```bash
cd /data/app/igny8/frontend
# Run full lint check
npm run lint
# If errors exist, fix them:
npm run lint -- --fix
```
**✅ CHECKPOINT:** Zero lint errors.
---
## 1.7 Post-Cleanup Verification
```bash
# 1. Full build
cd /data/app/igny8/frontend
npm run build
# 2. Type check
npx tsc --noEmit
# 3. Start dev server and manually verify app loads
npm run dev
# Open http://localhost:5173 and verify:
# - Dashboard loads
# - All sidebar navigation works
# - No console errors in browser
# 4. Commit changes
cd /data/app/igny8
git add -A
git status # Review all changes
git commit -m "Phase 1: Code cleanup - remove unused pages, components, and console.logs"
```
---
# Phase 5: UX Improvements
## 5.1 Pre-Implementation Setup
### 5.1.1 Create Feature Branch
```bash
cd /data/app/igny8
git checkout main # or your main branch
git pull
git checkout -b feature/phase-5-ux-improvements
```
---
## 5.2 Search Modal Enhancement
### 5.2.1 Current State Analysis
**Location:** Search functionality likely in header/navigation components
**Research Required:**
```bash
cd /data/app/igny8/frontend
# Find existing search component
grep -rn "search" src/components/header/ --include="*.tsx"
grep -rn "Search" src/components/ --include="*.tsx" | head -20
# Find search-related stores
grep -rn "search" src/store/ --include="*.ts"
```
### 5.2.2 Implementation Tasks
| Task | File(s) | Details |
|------|---------|---------|
| Add keyboard shortcut | `src/components/header/Search*.tsx` | Cmd/Ctrl+K to open |
| Add search filters | Search component | Filter by type (keyword, content, site) |
| Add recent searches | Search component + localStorage | Store last 5 searches |
| Improve results display | Search results component | Show context snippets |
| Add quick actions | Search results | Quick action buttons |
### 5.2.3 Keyboard Shortcut Implementation
```typescript
// Add to App.tsx or a global hook
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
e.preventDefault();
// Open search modal
setSearchOpen(true);
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
```
### 5.2.4 Verification
- [ ] Cmd/Ctrl+K opens search modal
- [ ] Search filters work correctly
- [ ] Recent searches persist across sessions
- [ ] Results show relevant context
- [ ] Quick actions function properly
---
## 5.3 Image Regeneration Feature
### 5.3.1 Backend Requirements
**Check existing endpoint:**
```bash
cd /data/app/igny8/backend
# Find image generation endpoints
grep -rn "image" igny8_core/modules/writer/ --include="*.py" | head -20
grep -rn "regenerate" igny8_core/ --include="*.py"
```
**Required API endpoint (if not exists):**
```
POST /api/v1/writer/images/{id}/regenerate/
Body: { reason?: string, prompt_adjustment?: string }
Response: { task_id: string, status: "queued" }
```
### 5.3.2 Frontend Implementation
**File: `frontend/src/pages/Writer/Images.tsx`**
Add regenerate button to each image card:
```typescript
// Add to image card actions
const handleRegenerate = async (imageId: number) => {
const reason = await showRegenerateModal();
if (reason !== null) {
await api.post(`/v1/writer/images/${imageId}/regenerate/`, { reason });
// Refresh list or show status
}
};
// Button in image card
<Button
variant="outline"
size="sm"
onClick={() => handleRegenerate(image.id)}
>
<RefreshIcon /> Regenerate
</Button>
```
**File: `frontend/src/pages/Writer/ContentView.tsx`**
Add regenerate for featured image:
```typescript
// In featured image section (admin/editor only)
{(user.role === 'admin' || user.role === 'editor') && (
<Button onClick={() => handleRegenerateFeaturedImage(content.id)}>
Regenerate Featured Image
</Button>
)}
```
### 5.3.3 Verification
- [ ] Regenerate button appears on `/writer/images` page
- [ ] Regenerate modal prompts for reason
- [ ] API call succeeds and image regenerates
- [ ] Status updates correctly
- [ ] Featured image regenerate works in content view
- [ ] Role-based visibility works (admin/editor only)
---
## 5.4 User Flow Polish
### 5.4.1 Signup to First Content Flow Testing
**Test Checklist:**
| Step | URL | Verify |
|------|-----|--------|
| 1. Signup | `/signup` | Form submits, verification email sent |
| 2. Verify Email | `/verify-email?token=...` | Email verified, redirect to app |
| 3. Onboarding | `/setup/wizard` | All steps complete without errors |
| 4. Add Site | Sites → Add Site | WordPress connection successful |
| 5. Add Keywords | `/planner/keywords` | Import works, keywords appear |
| 6. Clustering | `/planner/clusters` | AI clustering completes |
| 7. Generate Content | `/writer/tasks` | Content generates successfully |
| 8. Publish | Content → Publish | Content appears on WordPress |
### 5.4.2 Issue Documentation Template
```markdown
## Issue: [Brief Description]
**Step:** [Which step in flow]
**URL:** [Page URL]
**Expected:** [What should happen]
**Actual:** [What happened]
**Steps to Reproduce:**
1. ...
2. ...
**Screenshots/Logs:** [Attach if applicable]
**Severity:** [Blocking/Major/Minor]
```
### 5.4.3 Post-Implementation Verification
```bash
# Build and test
cd /data/app/igny8/frontend
npm run build
npm run lint
# Commit
git add -A
git commit -m "Phase 5: UX improvements - search modal, image regeneration, flow polish"
```
---
# Phase 6: Data Backup & Cleanup
## 6.1 Pre-Backup Safety Steps
### 6.1.1 Create Backup Branch
```bash
cd /data/app/igny8
git checkout main
git checkout -b backup/pre-v1-cleanup-$(date +%Y%m%d)
```
### 6.1.2 Full Database Backup
```bash
# Create backup directory
mkdir -p /data/app/igny8/backups/$(date +%Y%m%d)
# PostgreSQL full backup
pg_dump -h localhost -U your_user -d igny8_db > /data/app/igny8/backups/$(date +%Y%m%d)/full_backup.sql
# Verify backup
ls -la /data/app/igny8/backups/$(date +%Y%m%d)/
head -50 /data/app/igny8/backups/$(date +%Y%m%d)/full_backup.sql
```
**✅ CHECKPOINT:** Backup file exists and has content.
---
## 6.2 System Configuration Export
### 6.2.1 Create Export Directory Structure
```bash
mkdir -p /data/app/igny8/backups/config
```
### 6.2.2 Export Django Models (System Config)
**Create management command:**
```bash
# File: backend/igny8_core/management/commands/export_system_config.py
```
```python
# backend/igny8_core/management/commands/export_system_config.py
from django.core.management.base import BaseCommand
from django.core import serializers
import json
import os
from datetime import datetime
class Command(BaseCommand):
help = 'Export system configuration data to JSON files'
def add_arguments(self, parser):
parser.add_argument(
'--output-dir',
default='backups/config',
help='Output directory for config files'
)
def handle(self, *args, **options):
output_dir = options['output_dir']
os.makedirs(output_dir, exist_ok=True)
# Import models here to avoid circular imports
from igny8_core.modules.billing.models import Plan, CreditCostConfig
from igny8_core.modules.system.models import (
AIModelConfig, GlobalIntegrationSettings, SystemSettings
)
from igny8_core.auth.models import Industry, Sector, SeedKeyword
# Add other system models as needed
exports = {
'plans': Plan.objects.all(),
'credit_costs': CreditCostConfig.objects.all(),
'ai_models': AIModelConfig.objects.all(),
# 'global_integrations': GlobalIntegrationSettings.objects.all(),
# 'system_settings': SystemSettings.objects.all(),
'industries': Industry.objects.all(),
'sectors': Sector.objects.all(),
'seed_keywords': SeedKeyword.objects.all(),
}
for name, queryset in exports.items():
try:
data = serializers.serialize('json', queryset, indent=2)
filepath = os.path.join(output_dir, f'{name}.json')
with open(filepath, 'w') as f:
f.write(data)
self.stdout.write(
self.style.SUCCESS(f'Exported {queryset.count()} {name} to {filepath}')
)
except Exception as e:
self.stdout.write(
self.style.ERROR(f'Failed to export {name}: {str(e)}')
)
# Export timestamp
with open(os.path.join(output_dir, 'export_metadata.json'), 'w') as f:
json.dump({
'exported_at': datetime.now().isoformat(),
'exports': list(exports.keys())
}, f, indent=2)
self.stdout.write(self.style.SUCCESS('System config export complete!'))
```
### 6.2.3 Run Export
```bash
cd /data/app/igny8/backend
# Run the export command
python manage.py export_system_config --output-dir=../backups/config
# Verify exports
ls -la ../backups/config/
```
**✅ CHECKPOINT:** All config JSON files exist and contain data.
---
## 6.3 User Data Cleanup
### ⚠️ DANGER ZONE - READ CAREFULLY ⚠️
This section PERMANENTLY DELETES user data. Ensure:
1. Full backup completed (6.1.2)
2. Config exported (6.2)
3. You are in the correct environment (NOT PRODUCTION until ready)
### 6.3.1 Create Cleanup Management Command
```python
# backend/igny8_core/management/commands/cleanup_user_data.py
from django.core.management.base import BaseCommand
from django.db import transaction
class Command(BaseCommand):
help = 'Clean up all user-generated data (DESTRUCTIVE)'
def add_arguments(self, parser):
parser.add_argument(
'--confirm',
action='store_true',
help='Confirm you want to delete all user data'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be deleted without deleting'
)
def handle(self, *args, **options):
if not options['confirm'] and not options['dry_run']:
self.stdout.write(
self.style.ERROR('Must use --confirm or --dry-run flag')
)
return
# Import models
from igny8_core.auth.models import Site
from igny8_core.business.planning.models import Keywords, Clusters
from igny8_core.modules.writer.models import (
ContentIdea, Task, Content, ContentImage
)
from igny8_core.modules.publisher.models import PublishingRecord, SyncEvent
from igny8_core.modules.billing.models import CreditTransaction, CreditUsageLog
# Add other models
models_to_clear = [
('Keywords', Keywords),
('Clusters', Clusters),
('ContentIdeas', ContentIdea),
('Tasks', Task),
('Content', Content),
('ContentImages', ContentImage),
('PublishingRecords', PublishingRecord),
('SyncEvents', SyncEvent),
('CreditTransactions', CreditTransaction),
('CreditUsageLogs', CreditUsageLog),
# Sites should be last (foreign keys)
('Sites', Site),
]
if options['dry_run']:
self.stdout.write(self.style.WARNING('DRY RUN - No data will be deleted'))
for name, model in models_to_clear:
count = model.objects.count()
self.stdout.write(f' Would delete {count} {name}')
return
# Actual deletion
with transaction.atomic():
for name, model in models_to_clear:
count = model.objects.count()
model.objects.all().delete()
self.stdout.write(
self.style.SUCCESS(f'Deleted {count} {name}')
)
self.stdout.write(self.style.SUCCESS('User data cleanup complete!'))
```
### 6.3.2 Execute Cleanup (Step by Step)
```bash
cd /data/app/igny8/backend
# Step 1: DRY RUN - See what will be deleted
python manage.py cleanup_user_data --dry-run
# Step 2: Review output carefully
# - Are the counts expected?
# - Is this the right environment?
# Step 3: ONLY if absolutely sure, run actual cleanup
python manage.py cleanup_user_data --confirm
```
### 6.3.3 Clear Media Storage
```bash
# List media files first
ls -la /data/app/igny8/backend/media/
# Backup media if needed
cp -r /data/app/igny8/backend/media/ /data/app/igny8/backups/$(date +%Y%m%d)/media/
# Clear generated images (be specific about paths)
rm -rf /data/app/igny8/backend/media/generated_images/*
rm -rf /data/app/igny8/backend/media/content_images/*
# Verify
ls -la /data/app/igny8/backend/media/
```
### 6.3.4 Clear Logs
```bash
# Backup logs first
cp -r /data/app/igny8/backend/logs/ /data/app/igny8/backups/$(date +%Y%m%d)/logs/
# Clear log files (keep empty files for app to write)
> /data/app/igny8/backend/logs/publish-sync-logs/*.log
> /data/app/igny8/backend/celerybeat-schedule
# Or remove all logs
rm -f /data/app/igny8/backend/logs/**/*.log
```
---
## 6.4 Configuration Lock (V1.0)
### 6.4.1 Document Final Configuration
Create documentation file:
```bash
mkdir -p /data/app/igny8/docs/90-REFERENCE/
```
```markdown
# V1.0 Configuration Reference
**Locked:** [DATE]
**Version:** 1.0.0
## Plans Configuration
| Plan | Credits | Sites | Price | Interval |
|------|---------|-------|-------|----------|
| Starter | X | 1 | $X/mo | monthly |
| Growth | X | 3 | $X/mo | monthly |
| Scale | X | 10 | $X/mo | monthly |
## Credit Costs
| Operation | Cost |
|-----------|------|
| Basic Image | 1 credit |
| Quality Image | 5 credits |
| Premium Image | 15 credits |
| Clustering | Token-based |
| Content Generation | Token-based |
## AI Model Configurations
[Document all AI model settings]
---
**CHANGE POLICY:** Any changes require version bump and documented release.
```
### 6.4.2 Git Tag for V1.0
```bash
cd /data/app/igny8
# Ensure all changes committed
git status
git add -A
git commit -m "Phase 6: Pre-launch cleanup complete"
# Create annotated tag
git tag -a v1.0.0 -m "IGNY8 V1.0.0 - Production Release"
# Push tag
git push origin v1.0.0
```
---
## 6.5 Post-Cleanup Verification
### 6.5.1 Database Verification
```bash
cd /data/app/igny8/backend
# Run Django check
python manage.py check
# Verify system config still exists
python manage.py shell << 'EOF'
from igny8_core.modules.billing.models import Plan
from igny8_core.auth.models import Industry, Sector
print(f"Plans: {Plan.objects.count()}")
print(f"Industries: {Industry.objects.count()}")
print(f"Sectors: {Sector.objects.count()}")
EOF
# Verify user data cleared
python manage.py shell << 'EOF'
from igny8_core.auth.models import Site
from igny8_core.business.planning.models import Keywords
print(f"Sites: {Site.objects.count()}")
print(f"Keywords: {Keywords.objects.count()}")
EOF
```
### 6.5.2 Application Verification
```bash
# Start backend
cd /data/app/igny8/backend
python manage.py runserver &
# Start frontend
cd /data/app/igny8/frontend
npm run dev &
# Manual checks:
# 1. Can login as admin
# 2. Dashboard loads (empty state)
# 3. Plans visible in settings
# 4. Can create new user account
# 5. Onboarding flow works
```
---
# Execution Checklist
## Phase 1 Checklist
- [ ] Created safety branch
- [ ] Ran baseline tests (all pass)
- [ ] Deleted empty folders (6 folders)
- [ ] Build succeeds after empty folder deletion
- [ ] Deleted template/sample components (ecommerce, sample-componeents, charts, tables)
- [ ] Build succeeds after template deletion
- [ ] Deleted `CurrentProcessingCard.old.tsx`
- [ ] Removed console.log statements (reviewed each)
- [ ] ESLint passes with zero errors
- [ ] TypeScript compiles without errors
- [ ] Manual app verification complete
- [ ] Changes committed
## Phase 5 Checklist
- [ ] Created feature branch
- [ ] Researched existing search implementation
- [ ] Implemented keyboard shortcut (Cmd/Ctrl+K)
- [ ] Added search filters
- [ ] Added recent searches
- [ ] Improved results display
- [ ] Added image regenerate to `/writer/images`
- [ ] Added featured image regenerate to content view
- [ ] Backend endpoint created/verified
- [ ] Role-based visibility works
- [ ] Tested full signup-to-publish flow
- [ ] Documented any issues found
- [ ] Changes committed
## Phase 6 Checklist
- [ ] Created backup branch
- [ ] Full database backup created
- [ ] Backup file verified (has content)
- [ ] Created export_system_config command
- [ ] Exported all system config (plans, industries, etc.)
- [ ] Config files verified (JSON valid)
- [ ] Created cleanup_user_data command
- [ ] Ran dry-run cleanup (reviewed counts)
- [ ] **CONFIRMED correct environment**
- [ ] Executed user data cleanup
- [ ] Cleared media storage
- [ ] Backed up and cleared logs
- [ ] Created V1.0 config documentation
- [ ] Created git tag v1.0.0
- [ ] Verified system config still exists
- [ ] Verified user data cleared
- [ ] Application starts and functions
---
## Rollback Procedures
### Phase 1 Rollback
```bash
git checkout main
git branch -D cleanup/phase-1-*
```
### Phase 5 Rollback
```bash
git checkout main
git branch -D feature/phase-5-ux-improvements
```
### Phase 6 Rollback (Database)
```bash
# Restore from backup
psql -h localhost -U your_user -d igny8_db < /data/app/igny8/backups/YYYYMMDD/full_backup.sql
# Restore media
cp -r /data/app/igny8/backups/YYYYMMDD/media/* /data/app/igny8/backend/media/
```
---
**Document Owner:** IGNY8 Team
**Review:** Before each phase execution
**Approval Required:** Phase 6 cleanup requires explicit approval