temproary docs uplaoded
This commit is contained in:
346
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/ARCHITECTURE.md
Normal file
346
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/ARCHITECTURE.md
Normal file
@@ -0,0 +1,346 @@
|
||||
# System Architecture
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Backend Path:** `backend/igny8_core/`
|
||||
**Frontend Path:** `frontend/src/`
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology | Version | Purpose |
|
||||
|-------|------------|---------|---------|
|
||||
| **Backend Framework** | Django | 5.1 | Web framework |
|
||||
| **API Layer** | Django REST Framework | 3.15 | REST API |
|
||||
| **Database** | PostgreSQL | 16 | Primary data store |
|
||||
| **Cache/Queue** | Redis | 7 | Caching, Celery broker |
|
||||
| **Task Queue** | Celery | 5.4 | Async task processing |
|
||||
| **Frontend Framework** | React | 19 | UI framework |
|
||||
| **Build Tool** | Vite | 5 | Frontend bundler |
|
||||
| **Styling** | Tailwind CSS | 3 | Utility CSS |
|
||||
| **State Management** | Zustand | 4 | React state |
|
||||
| **Language** | TypeScript | 5 | Frontend typing |
|
||||
| **Web Server** | Caddy | 2 | Reverse proxy, SSL |
|
||||
| **Containerization** | Docker | - | Deployment |
|
||||
|
||||
---
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CLIENTS │
|
||||
│ React SPA (app.igny8.com) │ WordPress Plugin │ Admin │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CADDY (Reverse Proxy) │
|
||||
│ SSL termination, routing, static files │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ DJANGO REST FRAMEWORK │
|
||||
│ │
|
||||
│ Middleware Stack: │
|
||||
│ SecurityMiddleware → WhiteNoise → CORS → Session → CSRF → │
|
||||
│ DjangoAuth → RequestIDMiddleware → AccountContextMiddleware → │
|
||||
│ ResourceTrackingMiddleware │
|
||||
│ │
|
||||
│ API: /api/v1/* → ViewSets → Services → Models │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ PostgreSQL │ │ Redis │
|
||||
│ Primary Database │ │ Sessions, Cache, Celery Broker │
|
||||
└─────────────────────┘ └─────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CELERY WORKERS │
|
||||
│ AI Tasks, Automation, Publishing, Background Jobs │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ EXTERNAL SERVICES │
|
||||
│ OpenAI │ Anthropic │ Runware │ Bria │ WordPress Sites │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend Structure
|
||||
|
||||
```
|
||||
backend/igny8_core/
|
||||
├── settings.py # Django settings, all config
|
||||
├── urls.py # Root URL routing
|
||||
├── celery.py # Celery configuration
|
||||
├── wsgi.py / asgi.py # WSGI/ASGI entry points
|
||||
│
|
||||
├── auth/ # Authentication & tenancy
|
||||
│ ├── models.py # User, Account, Site, Sector, Plan
|
||||
│ ├── views.py # Login, register, password reset
|
||||
│ ├── middleware.py # AccountContextMiddleware
|
||||
│ └── urls.py # /api/v1/auth/*
|
||||
│
|
||||
├── api/ # API infrastructure
|
||||
│ ├── base.py # AccountModelViewSet, SiteSectorModelViewSet
|
||||
│ ├── authentication.py # JWT, API key auth classes
|
||||
│ └── pagination.py # Unified pagination
|
||||
│
|
||||
├── ai/ # AI engine
|
||||
│ ├── engine.py # AIEngine orchestrator
|
||||
│ ├── functions/ # AutoCluster, GenerateIdeas, GenerateContent, etc.
|
||||
│ ├── registry.py # Function registry
|
||||
│ ├── model_registry.py # Model Registry service (v1.3.0)
|
||||
│ └── progress.py # Progress tracking
|
||||
│
|
||||
├── modules/ # Feature modules (API layer)
|
||||
│ ├── planner/ # Keywords, Clusters, Ideas
|
||||
│ ├── writer/ # Tasks, Content, Images
|
||||
│ ├── billing/ # Credits, usage, transactions
|
||||
│ ├── integration/ # WordPress integration
|
||||
│ ├── system/ # Settings, prompts, AI config
|
||||
│ ├── linker/ # Internal linking (inactive)
|
||||
│ ├── optimizer/ # Content optimization (inactive)
|
||||
│ └── publisher/ # Publishing pipeline
|
||||
│
|
||||
├── business/ # Business logic (services)
|
||||
│ ├── automation/ # 7-stage automation pipeline
|
||||
│ ├── billing/ # Credit service, payment processing
|
||||
│ ├── content/ # Content generation orchestration
|
||||
│ ├── integration/ # Sync services
|
||||
│ ├── linking/ # Link processing
|
||||
│ ├── notifications/ # Notification system (v1.2.0)
|
||||
│ ├── optimization/ # Content optimization
|
||||
│ ├── planning/ # Clustering, idea generation
|
||||
│ └── publishing/ # Publishing orchestration
|
||||
│
|
||||
├── middleware/ # Custom middleware
|
||||
│ ├── request_id.py # X-Request-ID header
|
||||
│ └── resource_tracker.py # Resource tracking for admins
|
||||
│
|
||||
└── tasks/ # Celery tasks
|
||||
└── *.py # Background job definitions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Structure
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── main.tsx # Entry point
|
||||
├── App.tsx # Root component, routing
|
||||
├── index.css # Global styles, Tailwind
|
||||
│
|
||||
├── api/ # API clients
|
||||
│ ├── linker.api.ts
|
||||
│ ├── optimizer.api.ts
|
||||
│ └── ...
|
||||
│
|
||||
├── services/
|
||||
│ ├── api.ts # Main API service (2500+ lines)
|
||||
│ └── notifications.api.ts # Notification API (v1.2.0)
|
||||
│
|
||||
├── store/ # Zustand stores
|
||||
│ ├── authStore.ts # Authentication state
|
||||
│ ├── siteStore.ts # Active site
|
||||
│ ├── sectorStore.ts # Active sector
|
||||
│ ├── billingStore.ts # Billing state
|
||||
│ ├── moduleStore.ts # Module enable/disable
|
||||
│ ├── notificationStore.ts # Notifications (v1.2.0)
|
||||
│ └── ...
|
||||
│
|
||||
├── pages/ # Route pages
|
||||
│ ├── Dashboard/
|
||||
│ ├── Planner/
|
||||
│ ├── Writer/
|
||||
│ ├── Automation/
|
||||
│ ├── Linker/
|
||||
│ ├── Optimizer/
|
||||
│ ├── Settings/
|
||||
│ ├── Billing/
|
||||
│ └── Auth/
|
||||
│
|
||||
├── components/ # Reusable components
|
||||
│ ├── common/
|
||||
│ │ ├── ProgressModal.tsx # AI progress display
|
||||
│ │ ├── SearchModal.tsx # Global search (v1.1.9)
|
||||
│ │ └── ...
|
||||
│ ├── dashboard/ # Dashboard widgets (v1.2.0)
|
||||
│ │ ├── WorkflowPipelineWidget.tsx
|
||||
│ │ ├── AIOperationsWidget.tsx
|
||||
│ │ ├── RecentActivityWidget.tsx
|
||||
│ │ ├── ContentVelocityWidget.tsx
|
||||
│ │ ├── AutomationStatusWidget.tsx
|
||||
│ │ ├── ThreeWidgetFooter.tsx
|
||||
│ │ └── ...
|
||||
│ └── header/
|
||||
│ └── NotificationDropdown.tsx
|
||||
│
|
||||
├── context/ # React contexts (v1.1.9)
|
||||
│ └── PageContext.tsx # Page-level state
|
||||
│
|
||||
├── layout/ # Layout components
|
||||
│ ├── AppLayout.tsx
|
||||
│ ├── AppHeader.tsx
|
||||
│ └── AppSidebar.tsx
|
||||
│
|
||||
├── hooks/ # Custom hooks
|
||||
│ └── useWorkflowStats.ts # Automation stats hook (v1.3.0)
|
||||
├── context/ # React contexts
|
||||
└── utils/ # Utility functions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Design Patterns
|
||||
|
||||
### 1. Multi-Tenant Data Isolation
|
||||
|
||||
All data is scoped to Account, with optional Site and Sector filtering:
|
||||
|
||||
```
|
||||
Account (Tenant)
|
||||
└── Site (e.g., myblog.com)
|
||||
└── Sector (e.g., Technology, Health)
|
||||
└── Keywords/Clusters/Ideas/Content
|
||||
```
|
||||
|
||||
**Implementation:**
|
||||
- `AccountBaseModel` - Base class for account-scoped models
|
||||
- `SiteSectorBaseModel` - Base class for site/sector-scoped models
|
||||
- `AccountModelViewSet` - Auto-filters queryset by request.account
|
||||
- `SiteSectorModelViewSet` - Auto-filters by account + site + sector
|
||||
|
||||
### 2. Middleware-First Tenant Resolution
|
||||
|
||||
```python
|
||||
# Order in settings.py MIDDLEWARE
|
||||
1. SecurityMiddleware
|
||||
2. WhiteNoiseMiddleware
|
||||
3. CorsMiddleware
|
||||
4. SessionMiddleware
|
||||
5. DjangoAuthenticationMiddleware
|
||||
6. RequestIDMiddleware # Assigns X-Request-ID
|
||||
7. AccountContextMiddleware # Sets request.account from session/JWT
|
||||
8. ResourceTrackingMiddleware # Optional admin profiling
|
||||
```
|
||||
|
||||
### 3. Unified API Responses
|
||||
|
||||
All API responses follow this structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"message": "Optional message"
|
||||
}
|
||||
```
|
||||
|
||||
Error responses:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message",
|
||||
"code": "ERROR_CODE"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Credit-Based Operations
|
||||
|
||||
All AI operations check and deduct credits:
|
||||
1. Pre-check: `CreditService.check_credits()`
|
||||
2. Execute: AI function runs
|
||||
3. Post-deduct: `CreditService.deduct_credits_for_operation()`
|
||||
|
||||
---
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
Key environment variables (from `settings.py`):
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `DATABASE_URL` | PostgreSQL connection |
|
||||
| `REDIS_URL` | Redis connection |
|
||||
| `SECRET_KEY` | Django secret |
|
||||
| `JWT_SECRET_KEY` | JWT signing key |
|
||||
| `CORS_ALLOWED_ORIGINS` | Allowed frontend origins |
|
||||
| `CELERY_BROKER_URL` | Celery broker (Redis) |
|
||||
|
||||
AI keys are stored in `GlobalIntegrationSettings` (database), not env vars.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Docker Compose │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Frontend │ │ Backend │ │ Worker │ │
|
||||
│ │ (Caddy) │ │ (Django) │ │ (Celery) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └────────────────┼────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ PostgreSQL │ │ Redis │ │
|
||||
│ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Registry (v1.3.0)
|
||||
|
||||
The Model Registry provides centralized AI model configuration with database-stored pricing and settings:
|
||||
|
||||
```python
|
||||
# backend/igny8_core/ai/model_registry.py
|
||||
from igny8_core.modules.system.models import AIModelConfig
|
||||
|
||||
class ModelRegistry:
|
||||
"""Centralized AI model configuration service"""
|
||||
|
||||
@classmethod
|
||||
def get_model(cls, model_id: str) -> AIModelConfig:
|
||||
"""Get model config by ID"""
|
||||
return AIModelConfig.objects.get(model_id=model_id)
|
||||
|
||||
@classmethod
|
||||
def get_active_models_by_type(cls, model_type: str) -> QuerySet:
|
||||
"""Get all active models for a type (text, image, etc.)"""
|
||||
return AIModelConfig.objects.filter(
|
||||
model_type=model_type,
|
||||
is_active=True
|
||||
)
|
||||
```
|
||||
|
||||
**Supported Providers:**
|
||||
| Provider | Types | Integration |
|
||||
|----------|-------|-------------|
|
||||
| OpenAI | Text, Image | GPT-4o, GPT-4-turbo, DALL-E 3 |
|
||||
| Anthropic | Text | Claude 3.5 Sonnet, Claude 3 Opus |
|
||||
| Runware | Image | RunwareFLUX, SD 3.5 |
|
||||
| Bria | Image | Bria 2.3 |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| ~~AIModelConfig Database~~ | ✅ v1.3.0 | ~~Move AI model pricing from constants to database~~ |
|
||||
| ~~Multi-provider AI~~ | ✅ v1.3.0 | ~~Support for Anthropic, Bria~~ |
|
||||
| Module Guard Extension | 🔜 Planned | Extend linker/optimizer disable to all pages (currently sidebar only) |
|
||||
| Google AI Integration | 🔜 Planned | Support for Gemini models |
|
||||
255
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/AUTH-FLOWS.md
Normal file
255
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/AUTH-FLOWS.md
Normal file
@@ -0,0 +1,255 @@
|
||||
# Authentication & Authorization
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Backend Path:** `backend/igny8_core/auth/`
|
||||
**Frontend Path:** `frontend/src/store/authStore.ts`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Functions |
|
||||
|------|------|---------------|
|
||||
| User Model | `auth/models.py` | `User`, `Account`, `Plan` |
|
||||
| Auth Views | `auth/views.py` | `LoginView`, `RegisterView`, `RefreshTokenView` |
|
||||
| Middleware | `auth/middleware.py` | `AccountContextMiddleware` |
|
||||
| JWT Auth | `api/authentication.py` | `JWTAuthentication`, `CookieJWTAuthentication` |
|
||||
| API Key Auth | `api/authentication.py` | `APIKeyAuthentication` |
|
||||
| Frontend Store | `store/authStore.ts` | `useAuthStore` |
|
||||
|
||||
---
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
### 1. JWT Token Authentication (Primary)
|
||||
|
||||
**Flow:**
|
||||
1. User logs in via `/api/v1/auth/login/`
|
||||
2. Backend returns `access_token` (15 min) + `refresh_token` (7 days)
|
||||
3. Frontend stores tokens in localStorage and Zustand store
|
||||
4. All API requests include `Authorization: Bearer <access_token>`
|
||||
5. Token refresh via `/api/v1/auth/token/refresh/`
|
||||
|
||||
**Token Payload:**
|
||||
```json
|
||||
{
|
||||
"user_id": 123,
|
||||
"account_id": 456,
|
||||
"email": "user@example.com",
|
||||
"exp": 1735123456,
|
||||
"iat": 1735122456
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Session Authentication (Admin/Fallback)
|
||||
|
||||
- Used by Django Admin interface
|
||||
- Cookie-based session with CSRF protection
|
||||
- Redis-backed sessions (prevents user swapping bug)
|
||||
|
||||
### 3. API Key Authentication (WordPress Bridge)
|
||||
|
||||
**Flow:**
|
||||
1. Account generates API key in settings
|
||||
2. WordPress plugin uses `Authorization: ApiKey <key>`
|
||||
3. Backend validates key, sets `request.account` and `request.site`
|
||||
|
||||
**Use Cases:**
|
||||
- WordPress content sync
|
||||
- External integrations
|
||||
- Headless CMS connections
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| POST | `/api/v1/auth/register/` | `RegisterView` | Create new user + account |
|
||||
| POST | `/api/v1/auth/login/` | `LoginView` | Authenticate, return tokens |
|
||||
| POST | `/api/v1/auth/logout/` | `LogoutView` | Invalidate tokens |
|
||||
| POST | `/api/v1/auth/token/refresh/` | `RefreshTokenView` | Refresh access token |
|
||||
| POST | `/api/v1/auth/password/change/` | `ChangePasswordView` | Change password |
|
||||
| POST | `/api/v1/auth/password/reset/` | `RequestPasswordResetView` | Request reset email |
|
||||
| POST | `/api/v1/auth/password/reset/confirm/` | `ResetPasswordView` | Confirm reset with token |
|
||||
|
||||
---
|
||||
|
||||
## User Roles
|
||||
|
||||
| Role | Code | Permissions |
|
||||
|------|------|-------------|
|
||||
| **Developer** | `developer` | Full access across ALL accounts (superuser) |
|
||||
| **Owner** | `owner` | Full access to own account (account creator) |
|
||||
| **Admin** | `admin` | Full access to own account, billing, team management |
|
||||
| **Editor** | `editor` | Content creation and editing only |
|
||||
| **Viewer** | `viewer` | Read-only access to content |
|
||||
| **System Bot** | `system_bot` | System automation (internal) |
|
||||
|
||||
**Role Hierarchy:**
|
||||
```
|
||||
developer > owner > admin > editor > viewer
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Middleware: AccountContextMiddleware
|
||||
|
||||
**File:** `auth/middleware.py`
|
||||
|
||||
**Purpose:** Injects `request.account` on every request
|
||||
|
||||
**Flow:**
|
||||
1. Check for JWT token → extract account_id
|
||||
2. Check for session → get account from session
|
||||
3. Check for API key → get account from key
|
||||
4. Validate account exists and is active
|
||||
5. Validate plan exists and is active
|
||||
6. Set `request.account`, `request.user`
|
||||
|
||||
**Error Responses:**
|
||||
- No account: 403 with JSON error
|
||||
- Inactive plan: 402 with JSON error
|
||||
|
||||
---
|
||||
|
||||
## Frontend Auth Store
|
||||
|
||||
**File:** `store/authStore.ts`
|
||||
|
||||
**State:**
|
||||
```typescript
|
||||
{
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
refreshToken: string | null;
|
||||
isAuthenticated: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Actions:**
|
||||
- `login(email, password)` - Authenticate and store tokens
|
||||
- `register(data)` - Create account and store tokens
|
||||
- `logout()` - Clear tokens and reset stores
|
||||
- `refreshToken()` - Refresh access token
|
||||
- `checkAuth()` - Verify current auth state
|
||||
|
||||
**Critical Implementation:**
|
||||
```typescript
|
||||
// Tokens are written synchronously to localStorage
|
||||
// This prevents race conditions where API calls happen before persist
|
||||
localStorage.setItem('auth-storage', JSON.stringify(authState));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session Security (Redis-Backed)
|
||||
|
||||
**Problem Solved:** User swapping / random logout issues
|
||||
|
||||
**Implementation:**
|
||||
```python
|
||||
# settings.py
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||
SESSION_CACHE_ALIAS = 'default' # Redis
|
||||
|
||||
# auth/backends.py
|
||||
class NoCacheModelBackend(ModelBackend):
|
||||
"""Authentication backend without user caching"""
|
||||
pass
|
||||
```
|
||||
|
||||
**Session Integrity:**
|
||||
- Stores `account_id` and `user_id` in session
|
||||
- Validates on every request
|
||||
- Prevents cross-request contamination
|
||||
|
||||
---
|
||||
|
||||
## API Key Management
|
||||
|
||||
**Model:** `APIKey` in `auth/models.py`
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| key | CharField | Hashed API key |
|
||||
| account | ForeignKey | Owner account |
|
||||
| site | ForeignKey | Optional: specific site |
|
||||
| name | CharField | Key name/description |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
| created_at | DateTime | Creation time |
|
||||
| last_used_at | DateTime | Last usage time |
|
||||
|
||||
**Generation:**
|
||||
- 32-character random key
|
||||
- Stored hashed (SHA-256)
|
||||
- Shown once on creation
|
||||
|
||||
---
|
||||
|
||||
## Permission Checking
|
||||
|
||||
**In ViewSets:**
|
||||
```python
|
||||
class MyViewSet(AccountModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
# Automatically filtered by request.account
|
||||
return super().get_queryset()
|
||||
```
|
||||
|
||||
**Role Checks:**
|
||||
```python
|
||||
if request.user.is_admin_or_developer:
|
||||
# Admin/developer access
|
||||
pass
|
||||
elif request.user.role == 'editor':
|
||||
# Editor access
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logout Flow
|
||||
|
||||
**Backend:**
|
||||
1. Blacklist refresh token (if using token blacklist)
|
||||
2. Clear session
|
||||
|
||||
**Frontend (Critical):**
|
||||
```typescript
|
||||
logout: () => {
|
||||
// NEVER use localStorage.clear() - breaks Zustand persist
|
||||
const authKeys = ['auth-storage', 'site-storage', 'sector-storage', 'billing-storage'];
|
||||
authKeys.forEach(key => localStorage.removeItem(key));
|
||||
|
||||
// Reset dependent stores
|
||||
useSiteStore.setState({ activeSite: null });
|
||||
useSectorStore.setState({ activeSector: null, sectors: [] });
|
||||
|
||||
set({ user: null, token: null, isAuthenticated: false });
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| 403 after login | Tokens not persisted before API call | Write to localStorage synchronously |
|
||||
| User swapping | DB-backed sessions with user caching | Redis sessions + NoCacheModelBackend |
|
||||
| Token refresh loop | Refresh token expired | Redirect to login |
|
||||
| API key not working | Missing site scope | Check API key has correct site assigned |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Token blacklist | 🔜 Planned | Proper refresh token invalidation |
|
||||
| 2FA | 🔜 Planned | Two-factor authentication |
|
||||
| SSO | 🔜 Planned | Google/GitHub OAuth |
|
||||
383
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/IGNY8-APP.md
Normal file
383
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/IGNY8-APP.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# IGNY8 - AI-Powered SEO Content Platform
|
||||
|
||||
**Version:** 1.8.4
|
||||
**Last Updated:** January 20, 2026
|
||||
**Status:** Production Ready
|
||||
|
||||
---
|
||||
|
||||
## What is IGNY8?
|
||||
|
||||
IGNY8 is an enterprise-grade AI content platform that transforms keyword research into published, SEO-optimized articles at scale. The platform automates the entire content lifecycle—from discovering keywords to publishing polished articles with AI-generated images—reducing what typically takes days of manual work into hours.
|
||||
|
||||
**The Problem:** Creating SEO content at scale requires keyword research, content planning, writing, image creation, optimization, and publishing—a process that's labor-intensive and inconsistent.
|
||||
|
||||
**The Solution:** IGNY8 provides an end-to-end automated pipeline where AI handles clustering, ideation, writing, and image generation while you maintain editorial control.
|
||||
|
||||
---
|
||||
|
||||
## Platform Architecture
|
||||
|
||||
| Aspect | Details |
|
||||
|--------|---------|
|
||||
| **Type** | Full-stack SaaS Platform |
|
||||
| **Architecture** | Multi-tenant with complete data isolation |
|
||||
| **Target Users** | Content marketers, SEO agencies, digital publishers |
|
||||
| **Deployment** | Docker-based, cloud-hosted |
|
||||
| **Pricing Model** | Credits + Monthly subscription plans |
|
||||
|
||||
---
|
||||
|
||||
## Core Workflow
|
||||
|
||||
IGNY8 follows a structured 8-stage content pipeline:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ SETUP WORKFLOW │
|
||||
│ ───── ──────── │
|
||||
│ Sites → Keywords → Clusters → Ideas → Tasks → Content → Images → Published │
|
||||
│ ↑ ↑ ↑ │
|
||||
│ Configure AI Groups AI Writes │
|
||||
│ WordPress Related Full Articles │
|
||||
│ Keywords + SEO Meta │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Manual Mode
|
||||
Walk through each stage with full control—review and edit at every step.
|
||||
|
||||
### Automation Mode
|
||||
Configure once, let IGNY8 process all 7 stages automatically on a schedule (daily, weekly, or monthly). Content lands in your review queue ready for approval.
|
||||
|
||||
---
|
||||
|
||||
## Feature Deep-Dive
|
||||
|
||||
### 1. Keyword Management (Planner)
|
||||
|
||||
**Import Options:**
|
||||
- Upload CSV with thousands of keywords
|
||||
- Browse 50+ industries of pre-curated seed keywords
|
||||
- Filter by country, difficulty, search volume
|
||||
|
||||
**AI Clustering:**
|
||||
- GPT-4 analyzes keyword intent and relationships
|
||||
- Groups related keywords into topical clusters
|
||||
- Enables one comprehensive article per cluster (instead of keyword stuffing)
|
||||
|
||||
**Metrics Tracked:**
|
||||
- Search volume (monthly)
|
||||
- Keyword difficulty (0-100)
|
||||
- CPC (cost-per-click)
|
||||
- Intent classification (informational, commercial, transactional)
|
||||
|
||||
---
|
||||
|
||||
### 2. Content Ideation (Planner)
|
||||
|
||||
**AI-Generated Ideas:**
|
||||
- Each cluster becomes a content brief
|
||||
- Suggested titles, angles, and outlines
|
||||
- Word count recommendations based on competition
|
||||
- Priority scoring by SEO potential
|
||||
|
||||
**Bulk Operations:**
|
||||
- Generate ideas for multiple clusters at once
|
||||
- Queue ideas directly to Writer module
|
||||
- Batch status updates
|
||||
|
||||
---
|
||||
|
||||
### 3. Content Generation (Writer)
|
||||
|
||||
**AI Writing Engine:**
|
||||
- Powered by GPT-4/GPT-4 Turbo
|
||||
- Produces structured articles (H2s, H3s, lists, paragraphs)
|
||||
- SEO-optimized meta titles and descriptions
|
||||
- Configurable length: 500 to 5,000+ words
|
||||
|
||||
**Content Types:**
|
||||
- Blog posts and articles
|
||||
- How-to guides
|
||||
- Product comparisons
|
||||
- Reviews and roundups
|
||||
|
||||
**Customization:**
|
||||
- Custom prompt additions (append to all AI prompts)
|
||||
- Default tone selection (professional, casual, authoritative, etc.)
|
||||
- Default article length preferences
|
||||
|
||||
**Workflow States:**
|
||||
- Queue → Draft → Review → Published
|
||||
- Each stage has dedicated management views
|
||||
|
||||
---
|
||||
|
||||
### 4. Image Generation (Writer)
|
||||
|
||||
**Dual AI Providers:**
|
||||
| Provider | Quality Tier | Best For |
|
||||
|----------|--------------|----------|
|
||||
| DALL-E 2 | Standard | Fast, economical |
|
||||
| DALL-E 3 | Premium | High quality, detailed |
|
||||
| Runware | Best | Alternative variety |
|
||||
|
||||
**Image Types:**
|
||||
- Featured images (hero/thumbnail)
|
||||
- In-article images (embedded in content)
|
||||
- Desktop and mobile sizes (responsive)
|
||||
|
||||
**Smart Features:**
|
||||
- AI extracts image prompts from article content
|
||||
- Negative prompts for style control
|
||||
- Multiple format support (WebP, JPG, PNG)
|
||||
- Configurable max images per article
|
||||
|
||||
---
|
||||
|
||||
### 5. Automation Pipeline
|
||||
|
||||
**7-Stage Automated Workflow:**
|
||||
```
|
||||
Stage 1: Process new keywords
|
||||
Stage 2: AI cluster keywords
|
||||
Stage 3: Generate content ideas
|
||||
Stage 4: Create writer tasks
|
||||
Stage 5: Generate article content
|
||||
Stage 6: Extract image prompts
|
||||
Stage 7: Generate images → Review queue
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
- Schedule: Daily, weekly, or monthly runs
|
||||
- Run controls: Start, pause, resume
|
||||
- Credit estimation before running
|
||||
- Real-time progress tracking
|
||||
- Activity log and run history
|
||||
|
||||
---
|
||||
|
||||
### 6. WordPress Integration
|
||||
|
||||
**Publishing:**
|
||||
- One-click publish from Review tab
|
||||
- Direct WordPress REST API integration
|
||||
- Supports multiple WordPress sites per account
|
||||
|
||||
**Synchronization:**
|
||||
- Two-way content sync (import/export)
|
||||
- Category and tag mapping
|
||||
- Featured image upload
|
||||
- Post type configuration (posts, pages, custom)
|
||||
|
||||
**Setup:**
|
||||
- Site URL and REST API authentication
|
||||
- Content type mapping in Site Settings
|
||||
- Auto-publish toggle (skip review step)
|
||||
- Auto-sync toggle (keep WordPress updated)
|
||||
|
||||
---
|
||||
|
||||
### 7. Team & Account Management
|
||||
|
||||
**User Roles:**
|
||||
| Role | Permissions |
|
||||
|------|-------------|
|
||||
| Admin | Full access, billing, team management |
|
||||
| Manager | Content + billing view, no team management |
|
||||
| Editor | AI content, clusters, tasks |
|
||||
| Viewer | Read-only dashboards |
|
||||
|
||||
**Team Features:**
|
||||
- Invite team members by email
|
||||
- Remove members
|
||||
- Role display (editing roles coming soon)
|
||||
|
||||
**Account Settings:**
|
||||
- Organization name and billing address
|
||||
- Tax ID / VAT number
|
||||
- Billing email
|
||||
|
||||
---
|
||||
|
||||
### 8. Usage & Billing
|
||||
|
||||
**Credit System:**
|
||||
| Operation | Typical Credits |
|
||||
|-----------|-----------------|
|
||||
| Keyword clustering (batch) | 10 |
|
||||
| Content idea generation | 2 |
|
||||
| Article (per 100 words) | 5 |
|
||||
| Image (standard) | 3 |
|
||||
| Image (premium/best) | 5 |
|
||||
|
||||
**Usage Tracking:**
|
||||
- Real-time credit balance
|
||||
- Monthly usage vs. limits
|
||||
- Transaction history
|
||||
- Hard limits (sites, users, keywords, clusters)
|
||||
- Monthly limits (ideas, words, images)
|
||||
|
||||
**Subscription Plans:**
|
||||
| Plan | Sites | Users | Credits/Month | Best For |
|
||||
|------|-------|-------|---------------|----------|
|
||||
| Free | 1 | 1 | 100 | Trial/Evaluation |
|
||||
| Starter | 3 | 3 | 1,000 | Individual creators |
|
||||
| Growth | 10 | 10 | 5,000 | Small teams |
|
||||
| Scale | Unlimited | Unlimited | 25,000 | Agencies |
|
||||
|
||||
---
|
||||
|
||||
## Module Status
|
||||
|
||||
| Module | Status | Location |
|
||||
|--------|--------|----------|
|
||||
| **Dashboard** | ✅ Active | `/` |
|
||||
| **Add Keywords** | ✅ Active | `/setup/add-keywords` |
|
||||
| **Content Settings** | ✅ Active | `/account/content-settings` |
|
||||
| **Sites** | ✅ Active | `/sites` |
|
||||
| **Thinker** | ✅ Active (Admin) | `/thinker/prompts` |
|
||||
| **Planner** | ✅ Active | `/planner/keywords` |
|
||||
| **Writer** | ✅ Active | `/writer/tasks` |
|
||||
| **Automation** | ✅ Active | `/automation` |
|
||||
| **Account Settings** | ✅ Active | `/account/settings` |
|
||||
| **Plans & Billing** | ✅ Active | `/account/plans` |
|
||||
| **Usage** | ✅ Active | `/account/usage` |
|
||||
| **AI Models** | ✅ Active (Admin) | `/settings/integration` |
|
||||
| **Help** | ✅ Active | `/help` |
|
||||
| **SiteBuilder** | ❌ Deprecated | Removed - was for site structure generation |
|
||||
| **Linker** | ⏸️ Phase 2 | Internal linking suggestions (disabled by default) |
|
||||
| **Optimizer** | ⏸️ Phase 2 | Content optimization (disabled by default) |
|
||||
|
||||
### Module Status Details
|
||||
|
||||
| Module | Status | Notes |
|
||||
|--------|--------|-------|
|
||||
| **SiteBuilder** | ❌ Deprecated | Code exists but feature is removed. Marked for cleanup. |
|
||||
| **Linker** | ⏸️ Phase 2 | Feature flag: `linker_enabled`. Available but disabled by default. |
|
||||
| **Optimizer** | ⏸️ Phase 2 | Feature flag: `optimizer_enabled`. Available but disabled by default. |
|
||||
|
||||
To enable Phase 2 modules, update via Django Admin:
|
||||
- `GlobalModuleSettings` (pk=1) for platform-wide settings
|
||||
- `ModuleEnableSettings` for per-account settings
|
||||
|
||||
---
|
||||
|
||||
## Navigation Structure
|
||||
|
||||
```
|
||||
Sidebar Menu
|
||||
├── Dashboard
|
||||
├── SETUP
|
||||
│ ├── Add Keywords
|
||||
│ ├── Content Settings
|
||||
│ ├── Sites (if enabled)
|
||||
│ └── Thinker (admin only, if enabled)
|
||||
├── WORKFLOW
|
||||
│ ├── Planner (Keywords → Clusters → Ideas)
|
||||
│ ├── Writer (Queue → Drafts → Images → Review → Published)
|
||||
│ ├── Automation
|
||||
│ ├── Linker (if enabled)
|
||||
│ └── Optimizer (if enabled)
|
||||
├── ACCOUNT
|
||||
│ ├── Account Settings (Account → Profile → Team)
|
||||
│ ├── Plans & Billing (Plan → Upgrade → History)
|
||||
│ ├── Usage (Limits → Credit History → API Activity)
|
||||
│ └── AI Models (admin only)
|
||||
└── HELP
|
||||
└── Help & Docs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|------------|
|
||||
| **Backend** | Django 5.x, Django REST Framework, Python 3.11+ |
|
||||
| **Frontend** | React 19, TypeScript, Vite, TailwindCSS |
|
||||
| **Database** | PostgreSQL 15+ |
|
||||
| **Cache/Sessions** | Redis |
|
||||
| **Task Queue** | Celery + Celery Beat |
|
||||
| **AI Services** | OpenAI GPT-4, DALL-E 3, Runware |
|
||||
| **Deployment** | Docker, Docker Compose |
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
- **Data Isolation:** Complete multi-tenant separation at database level
|
||||
- **Authentication:** JWT tokens with Redis session management
|
||||
- **Encryption:** Data encrypted at rest and in transit
|
||||
- **Access Control:** Role-based permissions per account
|
||||
- **Session Security:** Secure cookie handling, session integrity checks
|
||||
|
||||
---
|
||||
|
||||
## Current Limitations & Known Issues
|
||||
|
||||
**Payment Processing:**
|
||||
- Stripe/PayPal pending production credentials
|
||||
- Manual payment methods available
|
||||
|
||||
**Pending Backend Implementation:**
|
||||
- Content Generation settings (append prompt, tone, length) - UI exists, API pending
|
||||
- Publishing settings (auto-publish, sync) - UI exists, API pending
|
||||
- Profile settings save - UI exists, API pending
|
||||
- Password change functionality
|
||||
- API Activity tracking (currently placeholder data)
|
||||
|
||||
**Disabled Modules:**
|
||||
- Linker (internal linking) - Available but disabled
|
||||
- Optimizer (content optimization) - Available but disabled
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### For New Users
|
||||
1. Create account and verify email
|
||||
2. Create your first site (industry + sectors)
|
||||
3. Connect WordPress (optional)
|
||||
4. Add keywords from seed database or import CSV
|
||||
5. Run AI clustering on keywords
|
||||
6. Generate content ideas from clusters
|
||||
7. Queue ideas to Writer
|
||||
8. Generate content and images
|
||||
9. Review and publish
|
||||
|
||||
### For Admins
|
||||
1. Configure AI Models (OpenAI API key, Runware key)
|
||||
2. Customize prompts in Thinker module
|
||||
3. Set up global module settings
|
||||
4. Configure automation schedules
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Location |
|
||||
|----------|----------|
|
||||
| Technical Docs | `/docs/INDEX.md` |
|
||||
| API Reference | `/docs/20-API/` |
|
||||
| Pre-Launch Audit | `/PRE-LAUNCH-AUDIT.md` |
|
||||
| Changelog | `/CHANGELOG.md` |
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Highlights |
|
||||
|---------|------|------------|
|
||||
| **1.1.0** | Dec 25, 2025 | UX overhaul, page consolidation, pre-launch audit |
|
||||
| 1.0.5 | Dec 12, 2025 | Purchase Credits tab |
|
||||
| 1.0.4 | Dec 12, 2025 | Credit Activity tab |
|
||||
| 1.0.3 | Dec 12, 2025 | Usage Limits improvements |
|
||||
| 1.0.2 | Dec 12, 2025 | Design system enforcement |
|
||||
| 1.0.1 | Dec 12, 2025 | Plan limits UI |
|
||||
| 1.0.0 | Dec 12, 2025 | Initial production release |
|
||||
|
||||
---
|
||||
|
||||
*For detailed technical implementation, see the `/docs` folder. For known issues and improvement roadmap, see `/PRE-LAUNCH-AUDIT.md`.*
|
||||
@@ -0,0 +1,268 @@
|
||||
# Repository Structure (App Repo)
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Scope:** `/data/app/igny8` (app repo)
|
||||
**Excluded from tree:** `.git/`, `node_modules/`, `dist/`, `staticfiles/`, `backups/`, `__pycache__/`
|
||||
|
||||
---
|
||||
|
||||
## Root
|
||||
|
||||
```
|
||||
/data/app/igny8
|
||||
├── backend/
|
||||
├── docs/
|
||||
├── frontend/
|
||||
├── KW_DB/
|
||||
├── plugins/
|
||||
├── scripts/
|
||||
├── .claude/
|
||||
├── .gitignore
|
||||
├── .rules
|
||||
├── CHANGELOG.md
|
||||
├── CLEANUP_SUMMARY_20260113.md
|
||||
├── content_generation.md
|
||||
├── docker-compose.app.yml
|
||||
├── git-credential-helper.sh
|
||||
├── idea_genration.md
|
||||
├── last-session.md
|
||||
├── README.md
|
||||
└── test_routes.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend
|
||||
|
||||
```
|
||||
backend/
|
||||
├── igny8_core/
|
||||
├── migrations/
|
||||
├── logs/
|
||||
├── scripts/
|
||||
├── Dockerfile
|
||||
├── container_startup.sh
|
||||
├── create_groups.py
|
||||
├── manage.py
|
||||
├── requirements.txt
|
||||
└── seed_keywords_import_template.csv
|
||||
```
|
||||
|
||||
### Django App Core
|
||||
|
||||
```
|
||||
backend/igny8_core/
|
||||
├── admin/
|
||||
├── ai/
|
||||
├── api/
|
||||
├── auth/
|
||||
├── business/
|
||||
├── common/
|
||||
├── management/
|
||||
├── middleware/
|
||||
├── migrations/
|
||||
├── modules/
|
||||
├── plugins/
|
||||
├── static/
|
||||
├── tasks/
|
||||
├── templates/
|
||||
├── urls/
|
||||
├── utils/
|
||||
├── __init__.py
|
||||
├── asgi.py
|
||||
├── celery.py
|
||||
├── settings.py
|
||||
├── tasks.py
|
||||
├── urls.py
|
||||
└── wsgi.py
|
||||
```
|
||||
|
||||
### Feature Modules
|
||||
|
||||
```
|
||||
backend/igny8_core/modules/
|
||||
├── billing/
|
||||
├── integration/
|
||||
├── linker/
|
||||
├── optimizer/
|
||||
├── planner/
|
||||
├── publisher/
|
||||
├── system/
|
||||
└── writer/
|
||||
```
|
||||
|
||||
### Business Services
|
||||
|
||||
```
|
||||
backend/igny8_core/business/
|
||||
├── automation/
|
||||
├── billing/
|
||||
├── content/
|
||||
├── integration/
|
||||
├── linking/
|
||||
├── notifications/
|
||||
├── optimization/
|
||||
├── planning/
|
||||
└── publishing/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── public/
|
||||
├── src/
|
||||
├── audit-results/
|
||||
├── eslint/
|
||||
├── Caddyfile
|
||||
├── Caddyfile.marketing
|
||||
├── Dockerfile
|
||||
├── Dockerfile.dev
|
||||
├── Dockerfile.marketing
|
||||
├── Dockerfile.marketing.dev
|
||||
├── LICENSE.md
|
||||
├── README.md
|
||||
├── container_startup.sh
|
||||
├── eslint.config.js
|
||||
├── index.html
|
||||
├── marketing.html
|
||||
├── package.json
|
||||
├── package-lock.json
|
||||
├── postcss.config.js
|
||||
├── tsconfig.app.json
|
||||
├── tsconfig.json
|
||||
├── tsconfig.node.json
|
||||
├── vite.config.ts
|
||||
└── vitest.config.ts
|
||||
```
|
||||
|
||||
### Frontend Source
|
||||
|
||||
```
|
||||
frontend/src/
|
||||
├── api/
|
||||
├── components/
|
||||
├── config/
|
||||
├── constants/
|
||||
├── context/
|
||||
├── hooks/
|
||||
├── icons/
|
||||
├── layout/
|
||||
├── marketing/
|
||||
├── modules/
|
||||
├── pages/
|
||||
├── services/
|
||||
├── store/
|
||||
├── styles/
|
||||
├── templates/
|
||||
├── types/
|
||||
├── utils/
|
||||
├── App.tsx
|
||||
├── main.tsx
|
||||
├── svg.d.ts
|
||||
└── vite-env.d.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
```
|
||||
docs/
|
||||
├── 00-SYSTEM/
|
||||
├── 10-MODULES/
|
||||
├── 20-API/
|
||||
├── 30-FRONTEND/
|
||||
├── 40-WORKFLOWS/
|
||||
├── 50-DEPLOYMENT/
|
||||
├── 60-PLUGINS/
|
||||
├── 90-REFERENCE/
|
||||
├── audits/
|
||||
├── plans/
|
||||
├── FILTER_GUIDELINES.md
|
||||
└── INDEX.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plugins
|
||||
|
||||
```
|
||||
plugins/
|
||||
└── wordpress/
|
||||
├── dist/
|
||||
├── source/
|
||||
│ └── igny8-wp-bridge/
|
||||
│ ├── admin/
|
||||
│ ├── data/
|
||||
│ ├── docs/
|
||||
│ ├── includes/
|
||||
│ ├── languages/
|
||||
│ ├── sync/
|
||||
│ ├── templates/
|
||||
│ ├── tests/
|
||||
│ ├── igny8-bridge.php
|
||||
│ └── uninstall.php
|
||||
└── UI-REDESIGN-v1.2.0.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## KW_DB (Seed Keyword CSV Library)
|
||||
|
||||
Top-level categories and subcategories (CSV files omitted for brevity):
|
||||
|
||||
```
|
||||
KW_DB/
|
||||
├── Advertising_&_Media/
|
||||
│ └── Printing_Services/
|
||||
├── Apparel_&_Fashion/
|
||||
│ ├── Jewelery/
|
||||
│ ├── Menswear/
|
||||
│ └── Womens_Wear/
|
||||
├── Automotive/
|
||||
│ ├── Car_Accessories/
|
||||
│ ├── Cars_General/
|
||||
│ └── Tyres_&_Wheels/
|
||||
├── Beauty_&_Personal_Care/
|
||||
│ ├── Haircare/
|
||||
│ ├── Makeup_&_Cosmetics/
|
||||
│ └── Skincare/
|
||||
├── Finance_&_Insurance/
|
||||
│ ├── Insurance/
|
||||
│ ├── Investment_&_Weallth_Management/
|
||||
│ └── Loans_&_Lending/
|
||||
├── Food_&_Beverage/
|
||||
│ ├── Food Delivery/
|
||||
│ └── Restaurants/
|
||||
├── HealthCare_Medical/
|
||||
│ ├── Health_Practitioners/
|
||||
│ ├── Massage_&_Therapy/
|
||||
│ ├── Physiotherapy_Rehabilitation/
|
||||
│ ├── Relaxation_Devices/
|
||||
│ └── Wellness_&_Fitness/
|
||||
├── Home_&_Garden/
|
||||
│ ├── Bedding_&_Mattress/
|
||||
│ ├── Furniture/
|
||||
│ ├── Home_Decor/
|
||||
│ └── Storage_&_Organization/
|
||||
├── management/
|
||||
├── Real_Estate_&_Construction/
|
||||
└── Technology_&_IT_Services/
|
||||
├── AI/
|
||||
├── cloud_services/
|
||||
├── DIgital_Marketing_&_SEO/
|
||||
├── SAAS/
|
||||
└── Web_Development_&_Design/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scripts
|
||||
|
||||
```
|
||||
scripts/
|
||||
└── package_app.sh
|
||||
```
|
||||
302
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/TENANCY.md
Normal file
302
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/TENANCY.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# Multi-Tenancy Architecture
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Backend Path:** `backend/igny8_core/auth/models.py`
|
||||
|
||||
---
|
||||
|
||||
## Data Hierarchy
|
||||
|
||||
```
|
||||
Account (Tenant)
|
||||
├── Users (team members)
|
||||
├── Subscription (plan binding)
|
||||
├── Sites
|
||||
│ ├── Sectors
|
||||
│ │ ├── Keywords
|
||||
│ │ ├── Clusters
|
||||
│ │ ├── ContentIdeas
|
||||
│ │ ├── Tasks
|
||||
│ │ ├── Content
|
||||
│ │ └── Images
|
||||
│ └── Integrations (WordPress)
|
||||
└── Billing (credits, transactions)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Models
|
||||
|
||||
### Account
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| name | CharField | Account/organization name |
|
||||
| is_active | Boolean | Enable/disable account |
|
||||
| credits | Decimal | Plan credits (reset on renewal) |
|
||||
| bonus_credits | Decimal | Purchased credits (never expire) |
|
||||
| account_timezone | CharField | IANA timezone (e.g., America/New_York) |
|
||||
| stripe_customer_id | CharField | Stripe integration |
|
||||
| paypal_customer_id | CharField | PayPal integration |
|
||||
| created_at | DateTime | Registration date |
|
||||
|
||||
> **Two-Pool Credit System (v1.8.3):** Plan credits consumed first, bonus credits only when plan = 0. Bonus credits never expire.
|
||||
|
||||
### Plan
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| name | CharField | Plan name (Free, Starter, Growth, Scale) |
|
||||
| included_credits | Integer | Monthly credit allocation |
|
||||
| max_sites | Integer | Site limit (hard limit) |
|
||||
| max_users | Integer | User limit (hard limit) |
|
||||
| max_keywords | Integer | Keyword limit (hard limit) |
|
||||
| max_ahrefs_queries | Integer | Monthly Ahrefs queries (monthly limit) |
|
||||
| is_active | Boolean | Available for purchase |
|
||||
| is_internal | Boolean | Internal/test plan |
|
||||
|
||||
> **Note (v1.5.0+):** Operation limits like `max_clusters`, `max_content_words`, `max_images_basic`, `max_images_premium` were removed. All AI operations are now credit-based.
|
||||
|
||||
### Site
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | ForeignKey | Owner account |
|
||||
| name | CharField | Site name |
|
||||
| domain | CharField | Primary domain |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
|
||||
### Sector
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| site | ForeignKey | Parent site |
|
||||
| account | ForeignKey | Owner account |
|
||||
| industry | ForeignKey | Industry template |
|
||||
| name | CharField | Sector name |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
|
||||
---
|
||||
|
||||
## Base Model Classes
|
||||
|
||||
### AccountBaseModel
|
||||
|
||||
**File:** `auth/models.py`
|
||||
|
||||
All account-scoped models inherit from this:
|
||||
|
||||
```python
|
||||
class AccountBaseModel(models.Model):
|
||||
account = models.ForeignKey(Account, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
```
|
||||
|
||||
**Used by:** Billing records, Settings, API keys
|
||||
|
||||
### SiteSectorBaseModel
|
||||
|
||||
**File:** `auth/models.py`
|
||||
|
||||
All content models inherit from this:
|
||||
|
||||
```python
|
||||
class SiteSectorBaseModel(AccountBaseModel):
|
||||
site = models.ForeignKey(Site, on_delete=models.CASCADE)
|
||||
sector = models.ForeignKey(Sector, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
```
|
||||
|
||||
**Used by:** Keywords, Clusters, Ideas, Tasks, Content, Images
|
||||
|
||||
---
|
||||
|
||||
## ViewSet Base Classes
|
||||
|
||||
### AccountModelViewSet
|
||||
|
||||
**File:** `api/base.py`
|
||||
|
||||
Automatically filters queryset by account:
|
||||
|
||||
```python
|
||||
class AccountModelViewSet(ModelViewSet):
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
if not self.request.user.is_admin_or_developer:
|
||||
qs = qs.filter(account=self.request.account)
|
||||
return qs
|
||||
```
|
||||
|
||||
### SiteSectorModelViewSet
|
||||
|
||||
**File:** `api/base.py`
|
||||
|
||||
Filters by account + site + sector:
|
||||
|
||||
```python
|
||||
class SiteSectorModelViewSet(AccountModelViewSet):
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
site_id = self.request.query_params.get('site_id')
|
||||
sector_id = self.request.query_params.get('sector_id')
|
||||
if site_id:
|
||||
qs = qs.filter(site_id=site_id)
|
||||
if sector_id:
|
||||
qs = qs.filter(sector_id=sector_id)
|
||||
return qs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Where Tenancy Applies
|
||||
|
||||
### Uses Site/Sector Filtering
|
||||
|
||||
| Module | Filter |
|
||||
|--------|--------|
|
||||
| Planner (Keywords, Clusters, Ideas) | site + sector |
|
||||
| Writer (Tasks, Content, Images) | site + sector |
|
||||
| Linker | site + sector |
|
||||
| Optimizer | site + sector |
|
||||
| Setup/Add Keywords | site + sector |
|
||||
|
||||
### Account-Level Only (No Site/Sector)
|
||||
|
||||
| Module | Filter |
|
||||
|--------|--------|
|
||||
| Billing/Plans | account only |
|
||||
| Account Settings | account only |
|
||||
| Team Management | account only |
|
||||
| User Profile | user only |
|
||||
| System Settings | account only |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### Site Selection
|
||||
|
||||
**Store:** `store/siteStore.ts`
|
||||
|
||||
```typescript
|
||||
const useSiteStore = create({
|
||||
activeSite: Site | null,
|
||||
sites: Site[],
|
||||
loadSites: () => Promise<void>,
|
||||
setActiveSite: (site: Site) => void,
|
||||
});
|
||||
```
|
||||
|
||||
### Sector Selection
|
||||
|
||||
**Store:** `store/sectorStore.ts`
|
||||
|
||||
```typescript
|
||||
const useSectorStore = create({
|
||||
activeSector: Sector | null,
|
||||
sectors: Sector[],
|
||||
loadSectorsForSite: (siteId: number) => Promise<void>,
|
||||
setActiveSector: (sector: Sector) => void,
|
||||
});
|
||||
```
|
||||
|
||||
### Sector Loading Pattern
|
||||
|
||||
Sectors are loaded by `PageHeader` component, not `AppLayout`:
|
||||
|
||||
```typescript
|
||||
// PageHeader.tsx
|
||||
useEffect(() => {
|
||||
if (hideSiteSector) return; // Skip for account pages
|
||||
|
||||
if (activeSite?.id && activeSite?.is_active) {
|
||||
loadSectorsForSite(activeSite.id);
|
||||
}
|
||||
}, [activeSite?.id, hideSiteSector]);
|
||||
```
|
||||
|
||||
**Pages with `hideSiteSector={true}`:**
|
||||
- `/account/*` (settings, team, billing)
|
||||
- User profile pages
|
||||
|
||||
---
|
||||
|
||||
## Global Resources (No Tenancy)
|
||||
|
||||
These are system-wide, not tenant-specific:
|
||||
|
||||
| Model | Purpose |
|
||||
|-------|---------|
|
||||
| Industry | Global industry taxonomy |
|
||||
| IndustrySector | Sub-categories within industries |
|
||||
| SeedKeyword | Global keyword database |
|
||||
| GlobalIntegrationSettings | Platform API keys |
|
||||
| GlobalAIPrompt | Default prompt templates |
|
||||
| GlobalAuthorProfile | Author persona templates |
|
||||
|
||||
---
|
||||
|
||||
## Tenant Data vs System Data
|
||||
|
||||
### System Data (KEEP on reset)
|
||||
|
||||
- Plans, CreditPackages
|
||||
- Industries, IndustrySectors
|
||||
- GlobalIntegrationSettings
|
||||
- GlobalAIPrompt, GlobalAuthorProfile
|
||||
- CreditCostConfig
|
||||
- PaymentMethodConfig
|
||||
|
||||
### Tenant Data (CLEAN on reset)
|
||||
|
||||
- Accounts, Users, Sites, Sectors
|
||||
- Keywords, Clusters, Ideas, Tasks, Content, Images
|
||||
- Subscriptions, Invoices, Payments
|
||||
- CreditTransactions, CreditUsageLogs
|
||||
- SiteIntegrations, SyncEvents
|
||||
- AutomationConfigs, AutomationRuns
|
||||
|
||||
---
|
||||
|
||||
## Admin/Developer Bypass
|
||||
|
||||
Admin and developer users can access all tenants:
|
||||
|
||||
```python
|
||||
# In auth/models.py
|
||||
class User:
|
||||
@property
|
||||
def is_admin_or_developer(self):
|
||||
return self.role in ['admin', 'developer']
|
||||
```
|
||||
|
||||
**Bypass Rules:**
|
||||
- Admin: Full access to own account
|
||||
- Developer: Full access to ALL accounts
|
||||
- System accounts protected from deletion
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Data leak between accounts | Missing account filter | Use AccountModelViewSet |
|
||||
| Wrong site data | Site not validated for account | Validate site.account == request.account |
|
||||
| Sector not loading | Site changed but sector not reset | Clear activeSector when activeSite changes |
|
||||
| Admin can't see all data | Incorrect role check | Check is_admin_or_developer |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Account switching | 🔜 Planned | Allow users to switch between accounts |
|
||||
| Sub-accounts | 🔜 Planned | Hierarchical account structure |
|
||||
66
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/TIMEZONE.md
Normal file
66
v2/Live Docs on Server/igny8-app-docs/00-SYSTEM/TIMEZONE.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Timezone Standard
|
||||
|
||||
## Purpose
|
||||
This document defines the single-source timezone standard for IGNY8 and how all future time-related features must use it.
|
||||
|
||||
## Single Source of Truth
|
||||
- **Account timezone** is the canonical timezone for all user-visible times and scheduling UI.
|
||||
- The value is stored on the Account model as `account_timezone` (IANA name, e.g., `America/New_York`).
|
||||
- Selection modes:
|
||||
- **Country-derived**: timezone is derived from billing country.
|
||||
- **Manual**: user picks an IANA timezone that maps to a UTC offset list.
|
||||
|
||||
## Storage Rules
|
||||
- **Persist timestamps in UTC** in the database.
|
||||
- **Never store local times** without timezone context.
|
||||
- Store user selection in `Account.account_timezone` and (when needed) `timezone_mode` and `timezone_offset` for UI display.
|
||||
|
||||
## Display Rules (Frontend)
|
||||
- All UI formatting must use the account timezone.
|
||||
- Use shared helpers:
|
||||
- `getAccountTimezone()` for the active timezone.
|
||||
- `formatDate()`, `formatDateTime()`, `formatRelativeDate()` for consistent formatting.
|
||||
- **Do not** call `toLocaleDateString()` or `toLocaleTimeString()` without passing the account timezone.
|
||||
|
||||
## Scheduling Rules
|
||||
- All scheduling inputs in UI are **account timezone**.
|
||||
- Convert to UTC before sending to the backend.
|
||||
- All API payloads for scheduling must send ISO-8601 with timezone offset.
|
||||
- The backend stores scheduled datetimes in UTC.
|
||||
|
||||
## Backend API Contract
|
||||
- Endpoints that return timestamps should return UTC ISO strings.
|
||||
- Endpoints that return “server time” should return **account-local time** for display, plus the account timezone identifier.
|
||||
- If the account timezone is invalid or missing, fall back to `UTC`.
|
||||
|
||||
## Country List Source
|
||||
- Country list must be fetched from `/v1/auth/countries/`.
|
||||
- No hardcoded country lists in UI or backend responses.
|
||||
|
||||
## Implementation Checklist (New Features)
|
||||
1. **Input**: confirm user inputs are in account timezone.
|
||||
2. **Conversion**: convert to UTC before persistence or scheduling.
|
||||
3. **Storage**: store in UTC only.
|
||||
4. **Output**: format all timestamps with account timezone helpers.
|
||||
5. **API**: ensure responses include timezone-aware context when needed.
|
||||
|
||||
## Guardrails
|
||||
- Never introduce a second timezone source per user/site.
|
||||
- Do not mix server timezone with account timezone in UI.
|
||||
- Avoid timezone math in the UI; prefer helpers and backend-provided values when possible.
|
||||
|
||||
## Examples
|
||||
- **Display date in UI**:
|
||||
- Use `formatDateTime(timestamp)` to render in account timezone.
|
||||
- **Schedule content**:
|
||||
- User selects date/time in account timezone → convert to ISO → send to `/schedule/`.
|
||||
|
||||
## Troubleshooting
|
||||
- If times appear “off”:
|
||||
- Check account timezone is set.
|
||||
- Confirm UI uses helpers.
|
||||
- Confirm backend converts to UTC before save.
|
||||
|
||||
---
|
||||
Owner: Platform
|
||||
Last updated: 2026-01-19
|
||||
452
v2/Live Docs on Server/igny8-app-docs/10-MODULES/AUTOMATION.md
Normal file
452
v2/Live Docs on Server/igny8-app-docs/10-MODULES/AUTOMATION.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# Automation Module
|
||||
|
||||
**Last Verified:** January 18, 2026
|
||||
**Version:** 1.8.1
|
||||
**Status:** ✅ Active
|
||||
**Backend Path:** `backend/igny8_core/business/automation/`
|
||||
**Frontend Path:** `frontend/src/pages/Automation/`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Models | `business/automation/models.py` | `AutomationConfig`, `AutomationRun`, `DefaultAutomationConfig` |
|
||||
| Service | `business/automation/services/automation_service.py` | `AutomationService` |
|
||||
| Logger | `business/automation/services/automation_logger.py` | `AutomationLogger` |
|
||||
| Celery Tasks | `business/automation/tasks.py` | `run_automation_task`, `check_scheduled_automations`, `check_test_triggers` |
|
||||
| Publishing Tasks | `igny8_core/tasks/publishing_scheduler.py` | Scheduled publishing |
|
||||
| **Unified Settings** | `api/unified_settings.py` | **v1.8.0** Consolidated settings API |
|
||||
| **Default Settings API** | `api/unified_settings.py` | **v1.8.1** `DefaultSettingsAPIView` for reset |
|
||||
| Frontend Overview | `pages/Automation/AutomationOverview.tsx` | **v1.8.0** Run history dashboard |
|
||||
| Frontend Run Detail | `pages/Automation/AutomationRunDetail.tsx` | **v1.8.0** Detailed run view |
|
||||
| Frontend Manual Run | `pages/Automation/AutomationPage.tsx` | Manual run UI |
|
||||
| **Site Settings** | `pages/Sites/AIAutomationSettings.tsx` | **v1.8.0** Unified settings UI |
|
||||
| Progress Bar | `components/Automation/GlobalProgressBar.tsx` | Full pipeline progress |
|
||||
| Processing Card | `components/Automation/CurrentProcessingCard.tsx` | Real-time progress |
|
||||
| **Scheduling Doc** | `docs/40-WORKFLOWS/AUTOMATION-AND-PUBLISHING-SCHEDULING.md` | **v1.8.1** Complete scheduling guide |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Automation module runs the complete 7-stage content pipeline automatically:
|
||||
|
||||
```
|
||||
Keywords → Clusters → Ideas → Tasks → Content → Image Prompts → Images → Published
|
||||
```
|
||||
|
||||
**Settings Location (v1.8.0+):** Site Settings → Automation tab
|
||||
|
||||
---
|
||||
|
||||
## Scheduling (v1.8.1)
|
||||
|
||||
| Task | Schedule | Purpose |
|
||||
|------|----------|---------|
|
||||
| `check_scheduled_automations` | Every hour at `:05` | Check if automations should run |
|
||||
| `check_test_triggers` | Every minute | Check for admin test triggers |
|
||||
|
||||
**How it works:**
|
||||
- Users select hour (12-hour AM/PM format), stored as `HH:00` (24-hour)
|
||||
- Celery compares `scheduled_hour == current_hour`
|
||||
- 23-hour block prevents re-runs within same day
|
||||
|
||||
**Test Mode (Admin):**
|
||||
- `test_mode_enabled` + `test_trigger_at` fields on `AutomationConfig`
|
||||
- Allows immediate triggering without waiting for schedule
|
||||
- Bypasses 23-hour blocking
|
||||
|
||||
---
|
||||
|
||||
## 7-Stage Pipeline
|
||||
|
||||
| Stage | Name | AI Function | Credit Cost | Can Skip |
|
||||
|-------|------|-------------|-------------|----------|
|
||||
| 1 | Keywords → Clusters | `AutoClusterFunction` | Per batch | ✅ |
|
||||
| 2 | Clusters → Ideas | `GenerateIdeasFunction` | Per idea | ✅ |
|
||||
| 3 | Ideas → Tasks | None (local) | None | ✅ |
|
||||
| 4 | Tasks → Content | `GenerateContentFunction` | Per 100 words | ✅ |
|
||||
| 5 | Content → Image Prompts | `GenerateImagePromptsFunction` | Per prompt | ✅ |
|
||||
| 6 | Image Prompts → Images | `process_image_generation_queue` | Per image | ✅ |
|
||||
| 7 | Review → Published | Publishing Scheduler | None | ✅ |
|
||||
|
||||
**Note:** Stage 7 uses the Publishing Scheduler with `PublishingSettings` for auto-approval and scheduling.
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### AutomationConfig
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Target site |
|
||||
| enabled | Boolean | Enable/disable automation |
|
||||
| frequency | CharField | hourly/daily/weekly |
|
||||
| scheduled_time | TimeField | Time to run |
|
||||
| stage_1_batch_size | Integer | Keywords per batch |
|
||||
| stage_2_batch_size | Integer | Clusters per batch |
|
||||
| stage_3_batch_size | Integer | Ideas per batch |
|
||||
| stage_4_batch_size | Integer | Tasks per batch |
|
||||
| stage_5_batch_size | Integer | Content per batch |
|
||||
| stage_6_batch_size | Integer | Images per batch |
|
||||
| **stage_1_enabled** | Boolean | **v1.8.0** Enable stage 1 |
|
||||
| **stage_2_enabled** | Boolean | **v1.8.0** Enable stage 2 |
|
||||
| **stage_3_enabled** | Boolean | **v1.8.0** Enable stage 3 |
|
||||
| **stage_4_enabled** | Boolean | **v1.8.0** Enable stage 4 |
|
||||
| **stage_5_enabled** | Boolean | **v1.8.0** Enable stage 5 |
|
||||
| **stage_6_enabled** | Boolean | **v1.8.0** Enable stage 6 |
|
||||
| **stage_7_enabled** | Boolean | **v1.8.0** Enable stage 7 |
|
||||
| **stage_X_use_testing** | Boolean | **v1.8.1** Use testing model (AI stages 1,2,4,5,6) |
|
||||
| **stage_X_budget_pct** | Integer | **v1.8.1** Credit budget % (AI stages) |
|
||||
| **max_keywords_per_run** | Integer | **v1.8.0** Per-run limit stage 1 |
|
||||
| **max_clusters_per_run** | Integer | **v1.8.0** Per-run limit stage 2 |
|
||||
| **max_ideas_per_run** | Integer | **v1.8.0** Per-run limit stage 3 |
|
||||
| **max_tasks_per_run** | Integer | **v1.8.0** Per-run limit stage 4 |
|
||||
| **max_content_per_run** | Integer | **v1.8.0** Per-run limit stage 5 |
|
||||
| **max_images_per_run** | Integer | **v1.8.0** Per-run limit stage 6 |
|
||||
| within_stage_delay | Integer | Seconds between batches |
|
||||
| between_stage_delay | Integer | Seconds between stages |
|
||||
| last_run_at | DateTime | Last execution |
|
||||
| next_run_at | DateTime | Next scheduled run |
|
||||
| **test_mode_enabled** | Boolean | **v1.8.1** Enable test mode for admin |
|
||||
| **test_trigger_at** | DateTime | **v1.8.1** When to trigger test run |
|
||||
|
||||
### DefaultAutomationConfig (v1.8.1)
|
||||
|
||||
Singleton model for centralized default settings. See [AUTOMATION-AND-PUBLISHING-SCHEDULING.md](../40-WORKFLOWS/AUTOMATION-AND-PUBLISHING-SCHEDULING.md) for full schema.
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| is_enabled | Boolean | Default: Enable scheduled automation |
|
||||
| frequency | CharField | Default frequency (daily/weekly/monthly) |
|
||||
| next_scheduled_hour | Integer | Next hour to assign (auto-increments) |
|
||||
| stage_X_enabled | Boolean | Default: Enable each stage |
|
||||
| stage_X_batch_size | Integer | Default: Batch size per stage |
|
||||
| stage_X_use_testing | Boolean | Default: Use testing model |
|
||||
| stage_X_budget_pct | Integer | Default: Credit budget % |
|
||||
| max_X_per_run | Integer | Default: Per-run limits |
|
||||
| auto_approval_enabled | Boolean | Default: Auto-approve content |
|
||||
| auto_publish_enabled | Boolean | Default: Auto-publish content |
|
||||
| publish_days | JSONField | Default: Days to publish |
|
||||
| publish_time_slots | JSONField | Default: Time slots to publish |
|
||||
|
||||
### AutomationRun
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| config | FK | Parent config |
|
||||
| trigger_type | CharField | manual/scheduled |
|
||||
| status | CharField | running/paused/cancelled/completed/failed |
|
||||
| current_stage | Integer | Current stage (1-7) |
|
||||
| started_at | DateTime | Start time |
|
||||
| paused_at | DateTime | Pause time (nullable) |
|
||||
| resumed_at | DateTime | Resume time (nullable) |
|
||||
| cancelled_at | DateTime | Cancel time (nullable) |
|
||||
| completed_at | DateTime | Completion time (nullable) |
|
||||
| total_credits_used | Decimal | Total credits consumed |
|
||||
| **initial_snapshot** | JSON | **v1.3.0** Queue sizes at run start |
|
||||
| stage_1_result | JSON | Stage 1 results |
|
||||
| stage_2_result | JSON | Stage 2 results |
|
||||
| stage_3_result | JSON | Stage 3 results |
|
||||
| stage_4_result | JSON | Stage 4 results |
|
||||
| stage_5_result | JSON | Stage 5 results |
|
||||
| stage_6_result | JSON | Stage 6 results |
|
||||
| stage_7_result | JSON | Stage 7 results |
|
||||
| error_message | TextField | Error details (nullable) |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/automation/config/` | Get/create config | Get automation config |
|
||||
| PUT | `/api/v1/automation/update_config/` | Update config | Update settings |
|
||||
| POST | `/api/v1/automation/run_now/` | Start manual run | Start automation |
|
||||
| GET | `/api/v1/automation/current_run/` | Get current run | Run status/progress |
|
||||
| GET | `/api/v1/automation/pipeline_overview/` | Get pipeline | Stage status counts |
|
||||
| GET | `/api/v1/automation/current_processing/` | Get processing | Live processing status |
|
||||
| **GET** | `/api/v1/automation/run_progress/` | **v1.3.0** | Unified progress data |
|
||||
| POST | `/api/v1/automation/pause/` | Pause run | Pause after current item |
|
||||
| POST | `/api/v1/automation/resume/` | Resume run | Resume from saved stage |
|
||||
| POST | `/api/v1/automation/cancel/` | Cancel run | Cancel after current item |
|
||||
| GET | `/api/v1/automation/history/` | Get history | Last 20 runs |
|
||||
| GET | `/api/v1/automation/logs/` | Get logs | Activity log for run |
|
||||
| GET | `/api/v1/automation/estimate/` | Get estimate | Credit estimate |
|
||||
| **GET** | `/api/v1/integration/settings/defaults/` | **v1.8.1** | Get default settings for reset |
|
||||
|
||||
**Query Parameters:** All require `?site_id=`, run-specific require `?run_id=`
|
||||
|
||||
### Default Settings Endpoint (v1.8.1)
|
||||
|
||||
Returns centralized defaults from `DefaultAutomationConfig`:
|
||||
```json
|
||||
{
|
||||
"automation": { "enabled": false, "frequency": "daily", "time": "02:00" },
|
||||
"stages": [
|
||||
{ "number": 1, "enabled": true, "batch_size": 50, "per_run_limit": 0, "use_testing": false, "budget_pct": 15 },
|
||||
...
|
||||
],
|
||||
"delays": { "within_stage": 3, "between_stage": 5 },
|
||||
"publishing": {
|
||||
"auto_approval_enabled": false,
|
||||
"auto_publish_enabled": false,
|
||||
"daily_publish_limit": 3,
|
||||
"publish_days": ["mon", "tue", "wed", "thu", "fri"],
|
||||
"time_slots": ["09:00", "14:00", "18:00"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### run_progress Endpoint (v1.3.0)
|
||||
|
||||
Returns unified progress data for frontend:
|
||||
```json
|
||||
{
|
||||
"run": { "run_id": "...", "status": "running", "current_stage": 3 },
|
||||
"global_progress": { "total_items": 100, "completed_items": 45, "percentage": 45 },
|
||||
"stages": [
|
||||
{ "number": 1, "status": "completed", "input_count": 50, "processed_count": 50 },
|
||||
...
|
||||
],
|
||||
"metrics": { "credits_used": 120, "duration_seconds": 3600 },
|
||||
"initial_snapshot": { "stage_1_initial": 50, ... }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Flow
|
||||
|
||||
### Manual Run
|
||||
|
||||
1. User clicks "Run Now" on frontend
|
||||
2. Frontend calls `POST /automation/run_now/?site_id=X`
|
||||
3. Backend acquires cache lock `automation_lock_{site_id}`
|
||||
4. **v1.3.0:** Captures initial snapshot with `_capture_initial_snapshot()`
|
||||
5. Estimates credits required (1.2x buffer)
|
||||
6. Validates balance >= estimate
|
||||
7. Creates `AutomationRun` record
|
||||
8. Enqueues `run_automation_task` Celery task
|
||||
8. Returns run ID immediately
|
||||
|
||||
### Stage Execution
|
||||
|
||||
For each stage (1-7):
|
||||
|
||||
1. Check `_check_should_stop()` (paused/cancelled?)
|
||||
2. Load items for processing
|
||||
3. Process in batches (respecting batch_size)
|
||||
4. For AI stages: Call AIEngine function
|
||||
5. Wait `within_stage_delay` between batches
|
||||
6. Save stage result JSON
|
||||
7. Wait `between_stage_delay` before next stage
|
||||
|
||||
### Stage Result Fields
|
||||
|
||||
**Stage 1 (Clustering):**
|
||||
```json
|
||||
{
|
||||
"keywords_processed": 150,
|
||||
"clusters_created": 12,
|
||||
"batches_run": 3,
|
||||
"credits_used": 45,
|
||||
"time_elapsed": 120,
|
||||
"skipped": false,
|
||||
"partial": false
|
||||
}
|
||||
```
|
||||
|
||||
**Stage 2 (Ideas):**
|
||||
```json
|
||||
{
|
||||
"clusters_processed": 12,
|
||||
"ideas_created": 36,
|
||||
"batches_run": 2,
|
||||
"credits_used": 72
|
||||
}
|
||||
```
|
||||
|
||||
**Stage 3 (Tasks):**
|
||||
```json
|
||||
{
|
||||
"ideas_processed": 36,
|
||||
"tasks_created": 36,
|
||||
"batches_run": 4
|
||||
}
|
||||
```
|
||||
|
||||
**Stage 4 (Content):**
|
||||
```json
|
||||
{
|
||||
"tasks_processed": 36,
|
||||
"content_created": 36,
|
||||
"total_words": 54000,
|
||||
"batches_run": 6,
|
||||
"credits_used": 540
|
||||
}
|
||||
```
|
||||
|
||||
**Stage 5 (Image Prompts):**
|
||||
```json
|
||||
{
|
||||
"content_processed": 36,
|
||||
"prompts_created": 180,
|
||||
"batches_run": 4,
|
||||
"credits_used": 36
|
||||
}
|
||||
```
|
||||
|
||||
**Stage 6 (Images):**
|
||||
```json
|
||||
{
|
||||
"images_processed": 180,
|
||||
"images_generated": 180,
|
||||
"batches_run": 18
|
||||
}
|
||||
```
|
||||
|
||||
**Stage 7 (Review):**
|
||||
```json
|
||||
{
|
||||
"ready_for_review": 36
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings Configuration (v1.8.0)
|
||||
|
||||
**Location:** Site Settings → Automation tab
|
||||
**API:** `GET/PATCH /api/v1/integration/sites/{site_id}/unified-settings/`
|
||||
|
||||
> ⚠️ **v1.8.0 Change:** Settings are now consolidated in Site Settings. The previous standalone `/automation/settings` page has been removed.
|
||||
|
||||
### Settings UI Sections
|
||||
|
||||
1. **Schedule & Frequency Card**
|
||||
- Enable/disable toggle
|
||||
- Frequency (hourly/daily/weekly)
|
||||
- Days of week selection
|
||||
- Time slot selection
|
||||
- Next run display
|
||||
|
||||
2. **Capacity Card**
|
||||
- Total items per run (calculated)
|
||||
- Stage-by-stage breakdown
|
||||
|
||||
3. **AI Configuration Card**
|
||||
- Testing model selection (is_testing=true)
|
||||
- Live model selection (is_testing=false)
|
||||
- Image model selection
|
||||
|
||||
4. **Stage Configuration (Matrix)**
|
||||
- Per-stage Enable/Disable toggle
|
||||
- Per-stage batch size
|
||||
- Per-stage max items per run (**new in v1.8.0**)
|
||||
|
||||
5. **Help Cards**
|
||||
- Pipeline flow visualization
|
||||
- Stage descriptions
|
||||
|
||||
---
|
||||
|
||||
## Scheduling
|
||||
|
||||
**Celery Beat Task:** `check_scheduled_automations`
|
||||
**Frequency:** Hourly
|
||||
|
||||
**Logic:**
|
||||
1. Find configs where `enabled=True`
|
||||
2. Check if `next_run_at <= now`
|
||||
3. Check if no active run exists
|
||||
4. Start `run_automation_task` for eligible configs
|
||||
5. Update `next_run_at` based on frequency
|
||||
|
||||
---
|
||||
|
||||
## Lock Mechanism
|
||||
|
||||
**Purpose:** Prevent concurrent runs for same site
|
||||
|
||||
**Key:** `automation_lock_{site_id}`
|
||||
**Storage:** Redis cache
|
||||
**Acquired:** On run start
|
||||
**Released:** On completion/failure/cancel
|
||||
|
||||
---
|
||||
|
||||
## Credit Validation
|
||||
|
||||
Before starting:
|
||||
1. Calculate estimated credits for all stages
|
||||
2. Apply 1.2x safety buffer
|
||||
3. Compare with account balance
|
||||
4. Reject if balance < estimate
|
||||
|
||||
During execution:
|
||||
- Each AI stage checks credits before processing
|
||||
- Deductions happen after successful AI calls
|
||||
- `total_credits_used` accumulates across stages
|
||||
|
||||
---
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### AutomationOverview (v1.8.0)
|
||||
|
||||
**Path:** `/automation/overview`
|
||||
**Purpose:** Run history dashboard with:
|
||||
- All automation runs with status
|
||||
- Filter by status, date range
|
||||
- Click to view detailed run information
|
||||
|
||||
### AutomationRunDetail (v1.8.0)
|
||||
|
||||
**Path:** `/automation/runs/:runId`
|
||||
**Purpose:** Detailed view of individual run with:
|
||||
- Stage-by-stage progress
|
||||
- Items processed per stage
|
||||
- Errors and logs
|
||||
|
||||
### AutomationPage
|
||||
|
||||
**Path:** `/automation`
|
||||
**Purpose:** Manual run control with:
|
||||
- **Pipeline Cards:** Stage-by-stage status with pending counts
|
||||
- **Processing Card:** Live processing status during run
|
||||
- **Control Buttons:** Run Now, Pause, Resume, Cancel
|
||||
- **Activity Log:** Real-time log streaming
|
||||
|
||||
### Polling
|
||||
|
||||
- Every ~5s while run is running/paused
|
||||
- Fetches: `current_run`, `pipeline_overview`, `current_processing`
|
||||
- Lighter polling when idle
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| "Already running" error | Lock exists from previous run | Wait or check if stuck |
|
||||
| Insufficient credits | Balance < 1.2x estimate | Add credits |
|
||||
| Stage skipped | No items to process | Check previous stages |
|
||||
| Run stuck | Worker crashed | Clear lock, restart |
|
||||
| Images not generating | Stage 5 didn't create prompts | Check stage 5 result |
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
| Version | Changes |
|
||||
|---------|---------|
|
||||
| v1.8.0 | Unified settings in Site Settings, per-run limits, skip stage functionality |
|
||||
| v1.7.0 | AutomationOverview dashboard, AutomationRunDetail page |
|
||||
| v1.3.2 | Stage 7 publishing scheduler integration |
|
||||
| v1.3.0 | Progress tracking with initial snapshots |
|
||||
@@ -0,0 +1,756 @@
|
||||
# IGNY8 Billing & Payments - Complete Reference
|
||||
|
||||
> **Last Updated:** January 20, 2026
|
||||
> **Version:** 2.0 (Two-Pool Credit System)
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [System Overview](#1-system-overview)
|
||||
2. [Credit System](#2-credit-system)
|
||||
3. [Payment Methods](#3-payment-methods)
|
||||
4. [Subscription Lifecycle](#4-subscription-lifecycle)
|
||||
5. [Credit Packages](#5-credit-packages)
|
||||
6. [Invoice System](#6-invoice-system)
|
||||
7. [Renewal Workflow](#7-renewal-workflow)
|
||||
8. [Admin Operations](#8-admin-operations)
|
||||
9. [API Reference](#9-api-reference)
|
||||
10. [Database Schema](#10-database-schema)
|
||||
|
||||
---
|
||||
|
||||
## 1. System Overview
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ IGNY8 BILLING SYSTEM │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ STRIPE │ │ PAYPAL │ │ BANK TRANSFER│ │
|
||||
│ │ (Card/Intl) │ │ (Intl) │ │ (PK Only) │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ PAYMENT GATEWAY LAYER │ │
|
||||
│ │ • Webhook Processing • Payment Verification • Logging │ │
|
||||
│ └─────────────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ INVOICE SERVICE │ │
|
||||
│ │ • Create Invoice • Update Status • PDF Generation │ │
|
||||
│ └─────────────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ CREDIT SERVICE │ │
|
||||
│ │ • Plan Credits • Bonus Credits • Deduction • Reset │ │
|
||||
│ └─────────────────────────────┬───────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ ACCOUNT │ │
|
||||
│ │ credits (plan) │ bonus_credits (purchased) │ status │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Files
|
||||
|
||||
| Component | File Path |
|
||||
|-----------|-----------|
|
||||
| Credit Service | `business/billing/services/credit_service.py` |
|
||||
| Invoice Service | `business/billing/services/invoice_service.py` |
|
||||
| Payment Service | `business/billing/services/payment_service.py` |
|
||||
| Email Service | `business/billing/services/email_service.py` |
|
||||
| Stripe Webhooks | `business/billing/views/stripe_views.py` |
|
||||
| PayPal Webhooks | `business/billing/views/paypal_views.py` |
|
||||
| Subscription Renewal | `business/billing/tasks/subscription_renewal.py` |
|
||||
| Invoice Lifecycle | `business/billing/tasks/invoice_lifecycle.py` |
|
||||
| Billing Admin | `modules/billing/admin.py` |
|
||||
| Billing Models | `business/billing/models.py` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Credit System
|
||||
|
||||
### Two-Pool Credit Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ ACCOUNT CREDITS │
|
||||
├────────────────────────────┬────────────────────────────────┤
|
||||
│ PLAN CREDITS │ BONUS CREDITS │
|
||||
│ (account.credits) │ (account.bonus_credits) │
|
||||
├────────────────────────────┼────────────────────────────────┤
|
||||
│ • From subscription plan │ • From credit packages │
|
||||
│ • Resets on renewal │ • NEVER expire │
|
||||
│ • Used FIRST │ • Used SECOND (after plan=0) │
|
||||
│ • Max = plan.included_ │ • No maximum limit │
|
||||
│ credits │ │
|
||||
└────────────────────────────┴────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Credit Deduction Flow
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ CREDIT REQUEST │
|
||||
│ (e.g., 50) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Check Total │
|
||||
│ credits + bonus │
|
||||
│ >= requested? │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌──────────────┴──────────────┐
|
||||
│ NO │ YES
|
||||
▼ ▼
|
||||
┌────────────────┐ ┌────────────────────┐
|
||||
│ INSUFFICIENT │ │ Plan Credits >= 50?│
|
||||
│ Return False │ └─────────┬──────────┘
|
||||
└────────────────┘ │
|
||||
┌──────────┴──────────┐
|
||||
│ YES │ NO
|
||||
▼ ▼
|
||||
┌───────────────┐ ┌────────────────────┐
|
||||
│ Deduct from │ │ Deduct plan credits│
|
||||
│ plan credits │ │ to 0, remainder │
|
||||
│ only │ │ from bonus_credits │
|
||||
└───────────────┘ └────────────────────┘
|
||||
```
|
||||
|
||||
> **Note:** The two-pool logic is internal to `CreditService.deduct_credits()`. All AI functions and other credit consumers are unaffected - they call the same credit check/deduct methods as before.
|
||||
|
||||
### Credit Operations
|
||||
|
||||
| Operation | Method | Affects | Description |
|
||||
|-----------|--------|---------|-------------|
|
||||
| Add Plan Credits | `add_credits()` | `credits` | Initial subscription, manual adjustment |
|
||||
| Add Bonus Credits | `add_bonus_credits()` | `bonus_credits` | Credit package purchases |
|
||||
| Deduct Credits | `deduct_credits()` | Both pools | AI operations, uses plan first |
|
||||
| Reset on Renewal | `reset_credits_for_renewal()` | `credits` only | Sets plan credits to new amount |
|
||||
| Check Balance | `check_credits()` | Read-only | Returns total available |
|
||||
|
||||
### Credit Transaction Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `subscription` | Plan credits from subscription |
|
||||
| `purchase` | Bonus credits from credit package |
|
||||
| `usage` | Credit consumption for AI operations |
|
||||
| `refund` | Credits returned due to failed operation |
|
||||
| `manual` | Admin adjustment |
|
||||
| `renewal` | Reset during subscription renewal |
|
||||
| `bonus` | Promotional bonus credits |
|
||||
|
||||
---
|
||||
|
||||
## 3. Payment Methods
|
||||
|
||||
### Payment Method by Country
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ PAYMENT METHOD MATRIX │
|
||||
├─────────────────┬───────────────────────────────────────────┤
|
||||
│ COUNTRY │ AVAILABLE METHODS │
|
||||
├─────────────────┼───────────────────────────────────────────┤
|
||||
│ Pakistan (PK) │ ✅ Bank Transfer ✅ Stripe (Card) │
|
||||
│ │ ❌ PayPal (not available) │
|
||||
├─────────────────┼───────────────────────────────────────────┤
|
||||
│ Other (Intl) │ ✅ Stripe (Card) ✅ PayPal │
|
||||
│ │ ❌ Bank Transfer (not available) │
|
||||
└─────────────────┴───────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Payment Flow by Method
|
||||
|
||||
#### Stripe (Card) Flow
|
||||
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ User │────▶│ Frontend │────▶│ Create │────▶│ Stripe │
|
||||
│ Clicks │ │ Checkout │ │ Checkout │ │ Hosted │
|
||||
│ Pay │ │ │ │ Session │ │ Page │
|
||||
└─────────┘ └──────────┘ └─────────────┘ └────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ Account │◀────│ Credit │◀────│ Invoice │◀────│ Webhook │
|
||||
│ Active │ │ Added │ │ Paid │ │ Received │
|
||||
└─────────┘ └──────────┘ └─────────────┘ └──────────┘
|
||||
```
|
||||
|
||||
#### PayPal Flow
|
||||
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ User │────▶│ Frontend │────▶│ Create │────▶│ PayPal │
|
||||
│ Clicks │ │ Checkout │ │ Order/Sub │ │ Hosted │
|
||||
│ Pay │ │ │ │ │ │ Page │
|
||||
└─────────┘ └──────────┘ └─────────────┘ └────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ Account │◀────│ Credit │◀────│ Invoice │◀────│ Webhook │
|
||||
│ Active │ │ Added │ │ Paid │ │ CAPTURE │
|
||||
└─────────┘ └──────────┘ └─────────────┘ └──────────┘
|
||||
```
|
||||
|
||||
#### Bank Transfer Flow (PK Only)
|
||||
|
||||
```
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ User │────▶│ View │────▶│ Manual │────▶│ Upload │
|
||||
│ Selects │ │ Bank │ │ Transfer │ │ Proof │
|
||||
│ Bank │ │ Details │ │ to Bank │ │ │
|
||||
└─────────┘ └──────────┘ └─────────────┘ └────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌─────────┐ ┌──────────┐ ┌─────────────┐ ┌──────────┐
|
||||
│ Account │◀────│ Credit │◀────│ Admin │◀────│ Payment │
|
||||
│ Active │ │ Added │ │ Approves │ │ Created │
|
||||
└─────────┘ └──────────┘ └─────────────┘ └──────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Subscription Lifecycle
|
||||
|
||||
### Subscription States
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ SUBSCRIPTION STATE MACHINE │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────┐
|
||||
│ NEW │
|
||||
│ SIGNUP │
|
||||
└────┬────┘
|
||||
│ Create subscription + invoice
|
||||
▼
|
||||
┌─────────────┐ Payment Failed ┌─────────────┐
|
||||
│ PENDING │─────────────────────────▶│ FAILED │
|
||||
│ (awaiting │ │ │
|
||||
│ payment) │ └─────────────┘
|
||||
└──────┬──────┘
|
||||
│ Payment Success
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ ACTIVE │◀─────────────────────────────────────┐
|
||||
│ │ Renewal Payment Success │
|
||||
└──────┬──────┘ │
|
||||
│ Renewal Date │
|
||||
▼ │
|
||||
┌─────────────┐ Payment Within ┌───────────┴─┐
|
||||
│ PENDING │ Grace Period │ │
|
||||
│ RENEWAL │────────────────────────▶│ ACTIVE │
|
||||
│ │ │ (renewed) │
|
||||
└──────┬──────┘ └─────────────┘
|
||||
│ Grace Period Expired (7 days)
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ EXPIRED │ Manual Reactivation
|
||||
│ │────────────────────────▶ Back to PENDING
|
||||
└─────────────┘
|
||||
|
||||
┌─────────────┐
|
||||
│ CANCELLED │ User-initiated cancellation
|
||||
│ │ (end of current period)
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Subscription Status Reference
|
||||
|
||||
| Status | Credits Access | Can Use Features | Next Action |
|
||||
|--------|----------------|------------------|-------------|
|
||||
| `pending` | ❌ No | ❌ No | Complete payment |
|
||||
| `active` | ✅ Yes | ✅ Yes | None (auto-renews) |
|
||||
| `pending_renewal` | ✅ Yes (24h) | ✅ Yes | Pay invoice |
|
||||
| `expired` | ❌ No | ❌ Limited | Resubscribe |
|
||||
| `cancelled` | ✅ Until end | ✅ Until end | None |
|
||||
| `failed` | ❌ No | ❌ No | Retry payment |
|
||||
|
||||
---
|
||||
|
||||
## 5. Credit Packages
|
||||
|
||||
### Available Packages
|
||||
|
||||
| Package | Credits | USD Price | PKR Price | Per Credit |
|
||||
|---------|---------|-----------|-----------|------------|
|
||||
| Starter | 500 | $50.00 | ≈ PKR 14,000 | $0.10 |
|
||||
| Growth | 2,000 | $200.00 | ≈ PKR 56,000 | $0.10 |
|
||||
| Scale | 5,000 | $300.00 | ≈ PKR 83,000 | $0.06 |
|
||||
| Enterprise | 20,000 | $1,200.00 | ≈ PKR 334,000 | $0.06 |
|
||||
|
||||
### Credit Package Purchase Flow
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────────────┐
|
||||
│ CREDIT PACKAGE PURCHASE FLOW │
|
||||
└───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
User selects package
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ Create Invoice │
|
||||
│ type='credit_ │
|
||||
│ package' │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
|
||||
│ Stripe/PayPal? │────▶│ Auto-process via │────▶│ Webhook confirms │
|
||||
│ │ │ payment gateway │ │ payment success │
|
||||
└─────────┬─────────┘ └───────────────────┘ └─────────┬─────────┘
|
||||
│ │
|
||||
│ Bank Transfer? │
|
||||
▼ │
|
||||
┌───────────────────┐ ┌───────────────────┐ │
|
||||
│ Payment created │────▶│ Admin reviews & │ │
|
||||
│ status='pending_ │ │ approves payment │ │
|
||||
│ approval' │ └─────────┬─────────┘ │
|
||||
└───────────────────┘ │ │
|
||||
▼ ▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ PAYMENT APPROVED │
|
||||
└─────────────────┬─────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────┐
|
||||
│ CreditService.add_bonus_credits() │
|
||||
│ • Adds to account.bonus_credits │
|
||||
│ • Creates CreditTransaction │
|
||||
│ • NEVER expires │
|
||||
└───────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Credit Package Invoice Lifecycle
|
||||
|
||||
```
|
||||
Invoice Created ──▶ 48 hours ──▶ Reminder Sent ──▶ 48 hours ──▶ Invoice Voided
|
||||
│ │ │
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
status='pending' status='pending' status='void'
|
||||
(reminder sent) (auto-cancelled)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Invoice System
|
||||
|
||||
### Invoice Types
|
||||
|
||||
| Type | Description | Auto-Pay | Manual Pay |
|
||||
|------|-------------|----------|------------|
|
||||
| `subscription` | Monthly plan payment | Stripe/PayPal | Bank Transfer |
|
||||
| `credit_package` | One-time credit purchase | Stripe/PayPal | Bank Transfer |
|
||||
| `addon` | Additional features | Stripe/PayPal | Bank Transfer |
|
||||
| `custom` | Manual/admin-created | ❌ | ✅ |
|
||||
|
||||
### Invoice Status Flow
|
||||
|
||||
```
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ DRAFT │────▶│ SENT │────▶│ PENDING │────▶│ PAID │
|
||||
└─────────┘ └─────────┘ └────┬────┘ └─────────┘
|
||||
│
|
||||
┌────────┴────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ OVERDUE │ │ FAILED │
|
||||
└────┬─────┘ └──────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐ ┌──────────┐
|
||||
│ VOID │ │CANCELLED │
|
||||
└──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
### Invoice Status Reference
|
||||
|
||||
| Status | Description | User Action |
|
||||
|--------|-------------|-------------|
|
||||
| `draft` | Being created | - |
|
||||
| `sent` | Delivered to user | Pay Now |
|
||||
| `pending` | Awaiting payment | Pay Now |
|
||||
| `overdue` | Past due date | Pay Now (urgent) |
|
||||
| `paid` | Payment received | Download PDF |
|
||||
| `failed` | Payment failed | Retry/Pay Now |
|
||||
| `void` | Cancelled by system | - |
|
||||
| `cancelled` | Cancelled by admin | - |
|
||||
|
||||
---
|
||||
|
||||
## 7. Renewal Workflow
|
||||
|
||||
### Renewal Timeline by Payment Method
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ RENEWAL TIMELINE - STRIPE/PAYPAL (AUTO-PAY) │
|
||||
│ Industry Standard: No Advance Notice │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Day 0 (Renewal) Day +1 Day +7 │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
|
||||
│ │Auto-Pay │ │ If Failed: │ │ Expired │ │
|
||||
│ │ Attempt │ │ Retry + │ │ (after │ │
|
||||
│ └────┬─────┘ │ Email Sent │ │ retries) │ │
|
||||
│ │ └────────────┘ └──────────┘ │
|
||||
│ ┌────┴────┐ │
|
||||
│ │ SUCCESS │──▶ Receipt email + Credits reset to plan amount │
|
||||
│ └────┬────┘ │
|
||||
│ │ │
|
||||
│ ┌────┴────┐ │
|
||||
│ │ FAILURE │──▶ Payment failed email, Stripe retries 4x over 7 days │
|
||||
│ └─────────┘ │
|
||||
│ │
|
||||
│ EMAIL SCHEDULE (Industry Standard - Netflix, Spotify, Adobe): │
|
||||
│ • Payment Success: Receipt immediately │
|
||||
│ • Payment Failed: Notification after each retry attempt │
|
||||
│ • Final Warning: 1 day before account suspension │
|
||||
│ • Account Suspended: When grace period ends │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ RENEWAL TIMELINE - BANK TRANSFER │
|
||||
│ Simplified 3-Email Flow │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Day -3 Day 0 Day +1 Day +7 │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ ┌────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │Invoice │ │Reminder │ │ Urgent │ │ Expired │ │
|
||||
│ │Created │ │ Email │ │Reminder │ │ │ │
|
||||
│ │+Email │ │(if not │ │+Credits │ │ │ │
|
||||
│ │ │ │ paid) │ │ Reset │ │ │ │
|
||||
│ └────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
│ Timeline: │
|
||||
│ • Day -3: Invoice created + Email sent with payment instructions │
|
||||
│ • Day 0: Reminder email (renewal day, if still unpaid) │
|
||||
│ • Day +1: Urgent reminder + credits reset to 0 (if unpaid) │
|
||||
│ • Day +7: Subscription expired │
|
||||
│ │
|
||||
│ EMAILS: │
|
||||
│ 1. Invoice Email (Day -3): Invoice attached, bank details, Pay Now link │
|
||||
│ 2. Renewal Reminder (Day 0): "Your subscription renews today" │
|
||||
│ 3. Urgent Reminder (Day +1): "Payment overdue - action required" │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Renewal Credit Behavior
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────┐
|
||||
│ CREDIT RESET ON RENEWAL │
|
||||
├───────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ SCENARIO 1: Payment Before Renewal │
|
||||
│ ───────────────────────────────────── │
|
||||
│ • Credits NOT reset early │
|
||||
│ • On payment confirmation: │
|
||||
│ - plan credits → reset to plan.included_credits │
|
||||
│ - bonus_credits → UNCHANGED (never affected) │
|
||||
│ │
|
||||
│ SCENARIO 2: Payment After Renewal (within 24h) │
|
||||
│ ────────────────────────────────────────────── │
|
||||
│ • Credits stay unchanged for 24 hours │
|
||||
│ • On payment confirmation: │
|
||||
│ - plan credits → reset to plan.included_credits │
|
||||
│ - bonus_credits → UNCHANGED │
|
||||
│ │
|
||||
│ SCENARIO 3: No Payment After 24 Hours │
|
||||
│ ───────────────────────────────────── │
|
||||
│ • plan credits → reset to 0 (task: send_day_after_reminders) │
|
||||
│ • bonus_credits → UNCHANGED (user can still use these) │
|
||||
│ • Warning email sent │
|
||||
│ │
|
||||
│ SCENARIO 4: Payment After Credit Reset │
|
||||
│ ────────────────────────────────────── │
|
||||
│ • On payment confirmation: │
|
||||
│ - plan credits → reset to plan.included_credits (restored) │
|
||||
│ - bonus_credits → UNCHANGED │
|
||||
│ │
|
||||
└───────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Celery Tasks Schedule
|
||||
|
||||
| Task | Schedule | Purpose |
|
||||
|------|----------|---------|
|
||||
| `create_bank_transfer_invoices` | Daily 09:00 | Create invoices 3 days before renewal (bank transfer only) |
|
||||
| `process_subscription_renewals` | Daily 00:05 | Process auto-pay renewals (Stripe/PayPal) |
|
||||
| `send_renewal_day_reminders` | Daily 10:00 | Send Day 0 reminder for bank transfer (if unpaid) |
|
||||
| `send_day_after_reminders` | Daily 09:15 | Send Day +1 urgent reminders + reset credits |
|
||||
| `check_expired_renewals` | Daily 00:15 | Mark subscriptions expired after 7-day grace period |
|
||||
| `send_credit_invoice_expiry_reminders` | Daily 09:30 | Remind about expiring credit package invoices |
|
||||
| `void_expired_credit_invoices` | Daily 00:45 | Auto-void credit invoices after 48h |
|
||||
|
||||
### Email Schedule Summary
|
||||
|
||||
**Stripe/PayPal (Auto-Pay) - Industry Standard:**
|
||||
| Event | Email |
|
||||
|-------|-------|
|
||||
| Payment Success | ✅ Receipt/Confirmation |
|
||||
| Payment Failed | ⚠️ Retry notification (per attempt) |
|
||||
| Final Warning | 🚨 1 day before suspension |
|
||||
| Account Suspended | ❌ Subscription expired |
|
||||
|
||||
**Bank Transfer (Manual Pay):**
|
||||
| Day | Email |
|
||||
|-----|-------|
|
||||
| Day -3 | 📧 Invoice created + payment instructions |
|
||||
| Day 0 | ⏰ Renewal day reminder (if unpaid) |
|
||||
| Day +1 | 🚨 Urgent reminder + credits reset warning |
|
||||
|
||||
---
|
||||
|
||||
## 8. Admin Operations
|
||||
|
||||
### Webhook Events (Payment Logs)
|
||||
|
||||
**Location:** Admin → Billing → Webhook Events
|
||||
|
||||
| Column | Description |
|
||||
|--------|-------------|
|
||||
| Event ID | Unique ID from Stripe/PayPal |
|
||||
| Provider | STRIPE or PAYPAL badge |
|
||||
| Event Type | e.g., `checkout.session.completed`, `PAYMENT.CAPTURE.COMPLETED` |
|
||||
| Status | Processed ✓, Failed ✗, Pending ⏳ |
|
||||
| Process Time | Actual processing duration |
|
||||
| Created | When webhook received |
|
||||
|
||||
**Actions:**
|
||||
- Mark as processed
|
||||
- Retry processing
|
||||
- View full payload (JSON)
|
||||
|
||||
### Payment Approval (Bank Transfer)
|
||||
|
||||
**Location:** Admin → Billing → Payments
|
||||
|
||||
**Approval Flow:**
|
||||
1. Filter by `status = pending_approval`
|
||||
2. Review `manual_reference` and `manual_notes`
|
||||
3. Check proof of payment upload
|
||||
4. Change status to `succeeded`
|
||||
5. System automatically:
|
||||
- Updates invoice to `paid`
|
||||
- Activates account (if subscription)
|
||||
- Adds credits (plan or bonus based on invoice type)
|
||||
|
||||
### Manual Credit Adjustment
|
||||
|
||||
**Location:** Admin → Billing → Credit Transactions
|
||||
|
||||
**To add credits manually:**
|
||||
1. Go to Account admin
|
||||
2. Edit the account
|
||||
3. Modify `credits` (plan) or `bonus_credits` (purchased)
|
||||
4. Save with note in admin_notes
|
||||
|
||||
**OR use shell:**
|
||||
```python
|
||||
from igny8_core.business.billing.services.credit_service import CreditService
|
||||
|
||||
# Add plan credits
|
||||
CreditService.add_credits(
|
||||
account=account,
|
||||
amount=500,
|
||||
transaction_type='manual',
|
||||
description='Manual adjustment - support ticket #123'
|
||||
)
|
||||
|
||||
# Add bonus credits
|
||||
CreditService.add_bonus_credits(
|
||||
account=account,
|
||||
amount=500,
|
||||
description='Promotional bonus - January 2026'
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/api/v1/billing/credits/` | GET | Get credit balance |
|
||||
| `/api/v1/billing/credits/usage/` | GET | Get usage statistics |
|
||||
| `/api/v1/billing/invoices/` | GET | List invoices |
|
||||
| `/api/v1/billing/invoices/{id}/` | GET | Invoice detail |
|
||||
| `/api/v1/billing/invoices/{id}/pdf/` | GET | Download invoice PDF |
|
||||
| `/api/v1/billing/payments/` | GET | List payments |
|
||||
| `/api/v1/billing/plans/` | GET | List available plans |
|
||||
| `/api/v1/billing/subscriptions/` | GET | List subscriptions |
|
||||
| `/api/v1/billing/credit-packages/` | GET | List credit packages |
|
||||
| `/api/v1/billing/purchase/credits/` | POST | Purchase credit package |
|
||||
| `/api/v1/billing/subscribe/` | POST | Subscribe to plan |
|
||||
| `/api/v1/webhooks/stripe/` | POST | Stripe webhook endpoint |
|
||||
| `/api/v1/webhooks/paypal/` | POST | PayPal webhook endpoint |
|
||||
|
||||
### Credit Balance Response
|
||||
|
||||
```json
|
||||
{
|
||||
"credits": 3500,
|
||||
"bonus_credits": 2000,
|
||||
"total_credits": 5500,
|
||||
"credits_used_this_month": 1500,
|
||||
"plan_credits_per_month": 5000,
|
||||
"subscription_plan": "Scale",
|
||||
"period_end": "2026-02-12T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Database Schema
|
||||
|
||||
### Core Tables
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ accounts │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ id │ PK │
|
||||
│ name │ Account name │
|
||||
│ status │ pending, active, expired, etc. │
|
||||
│ credits │ Plan credits (resets on renewal) │
|
||||
│ bonus_credits │ Purchased credits (never expire) │
|
||||
│ plan_id │ FK → plans │
|
||||
│ billing_email │ Email for invoices │
|
||||
│ billing_country │ Country code (PK, US, etc.) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ subscriptions │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ id │ PK │
|
||||
│ account_id │ FK → accounts │
|
||||
│ plan_id │ FK → plans │
|
||||
│ status │ pending, active, pending_renewal, expired, etc. │
|
||||
│ current_period_start │ Start of current billing period │
|
||||
│ current_period_end │ End of current billing period (renewal date) │
|
||||
│ metadata │ JSON (stripe_subscription_id, paypal_sub_id) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ invoices │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ id │ PK │
|
||||
│ invoice_number │ Unique invoice number (INV-2026-00001) │
|
||||
│ account_id │ FK → accounts │
|
||||
│ subscription_id │ FK → subscriptions (nullable) │
|
||||
│ invoice_type │ subscription, credit_package, addon, custom │
|
||||
│ status │ draft, sent, pending, paid, overdue, void, etc. │
|
||||
│ total_amount │ Total amount │
|
||||
│ currency │ USD, PKR │
|
||||
│ due_date │ Payment due date │
|
||||
│ paid_at │ When payment received │
|
||||
│ metadata │ JSON (credit_package_id, etc.) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ payments │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ id │ PK │
|
||||
│ invoice_id │ FK → invoices │
|
||||
│ account_id │ FK → accounts │
|
||||
│ amount │ Payment amount │
|
||||
│ currency │ USD, PKR │
|
||||
│ payment_method │ stripe, paypal, bank_transfer │
|
||||
│ status │ pending_approval, processing, succeeded, failed│
|
||||
│ stripe_payment_intent_id│ Stripe reference │
|
||||
│ paypal_order_id │ PayPal reference │
|
||||
│ manual_reference │ Bank transfer reference │
|
||||
│ approved_by_id │ FK → users (admin who approved) │
|
||||
│ approved_at │ When approved │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ credit_transactions │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ id │ PK │
|
||||
│ account_id │ FK → accounts │
|
||||
│ transaction_type │ subscription, purchase, usage, refund, manual, etc. │
|
||||
│ amount │ Credits added (+) or deducted (-) │
|
||||
│ balance_after │ Balance after this transaction │
|
||||
│ description │ Human-readable description │
|
||||
│ metadata │ JSON (invoice_id, payment_id, etc.) │
|
||||
│ created_at │ Timestamp │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ webhook_events │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ id │ PK │
|
||||
│ event_id │ Unique event ID from provider │
|
||||
│ provider │ stripe, paypal │
|
||||
│ event_type │ checkout.session.completed, PAYMENT.CAPTURE.COMPLETED │
|
||||
│ payload │ JSON - full webhook payload │
|
||||
│ processed │ Boolean - successfully processed? │
|
||||
│ processed_at │ When processed │
|
||||
│ error_message│ Error if processing failed │
|
||||
│ retry_count │ Number of retry attempts │
|
||||
│ created_at │ When received │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
### Credit Consumption Priority
|
||||
1. **Plan credits** used first
|
||||
2. **Bonus credits** used only when plan credits = 0
|
||||
|
||||
### What Happens on Renewal
|
||||
|
||||
| Event | Plan Credits | Bonus Credits |
|
||||
|-------|--------------|---------------|
|
||||
| Payment success | Reset to plan amount | No change |
|
||||
| No payment (24h) | Reset to 0 | No change |
|
||||
| Late payment | Reset to plan amount | No change |
|
||||
|
||||
### Payment Method Availability
|
||||
|
||||
| Country | Stripe | PayPal | Bank Transfer |
|
||||
|---------|--------|--------|---------------|
|
||||
| Pakistan (PK) | ✅ | ❌ | ✅ |
|
||||
| Others | ✅ | ✅ | ❌ |
|
||||
|
||||
### Invoice Expiry
|
||||
|
||||
| Invoice Type | Expiry |
|
||||
|--------------|--------|
|
||||
| Subscription | 7 days (grace period) |
|
||||
| Credit Package | 48 hours |
|
||||
|
||||
---
|
||||
|
||||
*Document generated from current codebase implementation as of January 20, 2026*
|
||||
384
v2/Live Docs on Server/igny8-app-docs/10-MODULES/BILLING.md
Normal file
384
v2/Live Docs on Server/igny8-app-docs/10-MODULES/BILLING.md
Normal file
@@ -0,0 +1,384 @@
|
||||
# Billing Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Status:** ✅ Active (Two-Pool Credit System v2.0 January 2026)
|
||||
**Backend Path:** `backend/igny8_core/modules/billing/` + `backend/igny8_core/business/billing/`
|
||||
**Frontend Path:** `frontend/src/pages/Billing/` + `frontend/src/pages/Account/`
|
||||
|
||||
> **Complete Billing Reference:** For comprehensive billing & payment documentation, see [BILLING-PAYMENTS-COMPLETE.md](BILLING-PAYMENTS-COMPLETE.md)
|
||||
> **Payment System Reference:** For payment gateway documentation (Stripe, PayPal, Bank Transfer), see [PAYMENT-SYSTEM.md](../90-REFERENCE/PAYMENT-SYSTEM.md)
|
||||
|
||||
---
|
||||
|
||||
## Two-Pool Credit System (v2.0)
|
||||
|
||||
| Pool | Field | Source | Behavior |
|
||||
|------|-------|--------|----------|
|
||||
| **Plan Credits** | `account.credits` | Subscription plan | Reset on renewal, reset to 0 if unpaid after 24h |
|
||||
| **Bonus Credits** | `account.bonus_credits` | Credit packages | **NEVER expire**, **NEVER reset** |
|
||||
|
||||
### Credit Usage Priority
|
||||
1. **Plan credits** used FIRST
|
||||
2. **Bonus credits** only used when plan credits = 0
|
||||
|
||||
### What Happens on Renewal
|
||||
| Event | Plan Credits | Bonus Credits |
|
||||
|-------|--------------|---------------|
|
||||
| Payment success | Reset to plan amount | No change |
|
||||
| No payment (24h) | Reset to 0 | No change |
|
||||
| Late payment | Reset to plan amount | No change |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Models | `business/billing/models.py` | `CreditTransaction`, `CreditUsageLog`, `CreditCostConfig`, `AIModelConfig` |
|
||||
| Service | `business/billing/services/credit_service.py` | `CreditService` |
|
||||
| Limit Service | `business/billing/services/limit_service.py` | `LimitService` (4 limits only) |
|
||||
| Views | `modules/billing/views.py` | `CreditBalanceViewSet`, `CreditUsageViewSet` |
|
||||
| Frontend | `pages/Account/PlansAndBillingPage.tsx` | Plans, credits, billing history |
|
||||
| Store | `store/billingStore.ts` | `useBillingStore` |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Billing module manages:
|
||||
- Credit balance and transactions
|
||||
- AI model pricing and credit configuration (v1.4.0)
|
||||
- Usage tracking with 4 simplified limits (v1.5.0)
|
||||
- Plan enforcement
|
||||
- Payment processing
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### AIModelConfig (NEW v1.4.0)
|
||||
|
||||
Single Source of Truth for all AI models with pricing.
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| model_name | CharField(100) | Model identifier (gpt-4o-mini, dall-e-3) |
|
||||
| model_type | CharField(20) | text / image |
|
||||
| provider | CharField(50) | openai, anthropic, runware |
|
||||
| display_name | CharField(200) | Human-readable name |
|
||||
| is_default | BooleanField | One default per type |
|
||||
| is_active | BooleanField | Enable/disable |
|
||||
| cost_per_1k_input | DecimalField | USD cost per 1K input tokens (text) |
|
||||
| cost_per_1k_output | DecimalField | USD cost per 1K output tokens (text) |
|
||||
| tokens_per_credit | IntegerField | Text: tokens per 1 credit (e.g., 1000) |
|
||||
| credits_per_image | IntegerField | Image: credits per image (1, 5, 15) |
|
||||
| quality_tier | CharField(20) | basic / quality / premium |
|
||||
| max_tokens | IntegerField | Model token limit |
|
||||
| context_window | IntegerField | Model context size |
|
||||
| capabilities | JSONField | vision, function_calling, etc. |
|
||||
|
||||
**Credit Examples:**
|
||||
|
||||
| Model | tokens_per_credit | credits_per_image | quality_tier |
|
||||
|-------|-------------------|-------------------|--------------|
|
||||
| gpt-4o | 1000 | - | - |
|
||||
| gpt-4o-mini | 10000 | - | - |
|
||||
| runware:97@1 | - | 1 | basic |
|
||||
| dall-e-3 | - | 5 | quality |
|
||||
| google:4@2 | - | 15 | premium |
|
||||
|
||||
### CreditTransaction (Ledger)
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| transaction_type | CharField | purchase/subscription/refund/deduction/adjustment |
|
||||
| amount | Decimal | Positive (add) or negative (deduct) |
|
||||
| balance_after | Decimal | Balance after transaction |
|
||||
| description | CharField | Transaction description |
|
||||
| metadata | JSON | Additional data |
|
||||
| payment | FK(Payment) | Payment that triggered this (v1.4.0) |
|
||||
| reference_id | CharField | DEPRECATED: Use payment FK |
|
||||
| created_at | DateTime | Transaction time |
|
||||
|
||||
### CreditUsageLog (Analytics)
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| operation_type | CharField | clustering/idea_generation/content_generation/image_generation |
|
||||
| credits_used | Decimal | Credits consumed |
|
||||
| cost_usd | Decimal | USD cost |
|
||||
| model_used | CharField | AI model used |
|
||||
| tokens_in | Integer | Input tokens |
|
||||
| tokens_out | Integer | Output tokens |
|
||||
| content_type | FK | Related content type |
|
||||
| object_id | Integer | Related object ID |
|
||||
| metadata | JSON | Additional data |
|
||||
| created_at | DateTime | Usage time |
|
||||
|
||||
### CreditCostConfig (Updated v1.4.0)
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| operation_type | CharField(50) PK | Unique operation ID |
|
||||
| display_name | CharField(100) | Human-readable name |
|
||||
| base_credits | IntegerField | Fixed credits per operation |
|
||||
| is_active | BooleanField | Enable/disable |
|
||||
| description | TextField | Admin notes |
|
||||
|
||||
**Note:** `tokens_per_credit` moved to AIModelConfig in v1.4.0.
|
||||
|
||||
---
|
||||
|
||||
## Credit Calculation Methods
|
||||
|
||||
### Method 1: Token-Based (Text Models) - Updated v1.4.0
|
||||
|
||||
Used for: Clustering, Ideas, Content Generation, Optimization
|
||||
|
||||
**Flow:**
|
||||
1. AI call completes
|
||||
2. OpenAI returns actual token usage
|
||||
3. Calculate: `total_tokens = input_tokens + output_tokens`
|
||||
4. Look up `AIModelConfig.tokens_per_credit` for the model used
|
||||
5. Calculate: `credits = ceil(total_tokens / tokens_per_credit)`
|
||||
6. Deduct from balance
|
||||
|
||||
**Example:**
|
||||
- Model: `gpt-4o-mini` (tokens_per_credit = 10000)
|
||||
- Tokens used: 15000
|
||||
- Credits: `ceil(15000 / 10000)` = 2 credits
|
||||
|
||||
### Method 2: Fixed Cost (Image Models) - Updated v1.4.0
|
||||
|
||||
Used for: Image Generation
|
||||
|
||||
**Flow:**
|
||||
1. User selects quality tier (basic/quality/premium)
|
||||
2. Look up `AIModelConfig.credits_per_image` for selected tier
|
||||
3. Check balance sufficient: `balance >= num_images * credits_per_image`
|
||||
4. Deduct credits
|
||||
5. Make AI call
|
||||
|
||||
**Example:**
|
||||
- Quality Tier: "quality" (dall-e-3, credits_per_image = 5)
|
||||
- Images: 3
|
||||
- Credits: 3 × 5 = 15 credits
|
||||
2. Calculate: `credits = num_images * cost_per_image`
|
||||
3. Check balance sufficient
|
||||
4. Deduct credits
|
||||
5. Then make AI call
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/billing/balance/` | `CreditBalanceViewSet.list` | Current balance + monthly usage |
|
||||
| GET | `/api/v1/billing/usage/` | `CreditUsageViewSet.list` | Usage log (paginated) |
|
||||
| GET | `/api/v1/billing/usage/summary/` | `CreditUsageViewSet.summary` | Aggregated by operation |
|
||||
| GET | `/api/v1/billing/usage/limits/` | `CreditUsageViewSet.limits` | Plan limits + current usage |
|
||||
| GET | `/api/v1/billing/transactions/` | `TransactionViewSet.list` | Transaction history |
|
||||
|
||||
---
|
||||
|
||||
## Credit Service API
|
||||
|
||||
### Check Credits
|
||||
|
||||
```python
|
||||
CreditService.check_credits(account, required_credits)
|
||||
# Raises InsufficientCreditsError if balance < required
|
||||
```
|
||||
|
||||
### Deduct Credits
|
||||
|
||||
```python
|
||||
CreditService.deduct_credits_for_operation(
|
||||
account=account,
|
||||
operation_type='content_generation',
|
||||
amount=word_count, # For per_word operations
|
||||
model='gpt-4o-mini',
|
||||
tokens_in=2500,
|
||||
tokens_out=1500,
|
||||
metadata={'content_id': 123}
|
||||
)
|
||||
```
|
||||
|
||||
### Add Credits
|
||||
|
||||
```python
|
||||
CreditService.add_credits(
|
||||
account=account,
|
||||
amount=1000,
|
||||
transaction_type='purchase',
|
||||
description='Credit package purchase'
|
||||
)
|
||||
```
|
||||
|
||||
### Calculate Credits for Images (NEW v1.4.0)
|
||||
|
||||
```python
|
||||
# Calculate credits needed for image generation based on model
|
||||
credits = CreditService.calculate_credits_for_image(
|
||||
model_name='dall-e-3', # Model with credits_per_image = 5
|
||||
num_images=3
|
||||
)
|
||||
# Returns: 15 (3 images × 5 credits)
|
||||
```
|
||||
|
||||
### Check Credits for Image Generation (NEW v1.7.1)
|
||||
|
||||
```python
|
||||
# Pre-check credits before image generation starts
|
||||
# Raises InsufficientCreditsError if not enough credits
|
||||
required = CreditService.check_credits_for_image(
|
||||
account=account,
|
||||
model_name='dall-e-3', # Model with credits_per_image = 5
|
||||
num_images=3
|
||||
)
|
||||
# Returns: 15 (required credits) if sufficient, raises exception if not
|
||||
```
|
||||
|
||||
### Deduct Credits for Image Generation (v1.7.1 Verified)
|
||||
|
||||
```python
|
||||
# Called after each successful image generation
|
||||
credits_deducted = CreditService.deduct_credits_for_image(
|
||||
account=account,
|
||||
model_name='dall-e-3',
|
||||
num_images=1,
|
||||
description='Image generation: Article Title',
|
||||
metadata={'image_id': 123, 'content_id': 456},
|
||||
cost_usd=0.04,
|
||||
related_object_type='image',
|
||||
related_object_id=123
|
||||
)
|
||||
# Logs to CreditTransaction and CreditUsageLog
|
||||
```
|
||||
|
||||
### Calculate Credits from Tokens by Model (NEW v1.4.0)
|
||||
|
||||
```python
|
||||
# Calculate credits from token usage based on model's tokens_per_credit
|
||||
credits = CreditService.calculate_credits_from_tokens_by_model(
|
||||
model_name='gpt-4o-mini', # Model with tokens_per_credit = 10000
|
||||
total_tokens=15000
|
||||
)
|
||||
# Returns: 2 (ceil(15000 / 10000))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Plan Limits
|
||||
|
||||
### Hard Limits (Never Reset)
|
||||
|
||||
| Limit | Field | Description |
|
||||
|-------|-------|-------------|
|
||||
| Sites | `max_sites` | Maximum sites per account |
|
||||
| Users | `max_users` | Maximum team members |
|
||||
| Keywords | `max_keywords` | Total keywords allowed |
|
||||
|
||||
### Monthly Limits (Reset on Billing Cycle)
|
||||
|
||||
| Limit | Field | Description |
|
||||
|-------|-------|-------------|
|
||||
| Ahrefs Queries | `max_ahrefs_queries` | Live Ahrefs API queries per month |
|
||||
|
||||
**Note:** As of January 2026, the limit system was simplified from 10+ limits to just 4. Credits handle all AI operation costs (content generation, image generation, clustering, etc.) instead of separate per-operation limits.
|
||||
|
||||
---
|
||||
|
||||
## Usage Limits Panel
|
||||
|
||||
**Component:** `UsageLimitsPanel.tsx`
|
||||
|
||||
Displays:
|
||||
- Progress bars for 4 limits only (Sites, Users, Keywords, Ahrefs Queries)
|
||||
- Color coding: blue (safe), yellow (warning), red (critical)
|
||||
- Days until reset for monthly limits (Ahrefs Queries)
|
||||
- Upgrade CTA when approaching limits
|
||||
|
||||
---
|
||||
|
||||
## Credit Costs Reference (Updated v1.4.0)
|
||||
|
||||
**Text Model Credits** (from `AIModelConfig.tokens_per_credit`):
|
||||
|
||||
| Model | tokens_per_credit | Cost/Credit | Notes |
|
||||
|-------|-------------------|-------------|-------|
|
||||
| gpt-4o | 1000 | ~$0.015 | High quality, lower throughput |
|
||||
| gpt-4o-mini | 10000 | ~$0.001 | Fast, cost-effective |
|
||||
| gpt-4.5-preview | 500 | ~$0.05 | Highest quality |
|
||||
|
||||
**Image Model Credits** (from `AIModelConfig.credits_per_image`):
|
||||
|
||||
| Quality Tier | credits_per_image | Model Example | Notes |
|
||||
|--------------|-------------------|---------------|-------|
|
||||
| Basic | 1 | runware:97@1 | Fast generation |
|
||||
| Quality | 5 | dall-e-3 | Balanced |
|
||||
| Premium | 15 | google:4@2 | Best quality |
|
||||
|
||||
**Operation Base Costs** (from `CreditCostConfig.base_credits`):
|
||||
|
||||
| Operation | Base Credits | Notes |
|
||||
|-----------|--------------|-------|
|
||||
| Clustering | 10 | Per clustering request |
|
||||
| Idea Generation | 2 | Per idea generated |
|
||||
| Content Optimization | 5 | Per optimization run |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Plans & Billing (`/account/plans`)
|
||||
|
||||
**Tabs:**
|
||||
1. **Current Plan** - Active plan details, renewal date, "View Usage" link
|
||||
2. **Upgrade Plan** - Pricing table with plan comparison
|
||||
3. **Billing History** - Invoices and payment history
|
||||
|
||||
### Usage Analytics (`/account/usage`)
|
||||
|
||||
**Tabs:**
|
||||
1. **Limits & Usage** - Plan limits with progress bars (4 limits only)
|
||||
2. **Credit History** - Credit transaction history
|
||||
3. **Credit Insights** - Charts: credits by type, daily timeline, operations breakdown
|
||||
4. **Activity Log** - API call statistics and operation details
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| AIEngine | CreditService | Pre-check and post-deduct |
|
||||
| Automation | CreditService | Per-stage credit tracking |
|
||||
| Subscription | Account | Credit allocation |
|
||||
| Admin | CreditCostConfig | Price adjustments |
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| 402 error | Insufficient credits | Add credits or reduce operation |
|
||||
| Usage not showing | Log not created | Check CreditService called |
|
||||
| Limits wrong | Cache stale | Clear cache, reload |
|
||||
| Monthly usage high | Automation running | Pause automation |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| ~~AI Model Config database~~ | ✅ v1.4.0 | Model pricing moved to AIModelConfig |
|
||||
| ~~Image model quality tiers~~ | ✅ v1.4.0 | credits_per_image per quality tier |
|
||||
| ~~Credit service enhancements~~ | ✅ v1.7.1 | Model-specific calculation methods |
|
||||
| ~~Image generation credit check~~ | ✅ v1.7.1 | Pre-generation credit verification |
|
||||
| ~~Image generation logging~~ | ✅ v1.7.1 | AITaskLog + notifications for images |
|
||||
368
v2/Live Docs on Server/igny8-app-docs/10-MODULES/INTEGRATIONS.md
Normal file
368
v2/Live Docs on Server/igny8-app-docs/10-MODULES/INTEGRATIONS.md
Normal file
@@ -0,0 +1,368 @@
|
||||
# Integrations Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ✅ Active
|
||||
**Backend Path:** `backend/igny8_core/modules/integration/` + `backend/igny8_core/business/integration/`
|
||||
**Frontend Path:** `frontend/src/pages/Settings/IntegrationSettings.tsx`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Models | `business/integration/models.py` | `SiteIntegration`, `SyncEvent` |
|
||||
| Views | `modules/integration/views.py` | `SiteIntegrationViewSet` |
|
||||
| Webhooks | `modules/integration/webhooks.py` | `wordpress_webhook` |
|
||||
| Services | `business/integration/services/*.py` | Sync services |
|
||||
| AI Core | `ai/ai_core.py` | OpenAI, Anthropic, Runware, Bria clients |
|
||||
| Model Registry | `ai/model_registry.py` | Centralized model configs |
|
||||
| Frontend | `pages/Settings/IntegrationSettings.tsx` | Integration UI |
|
||||
|
||||
---
|
||||
|
||||
## AI Provider Integrations (v1.3.0)
|
||||
|
||||
### Supported Providers
|
||||
|
||||
| Provider | Type | Models | API Key Field |
|
||||
|----------|------|--------|---------------|
|
||||
| OpenAI | Text/Image | gpt-4o, gpt-4o-mini, dall-e-3 | `openai_api_key` |
|
||||
| Anthropic | Text | claude-3-5-sonnet, claude-3-opus, claude-3-haiku | `anthropic_api_key` |
|
||||
| Runware | Image | runware-v2 | `runware_api_key` |
|
||||
| Bria AI | Image | bria-2.3, bria-2.3-fast, bria-2.2 | `bria_api_key` |
|
||||
|
||||
### GlobalIntegrationSettings
|
||||
|
||||
Located in `modules/system/global_settings_models.py`:
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| openai_api_key | CharField | OpenAI API key |
|
||||
| anthropic_api_key | CharField | Anthropic API key (v1.3.0) |
|
||||
| runware_api_key | CharField | Runware API key |
|
||||
| bria_api_key | CharField | Bria AI API key (v1.3.0) |
|
||||
| default_text_model | CharField | Default text model |
|
||||
| default_image_model | CharField | Default image model |
|
||||
| default_image_provider | CharField | openai/runware/bria |
|
||||
|
||||
### Model Registry
|
||||
|
||||
Centralized model configuration with caching:
|
||||
|
||||
```python
|
||||
from igny8_core.ai.model_registry import ModelRegistry
|
||||
|
||||
# Get model config
|
||||
model = ModelRegistry.get_model('gpt-4o-mini')
|
||||
|
||||
# Calculate cost
|
||||
cost = ModelRegistry.calculate_cost('gpt-4o-mini', input_tokens=1000, output_tokens=500)
|
||||
|
||||
# List all models
|
||||
models = ModelRegistry.list_models(model_type='text', provider='openai')
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
- `0012_add_bria_integration.py` - Adds Bria AI integration settings
|
||||
- `0013_add_anthropic_integration.py` - Adds Anthropic integration settings
|
||||
- `0009_seed_ai_model_configs.py` - Seeds model configurations
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Integrations module manages:
|
||||
- AI provider connections (OpenAI, Anthropic, Runware, Bria)
|
||||
- WordPress site connections
|
||||
- Two-way content synchronization
|
||||
- Webhook handling
|
||||
- External platform integrations
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### Authentication Note
|
||||
|
||||
**⚠️ Important:** For WordPress integrations, `Site.wp_api_key` is the **SINGLE source of truth** for API authentication, NOT SiteIntegration fields. The SiteIntegration model is used for sync tracking and multi-platform support (future: Shopify).
|
||||
|
||||
### SiteIntegration
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | IGNY8 site |
|
||||
| platform | CharField | wordpress/shopify |
|
||||
| external_site_url | URLField | External site URL |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
| sync_enabled | Boolean | Enable auto-sync |
|
||||
| last_sync_at | DateTime | Last sync time |
|
||||
| sync_status | CharField | pending/syncing/completed/error |
|
||||
| sync_error | TextField | Sync error message |
|
||||
| connection_status | CharField | connected/error |
|
||||
| config_json | JSON | Platform-specific configuration |
|
||||
| credentials_json | JSON | (Reserved for future platforms, NOT used for WordPress) |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
### SyncEvent
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| integration | FK | Parent integration |
|
||||
| event_type | CharField | content_push/content_pull/webhook |
|
||||
| direction | CharField | igny8_to_wp/wp_to_igny8 |
|
||||
| content_type | CharField | post/page/product |
|
||||
| content_id | Integer | IGNY8 content ID |
|
||||
| external_id | Integer | WordPress post ID |
|
||||
| status | CharField | pending/success/failed |
|
||||
| error_message | TextField | Error details |
|
||||
| metadata | JSON | Additional data |
|
||||
| created_at | DateTime | Event time |
|
||||
|
||||
### PublishingSettings (v1.3.2)
|
||||
|
||||
Site-level publishing configuration. Used by the publishing scheduler.
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| site | OneToOneField | Parent site (unique per site) |
|
||||
| auto_approval_enabled | BooleanField | Auto-approve content (default: False) |
|
||||
| auto_publish_enabled | BooleanField | Auto-publish approved content (default: False) |
|
||||
| daily_publish_limit | IntegerField | Max publications per day (default: 5) |
|
||||
| weekly_publish_limit | IntegerField | Max per week (default: 20) |
|
||||
| monthly_publish_limit | IntegerField | Max per month (default: 60) |
|
||||
| publish_days | JSONField | Days for publishing ["mon","wed","fri"] |
|
||||
| publish_time_slots | JSONField | Time slots [{"start":"09:00","end":"17:00"}] |
|
||||
|
||||
**Helper Method:**
|
||||
```python
|
||||
# Get or create settings for a site
|
||||
settings, created = PublishingSettings.get_or_create_for_site(site)
|
||||
```
|
||||
|
||||
**Related:** See [PUBLISHER.md](PUBLISHER.md) for publishing scheduler details.
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/integration/` | `SiteIntegrationViewSet.list` | List integrations |
|
||||
| POST | `/api/v1/integration/` | `SiteIntegrationViewSet.create` | Create integration |
|
||||
| PUT | `/api/v1/integration/{id}/` | `SiteIntegrationViewSet.update` | Update integration |
|
||||
| DELETE | `/api/v1/integration/{id}/` | `SiteIntegrationViewSet.destroy` | Remove integration |
|
||||
| POST | `/api/v1/integration/{id}/test_connection/` | Test connection | Verify credentials |
|
||||
| POST | `/api/v1/integration/{id}/test_collection_connection/` | Test per-collection | Test specific content type |
|
||||
| POST | `/api/v1/integration/{id}/sync/` | Trigger sync | Start synchronization |
|
||||
| GET | `/api/v1/integration/{id}/sync_status/` | Get sync status | Current sync progress |
|
||||
| POST | `/api/v1/integration/{id}/update_structure/` | Update structure | Refresh site structure |
|
||||
| GET | `/api/v1/integration/{id}/content_types/` | Get content types | Available types + counts |
|
||||
| GET | `/api/v1/integration/{id}/sync_health/` | Get sync health | Sync statistics |
|
||||
| POST | `/api/v1/integration/site_sync/` | Site-level sync | Sync by site ID |
|
||||
|
||||
### Webhooks
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| POST | `/api/v1/integration/webhook/wordpress/` | `wordpress_webhook` | Receive WP updates |
|
||||
|
||||
---
|
||||
|
||||
## WordPress Integration
|
||||
|
||||
### Setup Flow
|
||||
|
||||
1. User enters WordPress site URL
|
||||
2. User creates Application Password in WordPress
|
||||
3. User enters credentials in IGNY8
|
||||
4. IGNY8 tests connection via WordPress REST API
|
||||
5. If successful, saves `SiteIntegration` record
|
||||
|
||||
### Required WordPress Setup
|
||||
|
||||
- WordPress 5.6+ (REST API enabled)
|
||||
- Application Passwords enabled
|
||||
- Pretty permalinks enabled
|
||||
- REST API accessible (no blocking plugins)
|
||||
|
||||
### Test Connection
|
||||
|
||||
**Endpoint:** `POST /integration/{id}/test_connection/`
|
||||
|
||||
**Flow:**
|
||||
1. Call WordPress `/wp-json/wp/v2/users/me`
|
||||
2. Verify authentication works
|
||||
3. Return user info and site capabilities
|
||||
|
||||
---
|
||||
|
||||
## Content Synchronization
|
||||
|
||||
### IGNY8 → WordPress (Push)
|
||||
|
||||
**Trigger:** User clicks "Publish to WordPress"
|
||||
|
||||
**Flow:**
|
||||
1. Content is in `approved` status
|
||||
2. Get `SiteIntegration` for content's site
|
||||
3. Build WordPress post payload:
|
||||
- title, content, excerpt, slug
|
||||
- status: `publish` or `draft`
|
||||
- categories, tags (mapped)
|
||||
- featured media (if image exists)
|
||||
4. Call WordPress REST API:
|
||||
- `POST /wp-json/wp/v2/posts` (new)
|
||||
- `PUT /wp-json/wp/v2/posts/{id}` (update)
|
||||
5. Store `wordpress_id` on Content
|
||||
6. Create `SyncEvent` record
|
||||
7. Update Content status to `published`
|
||||
|
||||
### WordPress → IGNY8 (Pull)
|
||||
|
||||
**Trigger:** Manual sync or webhook
|
||||
|
||||
**Flow:**
|
||||
1. Call WordPress `/wp-json/wp/v2/posts`
|
||||
2. For each post:
|
||||
- Check if exists in IGNY8 by `wordpress_id`
|
||||
- If exists: Update if modified
|
||||
- If new: Create Content record
|
||||
3. Sync taxonomies (categories, tags)
|
||||
4. Create `SyncEvent` records
|
||||
|
||||
### Webhook Handling
|
||||
|
||||
**WordPress Plugin** sends webhooks to IGNY8 when:
|
||||
- Post created/updated/deleted
|
||||
- Post status changed (draft → published)
|
||||
|
||||
**Webhook Payload:**
|
||||
```json
|
||||
{
|
||||
"action": "post_updated",
|
||||
"post_id": 123,
|
||||
"post_type": "post",
|
||||
"site_url": "https://example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**IGNY8 Processing:**
|
||||
1. Validate webhook signature (optional)
|
||||
2. Find matching `SiteIntegration`
|
||||
3. Fetch full post from WordPress
|
||||
4. Update/create Content in IGNY8
|
||||
5. Create `SyncEvent` record
|
||||
|
||||
---
|
||||
|
||||
## Image Sync
|
||||
|
||||
### Push (IGNY8 → WordPress)
|
||||
|
||||
1. Image record has `image_url`
|
||||
2. Download image from URL
|
||||
3. Upload to WordPress Media Library
|
||||
4. Get WordPress attachment ID
|
||||
5. Set as featured image on post
|
||||
|
||||
### Pull (WordPress → IGNY8)
|
||||
|
||||
1. Get featured media ID from post
|
||||
2. Fetch media URL from WordPress
|
||||
3. Store URL in IGNY8 Images record
|
||||
|
||||
---
|
||||
|
||||
## Taxonomy Sync
|
||||
|
||||
### Categories
|
||||
|
||||
1. Get WordPress categories via `/wp-json/wp/v2/categories`
|
||||
2. Create `ContentTaxonomy` records (type=category)
|
||||
3. Store `wordpress_id` for mapping
|
||||
|
||||
### Tags
|
||||
|
||||
1. Get WordPress tags via `/wp-json/wp/v2/tags`
|
||||
2. Create `ContentTaxonomy` records (type=tag)
|
||||
3. Store `wordpress_id` for mapping
|
||||
|
||||
---
|
||||
|
||||
## Sync Status Tracking
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| idle | No sync in progress |
|
||||
| syncing | Sync currently running |
|
||||
| error | Last sync failed |
|
||||
|
||||
**Sync Health Metrics:**
|
||||
- Total synced posts
|
||||
- Last successful sync
|
||||
- Failed sync count
|
||||
- Pending items
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Integration Settings (`/settings/integration`)
|
||||
|
||||
- Add new integration button
|
||||
- List of configured integrations
|
||||
- Status indicators (connected/error)
|
||||
- Test connection button
|
||||
- Sync now button
|
||||
- Sync status and history
|
||||
|
||||
### WordPress Sync Dashboard (`/sites/{id}/sync`)
|
||||
|
||||
- Detailed sync status
|
||||
- Content sync queue
|
||||
- Error log
|
||||
- Manual sync triggers
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Connection failed | Invalid credentials | Verify Application Password |
|
||||
| 401 Unauthorized | Password expired | Regenerate Application Password |
|
||||
| 403 Forbidden | REST API blocked | Check security plugins |
|
||||
| Images not syncing | CORS/URL issues | Verify image URLs accessible |
|
||||
| Categories missing | Not synced | Run sync_structure first |
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### Credential Storage
|
||||
|
||||
- API keys encrypted at rest
|
||||
- Never exposed in API responses
|
||||
- Application Passwords rotatable
|
||||
|
||||
### Webhook Security
|
||||
|
||||
- Optional signature verification
|
||||
- Site URL validation
|
||||
- Rate limiting on webhook endpoint
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Shopify integration | 🔜 Planned | E-commerce platform support |
|
||||
| Ghost integration | 🔜 Planned | Ghost CMS support |
|
||||
| Webflow integration | 🔜 Planned | Webflow CMS support |
|
||||
| Scheduled sync | 🔜 Planned | Automatic periodic sync |
|
||||
| Conflict resolution | 🔜 Planned | Handle edit conflicts |
|
||||
184
v2/Live Docs on Server/igny8-app-docs/10-MODULES/LINKER.md
Normal file
184
v2/Live Docs on Server/igny8-app-docs/10-MODULES/LINKER.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Linker Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ⏸️ Inactive (Disabled by Default)
|
||||
**Backend Path:** `backend/igny8_core/modules/linker/`
|
||||
**Frontend Path:** `frontend/src/pages/Linker/`
|
||||
|
||||
---
|
||||
|
||||
## Module Status
|
||||
|
||||
| Aspect | Current State | Notes |
|
||||
|--------|---------------|-------|
|
||||
| Backend API | ✅ Implemented | Endpoints functional |
|
||||
| Frontend Pages | ✅ Implemented | UI exists |
|
||||
| Sidebar Nav | ⚠️ Conditional | Hidden when disabled |
|
||||
| Route Protection | ❌ Not Protected | Direct URL access still works |
|
||||
| Dashboard References | ❌ Not Hidden | May show in dashboard |
|
||||
| Default State | Disabled | `linker_enabled = True` in model, but typically disabled |
|
||||
|
||||
**⚠️ Pending Implementation:** Extend module disable to route-level protection and all page references.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Views | `modules/linker/views.py` | `LinkerViewSet` |
|
||||
| API | `modules/linker/urls.py` | Linker endpoints |
|
||||
| Frontend Pages | `pages/Linker/index.tsx` | Linker overview |
|
||||
| Frontend Pages | `pages/Linker/LinkerContent.tsx` | Content for linking |
|
||||
| API Client | `api/linker.api.ts` | `linkerApi` |
|
||||
| Module Control | `modules/system/settings_models.py` | `ModuleEnableSettings.linker_enabled` |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Linker module provides internal linking automation:
|
||||
- Identify link opportunities in content
|
||||
- Suggest relevant internal links
|
||||
- Auto-inject links into content body
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| POST | `/api/v1/linker/process/` | `LinkerViewSet.process` | Process single content for linking |
|
||||
| POST | `/api/v1/linker/batch_process/` | `LinkerViewSet.batch_process` | Process multiple content items |
|
||||
|
||||
### Process Request
|
||||
|
||||
```json
|
||||
{
|
||||
"content_id": 123,
|
||||
"max_links": 5,
|
||||
"anchor_strategy": "exact_match"
|
||||
}
|
||||
```
|
||||
|
||||
### Process Response
|
||||
|
||||
```json
|
||||
{
|
||||
"content_id": 123,
|
||||
"links_added": 3,
|
||||
"link_opportunities": [
|
||||
{
|
||||
"anchor_text": "SEO optimization",
|
||||
"target_url": "/blog/seo-guide/",
|
||||
"target_content_id": 456,
|
||||
"position": 234,
|
||||
"confidence": 0.85
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Logic
|
||||
|
||||
### Link Candidate Discovery
|
||||
|
||||
**Trigger:** User initiates linking process
|
||||
**Flow:**
|
||||
1. Load source content body
|
||||
2. Extract potential anchor phrases
|
||||
3. Find matching content by:
|
||||
- Keyword overlap
|
||||
- Title similarity
|
||||
- Same sector/industry
|
||||
4. Score candidates by relevance
|
||||
5. Filter to avoid over-linking
|
||||
|
||||
### Link Injection
|
||||
|
||||
**Trigger:** User approves link suggestions
|
||||
**Flow:**
|
||||
1. Locate anchor text in content
|
||||
2. Wrap in `<a>` tag with target URL
|
||||
3. Ensure no nested links
|
||||
4. Save updated content body
|
||||
|
||||
---
|
||||
|
||||
## Module Enable Control
|
||||
|
||||
### Backend Model
|
||||
|
||||
```python
|
||||
# modules/system/settings_models.py
|
||||
class ModuleEnableSettings(AccountBaseModel):
|
||||
linker_enabled = models.BooleanField(default=True)
|
||||
```
|
||||
|
||||
### Frontend Check
|
||||
|
||||
```typescript
|
||||
// layout/AppSidebar.tsx
|
||||
if (isModuleEnabled('linker')) {
|
||||
workflowItems.push({
|
||||
name: "Linker",
|
||||
path: "/linker/content",
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Current Limitation
|
||||
|
||||
Direct URL access to `/linker/*` routes still works even when module is disabled.
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Linker Overview (`/linker`)
|
||||
|
||||
- Module overview
|
||||
- Quick stats
|
||||
- Start linking action
|
||||
|
||||
### Content for Linking (`/linker/content`)
|
||||
|
||||
- List of content available for linking
|
||||
- Link status indicators
|
||||
- Process button per content
|
||||
- Batch processing action
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| Content | Linker | Manual process |
|
||||
| Linker | Content | Link injection |
|
||||
| Automation | Linker | Automated linking (future) |
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Module visible when disabled | Only sidebar hidden | Pending: route protection |
|
||||
| No link candidates | No matching content | Create more related content |
|
||||
| Links broken | Target content deleted | Validate target exists |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Route-level protection | 🔜 Pending | Block access when module disabled |
|
||||
| Dashboard card hiding | 🔜 Pending | Hide from dashboard when disabled |
|
||||
| Automation integration | 🔜 Planned | Add to automation pipeline |
|
||||
| Link density control | 🔜 Planned | Prevent over-linking |
|
||||
| External link support | 🔜 Planned | Add outbound links |
|
||||
@@ -0,0 +1,594 @@
|
||||
# Notifications Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ✅ Active
|
||||
**Backend Path:** `backend/igny8_core/business/notifications/`
|
||||
**Frontend Path:** `frontend/src/store/notificationStore.ts`, `frontend/src/components/header/NotificationDropdown.tsx`, `frontend/src/pages/account/NotificationsPage.tsx`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Models | `business/notifications/models.py` | `Notification`, `NotificationPreference` |
|
||||
| Views | `business/notifications/views.py` | `NotificationViewSet` |
|
||||
| Serializers | `business/notifications/serializers.py` | `NotificationSerializer` |
|
||||
| Services | `business/notifications/services.py` | `NotificationService` |
|
||||
| Frontend Store | `store/notificationStore.ts` | Zustand notification store |
|
||||
| Frontend Dropdown | `components/header/NotificationDropdown.tsx` | Notification UI (header bell) |
|
||||
| Frontend Page | `pages/account/NotificationsPage.tsx` | Full notifications history page |
|
||||
| API Client | `services/notifications.api.ts` | API methods |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Notifications module provides real-time user notifications for AI operations, system events, and workflow milestones. Notifications appear in the header dropdown and persist in the database.
|
||||
|
||||
**Key Features:**
|
||||
- Real-time notifications for AI task completion/failure
|
||||
- Account-wide or user-specific notifications
|
||||
- Read/unread state tracking
|
||||
- Action buttons with navigation
|
||||
- Auto-fetch and auto-sync with API
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### Notification
|
||||
|
||||
| Field | Type | Currently Used? | Purpose |
|
||||
|-------|------|----------------|---------|
|
||||
| account | FK | ✅ **Yes** | Owner account (required for multi-tenancy) |
|
||||
| user | FK | ❌ **No** | User-specific notifications (nullable - if null, visible to all account users). **Currently unused** - all notifications are account-wide |
|
||||
| notification_type | CharField | ✅ **Yes** | Type from NotificationType choices (for filtering, icons) |
|
||||
| title | CharField(200) | ✅ **Yes** | Notification title shown in dropdown |
|
||||
| message | TextField | ✅ **Yes** | Notification body text shown in dropdown |
|
||||
| severity | CharField | ✅ **Yes** | info/success/warning/error (affects icon color) |
|
||||
| site | FK | ✅ **Partial** | Related site (nullable). Created but **not displayed** in current dropdown |
|
||||
| content_type | FK | ❌ **No** | Generic relation to any object. **Future:** Link notification to specific Content/Task/etc. for direct access |
|
||||
| object_id | PositiveInteger | ❌ **No** | Generic relation ID. **Future:** Used with content_type for object linking |
|
||||
| action_url | CharField(500) | ✅ **Yes** | Frontend route for action button (e.g., `/planner/clusters`) |
|
||||
| action_label | CharField(50) | ✅ **Yes** | Action button text (e.g., "View Clusters") |
|
||||
| is_read | Boolean | ✅ **Yes** | Read status (default: False). Used for unread badge count |
|
||||
| read_at | DateTime | ✅ **Partial** | When marked read (nullable). Created but **not displayed** in dropdown |
|
||||
| metadata | JSON | ✅ **Partial** | Additional data (counts, details). Stored but **not displayed** in dropdown |
|
||||
| created_at | DateTime | ✅ **Yes** | Creation timestamp. Shown as relative time ("2 minutes ago") |
|
||||
| updated_at | DateTime | ❌ **No** | Last update timestamp. **Currently unused** |
|
||||
|
||||
**Indexes:**
|
||||
- `(account, -created_at)` - List notifications by account ✅ **Used**
|
||||
- `(account, is_read, -created_at)` - Filter unread ✅ **Used**
|
||||
- `(user, -created_at)` - User-specific notifications ❌ **Unused** (all notifications are account-wide)
|
||||
|
||||
---
|
||||
|
||||
### Why So Many Unused Fields?
|
||||
|
||||
The model was designed with **future expansion** in mind, but the current "dropdown-only" implementation uses less than half of the fields. Here's why they exist:
|
||||
|
||||
**Over-Engineered for Current Use:**
|
||||
- `content_type` + `object_id` → For direct object linking (planned: click notification, go to that exact content item)
|
||||
- `user` → For user-specific notifications (planned: "@John, your content is ready")
|
||||
- `metadata` → For rich data (planned: show counts, progress, details in dedicated page)
|
||||
- `read_at` → For analytics (planned: "You read this 2 days ago")
|
||||
- `updated_at` → For editing notifications (planned: update notification instead of creating duplicate)
|
||||
|
||||
**Currently Essential:**
|
||||
- `account`, `title`, `message`, `severity`, `notification_type` → Core notification data
|
||||
- `action_url`, `action_label` → Navigation buttons
|
||||
- `is_read`, `created_at` → Dropdown functionality
|
||||
|
||||
**The Design Mismatch:**
|
||||
You're right - this is a **database schema designed for a full-featured notification system** (with dedicated page, filtering, search, history) but the frontend only implements a **simple dropdown**. The backend is over-built for the current use case.
|
||||
|
||||
---
|
||||
|
||||
## Notification Types
|
||||
|
||||
### AI Operations
|
||||
|
||||
| Type | Severity | Trigger | Message Format |
|
||||
|------|----------|---------|----------------|
|
||||
| `ai_cluster_complete` | success | Clustering finishes | "Created X clusters from Y keywords" |
|
||||
| `ai_cluster_failed` | error | Clustering fails | "Failed to cluster keywords: [error]" |
|
||||
| `ai_ideas_complete` | success | Idea generation finishes | "Generated X content ideas from Y clusters" |
|
||||
| `ai_ideas_failed` | error | Idea generation fails | "Failed to generate ideas: [error]" |
|
||||
| `ai_content_complete` | success | Content generation finishes | "Generated X articles (Y words)" |
|
||||
| `ai_content_failed` | error | Content generation fails | "Failed to generate content: [error]" |
|
||||
| `ai_images_complete` | success | Image generation finishes | "Generated X images" |
|
||||
| `ai_images_failed` | error | Image generation fails | "Failed to generate X images: [error]" |
|
||||
| `ai_prompts_complete` | success | Image prompts created | "X image prompts ready (1 featured + Y in-article)" |
|
||||
| `ai_prompts_failed` | error | Image prompt creation fails | "Failed to create image prompts: [error]" |
|
||||
|
||||
### Workflow Events
|
||||
|
||||
| Type | Severity | Trigger | Message Format |
|
||||
|------|----------|---------|----------------|
|
||||
| `content_ready_review` | info | Content moved to review | "[Title] ready for review" |
|
||||
| `content_published` | success | Content published | '"[Title]" published to [site]' |
|
||||
| `content_publish_failed` | error | Publishing fails | 'Failed to publish "[Title]": [error]' |
|
||||
|
||||
### WordPress Sync
|
||||
|
||||
| Type | Severity | Trigger | Message Format |
|
||||
|------|----------|---------|----------------|
|
||||
| `wordpress_sync_success` | success | Sync completes | "Synced X items with [site]" |
|
||||
| `wordpress_sync_failed` | error | Sync fails | "WordPress sync failed for [site]: [error]" |
|
||||
|
||||
### Credits & Billing
|
||||
|
||||
| Type | Severity | Trigger | Message Format |
|
||||
|------|----------|---------|----------------|
|
||||
| `credits_low` | warning | Credits < 20% | "You've used X% of your credits. Y remaining." |
|
||||
| `credits_depleted` | error | Credits exhausted | "Your credits are exhausted. Upgrade to continue." |
|
||||
|
||||
### Setup & System
|
||||
|
||||
| Type | Severity | Trigger | Message Format |
|
||||
|------|----------|---------|----------------|
|
||||
| `site_setup_complete` | success | All setup steps done | "[Site] is fully configured and ready!" |
|
||||
| `keywords_imported` | info | Keywords added | "Added X keywords to [site]" |
|
||||
| `system_info` | info | System messages | Custom message |
|
||||
|
||||
---
|
||||
|
||||
## Notification Triggers
|
||||
|
||||
### AI Task Completion (AIEngine)
|
||||
|
||||
**Location:** `backend/igny8_core/ai/engine.py`
|
||||
**Methods:** `_create_success_notification()`, `_create_failure_notification()`
|
||||
|
||||
**Flow:**
|
||||
1. AIEngine executes AI function (`auto_cluster`, `generate_ideas`, etc.)
|
||||
2. On **success** (after DONE phase):
|
||||
- Calls `_create_success_notification(function_name, save_result, payload)`
|
||||
- Maps function to NotificationService method
|
||||
- Creates notification with counts/metrics
|
||||
3. On **failure** (in `_handle_error()`):
|
||||
- Calls `_create_failure_notification(function_name, error)`
|
||||
- Creates error notification with error message
|
||||
|
||||
**Mapping:**
|
||||
```python
|
||||
auto_cluster → NotificationService.notify_clustering_complete/failed
|
||||
generate_ideas → NotificationService.notify_ideas_complete/failed
|
||||
generate_content → NotificationService.notify_content_complete/failed
|
||||
generate_image_prompts → NotificationService.notify_prompts_complete/failed
|
||||
generate_images → NotificationService.notify_images_complete/failed
|
||||
```
|
||||
|
||||
### WordPress Publishing
|
||||
|
||||
**Location:** `backend/igny8_core/tasks/wordpress_publishing.py`
|
||||
**Trigger:** After publishing content to WordPress
|
||||
|
||||
```python
|
||||
NotificationService.notify_content_published(
|
||||
account=account,
|
||||
site=site,
|
||||
title=content.title,
|
||||
content_object=content
|
||||
)
|
||||
```
|
||||
|
||||
### WordPress Sync
|
||||
|
||||
**Location:** `backend/igny8_core/tasks/wordpress_publishing.py`
|
||||
**Trigger:** After syncing posts from WordPress
|
||||
|
||||
```python
|
||||
NotificationService.notify_wordpress_sync_success(
|
||||
account=account,
|
||||
site=site,
|
||||
count=synced_count
|
||||
)
|
||||
```
|
||||
|
||||
### Credit Alerts
|
||||
|
||||
**Location:** `backend/igny8_core/business/billing/services/credit_service.py`
|
||||
**Trigger:** After deducting credits
|
||||
|
||||
```python
|
||||
# When credits drop below threshold
|
||||
NotificationService.notify_credits_low(
|
||||
account=account,
|
||||
percentage_used=80,
|
||||
credits_remaining=remaining
|
||||
)
|
||||
|
||||
# When credits exhausted
|
||||
NotificationService.notify_credits_depleted(account=account)
|
||||
```
|
||||
|
||||
### Manual Triggers (Optional)
|
||||
|
||||
Notifications can also be created manually from anywhere:
|
||||
|
||||
```python
|
||||
from igny8_core.business.notifications.services import NotificationService
|
||||
|
||||
NotificationService.notify_keywords_imported(
|
||||
account=account,
|
||||
site=site,
|
||||
count=keyword_count
|
||||
)
|
||||
```
|
||||
|
||||
**Note:** As of v1.2.1, the following actions **DO create notifications**:
|
||||
- ✅ AI task completion/failure (clustering, ideas, content, images, prompts)
|
||||
- ✅ Keyword import via "Add to Workflow" - **Fixed in v1.2.1**
|
||||
|
||||
**Actions that DON'T yet create notifications** (planned):
|
||||
- ❌ WordPress publishing (needs integration in wordpress_publishing.py)
|
||||
- ❌ WordPress sync (needs integration in wordpress_publishing.py)
|
||||
- ❌ Credit alerts (needs integration in credit_service.py)
|
||||
- ❌ Automation completion (planned for future)
|
||||
|
||||
---
|
||||
|
||||
## User Interface
|
||||
|
||||
### Notification Dropdown (Header)
|
||||
|
||||
**Location:** Header bell icon (top right)
|
||||
**File:** `frontend/src/components/header/NotificationDropdown.tsx`
|
||||
|
||||
**Features:**
|
||||
- Shows last 50 notifications
|
||||
- Animated badge with unread count
|
||||
- Click notification to mark read + navigate
|
||||
- "Mark all read" button
|
||||
- Auto-refreshes every 30 seconds
|
||||
- Refreshes when opened (if stale > 1 minute)
|
||||
- "View All Notifications" link to full page
|
||||
|
||||
### Notifications Page (Full History)
|
||||
|
||||
**Location:** `/account/notifications`
|
||||
**File:** `frontend/src/pages/account/NotificationsPage.tsx`
|
||||
**Access:** Sidebar → ACCOUNT → Notifications OR NotificationDropdown → "View All Notifications"
|
||||
|
||||
**Features:**
|
||||
- **Filters:**
|
||||
- Severity (info/success/warning/error)
|
||||
- Notification type (AI operations, WordPress sync, credits, etc.)
|
||||
- Read status (all/unread/read)
|
||||
- Site (filter by specific site)
|
||||
- Date range (from/to dates)
|
||||
- **Actions:**
|
||||
- Mark individual notifications as read
|
||||
- Mark all as read (bulk action)
|
||||
- Delete individual notifications
|
||||
- Click notification to navigate to action URL
|
||||
- **Display:**
|
||||
- Full notification history with pagination
|
||||
- Severity icons with color coding
|
||||
- Relative timestamps ("2 hours ago")
|
||||
- Site badge when applicable
|
||||
- Action buttons for related pages
|
||||
- Unread badge in sidebar menu
|
||||
|
||||
**v1.2.2 Implementation:**
|
||||
- ✅ Full-page notifications view created
|
||||
- ✅ Advanced filtering by severity, type, read status, site, date range
|
||||
- ✅ Bulk actions (mark all read)
|
||||
- ✅ Individual actions (mark read, delete)
|
||||
- ✅ Added to sidebar under ACCOUNT section
|
||||
- ✅ Unread count badge in sidebar
|
||||
- ✅ Fixed broken link in NotificationDropdown (was `/notifications`, now `/account/notifications`)
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### List Notifications
|
||||
|
||||
```http
|
||||
GET /api/v1/notifications/
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
- `page` - Page number (default: 1)
|
||||
- `page_size` - Results per page (default: 20)
|
||||
- `is_read` - Filter by read status (`true`/`false`)
|
||||
- `notification_type` - Filter by type (e.g., `ai_cluster_complete`)
|
||||
- `severity` - Filter by severity (`info`/`success`/`warning`/`error`)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"count": 42,
|
||||
"next": "/api/v1/notifications/?page=2",
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 123,
|
||||
"notification_type": "ai_cluster_complete",
|
||||
"severity": "success",
|
||||
"title": "Clustering Complete",
|
||||
"message": "Created 5 clusters from 50 keywords",
|
||||
"is_read": false,
|
||||
"created_at": "2025-12-28T10:30:00Z",
|
||||
"read_at": null,
|
||||
"action_label": "View Clusters",
|
||||
"action_url": "/planner/clusters",
|
||||
"site": {"id": 1, "name": "My Blog"},
|
||||
"metadata": {
|
||||
"cluster_count": 5,
|
||||
"keyword_count": 50
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Get Unread Count
|
||||
|
||||
```http
|
||||
GET /api/v1/notifications/unread-count/
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"unread_count": 7
|
||||
}
|
||||
```
|
||||
|
||||
### Mark as Read
|
||||
|
||||
```http
|
||||
POST /api/v1/notifications/{id}/read/
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"is_read": true,
|
||||
"read_at": "2025-12-28T10:35:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Mark All as Read
|
||||
|
||||
```http
|
||||
POST /api/v1/notifications/read-all/
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"updated_count": 7,
|
||||
"message": "Marked 7 notifications as read"
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Notification
|
||||
|
||||
```http
|
||||
DELETE /api/v1/notifications/{id}/
|
||||
```
|
||||
|
||||
**Response:** `204 No Content`
|
||||
|
||||
---
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### Notification Store
|
||||
|
||||
**File:** `frontend/src/store/notificationStore.ts`
|
||||
|
||||
**Features:**
|
||||
- Zustand store for state management
|
||||
- In-memory queue for optimistic UI updates
|
||||
- API sync for persistent notifications
|
||||
- Auto-fetch on mount and periodic sync (every 30 seconds)
|
||||
- Auto-refresh when dropdown opens (if stale > 1 minute)
|
||||
|
||||
**Key Methods:**
|
||||
```typescript
|
||||
// Add local notification (optimistic)
|
||||
addNotification(notification)
|
||||
|
||||
// Mark as read (optimistic + API sync)
|
||||
markAsRead(id)
|
||||
|
||||
// Mark all as read
|
||||
markAllAsRead()
|
||||
|
||||
// Fetch from API
|
||||
fetchNotifications()
|
||||
|
||||
// Sync unread count
|
||||
syncUnreadCount()
|
||||
```
|
||||
|
||||
### Notification Dropdown
|
||||
|
||||
**File:** `frontend/src/components/header/NotificationDropdown.tsx`
|
||||
|
||||
**Features:**
|
||||
- Badge with unread count (animated ping when > 0)
|
||||
- Dropdown with notification list
|
||||
- Click notification to mark read and navigate
|
||||
- "Mark all read" action
|
||||
- Empty state when no notifications
|
||||
- Auto-fetch on open if stale
|
||||
|
||||
**Icon Mapping:**
|
||||
```typescript
|
||||
auto_cluster → GroupIcon
|
||||
generate_ideas → BoltIcon
|
||||
generate_content → FileTextIcon
|
||||
generate_images → FileIcon
|
||||
system → AlertIcon
|
||||
success → CheckCircleIcon
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Logic
|
||||
|
||||
### Visibility Rules
|
||||
|
||||
1. **Account-wide notifications** (`user=NULL`):
|
||||
- Visible to ALL users in the account
|
||||
- Example: "Automation completed 10 tasks"
|
||||
|
||||
2. **User-specific notifications** (`user=User`):
|
||||
- Only visible to that specific user
|
||||
- Example: "Your content is ready for review"
|
||||
|
||||
3. **Site-filtered** (frontend):
|
||||
- Frontend can filter by site in UI
|
||||
- Backend always returns all account notifications
|
||||
|
||||
### Read Status
|
||||
|
||||
- Notifications start as `is_read=False`
|
||||
- Clicking notification marks it read (API call + optimistic update)
|
||||
- "Mark all read" bulk updates all unread notifications
|
||||
- Read notifications stay in dropdown (can be filtered out in future)
|
||||
|
||||
### Retention Policy
|
||||
|
||||
- Notifications never auto-delete (future: add retention policy)
|
||||
- Users can manually delete notifications
|
||||
- Admin can clean up old notifications via management command (future)
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Notifications not appearing | AIEngine not calling NotificationService | Fixed in v1.2.1 |
|
||||
| "Add to workflow" no notification | KeywordViewSet not calling NotificationService | Fixed in v1.2.1 |
|
||||
| Can't see notification history | No dedicated notifications page | Fixed in v1.2.2 - Page created at /account/notifications |
|
||||
| "View All" button → 404 | Link to `/notifications` but page doesn't exist | Fixed in v1.2.2 - Link updated to /account/notifications |
|
||||
| Duplicate notifications | Multiple AI task retries | Check task retry logic |
|
||||
| Missing notifications | Celery worker crashed | Check Celery logs |
|
||||
| Unread count wrong | Race condition in state | Refresh page or wait for sync |
|
||||
| Action URL not working | Incorrect route in action_url | Check NotificationService methods |
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| AIEngine | NotificationService | AI task success/failure |
|
||||
| WordPress Publisher | NotificationService | Publishing/sync events |
|
||||
| Credit Service | NotificationService | Low credits/depleted |
|
||||
| Automation | NotificationService | Automation milestones (future) |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Notification preferences | 🔜 Planned | User can toggle notification types |
|
||||
| Email notifications | 🔜 Planned | Send email for critical notifications |
|
||||
| Push notifications | 🔜 Planned | Browser push for real-time alerts |
|
||||
| Notification retention | 🔜 Planned | Auto-delete after 30/60 days |
|
||||
| Notification categories | 🔜 Planned | Group by module (Planner, Writer, etc.) |
|
||||
| Notification sounds | 🔜 Planned | Audio alerts for important events |
|
||||
| Webhook notifications | 🔜 Planned | POST to external webhook URLs |
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| v1.2.2 | Dec 28, 2025 | **NEW:** Full notifications page at /account/notifications with filtering, bulk actions, and sidebar integration |
|
||||
| v1.2.1 | Dec 28, 2025 | **Fixed:** Notifications now created on AI task completion + keyword import |
|
||||
| v1.2.0 | Dec 27, 2025 | Initial notifications system implementation |
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Notification Creation
|
||||
|
||||
```python
|
||||
# In Django shell: docker exec -it igny8_backend python manage.py shell
|
||||
from igny8_core.business.notifications.services import NotificationService
|
||||
from igny8_core.auth.models import Account
|
||||
|
||||
account = Account.objects.first()
|
||||
|
||||
# Test clustering notification
|
||||
NotificationService.notify_clustering_complete(
|
||||
account=account,
|
||||
cluster_count=5,
|
||||
keyword_count=50
|
||||
)
|
||||
|
||||
# Test error notification
|
||||
NotificationService.notify_clustering_failed(
|
||||
account=account,
|
||||
error="Insufficient credits"
|
||||
)
|
||||
```
|
||||
|
||||
### Test Frontend
|
||||
|
||||
1. Run AI operation (clustering, idea generation, etc.)
|
||||
2. Check notification dropdown (bell icon in header)
|
||||
3. Verify unread badge appears
|
||||
4. Click notification to mark read and navigate
|
||||
5. Click "Mark all read" to clear badge
|
||||
|
||||
---
|
||||
|
||||
## Debug Commands
|
||||
|
||||
```bash
|
||||
# Check Celery logs for notification creation
|
||||
docker logs igny8_celery_worker -f | grep -i "notification"
|
||||
|
||||
# Check notifications in database
|
||||
docker exec -it igny8_backend python manage.py shell
|
||||
>>> from igny8_core.business.notifications.models import Notification
|
||||
>>> Notification.objects.count()
|
||||
>>> Notification.objects.filter(is_read=False).count()
|
||||
>>> Notification.objects.last().title
|
||||
|
||||
# Test notification API
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
http://localhost:8011/api/v1/notifications/
|
||||
|
||||
# Check notification creation code
|
||||
grep -r "notify_clustering_complete" backend/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Notes
|
||||
|
||||
**Design Pattern:** Service Layer + Repository Pattern
|
||||
- `NotificationService` provides static methods for creating notifications
|
||||
- Each notification type has dedicated method with validation
|
||||
- `Notification.create_notification()` is class method for low-level creation
|
||||
- Views use `AccountModelViewSet` for automatic account filtering
|
||||
|
||||
**Why Not Signals?**
|
||||
- Explicit is better than implicit
|
||||
- Clear call sites for debugging
|
||||
- Easier to test and mock
|
||||
- No hidden side effects
|
||||
- Can pass context-specific data
|
||||
|
||||
**Lazy Imports:**
|
||||
- NotificationService uses lazy imports in AIEngine to avoid Django app loading issues
|
||||
- Import inside method, not at module level
|
||||
263
v2/Live Docs on Server/igny8-app-docs/10-MODULES/OPTIMIZER.md
Normal file
263
v2/Live Docs on Server/igny8-app-docs/10-MODULES/OPTIMIZER.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# Optimizer Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ⏸️ Inactive (Disabled by Default)
|
||||
**Backend Path:** `backend/igny8_core/modules/optimizer/` + `backend/igny8_core/business/optimization/`
|
||||
**Frontend Path:** `frontend/src/pages/Optimizer/`
|
||||
|
||||
---
|
||||
|
||||
## Module Status
|
||||
|
||||
| Aspect | Current State | Notes |
|
||||
|--------|---------------|-------|
|
||||
| Backend API | ✅ Implemented | Endpoints functional |
|
||||
| Business Logic | ✅ Implemented | Optimization service exists |
|
||||
| Frontend Pages | ✅ Implemented | UI exists |
|
||||
| Sidebar Nav | ⚠️ Conditional | Hidden when disabled |
|
||||
| Route Protection | ❌ Not Protected | Direct URL access still works |
|
||||
| Dashboard References | ❌ Not Hidden | May show in dashboard |
|
||||
| Default State | Disabled | `optimizer_enabled = True` in model, but typically disabled |
|
||||
|
||||
**⚠️ Pending Implementation:** Extend module disable to route-level protection and all page references.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Views | `modules/optimizer/views.py` | `OptimizerViewSet` |
|
||||
| Models | `business/optimization/models.py` | `OptimizationTask` |
|
||||
| Services | `business/optimization/services/*.py` | Optimization logic |
|
||||
| AI Function | `ai/functions/optimize.py` | `OptimizeContentFunction` |
|
||||
| Frontend Pages | `pages/Optimizer/index.tsx` | Optimizer overview |
|
||||
| Frontend Pages | `pages/Optimizer/OptimizerContent.tsx` | Select content |
|
||||
| Frontend Pages | `pages/Optimizer/OptimizerPreview.tsx` | Analysis preview |
|
||||
| API Client | `api/optimizer.api.ts` | `optimizerApi` |
|
||||
| Module Control | `modules/system/settings_models.py` | `ModuleEnableSettings.optimizer_enabled` |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Optimizer module provides AI-powered content optimization:
|
||||
- Analyze existing content for SEO improvements
|
||||
- Suggest and apply optimizations
|
||||
- Track before/after scores
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### OptimizationTask
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| content | FK | Target content |
|
||||
| entry_point | CharField | auto/igny8/wordpress/external/manual |
|
||||
| status | CharField | pending/analyzing/optimizing/completed/failed |
|
||||
| original_score | Integer | Score before optimization |
|
||||
| optimized_score | Integer | Score after optimization |
|
||||
| original_content | TextField | Content before changes |
|
||||
| optimized_content | TextField | Content after changes |
|
||||
| suggestions | JSON | Optimization suggestions |
|
||||
| applied_changes | JSON | Changes that were applied |
|
||||
| credits_used | Decimal | Credits consumed |
|
||||
| created_at | DateTime | Creation date |
|
||||
| completed_at | DateTime | Completion date |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| POST | `/api/v1/optimizer/optimize/` | `OptimizerViewSet.optimize` | Optimize single content |
|
||||
| POST | `/api/v1/optimizer/batch_optimize/` | `OptimizerViewSet.batch_optimize` | Optimize multiple items |
|
||||
| POST | `/api/v1/optimizer/analyze/` | `OptimizerViewSet.analyze` | Analyze without applying |
|
||||
|
||||
### Entry Points
|
||||
|
||||
| Entry Point | Description |
|
||||
|-------------|-------------|
|
||||
| `auto` | Auto-detect based on content source |
|
||||
| `igny8` | IGNY8-generated content |
|
||||
| `wordpress` | WordPress-synced content |
|
||||
| `external` | External platform content |
|
||||
| `manual` | Manual optimization trigger |
|
||||
|
||||
### Optimize Request
|
||||
|
||||
```json
|
||||
{
|
||||
"content_id": 123,
|
||||
"entry_point": "igny8",
|
||||
"optimization_goals": ["seo", "readability", "keyword_density"]
|
||||
}
|
||||
```
|
||||
|
||||
### Optimize Response
|
||||
|
||||
```json
|
||||
{
|
||||
"task_id": 456,
|
||||
"content_id": 123,
|
||||
"original_score": 65,
|
||||
"optimized_score": 85,
|
||||
"suggestions": [
|
||||
{
|
||||
"type": "keyword_density",
|
||||
"description": "Increase keyword 'SEO' usage",
|
||||
"applied": true
|
||||
},
|
||||
{
|
||||
"type": "meta_description",
|
||||
"description": "Meta description too short",
|
||||
"applied": true
|
||||
}
|
||||
],
|
||||
"credits_used": 15
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Logic
|
||||
|
||||
### Content Analysis
|
||||
|
||||
**Trigger:** User clicks "Analyze"
|
||||
**Credit Cost:** None (analysis only)
|
||||
|
||||
**Checks:**
|
||||
- Keyword density and distribution
|
||||
- Meta title and description optimization
|
||||
- Heading structure (H1, H2, H3)
|
||||
- Readability score
|
||||
- Content length
|
||||
- Internal/external link count
|
||||
- Image alt text presence
|
||||
|
||||
### Content Optimization (AI)
|
||||
|
||||
**Trigger:** User clicks "Optimize"
|
||||
**AI Function:** `OptimizeContentFunction`
|
||||
**Credit Cost:** Per 200 words
|
||||
|
||||
**Flow:**
|
||||
1. Load content with metadata
|
||||
2. Analyze current state
|
||||
3. AIEngine executes `OptimizeContentFunction`:
|
||||
- Reviews content against SEO best practices
|
||||
- Suggests improvements
|
||||
- Rewrites sections if needed
|
||||
4. Create `OptimizationTask` record
|
||||
5. Save original and optimized versions
|
||||
6. Return comparison view
|
||||
|
||||
---
|
||||
|
||||
## Module Enable Control
|
||||
|
||||
### Backend Model
|
||||
|
||||
```python
|
||||
# modules/system/settings_models.py
|
||||
class ModuleEnableSettings(AccountBaseModel):
|
||||
optimizer_enabled = models.BooleanField(default=True)
|
||||
```
|
||||
|
||||
### Frontend Check
|
||||
|
||||
```typescript
|
||||
// layout/AppSidebar.tsx
|
||||
if (isModuleEnabled('optimizer')) {
|
||||
workflowItems.push({
|
||||
name: "Optimizer",
|
||||
path: "/optimizer/content",
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Current Limitation
|
||||
|
||||
Direct URL access to `/optimizer/*` routes still works even when module is disabled.
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Optimizer Overview (`/optimizer`)
|
||||
|
||||
- Module overview
|
||||
- Quick stats
|
||||
- Recent optimizations
|
||||
|
||||
### Select Content (`/optimizer/content`)
|
||||
|
||||
- List of content available for optimization
|
||||
- Filter by score, status
|
||||
- Start analysis button
|
||||
- Batch optimize action
|
||||
|
||||
### Analysis Preview (`/optimizer/preview`)
|
||||
|
||||
- Side-by-side comparison
|
||||
- Suggestion list
|
||||
- Accept/reject changes
|
||||
- Apply optimization button
|
||||
|
||||
---
|
||||
|
||||
## Optimization Scoring
|
||||
|
||||
| Metric | Weight | Description |
|
||||
|--------|--------|-------------|
|
||||
| Keyword Density | 20% | Target 1-3% density |
|
||||
| Meta Quality | 15% | Title 50-60 chars, desc 150-160 chars |
|
||||
| Heading Structure | 15% | Proper H1-H6 hierarchy |
|
||||
| Readability | 15% | Flesch-Kincaid score |
|
||||
| Content Length | 10% | Meets word count target |
|
||||
| Internal Links | 10% | 2-5 internal links |
|
||||
| Image Optimization | 10% | Alt text, sizing |
|
||||
| Mobile Friendly | 5% | No wide elements |
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| Content | Optimizer | Manual analysis/optimize |
|
||||
| WordPress | Optimizer | Synced content optimization |
|
||||
| Optimizer | Content | Apply changes |
|
||||
| Automation | Optimizer | Automated optimization (future) |
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Module visible when disabled | Only sidebar hidden | Pending: route protection |
|
||||
| Optimization not improving | Content already good | Check original score |
|
||||
| High credit usage | Large content | Optimize sections |
|
||||
| Changes not saving | Apply not clicked | Click "Apply Changes" |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Route-level protection | 🔜 Pending | Block access when module disabled |
|
||||
| Dashboard card hiding | 🔜 Pending | Hide from dashboard when disabled |
|
||||
| Automation integration | 🔜 Planned | Add to automation pipeline |
|
||||
| Partial optimization | 🔜 Planned | Optimize specific sections only |
|
||||
| Competitor analysis | 🔜 Planned | Compare against top-ranking content |
|
||||
| A/B testing | 🔜 Planned | Track performance of optimizations |
|
||||
232
v2/Live Docs on Server/igny8-app-docs/10-MODULES/PLANNER.md
Normal file
232
v2/Live Docs on Server/igny8-app-docs/10-MODULES/PLANNER.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Planner Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ✅ Active
|
||||
**Backend Path:** `backend/igny8_core/modules/planner/`
|
||||
**Frontend Path:** `frontend/src/pages/Planner/`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Models | `modules/planner/models.py` | `Keywords`, `Clusters`, `ContentIdeas` |
|
||||
| Views | `modules/planner/views.py` | `KeywordViewSet`, `ClusterViewSet`, `ContentIdeaViewSet` |
|
||||
| Serializers | `modules/planner/serializers.py` | Keyword/Cluster/Idea serializers |
|
||||
| AI Functions | `ai/functions/clustering.py` | `AutoClusterFunction` |
|
||||
| AI Functions | `ai/functions/ideas.py` | `GenerateIdeasFunction` |
|
||||
| Frontend Pages | `pages/Planner/Keywords.tsx` | Keyword management |
|
||||
| Frontend Pages | `pages/Planner/Clusters.tsx` | Cluster management |
|
||||
| Frontend Pages | `pages/Planner/Ideas.tsx` | Content ideas |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Planner module manages the SEO content planning pipeline:
|
||||
|
||||
```
|
||||
SeedKeywords (Global) → Keywords (Site/Sector) → Clusters → ContentIdeas → Tasks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### Keywords
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| seed_keyword | FK | Reference to global SeedKeyword |
|
||||
| keyword | CharField | Keyword text |
|
||||
| search_volume | Integer | Monthly search volume |
|
||||
| difficulty | Integer | SEO difficulty (0-100) |
|
||||
| cpc | Decimal | Cost per click |
|
||||
| search_intent | CharField | informational/commercial/transactional/navigational |
|
||||
| status | CharField | new/mapped/used |
|
||||
| cluster | FK | Assigned cluster (nullable) |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
### Clusters
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| name | CharField | Cluster name |
|
||||
| description | TextField | Cluster description |
|
||||
| primary_keyword | FK | Main keyword |
|
||||
| status | CharField | draft/active/complete |
|
||||
| keyword_count | Integer | Number of keywords |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
### ContentIdeas
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| cluster | FK | Source cluster |
|
||||
| title | CharField | Content title |
|
||||
| description | TextField | Content brief |
|
||||
| target_keywords | JSON | Keywords to target |
|
||||
| content_type | CharField | blog_post/guide/comparison/etc. |
|
||||
| word_count_target | Integer | Target word count |
|
||||
| status | CharField | draft/queued/used |
|
||||
| priority | Integer | Priority score |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Keywords
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/planner/keywords/` | `KeywordViewSet.list` | List keywords with filtering |
|
||||
| POST | `/api/v1/planner/keywords/` | `KeywordViewSet.create` | Create single keyword |
|
||||
| GET | `/api/v1/planner/keywords/{id}/` | `KeywordViewSet.retrieve` | Get keyword detail |
|
||||
| PUT | `/api/v1/planner/keywords/{id}/` | `KeywordViewSet.update` | Update keyword |
|
||||
| DELETE | `/api/v1/planner/keywords/{id}/` | `KeywordViewSet.destroy` | Soft delete keyword |
|
||||
| POST | `/api/v1/planner/keywords/bulk_delete/` | `KeywordViewSet.bulk_delete` | Hard delete multiple |
|
||||
| POST | `/api/v1/planner/keywords/bulk_status/` | `KeywordViewSet.bulk_status` | Update status for multiple |
|
||||
| POST | `/api/v1/planner/keywords/add_to_workflow/` | `KeywordViewSet.add_to_workflow` | Add SeedKeywords to workflow |
|
||||
|
||||
**Filters:** `?site_id=`, `?sector_id=`, `?status=`, `?cluster_id=`, `?difficulty_min=`, `?difficulty_max=`, `?volume_min=`, `?volume_max=`
|
||||
|
||||
### Clusters
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/planner/clusters/` | `ClusterViewSet.list` | List clusters |
|
||||
| POST | `/api/v1/planner/clusters/` | `ClusterViewSet.create` | Create cluster manually |
|
||||
| POST | `/api/v1/planner/clusters/auto_cluster/` | `ClusterViewSet.auto_cluster` | AI-powered clustering |
|
||||
| POST | `/api/v1/planner/clusters/generate_ideas/` | `ClusterViewSet.generate_ideas` | Generate ideas from clusters |
|
||||
|
||||
### Content Ideas
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/planner/ideas/` | `ContentIdeaViewSet.list` | List ideas |
|
||||
| POST | `/api/v1/planner/ideas/` | `ContentIdeaViewSet.create` | Create idea manually |
|
||||
| POST | `/api/v1/planner/ideas/create_tasks/` | `ContentIdeaViewSet.create_tasks` | Convert ideas to tasks |
|
||||
|
||||
---
|
||||
|
||||
## Business Logic
|
||||
|
||||
### Auto Clustering (AI)
|
||||
|
||||
**Trigger:** User clicks "Auto Cluster" button
|
||||
**AI Function:** `AutoClusterFunction`
|
||||
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
|
||||
|
||||
**Flow:**
|
||||
1. User selects keywords (or all unclustered)
|
||||
2. Frontend calls `POST /clusters/auto_cluster/`
|
||||
3. Backend validates minimum 5 keywords
|
||||
4. AIEngine executes `AutoClusterFunction`:
|
||||
- Sends keywords to GPT-4
|
||||
- AI groups by semantic similarity
|
||||
- Returns cluster assignments
|
||||
5. Creates/updates `Clusters` records
|
||||
6. Assigns keywords to clusters
|
||||
7. Returns created clusters
|
||||
|
||||
### Generate Ideas (AI)
|
||||
|
||||
**Trigger:** User clicks "Generate Ideas" on cluster(s)
|
||||
**AI Function:** `GenerateIdeasFunction`
|
||||
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
|
||||
|
||||
**Flow:**
|
||||
1. User selects clusters
|
||||
2. Frontend calls `POST /clusters/generate_ideas/`
|
||||
3. Backend validates clusters have keywords
|
||||
4. AIEngine executes `GenerateIdeasFunction`:
|
||||
- Analyzes cluster keywords
|
||||
- Generates content titles + briefs
|
||||
- Suggests word counts and content types
|
||||
5. Creates `ContentIdeas` records
|
||||
6. Returns created ideas
|
||||
|
||||
### Add Keywords to Workflow
|
||||
|
||||
**Trigger:** User selects SeedKeywords in setup
|
||||
**Credit Cost:** None
|
||||
|
||||
**Flow:**
|
||||
1. User browses global SeedKeywords
|
||||
2. Selects keywords to add to their sector
|
||||
3. Frontend calls `POST /keywords/add_to_workflow/`
|
||||
4. Backend creates `Keywords` records linked to SeedKeywords
|
||||
5. Keywords appear in Planner for clustering
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Keywords Page (`/planner/keywords`)
|
||||
|
||||
- Table of all keywords with filtering
|
||||
- Bulk actions: delete, update status
|
||||
- Add keywords from SeedKeyword library
|
||||
- Import keywords from CSV
|
||||
- Assign to clusters manually
|
||||
|
||||
### Clusters Page (`/planner/clusters`)
|
||||
|
||||
- Grid/list of clusters
|
||||
- Auto-cluster action (AI)
|
||||
- Generate ideas action (AI)
|
||||
- View keywords in cluster
|
||||
- Cluster status management
|
||||
|
||||
### Ideas Page (`/planner/ideas`)
|
||||
|
||||
- Table of content ideas
|
||||
- Convert to tasks action
|
||||
- Edit idea details
|
||||
- Priority management
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| SeedKeywords | Keywords | Add to workflow |
|
||||
| Keywords | Clusters | Auto clustering |
|
||||
| Clusters | ContentIdeas | Generate ideas |
|
||||
| ContentIdeas | Tasks | Create tasks |
|
||||
| Automation Stage 1 | Clusters | Automated clustering |
|
||||
| Automation Stage 2 | ContentIdeas | Automated idea generation |
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Clustering fails | Less than 5 keywords | Ensure minimum keywords |
|
||||
| Ideas not generating | Cluster has no keywords | Assign keywords to cluster |
|
||||
| Keywords not showing | Wrong site/sector filter | Check active site/sector |
|
||||
| Duplicate keywords | Same keyword added twice | Check seed_keyword reference |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Keyword import improvements | 🔜 Planned | Better CSV parsing and validation |
|
||||
| Cluster merging | 🔜 Planned | Merge similar clusters |
|
||||
| Idea prioritization | 🔜 Planned | AI-based priority scoring |
|
||||
536
v2/Live Docs on Server/igny8-app-docs/10-MODULES/PUBLISHER.md
Normal file
536
v2/Live Docs on Server/igny8-app-docs/10-MODULES/PUBLISHER.md
Normal file
@@ -0,0 +1,536 @@
|
||||
# Publisher Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Status:** ✅ Active
|
||||
**Version:** 1.8.4
|
||||
**Backend Path:** `backend/igny8_core/modules/publisher/` + `backend/igny8_core/business/publishing/`
|
||||
**Frontend Path:** `frontend/src/pages/Publisher/`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Views | `modules/publisher/views.py` | `PublishingRecordViewSet`, `DeploymentViewSet`, `PublishContentViewSet` |
|
||||
| Models | `business/publishing/models.py` | `PublishingRecord`, `DeploymentRecord` |
|
||||
| Integration Models | `modules/integration/models.py` | `PublishingSettings` |
|
||||
| **Unified Settings** | `modules/integration/views/unified_settings.py` | **v1.8.0** Consolidated settings API |
|
||||
| Services | `business/publishing/services/*.py` | Publishing orchestration |
|
||||
| Scheduler Tasks | `igny8_core/tasks/publishing_scheduler.py` | Celery beat tasks |
|
||||
| URLs | `modules/publisher/urls.py` | Publisher endpoints |
|
||||
| Frontend | `pages/Publisher/ContentCalendar.tsx` | Content calendar view |
|
||||
| **Site Settings** | `pages/Sites/AIAutomationSettings.tsx` | **v1.8.0** Unified settings UI |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Publisher module manages:
|
||||
- Content publishing pipeline
|
||||
- **Publishing scheduler (automated publishing)**
|
||||
- Publishing record tracking
|
||||
- Deployment management
|
||||
- Multi-destination publishing
|
||||
- **Content calendar visualization**
|
||||
|
||||
**Settings Location (v1.8.0):** Site Settings → Automation tab
|
||||
|
||||
> ⚠️ **v1.8.0 Change:** The standalone `/publisher/settings` page has been removed. Publishing settings are now configured in Site Settings → Automation tab under "Capacity" and "Schedule" sections.
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### PublishingRecord
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| content | FK | Source content |
|
||||
| destination | CharField | wordpress/ghost/webflow |
|
||||
| external_id | CharField | ID on destination platform |
|
||||
| external_url | URLField | Published URL |
|
||||
| status | CharField | pending/published/failed/retracted |
|
||||
| published_at | DateTime | Publication time |
|
||||
| metadata | JSON | Additional data |
|
||||
| created_at | DateTime | Record creation |
|
||||
|
||||
### DeploymentRecord
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Target site |
|
||||
| deployment_type | CharField | full/incremental |
|
||||
| status | CharField | pending/deploying/completed/failed |
|
||||
| items_deployed | Integer | Number of items |
|
||||
| started_at | DateTime | Start time |
|
||||
| completed_at | DateTime | Completion time |
|
||||
| error_log | TextField | Errors encountered |
|
||||
| metadata | JSON | Deployment details |
|
||||
|
||||
### PublishingSettings (v1.3.2, updated v1.8.0)
|
||||
|
||||
Site-level publishing configuration:
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| site | OneToOne | Parent site (unique) |
|
||||
| auto_approval_enabled | Boolean | Auto-approve content |
|
||||
| auto_publish_enabled | Boolean | Auto-publish approved content |
|
||||
| daily_publish_limit | Integer | Max publications per day |
|
||||
| weekly_publish_limit | Integer | Max publications per week |
|
||||
| monthly_publish_limit | Integer | Max publications per month |
|
||||
| publish_days | JSON | Days of week for publishing ["mon","wed","fri"] |
|
||||
| publish_time_slots | JSON | Time slots for publishing [{"start":"09:00","end":"17:00"}] |
|
||||
| **total_items_per_run** | Integer | **v1.8.0** Computed capacity display |
|
||||
|
||||
### Content Site Status Fields (v1.3.2)
|
||||
|
||||
Added to Content model for scheduling:
|
||||
|
||||
| Field | Type | Values |
|
||||
|-------|------|--------|
|
||||
| site_status | CharField | not_published, scheduled, publishing, published, failed |
|
||||
| scheduled_publish_at | DateTime | Future publication time (nullable) |
|
||||
| site_status_updated_at | DateTime | Last status change timestamp |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Unified Settings API (v1.8.0)
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| **GET** | `/api/v1/integration/sites/{site_id}/unified-settings/` | Get all automation + publishing settings |
|
||||
| **PATCH** | `/api/v1/integration/sites/{site_id}/unified-settings/` | Update settings |
|
||||
|
||||
### Publishing & Records
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/publisher/records/` | `PublishingRecordViewSet.list` | List publishing records |
|
||||
| POST | `/api/v1/publisher/records/` | `PublishingRecordViewSet.create` | Create record |
|
||||
| GET | `/api/v1/publisher/deployments/` | `DeploymentViewSet.list` | List deployments |
|
||||
| POST | `/api/v1/publisher/publish/` | `PublishContentViewSet.publish` | Publish content immediately |
|
||||
| GET | `/api/v1/publisher/publish/status/` | `PublishContentViewSet.status` | Get publishing status |
|
||||
| GET | `/api/v1/publisher/site-definition/` | `SiteDefinitionViewSet.list` | Public site definitions |
|
||||
|
||||
### Scheduling Endpoints (v1.3.2+)
|
||||
|
||||
| Method | Path | Purpose | Request Body | Response |
|
||||
|--------|------|---------|--------------|----------|
|
||||
| **POST** | `/api/v1/writer/content/{id}/schedule/` | Schedule content for future publishing | `{ "scheduled_publish_at": "2025-01-20T09:00:00Z" }` | `{ "success": true, "scheduled_publish_at": "2025-01-20T09:00:00Z" }` |
|
||||
| **POST** | `/api/v1/writer/content/{id}/reschedule/` | Reschedule existing scheduled content | `{ "scheduled_at": "2025-01-21T10:00:00Z" }` | `{ "success": true, "scheduled_publish_at": "2025-01-21T10:00:00Z" }` |
|
||||
| **POST** | `/api/v1/writer/content/{id}/unschedule/` | Cancel scheduled publishing | `{}` | `{ "success": true, "message": "Content unscheduled" }` |
|
||||
| **POST** | `/api/v1/writer/content/bulk_schedule/` | Bulk schedule with site defaults | `{ "content_ids": [1,2,3], "use_site_defaults": true, "site_id": 5 }` | `{ "success": true, "scheduled_count": 3, "schedule_preview": [...] }` |
|
||||
| **POST** | `/api/v1/writer/content/bulk_schedule_preview/` | Preview bulk schedule times | `{ "content_ids": [1,2,3], "site_id": 5 }` | `{ "schedule_preview": [...], "site_settings": {...} }` |
|
||||
|
||||
### Legacy Publishing Settings (deprecated)
|
||||
|
||||
> ⚠️ **Deprecated in v1.8.0:** Use unified-settings API instead
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| ~~GET~~ | ~~`/api/v1/sites/{site_id}/settings?tab=publishing`~~ | ~~Get site publishing settings~~ |
|
||||
| ~~PUT~~ | ~~`/api/v1/sites/{site_id}/publishing-settings/`~~ | ~~Update publishing settings~~ |
|
||||
|
||||
---
|
||||
|
||||
## API Usage Examples
|
||||
|
||||
### Publish Content Immediately
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
POST /api/v1/publisher/publish/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content_id": 123,
|
||||
"destinations": ["wordpress"] # or ["shopify"], ["custom"]
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"success": true,
|
||||
"results": [
|
||||
{
|
||||
"destination": "wordpress",
|
||||
"success": true,
|
||||
"external_id": "456",
|
||||
"url": "https://mysite.com/article-title/",
|
||||
"publishing_record_id": 789,
|
||||
"platform_type": "wordpress"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response:**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Publishing API error: Invalid credentials"
|
||||
}
|
||||
```
|
||||
|
||||
### Schedule Content for Future Publishing
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
POST /api/v1/writer/content/123/schedule/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scheduled_publish_at": "2025-01-20T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"scheduled_publish_at": "2025-01-20T09:00:00Z",
|
||||
"site_status": "scheduled"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Content `site_status` changes from `not_published` → `scheduled`
|
||||
- Celery task `process_scheduled_publications` will publish at scheduled time
|
||||
- Runs every 5 minutes, so publishing happens within 5 min of scheduled time
|
||||
|
||||
### Reschedule Content
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
POST /api/v1/writer/content/123/reschedule/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scheduled_at": "2025-01-21T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"scheduled_publish_at": "2025-01-21T10:00:00Z",
|
||||
"site_status": "scheduled"
|
||||
}
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- Reschedule from `site_status='scheduled'` (change time)
|
||||
- Reschedule from `site_status='failed'` (retry at new time)
|
||||
|
||||
### Unschedule Content
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
POST /api/v1/writer/content/123/unschedule/
|
||||
Content-Type: application/json
|
||||
|
||||
{}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Content unscheduled successfully",
|
||||
"site_status": "not_published"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Removes content from publishing queue
|
||||
- Content returns to `site_status='not_published'`
|
||||
- Can be rescheduled or published immediately later
|
||||
|
||||
### Bulk Schedule with Site Defaults
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
POST /api/v1/writer/content/bulk_schedule/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content_ids": [123, 124, 125, 126],
|
||||
"use_site_defaults": true,
|
||||
"site_id": 45
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"scheduled_count": 4,
|
||||
"schedule_preview": [
|
||||
{
|
||||
"content_id": 123,
|
||||
"scheduled_at": "2025-01-17T09:00:00Z",
|
||||
"title": "First Article"
|
||||
},
|
||||
{
|
||||
"content_id": 124,
|
||||
"scheduled_at": "2025-01-17T09:15:00Z",
|
||||
"title": "Second Article"
|
||||
},
|
||||
{
|
||||
"content_id": 125,
|
||||
"scheduled_at": "2025-01-17T09:30:00Z",
|
||||
"title": "Third Article"
|
||||
},
|
||||
{
|
||||
"content_id": 126,
|
||||
"scheduled_at": "2025-01-17T09:45:00Z",
|
||||
"title": "Fourth Article"
|
||||
}
|
||||
],
|
||||
"site_settings": {
|
||||
"base_time": "09:00 AM",
|
||||
"stagger_interval": 15,
|
||||
"timezone": "America/New_York"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Uses site's default publishing schedule from Site Settings
|
||||
- Automatically staggers publications (e.g., 15 min intervals)
|
||||
- No limit on number of items (unlike direct publish which is limited to 5)
|
||||
- All items set to `site_status='scheduled'`
|
||||
|
||||
### Bulk Schedule Preview (Before Confirming)
|
||||
|
||||
**Request:**
|
||||
```bash
|
||||
POST /api/v1/writer/content/bulk_schedule_preview/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content_ids": [123, 124, 125],
|
||||
"site_id": 45
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"schedule_preview": [
|
||||
{"content_id": 123, "scheduled_at": "2025-01-17T09:00:00Z", "title": "Article 1"},
|
||||
{"content_id": 124, "scheduled_at": "2025-01-17T09:15:00Z", "title": "Article 2"},
|
||||
{"content_id": 125, "scheduled_at": "2025-01-17T09:30:00Z", "title": "Article 3"}
|
||||
],
|
||||
"site_settings": {
|
||||
"base_time": "09:00 AM",
|
||||
"stagger_interval": 15,
|
||||
"timezone": "America/New_York",
|
||||
"publish_days": ["mon", "tue", "wed", "thu", "fri"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Use Case:**
|
||||
- Show user what times items will be scheduled before confirming
|
||||
- Allow user to adjust site settings if needed
|
||||
- User clicks "Confirm" to execute actual bulk_schedule
|
||||
|
||||
---
|
||||
|
||||
## Publishing Scheduler (v1.3.2)
|
||||
|
||||
### Celery Beat Tasks
|
||||
|
||||
Located in `igny8_core/tasks/publishing_scheduler.py`:
|
||||
|
||||
| Task | Schedule | Purpose |
|
||||
|------|----------|---------|
|
||||
| `schedule_approved_content` | Every 15 minutes | Assigns publish times to approved content |
|
||||
| `process_scheduled_publications` | Every 5 minutes | Publishes content when scheduled time arrives |
|
||||
| `cleanup_failed_publications` | Daily at midnight | Retries or cleans up failed publications |
|
||||
|
||||
### Scheduling Flow
|
||||
|
||||
```
|
||||
Approved Content → schedule_approved_content (every 15 min)
|
||||
↓
|
||||
Check PublishingSettings
|
||||
↓
|
||||
Assign scheduled_publish_at based on:
|
||||
- publish_days configuration
|
||||
- publish_time_slots configuration
|
||||
- daily/weekly/monthly limits
|
||||
↓
|
||||
Set site_status = "scheduled"
|
||||
↓
|
||||
process_scheduled_publications (every 5 min)
|
||||
↓
|
||||
If scheduled_publish_at <= now:
|
||||
- Set site_status = "publishing"
|
||||
- Execute publication
|
||||
- Set site_status = "published" or "failed"
|
||||
```
|
||||
|
||||
### Site Status State Machine
|
||||
|
||||
```
|
||||
not_published → scheduled → publishing → published
|
||||
↓ ↓
|
||||
↓ → failed
|
||||
↓
|
||||
not_published (if unscheduled)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages (v1.3.2)
|
||||
|
||||
### Content Calendar (`/publisher/content-calendar`)
|
||||
|
||||
**Purpose:** Visualize and manage scheduled content
|
||||
|
||||
**Features:**
|
||||
- Calendar view of scheduled publications
|
||||
- List view alternative
|
||||
- Filter by site
|
||||
- Schedule/unschedule actions
|
||||
- Drag-and-drop rescheduling (planned)
|
||||
|
||||
**Components:**
|
||||
- `ContentCalendar.tsx` - Main page component
|
||||
- `CalendarItemTooltip` - Hover details for calendar items
|
||||
- `SiteInfoBar` - Site context header
|
||||
|
||||
### Publishing Queue (`/publisher/publishing-queue`)
|
||||
|
||||
**Purpose:** Review upcoming publications
|
||||
|
||||
**Features:**
|
||||
- List of content pending publication
|
||||
- Status indicators (scheduled, publishing, failed)
|
||||
- Publish now / unschedule actions
|
||||
|
||||
---
|
||||
|
||||
## Publishing Flow
|
||||
|
||||
### Single Content Publish
|
||||
|
||||
**Trigger:** User clicks "Publish" or API call
|
||||
**Flow:**
|
||||
1. Validate content is in publishable state
|
||||
2. Get site integration credentials
|
||||
3. Transform content for destination:
|
||||
- Format HTML for platform
|
||||
- Process images
|
||||
- Map taxonomies
|
||||
4. Push to destination API
|
||||
5. Create `PublishingRecord` with external ID
|
||||
6. Update Content status to `published`
|
||||
|
||||
### Batch Deployment
|
||||
|
||||
**Trigger:** Deployment action
|
||||
**Flow:**
|
||||
1. Create `DeploymentRecord`
|
||||
2. Gather all pending content
|
||||
3. Process each item:
|
||||
- Publish to destination
|
||||
- Create publishing record
|
||||
- Update content status
|
||||
4. Update deployment status
|
||||
5. Log any errors
|
||||
|
||||
---
|
||||
|
||||
## Destination Adapters
|
||||
|
||||
### WordPress Adapter
|
||||
|
||||
- Uses WordPress REST API
|
||||
- Supports posts, pages, custom post types
|
||||
- Handles media upload
|
||||
- Maps categories/tags
|
||||
|
||||
### Ghost Adapter (Planned)
|
||||
|
||||
- Ghost Admin API integration
|
||||
- Post publishing
|
||||
- Image handling
|
||||
|
||||
### Webflow Adapter (Planned)
|
||||
|
||||
- Webflow CMS API
|
||||
- Collection item publishing
|
||||
- Asset management
|
||||
|
||||
---
|
||||
|
||||
## Publishing States
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| pending | Ready to publish |
|
||||
| published | Successfully published |
|
||||
| failed | Publishing failed |
|
||||
| retracted | Unpublished/removed |
|
||||
|
||||
---
|
||||
|
||||
## Site Definition Endpoint
|
||||
|
||||
**Purpose:** Public endpoint for headless CMS use cases
|
||||
|
||||
Returns site structure for external consumption:
|
||||
- Site metadata
|
||||
- Available taxonomies
|
||||
- Content types
|
||||
- URL structure
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| Writer | Publisher | Publish action |
|
||||
| Automation Stage 7 | Publisher | Auto-publish (future) |
|
||||
| Publisher | Integrations | Destination APIs |
|
||||
| Publisher | Content | Status updates |
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Publish failed | Invalid credentials | Check integration settings |
|
||||
| Duplicate posts | Published twice | Check existing publishing record |
|
||||
| Images missing | Upload failed | Check media library access |
|
||||
| Wrong category | Mapping issue | Verify taxonomy sync |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Ghost integration | 🔜 Planned | Ghost CMS publishing |
|
||||
| Webflow integration | 🔜 Planned | Webflow publishing |
|
||||
| ~~Scheduled publishing~~ | ✅ Implemented (v1.3.2) | Future-date publishing |
|
||||
| Republish detection | 🔜 Planned | Detect and handle updates |
|
||||
| ~~Publishing queue~~ | ✅ Implemented (v1.3.2) | Batch publishing with queue |
|
||||
| Drag-and-drop calendar | 🔜 Planned | Reschedule via drag-and-drop |
|
||||
@@ -0,0 +1,293 @@
|
||||
# System Settings Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ✅ Active
|
||||
**Backend Path:** `backend/igny8_core/modules/system/`
|
||||
**Frontend Path:** `frontend/src/pages/Settings/`
|
||||
|
||||
> **Note (v1.8.0):** AI & Automation settings have been consolidated into Site Settings → Automation tab. See [AUTOMATION.md](AUTOMATION.md) for the unified settings API.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Global Models | `modules/system/global_settings_models.py` | `GlobalIntegrationSettings`, `GlobalAIPrompt`, `GlobalAuthorProfile` |
|
||||
| Account Models | `modules/system/settings_models.py` | `SystemSettings`, `UserSettings` |
|
||||
| Email Models | `modules/system/email_models.py` | `EmailSettings`, `EmailTemplate`, `EmailLog` |
|
||||
| AI Settings | `modules/system/ai_settings.py` | `SystemAISettings` |
|
||||
| Provider Model | `modules/system/models.py` | `IntegrationProvider` |
|
||||
| Views | `modules/system/settings_views.py` | Settings ViewSets |
|
||||
| Integration Views | `modules/system/integration_views.py` | AI integration settings |
|
||||
| **Unified Settings** | `api/unified_settings.py` | **v1.8.0** Site-level consolidated settings |
|
||||
| Frontend | `pages/Settings/*.tsx` | Settings pages |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The System Settings module manages:
|
||||
- Platform-wide global settings (API keys, defaults)
|
||||
- Per-account settings overrides
|
||||
- AI prompts and configurations
|
||||
- Module enable/disable
|
||||
- Author profiles and content strategies
|
||||
|
||||
---
|
||||
|
||||
## Settings Hierarchy
|
||||
|
||||
```
|
||||
Global Settings (Platform-wide)
|
||||
↓
|
||||
Account Settings (Per-account overrides)
|
||||
↓
|
||||
User Settings (Per-user preferences)
|
||||
```
|
||||
|
||||
**Priority:** User > Account > Global
|
||||
|
||||
---
|
||||
|
||||
## Global Settings Models
|
||||
|
||||
### GlobalIntegrationSettings (Singleton)
|
||||
|
||||
**Admin:** `/admin/system/globalintegrationsettings/`
|
||||
**Purpose:** Platform-wide API keys and defaults
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| openai_api_key | CharField | OpenAI API key (all accounts) |
|
||||
| openai_model | CharField | Default model (gpt-4o-mini) |
|
||||
| openai_temperature | Float | Default temperature (0.7) |
|
||||
| openai_max_tokens | Integer | Default max tokens (8192) |
|
||||
| dalle_api_key | CharField | DALL-E API key |
|
||||
| dalle_model | CharField | Default model (dall-e-3) |
|
||||
| dalle_size | CharField | Default size (1024x1024) |
|
||||
| dalle_quality | CharField | Default quality (standard) |
|
||||
| dalle_style | CharField | Default style (vivid) |
|
||||
| anthropic_api_key | CharField | Anthropic API key |
|
||||
| runware_api_key | CharField | Runware API key |
|
||||
|
||||
**Critical:** This is a singleton (only 1 record, pk=1). All accounts use these API keys.
|
||||
|
||||
### GlobalAIPrompt
|
||||
|
||||
**Admin:** `/admin/system/globalaiprompt/`
|
||||
**Purpose:** Default AI prompt templates
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| prompt_type | CharField | clustering/ideas/content_generation/etc. |
|
||||
| prompt_value | TextField | The actual prompt text |
|
||||
| description | TextField | What this prompt does |
|
||||
| variables | JSON | Available variables ({keyword}, {industry}) |
|
||||
| version | Integer | Prompt version |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
|
||||
### GlobalAuthorProfile
|
||||
|
||||
**Admin:** `/admin/system/globalauthorprofile/`
|
||||
**Purpose:** Default author persona templates
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| name | CharField | Profile name |
|
||||
| description | TextField | Description |
|
||||
| tone | CharField | professional/casual/technical |
|
||||
| language | CharField | en/es/fr |
|
||||
| structure_template | JSON | Content structure config |
|
||||
| category | CharField | saas/ecommerce/blog/technical |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
|
||||
---
|
||||
|
||||
## Account Settings Models
|
||||
|
||||
### IntegrationSettings
|
||||
|
||||
**Purpose:** Per-account AI model overrides (NOT API keys)
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| integration_type | CharField | openai/runware/image_generation |
|
||||
| config | JSON | Model, temperature, max_tokens overrides |
|
||||
| is_active | Boolean | Enable/disable |
|
||||
|
||||
**Important:**
|
||||
- Free plan cannot create overrides
|
||||
- Starter/Growth/Scale can override model/settings
|
||||
- API keys ALWAYS come from GlobalIntegrationSettings
|
||||
|
||||
### ModuleEnableSettings
|
||||
|
||||
**Purpose:** Enable/disable modules per account
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| planner_enabled | Boolean | Enable Planner |
|
||||
| writer_enabled | Boolean | Enable Writer |
|
||||
| thinker_enabled | Boolean | Enable Thinker (AI settings) |
|
||||
| automation_enabled | Boolean | Enable Automation |
|
||||
| site_builder_enabled | Boolean | Enable Site Builder |
|
||||
| linker_enabled | Boolean | Enable Linker |
|
||||
| optimizer_enabled | Boolean | Enable Optimizer |
|
||||
| publisher_enabled | Boolean | Enable Publisher |
|
||||
|
||||
**Current Implementation:**
|
||||
- Controls sidebar navigation visibility
|
||||
- ⚠️ **Pending:** Extend to other pages and references
|
||||
|
||||
### AIPrompt (Account-Level)
|
||||
|
||||
**Purpose:** Per-account prompt customizations
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| prompt_type | CharField | Prompt type |
|
||||
| prompt_value | TextField | Current prompt (custom or default) |
|
||||
| default_prompt | TextField | Original default (for reset) |
|
||||
| is_customized | Boolean | True if user modified |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Integration Settings
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/system/settings/integrations/openai/` | Get OpenAI settings | Get current model/params |
|
||||
| PUT | `/api/v1/system/settings/integrations/openai/` | Save OpenAI settings | Save overrides |
|
||||
| GET | `/api/v1/system/settings/integrations/image_generation/` | Get image settings | Get DALL-E/Runware settings |
|
||||
| PUT | `/api/v1/system/settings/integrations/image_generation/` | Save image settings | Save overrides |
|
||||
| POST | `/api/v1/system/settings/integrations/test/` | Test connection | Test API connectivity |
|
||||
|
||||
### Prompts
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/system/prompts/` | List prompts | Get all prompts |
|
||||
| GET | `/api/v1/system/prompts/{type}/` | Get prompt | Get specific prompt |
|
||||
| PUT | `/api/v1/system/prompts/{type}/` | Save prompt | Save customization |
|
||||
| POST | `/api/v1/system/prompts/{type}/reset/` | Reset prompt | Reset to default |
|
||||
|
||||
### Module Settings
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/system/modules/` | Get module settings | Get enable/disable state |
|
||||
| PUT | `/api/v1/system/modules/` | Save module settings | Update enabled modules |
|
||||
|
||||
---
|
||||
|
||||
## Settings Flow (Frontend → Backend)
|
||||
|
||||
### Getting OpenAI Settings
|
||||
|
||||
1. Frontend requests: `GET /system/settings/integrations/openai/`
|
||||
2. Backend checks account's `IntegrationSettings`
|
||||
3. Gets global defaults from `GlobalIntegrationSettings`
|
||||
4. Merges: account overrides > global defaults
|
||||
5. Returns (NEVER includes API keys):
|
||||
```json
|
||||
{
|
||||
"model": "gpt-4o-mini",
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 8192,
|
||||
"using_global": true
|
||||
}
|
||||
```
|
||||
|
||||
### Saving OpenAI Settings
|
||||
|
||||
1. Frontend sends: `PUT /system/settings/integrations/openai/`
|
||||
2. Backend STRIPS any API key fields (security)
|
||||
3. Validates account plan allows overrides
|
||||
4. Saves to `IntegrationSettings.config`
|
||||
5. Returns updated settings
|
||||
|
||||
---
|
||||
|
||||
## Module Enable/Disable
|
||||
|
||||
### How It Works (Current)
|
||||
|
||||
1. On app load, frontend fetches module settings
|
||||
2. `useModuleStore.isModuleEnabled(name)` checks state
|
||||
3. `AppSidebar.tsx` conditionally renders menu items:
|
||||
|
||||
```typescript
|
||||
if (isModuleEnabled('linker')) {
|
||||
workflowItems.push({
|
||||
name: "Linker",
|
||||
path: "/linker/content",
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Current Limitations (⚠️ Pending Implementation)
|
||||
|
||||
- Only hides sidebar menu items
|
||||
- Direct URL access still works
|
||||
- Other page references still show module links
|
||||
- Dashboard cards may still show disabled modules
|
||||
|
||||
### Required Extension
|
||||
|
||||
Need to add `ModuleGuard` component to:
|
||||
- Route-level protection
|
||||
- Dashboard cards/widgets
|
||||
- Cross-module references
|
||||
- Settings page links
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### AI Settings (`/settings/ai`)
|
||||
|
||||
- OpenAI model selection
|
||||
- Temperature and max tokens
|
||||
- Image generation settings
|
||||
- Test connection button
|
||||
|
||||
### Prompts (`/thinker/prompts`)
|
||||
|
||||
- List all prompt types
|
||||
- Edit prompt text
|
||||
- Reset to default
|
||||
- Variable reference
|
||||
|
||||
### Module Settings (Admin Only)
|
||||
|
||||
- Enable/disable modules
|
||||
- Per-account configuration
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Settings not saving | Plan restriction | Upgrade plan |
|
||||
| API key exposed | Security flaw | Should never happen - check code |
|
||||
| Module still visible | Cache stale | Clear cache, reload |
|
||||
| Prompt reset not working | default_prompt missing | Re-run migration |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Module guard extension | 🔜 Pending | Extend disable to all pages, not just sidebar |
|
||||
| AIModelConfig database | 🔜 Planned | Move model pricing to database |
|
||||
| Strategy templates | 🔜 Planned | Global strategy library |
|
||||
| Per-user preferences | 🔜 Planned | User-level setting overrides |
|
||||
295
v2/Live Docs on Server/igny8-app-docs/10-MODULES/WRITER.md
Normal file
295
v2/Live Docs on Server/igny8-app-docs/10-MODULES/WRITER.md
Normal file
@@ -0,0 +1,295 @@
|
||||
# Writer Module
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ✅ Active
|
||||
**Backend Path:** `backend/igny8_core/modules/writer/`
|
||||
**Frontend Path:** `frontend/src/pages/Writer/`
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | File | Key Items |
|
||||
|------|------|-----------|
|
||||
| Models | `modules/writer/models.py` | `Tasks`, `Content`, `Images`, `ContentTaxonomy` |
|
||||
| Views | `modules/writer/views.py` | `TaskViewSet`, `ContentViewSet`, `ImageViewSet` |
|
||||
| AI Functions | `ai/functions/content.py` | `GenerateContentFunction` |
|
||||
| AI Functions | `ai/functions/images.py` | `GenerateImagesFunction`, `GenerateImagePromptsFunction` |
|
||||
| Frontend Pages | `pages/Writer/Tasks.tsx` | Task management |
|
||||
| Frontend Pages | `pages/Writer/Content.tsx` | Content listing |
|
||||
| Frontend Pages | `pages/Writer/ContentViewer.tsx` | Content preview/edit |
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
The Writer module manages the content creation pipeline:
|
||||
|
||||
```
|
||||
ContentIdeas → Tasks → Content → Images → Review → Publish
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### Tasks
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| content_idea | FK | Source idea (nullable) |
|
||||
| title | CharField | Task/content title |
|
||||
| description | TextField | Task brief |
|
||||
| target_keywords | JSON | Keywords to target |
|
||||
| content_type | CharField | blog_post/guide/comparison/etc. |
|
||||
| word_count_target | Integer | Target word count |
|
||||
| status | CharField | pending/in_progress/completed/cancelled |
|
||||
| assigned_to | FK | Assigned user (nullable) |
|
||||
| due_date | DateTime | Due date (nullable) |
|
||||
| priority | Integer | Priority level |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
### Content
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| task | FK | Source task (nullable) |
|
||||
| title | CharField | Content title |
|
||||
| slug | SlugField | URL slug |
|
||||
| content_body | TextField | HTML content |
|
||||
| excerpt | TextField | Short summary |
|
||||
| meta_title | CharField | SEO meta title |
|
||||
| meta_description | CharField | SEO meta description |
|
||||
| word_count | Integer | Actual word count |
|
||||
| status | CharField | draft/review/approved/published |
|
||||
| content_type | CharField | post/page/product |
|
||||
| content_structure | CharField | article/guide/comparison/review/listicle |
|
||||
| source | CharField | igny8/wordpress/manual |
|
||||
| wordpress_id | Integer | WP post ID (if synced) |
|
||||
| published_at | DateTime | Publication date |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
### Images
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| sector | FK | Parent sector |
|
||||
| content | FK | Parent content |
|
||||
| image_type | CharField | featured/desktop/mobile/in_article |
|
||||
| prompt | TextField | Generation prompt |
|
||||
| image_url | URLField | Image URL |
|
||||
| alt_text | CharField | Alt text for SEO |
|
||||
| status | CharField | pending/generating/completed/failed |
|
||||
| position | Integer | Order for in-article images |
|
||||
| provider | CharField | dalle/runware |
|
||||
| model | CharField | dall-e-3/runware:97@1 |
|
||||
| created_at | DateTime | Creation date |
|
||||
|
||||
### ContentTaxonomy
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| account | FK | Owner account |
|
||||
| site | FK | Parent site |
|
||||
| name | CharField | Category/tag name |
|
||||
| slug | SlugField | URL slug |
|
||||
| taxonomy_type | CharField | category/tag |
|
||||
| parent | FK | Parent taxonomy (for hierarchy) |
|
||||
| description | TextField | Description |
|
||||
| wordpress_id | Integer | WP term ID (if synced) |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Tasks
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/writer/tasks/` | `TaskViewSet.list` | List tasks |
|
||||
| POST | `/api/v1/writer/tasks/` | `TaskViewSet.create` | Create task |
|
||||
| POST | `/api/v1/writer/tasks/bulk_create/` | `TaskViewSet.bulk_create` | Create multiple tasks |
|
||||
| POST | `/api/v1/writer/tasks/{id}/generate_content/` | `TaskViewSet.generate_content` | AI content generation |
|
||||
|
||||
### Content
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/writer/content/` | `ContentViewSet.list` | List content |
|
||||
| POST | `/api/v1/writer/content/` | `ContentViewSet.create` | Create content manually |
|
||||
| PUT | `/api/v1/writer/content/{id}/` | `ContentViewSet.update` | Update content |
|
||||
| POST | `/api/v1/writer/content/{id}/update_content/` | `ContentViewSet.update_content` | Update with validation |
|
||||
| POST | `/api/v1/writer/content/{id}/generate_images/` | `ContentViewSet.generate_images` | Generate images |
|
||||
| POST | `/api/v1/writer/content/{id}/publish_to_wordpress/` | `ContentViewSet.publish_to_wordpress` | Publish to WP |
|
||||
|
||||
### Images
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/api/v1/writer/images/` | `ImageViewSet.list` | List images |
|
||||
| POST | `/api/v1/writer/images/generate_for_content/` | `ImageViewSet.generate_for_content` | Generate images |
|
||||
| POST | `/api/v1/writer/images/regenerate/` | `ImageViewSet.regenerate` | Regenerate image |
|
||||
|
||||
---
|
||||
|
||||
## Business Logic
|
||||
|
||||
### Content Generation (AI)
|
||||
|
||||
**Trigger:** User clicks "Generate" on task
|
||||
**AI Function:** `GenerateContentFunction`
|
||||
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
|
||||
|
||||
**Flow:**
|
||||
1. User has task with title, keywords, word count target
|
||||
2. Frontend calls `POST /tasks/{id}/generate_content/`
|
||||
3. Backend validates task and credits
|
||||
4. AIEngine executes `GenerateContentFunction`:
|
||||
- Loads account's AI prompts and strategy
|
||||
- Sends to GPT-4 with structured output
|
||||
- Receives HTML content with proper structure
|
||||
5. Creates `Content` record linked to task
|
||||
6. Updates task status to `completed`
|
||||
7. Returns content for review
|
||||
|
||||
### Image Prompt Generation (AI)
|
||||
|
||||
**Trigger:** Part of content generation or explicit
|
||||
**AI Function:** `GenerateImagePromptsFunction`
|
||||
**Credit Cost:** Based on AI tokens used (see AIModelConfig)
|
||||
|
||||
**Flow:**
|
||||
1. Content exists with body
|
||||
2. AI analyzes content sections
|
||||
3. Generates prompts for:
|
||||
- Featured image (1)
|
||||
- In-article images (configurable count)
|
||||
4. Creates `Images` records with prompts, status=pending
|
||||
|
||||
### Image Generation (AI)
|
||||
|
||||
**Trigger:** User clicks "Generate Images" or automation
|
||||
**AI Function:** `GenerateImagesFunction`
|
||||
**Credit Cost:** Deducted per image (see AIModelConfig.credits_per_image)
|
||||
|
||||
**Flow:**
|
||||
1. Image record exists with prompt, status=pending
|
||||
2. Backend calls DALL-E or Runware API
|
||||
3. Receives image URL
|
||||
4. Updates `Images` record with URL, status=completed
|
||||
|
||||
### WordPress Publishing
|
||||
|
||||
**Trigger:** User clicks "Publish to WordPress"
|
||||
**Credit Cost:** None
|
||||
|
||||
**Flow:**
|
||||
1. Content is in `approved` status
|
||||
2. Site has WordPress integration configured
|
||||
3. Backend calls WordPress REST API:
|
||||
- Creates/updates post
|
||||
- Uploads featured image
|
||||
- Sets categories/tags
|
||||
4. Updates Content with `wordpress_id`
|
||||
5. Sets status to `published`
|
||||
|
||||
---
|
||||
|
||||
## Content Structures
|
||||
|
||||
| Structure | Purpose | Typical Sections |
|
||||
|-----------|---------|------------------|
|
||||
| article | General blog post | Intro, Body, Conclusion |
|
||||
| guide | How-to content | Steps, Tips, Summary |
|
||||
| comparison | Product comparison | Features, Pros/Cons, Verdict |
|
||||
| review | Product review | Overview, Features, Rating |
|
||||
| listicle | List-based content | Numbered items with details |
|
||||
| pillar | Long-form authority | Multiple sections with depth |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Pages
|
||||
|
||||
### Tasks Page (`/writer/tasks`)
|
||||
|
||||
- Table of all tasks
|
||||
- Filter by status, assigned user
|
||||
- Generate content action
|
||||
- Bulk actions
|
||||
|
||||
### Content Page (`/writer/content`)
|
||||
|
||||
- Table of all content
|
||||
- Filter by status, content type
|
||||
- Quick preview
|
||||
- Publish actions
|
||||
|
||||
### Content Viewer (`/writer/content/{id}`)
|
||||
|
||||
- Full content preview
|
||||
- Edit mode
|
||||
- Image management
|
||||
- Publish to WordPress
|
||||
- Status management
|
||||
|
||||
### Draft/Review/Approved Tabs
|
||||
|
||||
- Filtered views by status
|
||||
- Different actions per status
|
||||
- **v1.2.0**: "Published" tab renamed to "Approved"
|
||||
|
||||
---
|
||||
|
||||
## Content Status Flow
|
||||
|
||||
```
|
||||
draft → review → approved → published
|
||||
↓
|
||||
(rejected) → draft (revision)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Points
|
||||
|
||||
| From | To | Trigger |
|
||||
|------|----|---------|
|
||||
| ContentIdeas | Tasks | Create tasks |
|
||||
| Tasks | Content | Generate content |
|
||||
| Content | Images | Generate images |
|
||||
| Content | WordPress | Publish |
|
||||
| WordPress | Content | Sync imports |
|
||||
| Automation Stage 4 | Content | Automated generation |
|
||||
| Automation Stage 5-6 | Images | Automated image generation |
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| Content too short | Low word count target | Increase target in task |
|
||||
| Images not generating | No prompts created | Run image prompt generation first |
|
||||
| WordPress publish fails | Invalid credentials | Check integration settings |
|
||||
| Content stuck in draft | No manual status update | Update status via UI or API |
|
||||
| Duplicate content | Re-running generation | Check if content already exists |
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Content revisions | 🔜 Planned | Track content version history |
|
||||
| Multi-language | 🔜 Planned | Generate content in different languages |
|
||||
| Batch generation | 🔜 Planned | Generate multiple content pieces at once |
|
||||
| Regenerate sections | 🔜 Planned | AI regenerate specific sections |
|
||||
588
v2/Live Docs on Server/igny8-app-docs/20-API/ENDPOINTS.md
Normal file
588
v2/Live Docs on Server/igny8-app-docs/20-API/ENDPOINTS.md
Normal file
@@ -0,0 +1,588 @@
|
||||
# API Endpoints Reference
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Base URL:** `/api/v1/`
|
||||
**Documentation:** `/api/docs/` (Swagger) | `/api/redoc/` (ReDoc)
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require authentication unless noted.
|
||||
|
||||
**Methods:**
|
||||
- `Authorization: Bearer <jwt_token>` - JWT token
|
||||
- `Authorization: ApiKey <key>` - API key (WordPress integration)
|
||||
- Session cookie (Django Admin)
|
||||
|
||||
---
|
||||
|
||||
## Auth Endpoints (`/api/v1/auth/`)
|
||||
|
||||
| Method | Path | Handler | Auth | Purpose |
|
||||
|--------|------|---------|------|---------|
|
||||
| POST | `/register/` | `RegisterView` | ❌ | Create account |
|
||||
| POST | `/login/` | `LoginView` | ❌ | Get tokens |
|
||||
| POST | `/logout/` | `LogoutView` | ✅ | Invalidate session |
|
||||
| POST | `/token/refresh/` | `RefreshTokenView` | ✅ | Refresh access token |
|
||||
| POST | `/password/change/` | `ChangePasswordView` | ✅ | Change password |
|
||||
| POST | `/password/reset/` | `RequestPasswordResetView` | ❌ | Request reset email |
|
||||
| POST | `/password/reset/confirm/` | `ResetPasswordView` | ❌ | Confirm reset |
|
||||
| GET | `/groups/` | `RoleViewSet.list` | ✅ | List roles |
|
||||
| GET | `/users/` | `UserViewSet.list` | ✅ | List users |
|
||||
| POST | `/users/` | `UserViewSet.create` | ✅ | Create user |
|
||||
| GET | `/account/` | `AccountViewSet.retrieve` | ✅ | Get account |
|
||||
| PUT | `/account/` | `AccountViewSet.update` | ✅ | Update account |
|
||||
| GET | `/sites/` | `SiteViewSet.list` | ✅ | List sites |
|
||||
| POST | `/sites/` | `SiteViewSet.create` | ✅ | Create site |
|
||||
| GET | `/sectors/` | `SectorViewSet.list` | ✅ | List sectors |
|
||||
| POST | `/sectors/` | `SectorViewSet.create` | ✅ | Create sector |
|
||||
| GET | `/industries/` | `IndustryViewSet.list` | ✅ | List industries |
|
||||
| GET | `/keywords-library/` | `SeedKeywordViewSet.list` | ✅ | List keywords library (v1.8.2) |
|
||||
| GET | `/keywords-library/sector_stats/` | `SeedKeywordViewSet.sector_stats` | ✅ | Get sector statistics (v1.8.2) |
|
||||
|
||||
---
|
||||
|
||||
## Keywords Library Endpoints (v1.8.2)
|
||||
|
||||
**New Endpoint:** `/api/v1/keywords-library/`
|
||||
**Previous:** `/api/v1/auth/seed-keywords/` (deprecated, no backward compatibility)
|
||||
|
||||
### List Keywords
|
||||
|
||||
```
|
||||
GET /api/v1/keywords-library/
|
||||
```
|
||||
|
||||
**Query Parameters:**
|
||||
- `sector_ids` _(string, comma-separated)_ - Filter by specific sector IDs (e.g., `1,2,3`)
|
||||
- **New in v1.8.2:** Ensures sites only see keywords from their configured sectors
|
||||
- `search` _(string)_ - Search keyword names
|
||||
- `country` _(string)_ - Filter by country code (e.g., `US`, `UK`)
|
||||
- `volume_min` _(number)_ - Minimum search volume
|
||||
- `volume_max` _(number)_ - Maximum search volume
|
||||
- `difficulty` _(string)_ - SEO difficulty level (`easy`, `medium`, `hard`, `very_hard`)
|
||||
- `is_high_opportunity` _(boolean)_ - Filter high-opportunity keywords
|
||||
- `is_paid` _(boolean)_ - Filter paid/premium keywords
|
||||
- `ordering` _(string)_ - Sort field (prefix `-` for descending)
|
||||
- Options: `keyword`, `volume`, `difficulty`, `country`
|
||||
- Default: `-volume`
|
||||
- `page` _(number)_ - Page number for pagination
|
||||
- `page_size` _(number)_ - Results per page (default: 20)
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
GET /api/v1/keywords-library/?sector_ids=1,2,3&country=US&difficulty=easy&ordering=-volume&page=1
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"count": 150,
|
||||
"next": "http://api.igny8.com/api/v1/keywords-library/?page=2§or_ids=1,2,3",
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"keyword": "cloud computing services",
|
||||
"volume": 12000,
|
||||
"difficulty": "medium",
|
||||
"country": "US",
|
||||
"industry_sector": 1,
|
||||
"industry_sector_name": "Cloud Infrastructure & Services",
|
||||
"is_high_opportunity": true,
|
||||
"is_paid": false,
|
||||
"credit_cost": 0,
|
||||
"created_at": "2025-01-15T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Sector Statistics
|
||||
|
||||
```
|
||||
GET /api/v1/keywords-library/sector_stats/
|
||||
```
|
||||
|
||||
Returns aggregated statistics per sector for the keywords library.
|
||||
|
||||
**Query Parameters:**
|
||||
- `sector_ids` _(string, comma-separated)_ - Filter by specific sector IDs
|
||||
- **New in v1.8.2:** Returns stats only for specified sectors
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
GET /api/v1/keywords-library/sector_stats/?sector_ids=1,2,3
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"sector_id": 1,
|
||||
"sector_name": "Cloud Infrastructure & Services",
|
||||
"sector_description": "Keywords related to cloud computing, hosting, and infrastructure services",
|
||||
"stats": {
|
||||
"total": 250,
|
||||
"added": 45,
|
||||
"available": 205,
|
||||
"high_opportunity": 80,
|
||||
"paid": 30,
|
||||
"free": 220
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Stat Definitions:**
|
||||
- `total` - All keywords in sector
|
||||
- `added` - Keywords site has added to their workflow
|
||||
- `available` - Keywords not yet added (total - added)
|
||||
- `high_opportunity` - Premium keywords with high volume and potential
|
||||
- `paid` - Premium keywords requiring credits
|
||||
- `free` - Keywords with no credit cost
|
||||
|
||||
**Use Cases:**
|
||||
1. **Sector Metric Cards** - Display stats per sector with clickable filters
|
||||
2. **Smart Suggestions** - Show available counts for bulk-add operations
|
||||
3. **Progress Tracking** - Track keyword adoption across sectors
|
||||
4. **Budget Planning** - Understand credit requirements for paid keywords
|
||||
|
||||
---
|
||||
|
||||
## Planner Endpoints (`/api/v1/planner/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/keywords/` | `KeywordViewSet.list` | List keywords |
|
||||
| POST | `/keywords/` | `KeywordViewSet.create` | Create keyword |
|
||||
| GET | `/keywords/{id}/` | `KeywordViewSet.retrieve` | Get keyword |
|
||||
| PUT | `/keywords/{id}/` | `KeywordViewSet.update` | Update keyword |
|
||||
| DELETE | `/keywords/{id}/` | `KeywordViewSet.destroy` | Delete keyword |
|
||||
| POST | `/keywords/bulk_delete/` | `KeywordViewSet.bulk_delete` | Hard delete multiple |
|
||||
| POST | `/keywords/bulk_status/` | `KeywordViewSet.bulk_status` | Update status |
|
||||
| POST | `/keywords/add_to_workflow/` | `KeywordViewSet.add_to_workflow` | Add seed keywords |
|
||||
| GET | `/clusters/` | `ClusterViewSet.list` | List clusters |
|
||||
| POST | `/clusters/` | `ClusterViewSet.create` | Create cluster |
|
||||
| POST | `/clusters/auto_cluster/` | `ClusterViewSet.auto_cluster` | AI clustering |
|
||||
| POST | `/clusters/generate_ideas/` | `ClusterViewSet.generate_ideas` | Generate ideas |
|
||||
| GET | `/ideas/` | `ContentIdeaViewSet.list` | List ideas |
|
||||
| POST | `/ideas/` | `ContentIdeaViewSet.create` | Create idea |
|
||||
| POST | `/ideas/create_tasks/` | `ContentIdeaViewSet.create_tasks` | Convert to tasks |
|
||||
|
||||
**Query Parameters:**
|
||||
- `?site_id=` - Filter by site
|
||||
- `?sector_id=` - Filter by sector
|
||||
- `?status=` - Filter by status
|
||||
- `?cluster_id=` - Filter by cluster
|
||||
- `?difficulty_min=`, `?difficulty_max=` - Difficulty range
|
||||
- `?volume_min=`, `?volume_max=` - Volume range
|
||||
|
||||
---
|
||||
|
||||
## Writer Endpoints (`/api/v1/writer/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/tasks/` | `TaskViewSet.list` | List tasks |
|
||||
| POST | `/tasks/` | `TaskViewSet.create` | Create task |
|
||||
| POST | `/tasks/bulk_create/` | `TaskViewSet.bulk_create` | Create multiple |
|
||||
| POST | `/tasks/{id}/generate_content/` | `TaskViewSet.generate_content` | AI generation |
|
||||
| GET | `/content/` | `ContentViewSet.list` | List content |
|
||||
| POST | `/content/` | `ContentViewSet.create` | Create content |
|
||||
| GET | `/content/{id}/` | `ContentViewSet.retrieve` | Get content |
|
||||
| PUT | `/content/{id}/` | `ContentViewSet.update` | Update content |
|
||||
| POST | `/content/{id}/update_content/` | `ContentViewSet.update_content` | Update with validation |
|
||||
| POST | `/content/{id}/generate_images/` | `ContentViewSet.generate_images` | Generate images |
|
||||
| POST | `/content/{id}/publish_to_wordpress/` | `ContentViewSet.publish_to_wordpress` | Publish to WP |
|
||||
| POST | `/content/{id}/schedule/` | `ContentViewSet.schedule` | Schedule for publishing (v1.3.2) |
|
||||
| POST | `/content/{id}/unschedule/` | `ContentViewSet.unschedule` | Remove from schedule (v1.3.2) |
|
||||
| GET | `/images/` | `ImageViewSet.list` | List images |
|
||||
| POST | `/images/generate_for_content/` | `ImageViewSet.generate_for_content` | Generate images |
|
||||
| POST | `/images/regenerate/` | `ImageViewSet.regenerate` | Regenerate image |
|
||||
| GET | `/taxonomies/` | `TaxonomyViewSet.list` | List taxonomies |
|
||||
|
||||
**Content Query Parameters (v1.3.2):**
|
||||
- `?status__in=draft,review,approved` - Filter by multiple workflow statuses
|
||||
- `?site_status=scheduled` - Filter by publishing status
|
||||
- `?site_id=` - Filter by site
|
||||
|
||||
**Schedule Endpoint Request:**
|
||||
```json
|
||||
POST /api/v1/writer/content/{id}/schedule/
|
||||
{
|
||||
"scheduled_publish_at": "2026-01-15T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Schedule Endpoint Response:**
|
||||
```json
|
||||
{
|
||||
"content_id": 123,
|
||||
"site_status": "scheduled",
|
||||
"scheduled_publish_at": "2026-01-15T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Billing Endpoints (`/api/v1/billing/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/balance/` | `CreditBalanceViewSet.list` | Current balance |
|
||||
| GET | `/usage/` | `CreditUsageViewSet.list` | Usage log |
|
||||
| GET | `/usage/summary/` | `CreditUsageViewSet.summary` | Usage summary |
|
||||
| GET | `/usage/limits/` | `CreditUsageViewSet.limits` | Plan limits |
|
||||
| GET | `/transactions/` | `TransactionViewSet.list` | Transaction history |
|
||||
|
||||
---
|
||||
|
||||
## Integration Endpoints (`/api/v1/integration/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/` | `SiteIntegrationViewSet.list` | List integrations |
|
||||
| POST | `/` | `SiteIntegrationViewSet.create` | Create integration |
|
||||
| PUT | `/{id}/` | `SiteIntegrationViewSet.update` | Update integration |
|
||||
| DELETE | `/{id}/` | `SiteIntegrationViewSet.destroy` | Delete integration |
|
||||
| POST | `/{id}/test_connection/` | Test connection | Verify credentials |
|
||||
| POST | `/{id}/test_collection_connection/` | Test collection | Test specific type |
|
||||
| POST | `/{id}/sync/` | Trigger sync | Start sync |
|
||||
| GET | `/{id}/sync_status/` | Sync status | Current progress |
|
||||
| POST | `/{id}/update_structure/` | Update structure | Refresh site data |
|
||||
| GET | `/{id}/content_types/` | Content types | Available types |
|
||||
| GET | `/{id}/sync_health/` | Sync health | Statistics |
|
||||
| POST | `/site_sync/` | Site-level sync | Sync by site ID |
|
||||
| POST | `/webhook/wordpress/` | WordPress webhook | Receive WP updates |
|
||||
|
||||
### Unified Settings Endpoints (v1.8.0)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/sites/{site_id}/unified-settings/` | `UnifiedSiteSettingsViewSet.retrieve` | Get all site settings (automation, publishing, AI) |
|
||||
| PATCH | `/sites/{site_id}/unified-settings/` | `UnifiedSiteSettingsViewSet.partial_update` | Update settings (partial) |
|
||||
| **GET** | `/settings/defaults/` | **v1.8.1** `DefaultSettingsAPIView` | Get default settings for reset functionality |
|
||||
|
||||
### Default Settings Endpoint (v1.8.1)
|
||||
|
||||
Returns centralized defaults from `DefaultAutomationConfig` singleton:
|
||||
|
||||
```json
|
||||
GET /api/v1/integration/settings/defaults/
|
||||
|
||||
{
|
||||
"automation": {
|
||||
"enabled": false,
|
||||
"frequency": "daily",
|
||||
"time": "02:00"
|
||||
},
|
||||
"stages": [
|
||||
{"number": 1, "enabled": true, "batch_size": 50, "per_run_limit": 0, "use_testing": false, "budget_pct": 15},
|
||||
{"number": 2, "enabled": true, "batch_size": 1, "per_run_limit": 0, "use_testing": false, "budget_pct": 10},
|
||||
// ... stages 3-7
|
||||
],
|
||||
"delays": {
|
||||
"within_stage": 3,
|
||||
"between_stage": 5
|
||||
},
|
||||
"publishing": {
|
||||
"auto_approval_enabled": false,
|
||||
"auto_publish_enabled": false,
|
||||
"daily_publish_limit": 3,
|
||||
"weekly_publish_limit": 15,
|
||||
"monthly_publish_limit": 50,
|
||||
"publish_days": ["mon", "tue", "wed", "thu", "fri"],
|
||||
"time_slots": ["09:00", "14:00", "18:00"]
|
||||
},
|
||||
"images": {
|
||||
"style": "photorealistic",
|
||||
"max_images": 4
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unified Settings Response:**
|
||||
```json
|
||||
{
|
||||
"automation_config": {
|
||||
"enabled": true,
|
||||
"frequency": "daily",
|
||||
"scheduled_days": ["mon", "wed", "fri"],
|
||||
"scheduled_time": "09:00:00",
|
||||
"next_run_at": "2026-01-18T09:00:00Z",
|
||||
"stage_1_enabled": true, "stage_1_batch_size": 50, "max_keywords_per_run": 100,
|
||||
"stage_2_enabled": true, "stage_2_batch_size": 10, "max_clusters_per_run": 20,
|
||||
// ... stages 3-7
|
||||
},
|
||||
"publishing_settings": {
|
||||
"auto_approval_enabled": true,
|
||||
"auto_publish_enabled": true,
|
||||
"daily_publish_limit": 3,
|
||||
"weekly_publish_limit": 15,
|
||||
"monthly_publish_limit": 50,
|
||||
"publish_days": ["mon", "tue", "wed", "thu", "fri"],
|
||||
"publish_time_slots": [{"start": "09:00", "end": "17:00"}],
|
||||
"total_items_per_run": 500
|
||||
},
|
||||
"stage_configs": [
|
||||
{"stage": 1, "enabled": true, "batch_size": 50, "max_per_run": 100},
|
||||
// ... stages 2-7
|
||||
],
|
||||
"ai_settings": {
|
||||
"testing_model_id": 2,
|
||||
"live_model_id": 1,
|
||||
"image_model_id": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Publishing Settings Endpoints (deprecated v1.8.0)
|
||||
|
||||
> ⚠️ **Deprecated:** Use `/sites/{site_id}/unified-settings/` instead
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/sites/{site_id}/publishing-settings/` | `PublishingSettingsViewSet.retrieve` | Get publishing settings |
|
||||
| PUT | `/sites/{site_id}/publishing-settings/` | `PublishingSettingsViewSet.update` | Update settings (full) |
|
||||
| PATCH | `/sites/{site_id}/publishing-settings/` | `PublishingSettingsViewSet.partial_update` | Update settings (partial) |
|
||||
|
||||
**Publishing Settings Response:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"site": 5,
|
||||
"auto_approval_enabled": true,
|
||||
"auto_publish_enabled": true,
|
||||
"daily_publish_limit": 3,
|
||||
"weekly_publish_limit": 15,
|
||||
"monthly_publish_limit": 50,
|
||||
"publish_days": ["mon", "tue", "wed", "thu", "fri"],
|
||||
"publish_time_slots": ["09:00", "14:00", "18:00"],
|
||||
"created_at": "2026-01-03T10:00:00Z",
|
||||
"updated_at": "2026-01-03T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Endpoints (`/api/v1/system/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/settings/integrations/openai/` | Get OpenAI settings | Current config |
|
||||
| PUT | `/settings/integrations/openai/` | Save OpenAI settings | Update config |
|
||||
| GET | `/settings/integrations/image_generation/` | Get image settings | Current config |
|
||||
| PUT | `/settings/integrations/image_generation/` | Save image settings | Update config |
|
||||
| POST | `/settings/integrations/test/` | Test connection | Verify API keys |
|
||||
| GET | `/settings/content/{key}/` | `ContentSettingsViewSet.retrieve` | Get content settings |
|
||||
| POST | `/settings/content/{key}/save/` | `ContentSettingsViewSet.save_settings` | Save content settings |
|
||||
| GET | `/prompts/` | List prompts | All prompts |
|
||||
| GET | `/prompts/{type}/` | Get prompt | Specific prompt |
|
||||
| PUT | `/prompts/{type}/` | Save prompt | Update prompt |
|
||||
| POST | `/prompts/{type}/reset/` | Reset prompt | Reset to default |
|
||||
| GET | `/modules/` | Get modules | Module enable state |
|
||||
| PUT | `/modules/` | Save modules | Update enabled |
|
||||
| GET | `/health/` | Health check | System status |
|
||||
|
||||
**Content Settings Keys:**
|
||||
- `content_generation` - AI writing settings (default_article_length, default_tone, include_faq, enable_internal_linking, etc.)
|
||||
- `publishing` - Publishing defaults (default_publish_status, auto_schedule, schedule_frequency, schedule_times)
|
||||
|
||||
---
|
||||
|
||||
## Notification Endpoints (`/api/v1/notifications/`) - v1.2.0
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/` | `NotificationViewSet.list` | List notifications (paginated) |
|
||||
| GET | `/{id}/` | `NotificationViewSet.retrieve` | Get notification detail |
|
||||
| DELETE | `/{id}/` | `NotificationViewSet.destroy` | Delete notification |
|
||||
| POST | `/{id}/read/` | `NotificationViewSet.read` | Mark single notification as read |
|
||||
| POST | `/read-all/` | `NotificationViewSet.read_all` | Mark all notifications as read |
|
||||
| GET | `/unread-count/` | `NotificationViewSet.unread_count` | Get unread notification count |
|
||||
|
||||
**Query Parameters:**
|
||||
- `?page=` - Page number for pagination
|
||||
- `?page_size=` - Results per page (default 20)
|
||||
- `?is_read=` - Filter by read status (true/false)
|
||||
- `?notification_type=` - Filter by type (ai_task, system, credit, billing, integration, content, info)
|
||||
|
||||
**Notification Types:**
|
||||
- `ai_cluster_complete`, `ai_cluster_failed` - Clustering operations
|
||||
- `ai_ideas_complete`, `ai_ideas_failed` - Idea generation
|
||||
- `ai_content_complete`, `ai_content_failed` - Content generation
|
||||
- `ai_images_complete`, `ai_images_failed` - Image generation
|
||||
- `ai_prompts_complete`, `ai_prompts_failed` - Image prompt generation
|
||||
- `content_ready_review`, `content_published`, `content_publish_failed` - Workflow
|
||||
- `wordpress_sync_success`, `wordpress_sync_failed` - WordPress sync
|
||||
- `credits_low`, `credits_depleted` - Billing alerts
|
||||
- `site_setup_complete`, `keywords_imported` - Setup
|
||||
- `system_info` - System notifications
|
||||
|
||||
**Response Format:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"notification_type": "ai_content_complete",
|
||||
"severity": "success",
|
||||
"title": "Content Generated",
|
||||
"message": "Generated 5 articles successfully",
|
||||
"is_read": false,
|
||||
"created_at": "2025-12-27T12:00:00Z",
|
||||
"read_at": null,
|
||||
"action_label": "View Content",
|
||||
"action_url": "/writer/content",
|
||||
"metadata": {"count": 5, "credits": 250}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation Endpoints (`/api/v1/automation/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/config/` | Get config | Automation config |
|
||||
| PUT | `/update_config/` | Update config | Save settings |
|
||||
| POST | `/run_now/` | Run now | Start manual run |
|
||||
| GET | `/current_run/` | Current run | Run status |
|
||||
| GET | `/pipeline_overview/` | Pipeline | Stage counts |
|
||||
| GET | `/current_processing/` | Processing | Live status |
|
||||
| POST | `/pause/` | Pause | Pause run |
|
||||
| POST | `/resume/` | Resume | Resume run |
|
||||
| POST | `/cancel/` | Cancel | Cancel run |
|
||||
| GET | `/history/` | History | Past runs |
|
||||
| GET | `/logs/` | Logs | Activity log |
|
||||
| GET | `/estimate/` | Estimate | Credit estimate |
|
||||
| **GET** | `/runs/` | **v1.8.0** List runs | All automation runs (paginated) |
|
||||
| **GET** | `/runs/{id}/` | **v1.8.0** Run detail | Single run with stages, logs |
|
||||
|
||||
**Query Parameters:** All require `?site_id=`, run-specific require `?run_id=`
|
||||
|
||||
### Automation Runs Response (v1.8.0)
|
||||
|
||||
```json
|
||||
GET /api/v1/automation/runs/?site_id=1
|
||||
|
||||
{
|
||||
"count": 25,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"site": 1,
|
||||
"status": "completed",
|
||||
"started_at": "2026-01-18T09:00:00Z",
|
||||
"completed_at": "2026-01-18T09:45:00Z",
|
||||
"current_stage": 7,
|
||||
"total_stages": 7,
|
||||
"items_processed": 150,
|
||||
"credits_used": 450,
|
||||
"error_message": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Linker Endpoints (`/api/v1/linker/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| POST | `/process/` | `LinkerViewSet.process` | Process single content |
|
||||
| POST | `/batch_process/` | `LinkerViewSet.batch_process` | Process multiple |
|
||||
|
||||
---
|
||||
|
||||
## Optimizer Endpoints (`/api/v1/optimizer/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| POST | `/optimize/` | `OptimizerViewSet.optimize` | Optimize content |
|
||||
| POST | `/batch_optimize/` | `OptimizerViewSet.batch_optimize` | Batch optimize |
|
||||
| POST | `/analyze/` | `OptimizerViewSet.analyze` | Analyze only |
|
||||
|
||||
---
|
||||
|
||||
## Publisher Endpoints (`/api/v1/publisher/`)
|
||||
|
||||
| Method | Path | Handler | Purpose |
|
||||
|--------|------|---------|---------|
|
||||
| GET | `/records/` | `PublishingRecordViewSet.list` | List records |
|
||||
| POST | `/records/` | `PublishingRecordViewSet.create` | Create record |
|
||||
| GET | `/deployments/` | `DeploymentViewSet.list` | List deployments |
|
||||
| POST | `/publish/` | `PublishContentViewSet.publish` | Publish content |
|
||||
| GET | `/publish/status/` | `PublishContentViewSet.status` | Publish status |
|
||||
| GET | `/site-definition/` | `SiteDefinitionViewSet.list` | Public site def |
|
||||
|
||||
---
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"message": "Optional message"
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message",
|
||||
"code": "ERROR_CODE"
|
||||
}
|
||||
```
|
||||
|
||||
### Paginated Response
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 100,
|
||||
"next": "http://api.igny8.com/api/v1/planner/keywords/?page=2",
|
||||
"previous": null,
|
||||
"results": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Status Codes
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| 200 | Success |
|
||||
| 201 | Created |
|
||||
| 400 | Bad Request |
|
||||
| 401 | Unauthorized |
|
||||
| 402 | Payment Required (insufficient credits) |
|
||||
| 403 | Forbidden |
|
||||
| 404 | Not Found |
|
||||
| 429 | Rate Limited |
|
||||
| 500 | Server Error |
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Rate limits are scoped by operation type:
|
||||
|
||||
| Scope | Limit |
|
||||
|-------|-------|
|
||||
| AI operations | 60/min |
|
||||
| Content operations | 100/min |
|
||||
| Auth operations | 20/min |
|
||||
| General | 300/min |
|
||||
|
||||
Rate limit headers included in responses:
|
||||
- `X-RateLimit-Limit`
|
||||
- `X-RateLimit-Remaining`
|
||||
- `X-RateLimit-Reset`
|
||||
@@ -0,0 +1,683 @@
|
||||
# IGNY8 Frontend Component System
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
|
||||
> **🔒 ENFORCED BY ESLINT** - Violations will trigger warnings/errors during build.
|
||||
> This document is the single source of truth for all UI components.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
| Element | Component | Import Path |
|
||||
|---------|-----------|-------------|
|
||||
| Button | `Button` | `components/ui/button/Button` |
|
||||
| Icon Button | `IconButton` | `components/ui/button/IconButton` |
|
||||
| Button Group | `ButtonGroup` | `components/ui/button-group/ButtonGroup` |
|
||||
| Text Input | `InputField` | `components/form/input/InputField` |
|
||||
| Checkbox | `Checkbox` | `components/form/input/Checkbox` |
|
||||
| Radio | `Radio` | `components/form/input/Radio` |
|
||||
| File Upload | `FileInput` | `components/form/input/FileInput` |
|
||||
| Textarea | `TextArea` | `components/form/input/TextArea` |
|
||||
| Dropdown | `Select` | `components/form/Select` |
|
||||
| Searchable Dropdown | `SelectDropdown` | `components/form/SelectDropdown` |
|
||||
| Multi-Select | `MultiSelect` | `components/form/MultiSelect` |
|
||||
| Toggle Switch | `Switch` | `components/form/switch/Switch` |
|
||||
| Badge | `Badge` | `components/ui/badge/Badge` |
|
||||
| Card | `Card` | `components/ui/card/Card` |
|
||||
| Modal | `Modal` | `components/ui/modal` |
|
||||
| Alert | `Alert` | `components/ui/alert/Alert` |
|
||||
| Spinner | `Spinner` | `components/ui/spinner/Spinner` |
|
||||
| Tabs | `Tabs` | `components/ui/tabs/Tabs` |
|
||||
| Tooltip | `Tooltip` | `components/ui/tooltip/Tooltip` |
|
||||
| Calendar Tooltip | `CalendarItemTooltip` | `components/ui/tooltip/CalendarItemTooltip` |
|
||||
| Toast | `useToast` | `components/ui/toast/ToastContainer` |
|
||||
| Icons | `*Icon` | `icons` (e.g., `../../icons`) |
|
||||
|
||||
---
|
||||
|
||||
## 1. BUTTONS
|
||||
|
||||
### Button (Standard)
|
||||
|
||||
```tsx
|
||||
import Button from '../../components/ui/button/Button';
|
||||
|
||||
<Button
|
||||
variant="primary" // primary | secondary | outline | ghost | gradient
|
||||
tone="brand" // brand | success | warning | danger | neutral
|
||||
size="md" // xs | sm | md | lg | xl | 2xl
|
||||
shape="rounded" // rounded | pill
|
||||
startIcon={<Icon />} // Icon before text
|
||||
endIcon={<Icon />} // Icon after text
|
||||
onClick={handler}
|
||||
disabled={false}
|
||||
fullWidth={false}
|
||||
type="button" // button | submit | reset
|
||||
className="" // Additional classes
|
||||
>
|
||||
Button Text
|
||||
</Button>
|
||||
```
|
||||
|
||||
**Common Patterns:**
|
||||
```tsx
|
||||
// Primary action
|
||||
<Button variant="primary" tone="brand">Save</Button>
|
||||
|
||||
// Success/confirm
|
||||
<Button variant="primary" tone="success">Approve</Button>
|
||||
|
||||
// Danger/destructive
|
||||
<Button variant="primary" tone="danger">Delete</Button>
|
||||
|
||||
// Secondary/cancel
|
||||
<Button variant="outline" tone="neutral">Cancel</Button>
|
||||
|
||||
// With icon
|
||||
<Button startIcon={<PlusIcon className="w-4 h-4" />}>Add Item</Button>
|
||||
```
|
||||
|
||||
### IconButton (Icon-only)
|
||||
|
||||
```tsx
|
||||
import IconButton from '../../components/ui/button/IconButton';
|
||||
|
||||
<IconButton
|
||||
icon={<CloseIcon />} // Required: Icon component
|
||||
variant="ghost" // solid | outline | ghost
|
||||
tone="neutral" // brand | success | warning | danger | neutral
|
||||
size="sm" // xs | sm | md | lg
|
||||
shape="rounded" // rounded | circle
|
||||
onClick={handler}
|
||||
disabled={false}
|
||||
title="Close" // Required: Accessibility label
|
||||
aria-label="Close" // Optional: Override aria-label
|
||||
/>
|
||||
```
|
||||
|
||||
**Common Patterns:**
|
||||
```tsx
|
||||
// Close button
|
||||
<IconButton icon={<CloseIcon />} variant="ghost" tone="neutral" title="Close" />
|
||||
|
||||
// Delete button
|
||||
<IconButton icon={<TrashBinIcon />} variant="ghost" tone="danger" title="Delete" />
|
||||
|
||||
// Edit button
|
||||
<IconButton icon={<PencilIcon />} variant="ghost" tone="brand" title="Edit" />
|
||||
|
||||
// Add button (circular)
|
||||
<IconButton icon={<PlusIcon />} variant="solid" tone="brand" shape="circle" title="Add" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. FORM INPUTS
|
||||
|
||||
### InputField (Text/Number/Email/Password)
|
||||
|
||||
```tsx
|
||||
import InputField from '../../components/form/input/InputField';
|
||||
|
||||
<InputField
|
||||
type="text" // text | number | email | password | date | time
|
||||
label="Field Label"
|
||||
placeholder="Enter value..."
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
id="field-id"
|
||||
name="field-name"
|
||||
disabled={false}
|
||||
error={false} // Shows error styling
|
||||
success={false} // Shows success styling
|
||||
hint="Helper text" // Text below input
|
||||
min="0" // For number inputs
|
||||
max="100" // For number inputs
|
||||
step={1} // For number inputs
|
||||
/>
|
||||
```
|
||||
|
||||
### TextArea (Multi-line)
|
||||
|
||||
```tsx
|
||||
import TextArea from '../../components/form/input/TextArea';
|
||||
|
||||
<TextArea
|
||||
placeholder="Enter description..."
|
||||
rows={4}
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
disabled={false}
|
||||
error={false}
|
||||
hint="Helper text"
|
||||
/>
|
||||
```
|
||||
|
||||
### Checkbox
|
||||
|
||||
```tsx
|
||||
import Checkbox from '../../components/form/input/Checkbox';
|
||||
|
||||
<Checkbox
|
||||
label="Accept terms"
|
||||
checked={checked}
|
||||
onChange={(checked) => setChecked(checked)}
|
||||
id="checkbox-id"
|
||||
disabled={false}
|
||||
/>
|
||||
```
|
||||
|
||||
### Radio
|
||||
|
||||
```tsx
|
||||
import Radio from '../../components/form/input/Radio';
|
||||
|
||||
<Radio
|
||||
id="radio-option-1"
|
||||
name="radio-group"
|
||||
value="option1"
|
||||
label="Option 1"
|
||||
checked={selected === 'option1'}
|
||||
onChange={(value) => setSelected(value)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Select (Dropdown)
|
||||
|
||||
```tsx
|
||||
import Select from '../../components/form/Select';
|
||||
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'opt1', label: 'Option 1' },
|
||||
{ value: 'opt2', label: 'Option 2' },
|
||||
]}
|
||||
placeholder="Select..."
|
||||
defaultValue=""
|
||||
onChange={(value) => setValue(value)}
|
||||
className=""
|
||||
/>
|
||||
```
|
||||
|
||||
### SelectDropdown (Searchable)
|
||||
|
||||
```tsx
|
||||
import SelectDropdown from '../../components/form/SelectDropdown';
|
||||
|
||||
<SelectDropdown
|
||||
label="Select Item"
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={(value) => setValue(value)}
|
||||
placeholder="Search..."
|
||||
searchable={true}
|
||||
/>
|
||||
```
|
||||
|
||||
### Switch (Toggle)
|
||||
|
||||
```tsx
|
||||
import Switch from '../../components/form/switch/Switch';
|
||||
|
||||
<Switch
|
||||
label="Enable feature"
|
||||
checked={enabled} // Controlled mode
|
||||
onChange={(checked) => setEnabled(checked)}
|
||||
disabled={false}
|
||||
color="blue" // blue | gray
|
||||
/>
|
||||
```
|
||||
|
||||
### FileInput
|
||||
|
||||
```tsx
|
||||
import FileInput from '../../components/form/input/FileInput';
|
||||
|
||||
<FileInput
|
||||
onChange={(files) => handleFiles(files)}
|
||||
accept=".csv,.json"
|
||||
multiple={false}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. DISPLAY COMPONENTS
|
||||
|
||||
### Badge
|
||||
|
||||
```tsx
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
|
||||
<Badge
|
||||
tone="success" // brand | success | warning | danger | info | neutral | purple | indigo | pink | teal | cyan | blue
|
||||
variant="soft" // solid | soft | outline | light
|
||||
size="sm" // xs | sm | md
|
||||
startIcon={<Icon />}
|
||||
endIcon={<Icon />}
|
||||
>
|
||||
Label
|
||||
</Badge>
|
||||
```
|
||||
|
||||
**Status Badge Patterns:**
|
||||
```tsx
|
||||
<Badge tone="success" variant="soft">Active</Badge>
|
||||
<Badge tone="warning" variant="soft">Pending</Badge>
|
||||
<Badge tone="danger" variant="soft">Failed</Badge>
|
||||
<Badge tone="info" variant="soft">Draft</Badge>
|
||||
<Badge tone="neutral" variant="soft">Archived</Badge>
|
||||
```
|
||||
|
||||
### Card
|
||||
|
||||
```tsx
|
||||
import { Card, CardTitle, CardContent, CardDescription, CardAction, CardIcon } from '../../components/ui/card/Card';
|
||||
|
||||
<Card
|
||||
variant="surface" // surface | panel | frosted | borderless | gradient
|
||||
padding="md" // none | sm | md | lg
|
||||
shadow="sm" // none | sm | md
|
||||
>
|
||||
<CardIcon><Icon /></CardIcon>
|
||||
<CardTitle>Title</CardTitle>
|
||||
<CardDescription>Description text</CardDescription>
|
||||
<CardContent>Main content</CardContent>
|
||||
<CardAction onClick={handler}>Action</CardAction>
|
||||
</Card>
|
||||
```
|
||||
|
||||
### Alert
|
||||
|
||||
```tsx
|
||||
import Alert from '../../components/ui/alert/Alert';
|
||||
|
||||
<Alert
|
||||
variant="success" // success | error | warning | info
|
||||
title="Alert Title"
|
||||
message="Alert message text"
|
||||
showLink={false}
|
||||
linkHref="#"
|
||||
linkText="Learn more"
|
||||
/>
|
||||
```
|
||||
|
||||
### Modal
|
||||
|
||||
```tsx
|
||||
import { Modal } from '../../components/ui/modal';
|
||||
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
showCloseButton={true}
|
||||
isFullscreen={false}
|
||||
>
|
||||
<div className="p-6">
|
||||
Modal content
|
||||
</div>
|
||||
</Modal>
|
||||
```
|
||||
|
||||
### Spinner
|
||||
|
||||
```tsx
|
||||
import { Spinner } from '../../components/ui/spinner/Spinner';
|
||||
|
||||
<Spinner
|
||||
size="md" // sm | md | lg
|
||||
color="primary" // primary | success | error | warning | info
|
||||
/>
|
||||
```
|
||||
|
||||
### Tooltip
|
||||
|
||||
```tsx
|
||||
import { Tooltip } from '../../components/ui/tooltip/Tooltip';
|
||||
|
||||
<Tooltip text="Tooltip text" placement="top">
|
||||
<span>Hover target</span>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
### Toast (Notifications)
|
||||
|
||||
```tsx
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
// Show notifications
|
||||
toast.success('Success', 'Operation completed');
|
||||
toast.error('Error', 'Something went wrong');
|
||||
toast.warning('Warning', 'Please review');
|
||||
toast.info('Info', 'Here is some information');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. ICONS
|
||||
|
||||
### Importing Icons
|
||||
|
||||
```tsx
|
||||
// Always import from central icons folder
|
||||
import { PlusIcon, CloseIcon, CheckCircleIcon } from '../../icons';
|
||||
|
||||
// Use consistent sizing
|
||||
<PlusIcon className="w-4 h-4" /> // Small (in buttons, badges)
|
||||
<PlusIcon className="w-5 h-5" /> // Medium (standalone)
|
||||
<PlusIcon className="w-6 h-6" /> // Large (headers, features)
|
||||
```
|
||||
|
||||
### Available Icons
|
||||
|
||||
**Core Icons:**
|
||||
- `PlusIcon`, `CloseIcon`, `CheckCircleIcon`, `AlertIcon`, `InfoIcon`, `ErrorIcon`
|
||||
- `BoltIcon`, `ArrowUpIcon`, `ArrowDownIcon`, `ArrowRightIcon`, `ArrowLeftIcon`
|
||||
- `PencilIcon`, `TrashBinIcon`, `DownloadIcon`, `CopyIcon`
|
||||
- `EyeIcon`, `EyeCloseIcon`, `LockIcon`, `UserIcon`
|
||||
- `FolderIcon`, `FileIcon`, `GridIcon`, `ListIcon`
|
||||
- `ChevronDownIcon`, `ChevronUpIcon`, `ChevronLeftIcon`, `ChevronRightIcon`
|
||||
- `AngleDownIcon`, `AngleUpIcon`, `AngleLeftIcon`, `AngleRightIcon`
|
||||
|
||||
**Module Icons:**
|
||||
- `TaskIcon`, `PageIcon`, `TableIcon`, `CalendarIcon`
|
||||
- `PlugInIcon`, `DocsIcon`, `MailIcon`, `ChatIcon`
|
||||
- `PieChartIcon`, `BoxCubeIcon`, `GroupIcon`
|
||||
- `ShootingStarIcon`, `DollarLineIcon`
|
||||
|
||||
**Aliases (for compatibility):**
|
||||
- `TrashIcon` → `TrashBinIcon`
|
||||
- `XIcon` / `XMarkIcon` → `CloseIcon`
|
||||
- `SearchIcon` → `GridIcon`
|
||||
- `SettingsIcon` → `BoxCubeIcon`
|
||||
- `FilterIcon` → `ListIcon`
|
||||
|
||||
### Adding New Icons
|
||||
|
||||
1. Add SVG file to `src/icons/`
|
||||
2. Export in `src/icons/index.ts`:
|
||||
```ts
|
||||
import { ReactComponent as NewIcon } from "./new-icon.svg?react";
|
||||
export { NewIcon };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. FOLDER STRUCTURE
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── ui/ # UI Components (display/interaction)
|
||||
│ │ ├── accordion/
|
||||
│ │ ├── alert/
|
||||
│ │ │ ├── Alert.tsx
|
||||
│ │ │ └── AlertModal.tsx
|
||||
│ │ ├── avatar/
|
||||
│ │ ├── badge/
|
||||
│ │ │ └── Badge.tsx
|
||||
│ │ ├── breadcrumb/
|
||||
│ │ ├── button/
|
||||
│ │ │ ├── Button.tsx # ← Standard button
|
||||
│ │ │ ├── IconButton.tsx # ← Icon-only button
|
||||
│ │ │ └── ButtonWithTooltip.tsx
|
||||
│ │ ├── button-group/
|
||||
│ │ ├── card/
|
||||
│ │ ├── dataview/
|
||||
│ │ ├── dropdown/
|
||||
│ │ ├── list/
|
||||
│ │ ├── modal/
|
||||
│ │ ├── pagination/
|
||||
│ │ ├── progress/
|
||||
│ │ ├── ribbon/
|
||||
│ │ ├── spinner/
|
||||
│ │ ├── table/
|
||||
│ │ ├── tabs/
|
||||
│ │ ├── toast/
|
||||
│ │ ├── tooltip/
|
||||
│ │ └── videos/
|
||||
│ │
|
||||
│ └── form/ # Form Components (inputs)
|
||||
│ ├── input/
|
||||
│ │ ├── InputField.tsx # ← Text/number/email inputs
|
||||
│ │ ├── Checkbox.tsx # ← Checkbox
|
||||
│ │ ├── Radio.tsx # ← Radio button
|
||||
│ │ ├── RadioSm.tsx # ← Small radio button
|
||||
│ │ ├── FileInput.tsx # ← File upload
|
||||
│ │ └── TextArea.tsx # ← Multi-line text
|
||||
│ ├── switch/
|
||||
│ │ └── Switch.tsx # ← Toggle switch
|
||||
│ ├── Select.tsx # ← Dropdown select
|
||||
│ ├── SelectDropdown.tsx # ← Searchable dropdown
|
||||
│ ├── MultiSelect.tsx # ← Multi-select dropdown
|
||||
│ ├── Label.tsx # ← Form labels
|
||||
│ ├── Form.tsx # ← Form wrapper
|
||||
│ └── date-picker.tsx # ← Date picker
|
||||
│
|
||||
├── icons/ # All SVG icons
|
||||
│ ├── index.ts # ← Export all icons from here
|
||||
│ ├── plus.svg
|
||||
│ ├── close.svg
|
||||
│ └── ... (50+ icons)
|
||||
│
|
||||
└── styles/
|
||||
└── design-system.css # ← Global design tokens
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. ESLINT ENFORCEMENT
|
||||
|
||||
### Rules File Location
|
||||
`eslint-plugin-igny8-design-system.cjs` (project root)
|
||||
|
||||
### Active Rules
|
||||
|
||||
| Rule | Severity | Description |
|
||||
|------|----------|-------------|
|
||||
| `no-raw-button` | warn | Use `Button` or `IconButton` instead of `<button>` |
|
||||
| `no-raw-input` | warn | Use `InputField`, `Checkbox`, `Radio`, `FileInput` instead of `<input>` |
|
||||
| `no-raw-select` | warn | Use `Select` or `SelectDropdown` instead of `<select>` |
|
||||
| `no-raw-textarea` | warn | Use `TextArea` instead of `<textarea>` |
|
||||
| `no-restricted-imports` | error | Block imports from `@heroicons/*`, `lucide-react`, `@mui/icons-material` |
|
||||
|
||||
### Running Lint Check
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Viewing Violations
|
||||
```bash
|
||||
npm run lint 2>&1 | grep "igny8-design-system"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. MIGRATION EXAMPLES
|
||||
|
||||
### Raw Button → Button Component
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<button
|
||||
className="px-4 py-2 bg-brand-500 text-white rounded hover:bg-brand-600"
|
||||
onClick={handleClick}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
|
||||
// ✅ AFTER
|
||||
<Button variant="primary" tone="brand" onClick={handleClick}>
|
||||
Save
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Raw Button → IconButton
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<button
|
||||
className="p-1 hover:bg-gray-100 rounded"
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseIcon className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
// ✅ AFTER
|
||||
<IconButton
|
||||
icon={<CloseIcon />}
|
||||
variant="ghost"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={onClose}
|
||||
title="Close"
|
||||
/>
|
||||
```
|
||||
|
||||
### Raw Input → InputField
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<input
|
||||
type="text"
|
||||
className="border rounded px-3 py-2 focus:ring-2"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
|
||||
// ✅ AFTER
|
||||
<InputField
|
||||
type="text"
|
||||
label="Name"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
```
|
||||
|
||||
### Raw Select → Select Component
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
<select
|
||||
className="border rounded px-3 py-2"
|
||||
value={status}
|
||||
onChange={(e) => setStatus(e.target.value)}
|
||||
>
|
||||
<option value="">Select status</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
</select>
|
||||
|
||||
// ✅ AFTER
|
||||
<Select
|
||||
options={[
|
||||
{ value: 'active', label: 'Active' },
|
||||
{ value: 'inactive', label: 'Inactive' },
|
||||
]}
|
||||
placeholder="Select status"
|
||||
defaultValue={status}
|
||||
onChange={(value) => setStatus(value)}
|
||||
/>
|
||||
```
|
||||
|
||||
### External Icon → Internal Icon
|
||||
|
||||
```tsx
|
||||
// ❌ BEFORE
|
||||
import { XIcon } from '@heroicons/react/24/outline';
|
||||
<XIcon className="w-5 h-5" />
|
||||
|
||||
// ✅ AFTER
|
||||
import { CloseIcon } from '../../icons';
|
||||
<CloseIcon className="w-5 h-5" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. SITE-SPECIFIC COMPONENTS (v1.3.2)
|
||||
|
||||
### SiteInfoBar
|
||||
|
||||
Reusable site info header component for site-specific pages.
|
||||
|
||||
```tsx
|
||||
import { SiteInfoBar } from '../../components/common/SiteInfoBar';
|
||||
|
||||
<SiteInfoBar
|
||||
site={currentSite} // Site object with name, domain, etc.
|
||||
onSiteChange={handleSiteChange} // Optional: Callback when site changes
|
||||
showSelector={true} // Whether to show site selector dropdown
|
||||
/>
|
||||
```
|
||||
|
||||
### OnboardingWizard Components
|
||||
|
||||
Located in `components/onboarding/`:
|
||||
|
||||
```tsx
|
||||
import { OnboardingWizard } from '../../components/onboarding/OnboardingWizard';
|
||||
import {
|
||||
Step1Welcome,
|
||||
Step2AddSite,
|
||||
Step3ConnectIntegration,
|
||||
Step4AddKeywords,
|
||||
Step5Complete,
|
||||
} from '../../components/onboarding/steps';
|
||||
```
|
||||
|
||||
### CalendarItemTooltip
|
||||
|
||||
Tooltip specifically designed for calendar items.
|
||||
|
||||
```tsx
|
||||
import { CalendarItemTooltip } from '../../components/ui/tooltip';
|
||||
|
||||
<CalendarItemTooltip
|
||||
item={contentItem} // Content object
|
||||
onView={handleView} // Optional: View callback
|
||||
onRemove={handleRemove} // Optional: Remove callback
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. LIVE REFERENCE
|
||||
|
||||
View all components with live examples at: `/ui-elements`
|
||||
|
||||
This page shows every component with all prop variations.
|
||||
|
||||
---
|
||||
|
||||
## 10. AI AGENT INSTRUCTIONS
|
||||
|
||||
When working on this codebase, AI agents MUST:
|
||||
|
||||
1. **Never use raw HTML elements** (`<button>`, `<input>`, `<select>`, `<textarea>`)
|
||||
2. **Import icons only from `src/icons`** - never from external libraries
|
||||
3. **Follow the import paths** specified in this document
|
||||
4. **Check ESLint** after making changes: `npm run lint`
|
||||
5. **Use semantic color tokens** - never hardcoded hex values or Tailwind defaults
|
||||
|
||||
### Color Rules
|
||||
```tsx
|
||||
// ✅ CORRECT - Use semantic tokens
|
||||
className="bg-brand-500 text-gray-900 border-success-500"
|
||||
|
||||
// ❌ WRONG - Tailwind defaults (DISABLED)
|
||||
className="bg-blue-500 text-slate-900 border-green-500"
|
||||
|
||||
// ❌ WRONG - Hardcoded hex
|
||||
className="bg-[#0077B6]"
|
||||
style={{ color: '#DC2626' }}
|
||||
```
|
||||
5. **Reference this document** for correct component usage
|
||||
6. **Use consistent icon sizing**: `className="w-4 h-4"` for small, `w-5 h-5` for medium
|
||||
|
||||
If a component doesn't exist, create it in `components/ui/` or `components/form/` first.
|
||||
@@ -0,0 +1,242 @@
|
||||
# IGNY8 Design System Guide
|
||||
|
||||
> **Single Source of Truth for UI Components**
|
||||
>
|
||||
> 🔒 **STYLE LOCKED** - This design system is enforced by ESLint. All frontend code must comply.
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
|
||||
---
|
||||
|
||||
## Quick Links
|
||||
|
||||
| Resource | Path | Description |
|
||||
|----------|------|-------------|
|
||||
| **Component System** | [COMPONENT-SYSTEM.md](COMPONENT-SYSTEM.md) | Full component reference with props, examples, and usage |
|
||||
| **Design Tokens Doc** | [DESIGN-TOKENS.md](DESIGN-TOKENS.md) | Detailed token documentation |
|
||||
| **ESLint Plugin** | `frontend/eslint/` | Custom rules enforcing design system |
|
||||
| **Live Demo** | `/ui-elements` route | Interactive component showcase |
|
||||
| **CSS Tokens** | `frontend/src/styles/design-system.css` | CSS variables source file |
|
||||
| **Icons** | `frontend/src/icons/` | All SVG icons |
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Color System (CRITICAL!)
|
||||
|
||||
### Only 6 Base Colors in Entire System
|
||||
|
||||
All colors derive from 6 primary hex values defined in `design-system.css`:
|
||||
|
||||
| Token | Hex | Purpose |
|
||||
|-------|-----|---------|
|
||||
| `--color-primary` | #0077B6 | Brand Blue - main CTA, links |
|
||||
| `--color-success` | #2CA18E | Success Green - confirmations |
|
||||
| `--color-warning` | #D9A12C | Warning Amber - alerts |
|
||||
| `--color-danger` | #A12C40 | Danger Red - errors, destructive |
|
||||
| `--color-purple` | #2C40A1 | Purple - premium features |
|
||||
| `--color-gray-base` | #667085 | Neutral gray - text, borders |
|
||||
|
||||
### Tailwind Color Utilities
|
||||
|
||||
**⚠️ Tailwind default colors are DISABLED!** Only use:
|
||||
|
||||
```css
|
||||
/* ✅ AVAILABLE */
|
||||
brand-50 through brand-950 /* Primary blue scale */
|
||||
gray-25 through gray-950 /* Neutral scale */
|
||||
success-25 through success-950 /* Green scale */
|
||||
error-25 through error-950 /* Red scale */
|
||||
warning-25 through warning-950 /* Amber scale */
|
||||
purple-25 through purple-950 /* Purple scale */
|
||||
|
||||
/* ❌ DISABLED - These will NOT work */
|
||||
blue-*, red-*, green-*, emerald-*, amber-*, indigo-*,
|
||||
pink-*, rose-*, sky-*, teal-*, cyan-*, etc.
|
||||
```
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```tsx
|
||||
// ✅ CORRECT
|
||||
<div className="bg-brand-500 text-white">Primary Button</div>
|
||||
<div className="text-gray-700 bg-gray-50">Card content</div>
|
||||
<Badge tone="success">Active</Badge>
|
||||
<Alert variant="error" message="Failed" />
|
||||
|
||||
// ❌ WRONG
|
||||
<div className="bg-blue-500">...</div> // Default blue disabled
|
||||
<div className="bg-[#0693e3]">...</div> // Hardcoded hex
|
||||
<div style={{ color: '#ff0000' }}>...</div> // Inline style
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Use Components, Never Raw HTML
|
||||
|
||||
```tsx
|
||||
// ❌ NEVER
|
||||
<button className="...">Click</button>
|
||||
<input type="text" className="..." />
|
||||
<select className="...">...</select>
|
||||
<textarea className="..."></textarea>
|
||||
|
||||
// ✅ ALWAYS
|
||||
<Button variant="primary">Click</Button>
|
||||
<InputField type="text" label="Name" />
|
||||
<Select options={options} />
|
||||
<TextArea rows={4} />
|
||||
```
|
||||
|
||||
### 2. Import Icons from Central Location
|
||||
|
||||
```tsx
|
||||
// ❌ NEVER
|
||||
import { XIcon } from '@heroicons/react/24/outline';
|
||||
import { Trash } from 'lucide-react';
|
||||
|
||||
// ✅ ALWAYS
|
||||
import { CloseIcon, TrashBinIcon } from '../../icons';
|
||||
```
|
||||
|
||||
### 3. Consistent Sizing
|
||||
|
||||
```tsx
|
||||
// Icons in buttons/badges
|
||||
<Icon className="w-4 h-4" />
|
||||
|
||||
// Standalone icons
|
||||
<Icon className="w-5 h-5" />
|
||||
|
||||
// Large/header icons
|
||||
<Icon className="w-6 h-6" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Quick Reference
|
||||
|
||||
| Need | Component | Import |
|
||||
|------|-----------|--------|
|
||||
| Action button | `Button` | `components/ui/button/Button` |
|
||||
| Icon-only button | `IconButton` | `components/ui/button/IconButton` |
|
||||
| Text input | `InputField` | `components/form/input/InputField` |
|
||||
| Checkbox | `Checkbox` | `components/form/input/Checkbox` |
|
||||
| Radio | `Radio` | `components/form/input/Radio` |
|
||||
| Dropdown | `Select` | `components/form/Select` |
|
||||
| Multi-line text | `TextArea` | `components/form/input/TextArea` |
|
||||
| Toggle | `Switch` | `components/form/switch/Switch` |
|
||||
| Status label | `Badge` | `components/ui/badge/Badge` |
|
||||
| Container | `Card` | `components/ui/card/Card` |
|
||||
| Popup | `Modal` | `components/ui/modal` |
|
||||
| Loading | `Spinner` | `components/ui/spinner/Spinner` |
|
||||
| Notification | `useToast` | `components/ui/toast/ToastContainer` |
|
||||
|
||||
**→ See [COMPONENT-SYSTEM.md](docs/30-FRONTEND/COMPONENT-SYSTEM.md) for full props and examples**
|
||||
|
||||
---
|
||||
|
||||
## ESLint Enforcement
|
||||
|
||||
### Rules
|
||||
|
||||
| Rule | Level | Action |
|
||||
|------|-------|--------|
|
||||
| `no-raw-button` | warn | Use `Button` or `IconButton` |
|
||||
| `no-raw-input` | warn | Use `InputField`, `Checkbox`, `Radio` |
|
||||
| `no-raw-select` | warn | Use `Select` or `SelectDropdown` |
|
||||
| `no-raw-textarea` | warn | Use `TextArea` |
|
||||
| `no-icon-children` | warn | Use `startIcon`/`endIcon` props instead of icon children |
|
||||
| `no-restricted-imports` | error | Block `@heroicons/*`, `lucide-react`, `@mui/icons-material` |
|
||||
|
||||
### Check Violations
|
||||
|
||||
```bash
|
||||
# Inside container
|
||||
docker exec -it igny8_frontend npm run lint
|
||||
|
||||
# Or directly
|
||||
cd frontend
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Plugin Location
|
||||
|
||||
The custom ESLint plugin is at: `frontend/eslint/eslint-plugin-igny8-design-system.cjs`
|
||||
|
||||
---
|
||||
|
||||
## For AI Agents
|
||||
|
||||
When working on this codebase:
|
||||
|
||||
1. **Read first**: [docs/30-FRONTEND/COMPONENT-SYSTEM.md](docs/30-FRONTEND/COMPONENT-SYSTEM.md)
|
||||
2. **Never use**: `<button>`, `<input>`, `<select>`, `<textarea>` tags
|
||||
3. **Import icons from**: `src/icons` only
|
||||
4. **Verify after changes**: `npm run lint`
|
||||
5. **Reference pages**: Planner and Writer modules use correct patterns
|
||||
|
||||
### Correct Import Paths
|
||||
|
||||
```tsx
|
||||
// From a page in src/pages/
|
||||
import Button from '../components/ui/button/Button';
|
||||
import IconButton from '../components/ui/button/IconButton';
|
||||
import InputField from '../components/form/input/InputField';
|
||||
import { PlusIcon, CloseIcon } from '../icons';
|
||||
|
||||
// From a component in src/components/
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import { PlusIcon } from '../../icons';
|
||||
|
||||
// From a nested component
|
||||
// Adjust ../ based on depth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── eslint/
|
||||
│ └── eslint-plugin-igny8-design-system.cjs # Custom rules
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ # Display components
|
||||
│ │ │ ├── button/ # Button, IconButton
|
||||
│ │ │ ├── badge/ # Badge
|
||||
│ │ │ ├── card/ # Card
|
||||
│ │ │ ├── modal/ # Modal
|
||||
│ │ │ └── ...
|
||||
│ │ └── form/ # Form components
|
||||
│ │ ├── input/ # InputField, Checkbox, Radio, TextArea
|
||||
│ │ ├── switch/ # Switch
|
||||
│ │ ├── Select.tsx
|
||||
│ │ └── ...
|
||||
│ ├── icons/ # All SVG icons
|
||||
│ │ └── index.ts # Export all icons
|
||||
│ └── styles/
|
||||
│ └── design-system.css # Design tokens
|
||||
docs/
|
||||
└── 30-FRONTEND/
|
||||
└── COMPONENT-SYSTEM.md # Full component documentation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Checklist
|
||||
|
||||
When fixing violations:
|
||||
|
||||
- [ ] Replace `<button>` with `Button` or `IconButton`
|
||||
- [ ] Replace `<input type="text/email/password/number">` with `InputField`
|
||||
- [ ] Replace `<input type="checkbox">` with `Checkbox`
|
||||
- [ ] Replace `<input type="radio">` with `Radio`
|
||||
- [ ] Replace `<select>` with `Select` or `SelectDropdown`
|
||||
- [ ] Replace `<textarea>` with `TextArea`
|
||||
- [ ] Replace external icon imports with `src/icons`
|
||||
- [ ] Run `npm run lint` to verify
|
||||
- [ ] Run `npm run build` to confirm no errors
|
||||
@@ -0,0 +1,311 @@
|
||||
# IGNY8 Design Guide
|
||||
|
||||
> **🔒 MANDATORY** - All frontend code MUST follow these standards
|
||||
> **Version:** 1.8.3
|
||||
> **Last Updated:** January 20, 2026
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Need | Use | Import From |
|
||||
|------|-----|-------------|
|
||||
| Button | `<Button>` | `components/ui/button/Button` |
|
||||
| Icon button | `<IconButton>` | `components/ui/button/IconButton` |
|
||||
| Text input | `<InputField>` | `components/form/input/InputField` |
|
||||
| Dropdown | `<Select>` | `components/form/Select` |
|
||||
| Checkbox | `<Checkbox>` | `components/form/input/Checkbox` |
|
||||
| Toggle | `<Switch>` | `components/form/switch/Switch` |
|
||||
| Status label | `<Badge>` | `components/ui/badge/Badge` |
|
||||
| Card | `<Card>` | `components/ui/card/Card` |
|
||||
| Modal | `<Modal>` | `components/ui/modal` |
|
||||
| Toast | `useToast()` | `components/ui/toast/ToastContainer` |
|
||||
| Table | `<Table>` | `components/tables/Table` |
|
||||
|
||||
**Full component docs:** [docs/30-FRONTEND/COMPONENT-SYSTEM.md](docs/30-FRONTEND/COMPONENT-SYSTEM.md)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Color System
|
||||
|
||||
### Only 6 Base Colors (No Exceptions!)
|
||||
|
||||
| Token | Hex | Usage |
|
||||
|-------|-----|-------|
|
||||
| `brand` | #0077B6 | Primary actions, links |
|
||||
| `success` | #2CA18E | Success states |
|
||||
| `warning` | #D9A12C | Warnings, alerts |
|
||||
| `danger/error` | #A12C40 | Errors, destructive |
|
||||
| `purple` | #2C40A1 | Premium features |
|
||||
| `gray` | #667085 | Text, borders, neutrals |
|
||||
|
||||
### Tailwind Classes
|
||||
|
||||
```tsx
|
||||
// ✅ CORRECT - Use semantic colors
|
||||
<div className="bg-brand-500 text-white">Primary</div>
|
||||
<div className="bg-success-100 text-success-700">Success</div>
|
||||
<div className="bg-error-100 text-error-700">Error</div>
|
||||
<div className="text-gray-700 bg-gray-50">Neutral</div>
|
||||
|
||||
// ❌ WRONG - Default Tailwind colors are DISABLED
|
||||
<div className="bg-blue-500">...</div> // Will not work
|
||||
<div className="bg-red-500">...</div> // Will not work
|
||||
<div className="bg-[#ff0000]">...</div> // Hardcoded - forbidden
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Rules
|
||||
|
||||
### Rule 1: NEVER Use Raw HTML Elements
|
||||
|
||||
```tsx
|
||||
// ❌ NEVER
|
||||
<button onClick={...}>Click</button>
|
||||
<input type="text" />
|
||||
<select>...</select>
|
||||
<textarea></textarea>
|
||||
|
||||
// ✅ ALWAYS
|
||||
<Button onClick={...}>Click</Button>
|
||||
<InputField type="text" label="Name" />
|
||||
<Select options={options} />
|
||||
<TextArea rows={4} />
|
||||
```
|
||||
|
||||
### Rule 2: Icons from Central Location Only
|
||||
|
||||
```tsx
|
||||
// ❌ NEVER
|
||||
import { XIcon } from '@heroicons/react/24/outline';
|
||||
import { Trash } from 'lucide-react';
|
||||
|
||||
// ✅ ALWAYS
|
||||
import { CloseIcon, TrashBinIcon } from '../../icons';
|
||||
```
|
||||
|
||||
### Rule 3: Consistent Icon Sizing
|
||||
|
||||
```tsx
|
||||
// In buttons/badges
|
||||
<Icon className="w-4 h-4" />
|
||||
|
||||
// Standalone
|
||||
<Icon className="w-5 h-5" />
|
||||
|
||||
// Headers/large
|
||||
<Icon className="w-6 h-6" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Button Variants
|
||||
|
||||
```tsx
|
||||
// Primary action (main CTA)
|
||||
<Button variant="primary">Save Changes</Button>
|
||||
|
||||
// Secondary action
|
||||
<Button variant="secondary">Cancel</Button>
|
||||
|
||||
// Danger/destructive
|
||||
<Button variant="danger">Delete</Button>
|
||||
|
||||
// Outline style
|
||||
<Button variant="outline">Learn More</Button>
|
||||
|
||||
// Ghost (minimal)
|
||||
<Button variant="ghost">Skip</Button>
|
||||
|
||||
// With icon
|
||||
<Button variant="primary" leftIcon={<PlusIcon />}>Add Item</Button>
|
||||
|
||||
// Icon only
|
||||
<IconButton icon={<SettingsIcon />} aria-label="Settings" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form Patterns
|
||||
|
||||
### Input Fields
|
||||
|
||||
```tsx
|
||||
<InputField
|
||||
label="Email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
error={errors.email}
|
||||
required
|
||||
/>
|
||||
```
|
||||
|
||||
### Select Dropdowns
|
||||
|
||||
```tsx
|
||||
<Select
|
||||
label="Country"
|
||||
options={[
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
]}
|
||||
value={country}
|
||||
onChange={setCountry}
|
||||
/>
|
||||
```
|
||||
|
||||
### Checkboxes & Switches
|
||||
|
||||
```tsx
|
||||
// Checkbox
|
||||
<Checkbox
|
||||
label="I agree to terms"
|
||||
checked={agreed}
|
||||
onChange={setAgreed}
|
||||
/>
|
||||
|
||||
// Toggle switch
|
||||
<Switch
|
||||
label="Enable notifications"
|
||||
checked={enabled}
|
||||
onChange={setEnabled}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Badge Tones
|
||||
|
||||
```tsx
|
||||
<Badge tone="default">Draft</Badge>
|
||||
<Badge tone="success">Active</Badge>
|
||||
<Badge tone="warning">Pending</Badge>
|
||||
<Badge tone="error">Failed</Badge>
|
||||
<Badge tone="info">New</Badge>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modal Pattern
|
||||
|
||||
```tsx
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={() => setIsOpen(false)}
|
||||
title="Confirm Delete"
|
||||
>
|
||||
<p>Are you sure you want to delete this item?</p>
|
||||
<div className="flex gap-3 mt-4">
|
||||
<Button variant="secondary" onClick={() => setIsOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="danger" onClick={handleDelete}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Toast Notifications
|
||||
|
||||
```tsx
|
||||
import { useToast } from '../components/ui/toast/ToastContainer';
|
||||
|
||||
const { showToast } = useToast();
|
||||
|
||||
// Success
|
||||
showToast('Changes saved successfully', 'success');
|
||||
|
||||
// Error
|
||||
showToast('Failed to save changes', 'error');
|
||||
|
||||
// Warning
|
||||
showToast('Your session is about to expire', 'warning');
|
||||
|
||||
// Info
|
||||
showToast('New features available', 'info');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spacing & Layout
|
||||
|
||||
### Standard Spacing Scale
|
||||
|
||||
| Class | Size | Usage |
|
||||
|-------|------|-------|
|
||||
| `gap-2` | 8px | Tight grouping |
|
||||
| `gap-3` | 12px | Default spacing |
|
||||
| `gap-4` | 16px | Section spacing |
|
||||
| `gap-6` | 24px | Major sections |
|
||||
|
||||
### Card Layout
|
||||
|
||||
```tsx
|
||||
<Card>
|
||||
<Card.Header>
|
||||
<h3>Title</h3>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
Content here
|
||||
</Card.Body>
|
||||
<Card.Footer>
|
||||
<Button>Action</Button>
|
||||
</Card.Footer>
|
||||
</Card>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
```tsx
|
||||
// Page titles
|
||||
<h1 className="text-2xl font-semibold text-gray-900">Page Title</h1>
|
||||
|
||||
// Section headers
|
||||
<h2 className="text-lg font-medium text-gray-900">Section</h2>
|
||||
|
||||
// Card titles
|
||||
<h3 className="text-base font-medium text-gray-900">Card Title</h3>
|
||||
|
||||
// Body text
|
||||
<p className="text-sm text-gray-700">Body content</p>
|
||||
|
||||
// Secondary/muted text
|
||||
<span className="text-sm text-gray-500">Helper text</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ESLint Enforcement
|
||||
|
||||
These rules are **enforced via ESLint**:
|
||||
|
||||
| Rule | Level | Enforces |
|
||||
|------|-------|----------|
|
||||
| `no-raw-button` | warn | Use `<Button>` component |
|
||||
| `no-raw-input` | warn | Use `<InputField>` component |
|
||||
| `no-raw-select` | warn | Use `<Select>` component |
|
||||
| `no-raw-textarea` | warn | Use `<TextArea>` component |
|
||||
| `design-system-colors` | error | Only semantic colors |
|
||||
| `no-hardcoded-colors` | error | No hex codes in className |
|
||||
|
||||
---
|
||||
|
||||
## Live Demo
|
||||
|
||||
Visit `/ui-elements` in the app to see all components in action.
|
||||
|
||||
---
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [COMPONENT-SYSTEM.md](docs/30-FRONTEND/COMPONENT-SYSTEM.md) - Full component API
|
||||
- [DESIGN-TOKENS.md](docs/30-FRONTEND/DESIGN-TOKENS.md) - CSS variables
|
||||
- [PAGES.md](docs/30-FRONTEND/PAGES.md) - Route structure
|
||||
@@ -0,0 +1,459 @@
|
||||
# Design System & Component Guidelines
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
|
||||
> 🔒 **STYLE SYSTEM LOCKED** - This design system is **LOCKED**. Read this entire document before making any styling changes.
|
||||
|
||||
## 🎨 Design Token System
|
||||
|
||||
**Single Source of Truth**: `/src/styles/design-system.css`
|
||||
|
||||
⚠️ **CRITICAL**: Only 6 hex color values exist in the entire system. Everything else is derived using `color-mix()`.
|
||||
|
||||
### The 6 Base Colors
|
||||
|
||||
| Token | Hex | Purpose |
|
||||
|-------|-----|---------|
|
||||
| `--color-primary` | `#0077B6` | Brand blue |
|
||||
| `--color-success` | `#00B894` | Success green |
|
||||
| `--color-warning` | `#F59E0B` | Warning amber |
|
||||
| `--color-danger` | `#DC2626` | Error red |
|
||||
| `--color-purple` | `#7C3AED` | Premium purple |
|
||||
| `--color-gray-base` | `#667085` | Neutral gray |
|
||||
|
||||
### Color Scales (Derived)
|
||||
|
||||
Each base color generates a full scale (50-950) via `color-mix()`:
|
||||
|
||||
```css
|
||||
/* Example: brand color scale */
|
||||
--color-brand-50 /* Lightest */
|
||||
--color-brand-100
|
||||
--color-brand-200
|
||||
--color-brand-300
|
||||
--color-brand-400
|
||||
--color-brand-500 /* Base = --color-primary */
|
||||
--color-brand-600
|
||||
--color-brand-700
|
||||
--color-brand-800
|
||||
--color-brand-900
|
||||
--color-brand-950 /* Darkest */
|
||||
```
|
||||
|
||||
### Tailwind Color Classes
|
||||
|
||||
**Available (Use These):**
|
||||
```css
|
||||
/* Brand */ bg-brand-50 ... bg-brand-950, text-brand-*, border-brand-*
|
||||
/* Success */ bg-success-50 ... bg-success-950, text-success-*, border-success-*
|
||||
/* Warning */ bg-warning-50 ... bg-warning-950, text-warning-*, border-warning-*
|
||||
/* Error */ bg-error-50 ... bg-error-950, text-error-*, border-error-*
|
||||
/* Purple */ bg-purple-50 ... bg-purple-950, text-purple-*, border-purple-*
|
||||
/* Gray */ bg-gray-50 ... bg-gray-950, text-gray-*, border-gray-*
|
||||
/* Info */ bg-info-50 ... bg-info-950 (alias for brand)
|
||||
```
|
||||
|
||||
**DISABLED (DO NOT USE):**
|
||||
```css
|
||||
/* These Tailwind defaults are DISABLED and won't work */
|
||||
blue-*, red-*, green-*, yellow-*, orange-*, indigo-*, violet-*,
|
||||
pink-*, rose-*, cyan-*, teal-*, emerald-*, lime-*, amber-*,
|
||||
slate-*, zinc-*, neutral-*, stone-*
|
||||
```
|
||||
|
||||
### Using Colors
|
||||
|
||||
**✅ DO:**
|
||||
```tsx
|
||||
// Use semantic Tailwind utilities
|
||||
<div className="bg-brand-500 text-white">Primary action</div>
|
||||
<div className="bg-success-100 text-success-700">Success message</div>
|
||||
<div className="bg-error-50 border-error-500">Error alert</div>
|
||||
|
||||
// Use CSS variables for custom cases
|
||||
<div className="bg-[var(--color-primary)]">Custom</div>
|
||||
|
||||
// Use React components with tone prop
|
||||
<Button tone="brand">Primary</Button>
|
||||
<Button tone="success">Approve</Button>
|
||||
<Badge tone="warning">Pending</Badge>
|
||||
```
|
||||
|
||||
**❌ DON'T:**
|
||||
```tsx
|
||||
// Don't use Tailwind default colors (DISABLED)
|
||||
<div className="bg-blue-500">Won't work!</div>
|
||||
<div className="text-slate-700">Won't work!</div>
|
||||
|
||||
// Don't hardcode hex values
|
||||
<div className="bg-[#0077B6]">Bad!</div>
|
||||
<div style={{ backgroundColor: '#DC2626' }}>Bad!</div>
|
||||
```
|
||||
|
||||
## 🔒 Style System Lock Status
|
||||
|
||||
**DO NOT:**
|
||||
- ❌ Use Tailwind default color classes (blue-*, red-*, green-*, etc.)
|
||||
- ❌ Hardcode hex color values anywhere
|
||||
- ❌ Use inline styles for colors/spacing/typography
|
||||
- ❌ Import from external icon libraries (lucide-react, @heroicons)
|
||||
- ❌ Create new CSS classes without documenting
|
||||
- ❌ Duplicate existing styling patterns
|
||||
|
||||
**DO:**
|
||||
- ✅ Use semantic color tokens (brand-*, success-*, etc.)
|
||||
- ✅ Import icons from `src/icons`
|
||||
- ✅ Use React components (Button, Badge, Card, InputField)
|
||||
- ✅ Run `npm run lint` to check for violations
|
||||
- ✅ Check `/ui-elements` for component examples
|
||||
|
||||
---
|
||||
|
||||
## Module Color Scheme (v1.3.2)
|
||||
|
||||
Each module has a distinct color for visual identification:
|
||||
|
||||
| Module | Color | Tailwind Classes |
|
||||
|--------|-------|------------------|
|
||||
| **Planner** (Keywords/Clusters/Ideas) | Purple | `bg-purple-*`, `text-purple-*` |
|
||||
| **Writer** (Tasks/Content/Images) | Green | `bg-success-*`, `text-success-*` |
|
||||
| **Automation** | Blue | `bg-brand-*`, `text-brand-*` |
|
||||
| **Publisher** | Orange | `bg-warning-*`, `text-warning-*` |
|
||||
| **Billing** | Purple | `bg-purple-*`, `text-purple-*` |
|
||||
| **Settings** | Gray | `bg-gray-*`, `text-gray-*` |
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. **Reuse Existing Components Only**
|
||||
- ✅ **DO**: Use existing components from `/src/components/ui/` and `/src/components/common/`
|
||||
- ❌ **DON'T**: Create new components unless explicitly required and approved
|
||||
- ❌ **DON'T**: Duplicate existing component functionality
|
||||
|
||||
### 2. **No Inline Styles**
|
||||
- ✅ **DO**: Use Tailwind CSS utility classes
|
||||
- ✅ **DO**: Use existing CSS classes from the design system
|
||||
- ✅ **DO**: Use existing component props for styling
|
||||
- ❌ **DON'T**: Use inline `style` attributes
|
||||
- ❌ **DON'T**: Use inline `style={{}}` in JSX
|
||||
|
||||
### 3. **Component Consistency**
|
||||
- All UI Elements pages should follow the same structure:
|
||||
- Use `PageMeta` for SEO
|
||||
- Use `PageBreadcrumb` for navigation
|
||||
- Use `ComponentCard` for consistent card styling
|
||||
- Import and use existing reusable components
|
||||
|
||||
### 4. **Available Component Libraries**
|
||||
|
||||
#### UI Components (`/src/components/ui/`)
|
||||
- `alert/` - Alert components
|
||||
- `avatar/` - Avatar components
|
||||
- `badge/` - Badge components
|
||||
- `breadcrumb/` - Breadcrumb navigation
|
||||
- `button/` - Button components
|
||||
- `button-group/` - Button group components
|
||||
- `card/` - Card components
|
||||
- `dropdown/` - Dropdown menu components
|
||||
- `images/` - Image components
|
||||
- `list/` - List components
|
||||
- `modal/` - Modal components
|
||||
- `pagination/` - Pagination components
|
||||
- `progress/` - Progress bar components
|
||||
- `ribbon/` - Ribbon components
|
||||
- `spinner/` - Spinner/loading components
|
||||
- `tabs/` - Tab components
|
||||
- `table/` - Table components
|
||||
- `toast/` - Toast notification components
|
||||
- `tooltip/` - Tooltip components
|
||||
- `videos/` - Video components
|
||||
|
||||
#### Common Components (`/src/components/common/`)
|
||||
- `ComponentCard` - Consistent card wrapper for component showcases
|
||||
- `PageBreadcrumb` - Breadcrumb navigation
|
||||
- `PageMeta` - SEO meta tags
|
||||
- `ConfirmDialog` - Confirmation dialogs
|
||||
- `FormModal` - Form modals (when available)
|
||||
|
||||
### 5. **Styling Guidelines**
|
||||
- Use Tailwind CSS classes exclusively
|
||||
- Use design tokens from `tokens.css` via CSS variables: `var(--color-primary)`
|
||||
- Use Tailwind brand color utilities: `bg-brand-500`, `text-brand-500`, etc.
|
||||
- Use React components for buttons, badges, cards (Button, Badge, Card)
|
||||
- Use dark mode variants: `dark:bg-gray-900`, `dark:text-white`, etc.
|
||||
- Maintain consistent spacing using Tailwind spacing scale
|
||||
- Use existing utility classes: `shadow-theme-xs`, `text-theme-sm`, etc.
|
||||
|
||||
### 5a. **Deprecated Patterns (DO NOT USE)**
|
||||
- ❌ `.igny8-bg-*`, `.igny8-text-*`, `.igny8-border-*` utility classes
|
||||
- ❌ `var(--igny8-blue)` - use `var(--color-primary)` instead
|
||||
- ❌ Hardcoded hex colors like `#0693e3`
|
||||
- ❌ Inline `style={{ color: ... }}` attributes
|
||||
|
||||
**Exception**: `.igny8-table-*` and `.igny8-header-metric-*` classes are still active and should be used for table/header components.
|
||||
|
||||
### 6. **When Creating New Components is Acceptable**
|
||||
Only create new components if:
|
||||
1. Explicitly requested by the user
|
||||
2. Documented in an approved feature request
|
||||
3. No existing component can be adapted or extended
|
||||
|
||||
### 7. **Checking for Existing Components**
|
||||
Before implementing any UI element:
|
||||
1. Search existing components in `/src/components/ui/`
|
||||
2. Check common components in `/src/components/common/`
|
||||
3. Review existing UI Elements pages for patterns
|
||||
4. Check if similar functionality exists in other modules
|
||||
|
||||
---
|
||||
|
||||
## 📝 Typography Scale
|
||||
|
||||
Typography tokens are defined in `design-system.css` and should be used consistently:
|
||||
|
||||
### Title Sizes (for page/section headings)
|
||||
| Token | Size | Line Height | Use Case |
|
||||
|-------|------|-------------|----------|
|
||||
| `--text-title-2xl` | 72px | 90px | Hero sections only |
|
||||
| `--text-title-xl` | 60px | 72px | Landing page headings |
|
||||
| `--text-title-lg` | 48px | 60px | Major section headers |
|
||||
| `--text-title-md` | 36px | 44px | Page titles |
|
||||
| `--text-title-sm` | 30px | 38px | Section subtitles |
|
||||
|
||||
### Theme Sizes (for body text)
|
||||
| Token | Size | Line Height | Use Case |
|
||||
|-------|------|-------------|----------|
|
||||
| `--text-theme-xl` | 20px | 30px | Large body text, intro paragraphs |
|
||||
| `--text-theme-sm` | 14px | 20px | Standard body text, menu items |
|
||||
| `--text-theme-xs` | 12px | 18px | Small text, labels, captions |
|
||||
|
||||
### Usage in Tailwind
|
||||
```tsx
|
||||
// Use Tailwind utilities mapped to tokens
|
||||
<h1 className="text-title-md">Page Title</h1>
|
||||
<p className="text-theme-sm">Body text</p>
|
||||
<span className="text-theme-xs">Caption</span>
|
||||
|
||||
// Or use the custom utility classes
|
||||
<h2 className="text-2xl font-semibold">Section Title</h2>
|
||||
<p className="text-base">Body text</p>
|
||||
```
|
||||
|
||||
### Font Weights
|
||||
- `font-normal` (400) - Body text
|
||||
- `font-medium` (500) - Labels, menu items
|
||||
- `font-semibold` (600) - Headings, emphasis
|
||||
- `font-bold` (700) - Strong emphasis
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Module Colors
|
||||
|
||||
Module-specific colors are defined in `src/config/colors.config.ts`:
|
||||
|
||||
| Module | Primary Color | Usage |
|
||||
|--------|---------------|-------|
|
||||
| Planner (Keywords) | `brand-500` (blue) | Icons, progress bars, badges |
|
||||
| Planner (Clusters) | `purple-500` | Icons, progress bars, badges |
|
||||
| Planner (Ideas) | `purple-600` | Icons, progress bars, badges |
|
||||
| Writer (Tasks) | `success-600` (green) | Icons, progress bars, badges |
|
||||
| Writer (Content) | `success-500` | Icons, progress bars, badges |
|
||||
| Writer (Images) | `purple-500` | Icons, progress bars, badges |
|
||||
| Automation | `brand-500` | Pipeline cards |
|
||||
| Publisher | `warning-500` (amber) | Calendar, scheduling |
|
||||
| Billing | `purple-500` | Credit displays |
|
||||
|
||||
```tsx
|
||||
import { MODULE_COLORS } from '@/config/colors.config';
|
||||
|
||||
// Use in components
|
||||
<div className={MODULE_COLORS.keywords.bg}>Keywords Section</div>
|
||||
<span className={MODULE_COLORS.clusters.text}>Cluster Label</span>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 3, 2026
|
||||
**Status**: Active Design System Rules - 🔒 LOCKED
|
||||
|
||||
---
|
||||
|
||||
## 🚨 MANDATORY COMPONENT & STYLING RULES
|
||||
|
||||
> **FOR ALL DEVELOPERS AND AI AGENTS**: This section defines the ONLY allowed sources for components, styling, and colors. Violating these rules will result in inconsistent UI and technical debt.
|
||||
|
||||
### ALLOWED COMPONENT SOURCES
|
||||
|
||||
**ONLY import components from these locations:**
|
||||
|
||||
| Category | Allowed Path | Components |
|
||||
|----------|--------------|------------|
|
||||
| **UI Components** | `@/components/ui/` or relative `../components/ui/` | Button, Card, Modal, Alert, Badge, Dropdown, Tooltip, Spinner, Tabs, Toast, Pagination, Progress, Avatar, Breadcrumb |
|
||||
| **Common Components** | `@/components/common/` | PageHeader, ComponentCard, ConfirmDialog, FormModal, TablePageTemplate |
|
||||
| **Icons** | `@/icons` | All SVG icons (local icons only - external libraries disabled) |
|
||||
| **Templates** | `@/templates/` | TablePageTemplate, ContentViewTemplate |
|
||||
|
||||
### BANNED IMPORTS (DO NOT USE)
|
||||
|
||||
```tsx
|
||||
// ❌ BANNED - Material UI
|
||||
import { Button, Dialog, Alert, Box } from '@mui/material';
|
||||
import { SomeIcon } from '@mui/icons-material';
|
||||
|
||||
// ❌ BANNED - Chakra UI
|
||||
import { Button } from '@chakra-ui/react';
|
||||
|
||||
// ❌ BANNED - Ant Design
|
||||
import { Button } from 'antd';
|
||||
|
||||
// ❌ BANNED - Custom inline components
|
||||
const MyButton = () => <button className="...">; // If used more than once, create in ui/
|
||||
```
|
||||
|
||||
### ALLOWED CSS/STYLING SOURCES
|
||||
|
||||
**ONLY these CSS files should be used:**
|
||||
|
||||
| File | Purpose | When to Use |
|
||||
|------|---------|-------------|
|
||||
| `src/styles/design-system.css` | ALL design tokens, colors, shadows, typography | Single source - auto-imported via main.tsx |
|
||||
|
||||
**DO NOT CREATE OR USE:**
|
||||
- ❌ `src/styles/global.css` - **Deleted** (consolidated into design-system.css)
|
||||
- ❌ `src/styles/tokens.css` - **Deleted** (consolidated into design-system.css)
|
||||
- ❌ `src/styles/index.css` - **Deleted** (consolidated into design-system.css)
|
||||
- ❌ `components/shared/blocks/blocks.css` - For marketing only
|
||||
- ❌ `components/shared/layouts/layouts.css` - For marketing only
|
||||
- ❌ Any new `.css` files in component folders
|
||||
|
||||
### COLOR USAGE RULES
|
||||
|
||||
**Allowed Color Methods:**
|
||||
|
||||
```tsx
|
||||
// ✅ Tailwind brand colors (defined in index.css @theme)
|
||||
className="bg-brand-500 text-brand-600 border-brand-200"
|
||||
className="bg-success-500 text-warning-600 bg-error-50"
|
||||
className="bg-gray-100 text-gray-700 border-gray-300"
|
||||
|
||||
// ✅ CSS variables from tokens.css
|
||||
className="bg-[var(--color-primary)]"
|
||||
style={{ '--accent': 'var(--color-success)' }}
|
||||
|
||||
// ✅ Dark mode variants
|
||||
className="bg-white dark:bg-gray-900 text-gray-900 dark:text-white"
|
||||
```
|
||||
|
||||
**BANNED Color Methods:**
|
||||
|
||||
```tsx
|
||||
// ❌ Hardcoded hex colors
|
||||
className="bg-[#0693e3]"
|
||||
className="text-[#ff7a00]"
|
||||
style={{ color: '#4c1d95' }}
|
||||
|
||||
// ❌ Arbitrary Tailwind colors not in tokens
|
||||
className="bg-[#4c1d95]"
|
||||
|
||||
// ❌ Inline SVG fills with hardcoded colors (use currentColor)
|
||||
<svg fill="#F04438"> // ❌
|
||||
<svg fill="currentColor" className="text-error-500"> // ✅
|
||||
```
|
||||
|
||||
### COMPONENT-SPECIFIC RULES
|
||||
|
||||
#### Button Component
|
||||
```tsx
|
||||
// ✅ CORRECT - Use Button from ui/
|
||||
import Button from '@/components/ui/button/Button';
|
||||
|
||||
<Button variant="primary" tone="brand" size="md">
|
||||
Click Me
|
||||
</Button>
|
||||
|
||||
// ❌ WRONG
|
||||
import { Button } from '@mui/material';
|
||||
<button className="bg-blue-500 px-4 py-2">Click</button> // Use Button component
|
||||
```
|
||||
|
||||
#### Modal/Dialog Component
|
||||
```tsx
|
||||
// ✅ CORRECT - Use Modal from ui/
|
||||
import { Modal } from '@/components/ui/modal';
|
||||
|
||||
<Modal isOpen={open} onClose={() => setOpen(false)}>
|
||||
<div className="p-6">Modal content</div>
|
||||
</Modal>
|
||||
|
||||
// ❌ WRONG
|
||||
import { Dialog } from '@mui/material';
|
||||
```
|
||||
|
||||
#### Alert Component
|
||||
```tsx
|
||||
// ✅ CORRECT
|
||||
import Alert from '@/components/ui/alert/Alert';
|
||||
|
||||
<Alert variant="success" title="Success" message="Action completed" />
|
||||
|
||||
// ❌ WRONG
|
||||
import { Alert } from '@mui/material';
|
||||
```
|
||||
|
||||
#### Badge Component
|
||||
```tsx
|
||||
// ✅ CORRECT
|
||||
import Badge from '@/components/ui/badge/Badge';
|
||||
|
||||
<Badge tone="success" variant="soft">Active</Badge>
|
||||
|
||||
// ❌ WRONG
|
||||
import { Chip } from '@mui/material';
|
||||
```
|
||||
|
||||
### FOLDER STRUCTURE FOR NEW COMPONENTS
|
||||
|
||||
If a new component is absolutely necessary:
|
||||
|
||||
```
|
||||
src/components/ui/
|
||||
├── new-component/
|
||||
│ ├── NewComponent.tsx # Main component
|
||||
│ └── index.ts # Export barrel
|
||||
```
|
||||
|
||||
**Requirements for new components:**
|
||||
1. Must be approved in a feature request
|
||||
2. Must follow existing component patterns (see Button.tsx, Badge.tsx)
|
||||
3. Must use design tokens for all colors
|
||||
4. Must support dark mode
|
||||
5. Must be documented in this file
|
||||
6. Must NOT duplicate existing functionality
|
||||
|
||||
### REFERENCE: PROPERLY STYLED PAGES
|
||||
|
||||
**Use these pages as reference for correct styling:**
|
||||
|
||||
| Module | Pages | Path |
|
||||
|--------|-------|------|
|
||||
| Planner | Keywords, Clusters, Ideas | `src/pages/Planner/` |
|
||||
| Writer | Review, Approved, Content | `src/pages/Writer/` |
|
||||
| Publisher | ContentCalendar, PublishingQueue | `src/pages/Publisher/` |
|
||||
| Sites | List, Settings | `src/pages/Sites/` |
|
||||
| Dashboard | Home | `src/pages/Dashboard/` |
|
||||
| Setup | SetupWizard (Onboarding) | `src/pages/Setup/` |
|
||||
|
||||
### AUDIT CHECKLIST FOR CODE REVIEW
|
||||
|
||||
Before merging any PR, verify:
|
||||
|
||||
- [ ] No imports from `@mui/material` or `@mui/icons-material`
|
||||
- [ ] All buttons use `Button` from `ui/button/Button`
|
||||
- [ ] All modals use `Modal` from `ui/modal`
|
||||
- [ ] All alerts use `Alert` from `ui/alert/Alert`
|
||||
- [ ] No hardcoded hex colors (search for `#[0-9a-fA-F]{3,6}`)
|
||||
- [ ] No new CSS files created
|
||||
- [ ] Dark mode variants included where needed
|
||||
- [ ] Component props match design system (tone, variant, size)
|
||||
@@ -0,0 +1,351 @@
|
||||
# IGNY8 Filters Implementation
|
||||
|
||||
**Last Updated:** January 15, 2026
|
||||
**Version:** 1.0.0
|
||||
|
||||
> This document describes the current filter implementation across all pages that use the `TablePageTemplate` component.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Component Hierarchy
|
||||
|
||||
```
|
||||
Page Component (e.g., Keywords.tsx)
|
||||
└── createPageConfig() function
|
||||
├── Returns: columns, filters, formFields, headerMetrics
|
||||
└── Uses: handlers (state setters, filter values)
|
||||
└── TablePageTemplate.tsx
|
||||
├── Renders filters based on FilterConfig[]
|
||||
├── Manages filter visibility toggle
|
||||
└── Passes filterValues and onFilterChange to children
|
||||
```
|
||||
|
||||
### Filter Flow
|
||||
|
||||
1. **Page Component** defines filter state (`useState`)
|
||||
2. **Config Function** creates `FilterConfig[]` with options
|
||||
3. **TablePageTemplate** renders filter UI components
|
||||
4. **Filter Change** → `onFilterChange` callback → update state → re-fetch data
|
||||
|
||||
---
|
||||
|
||||
## Filter Types
|
||||
|
||||
| Type | Component | Description |
|
||||
|------|-----------|-------------|
|
||||
| `text` | `<Input>` | Text search input |
|
||||
| `select` | `<SelectDropdown>` | Single-select dropdown |
|
||||
| `custom` | `customRender()` | Custom component (e.g., volume range) |
|
||||
| `daterange` | (planned) | Date range picker |
|
||||
| `range` | (planned) | Numeric range |
|
||||
|
||||
---
|
||||
|
||||
## Backend FilterSet Classes
|
||||
|
||||
### Planner Module (`/backend/igny8_core/modules/planner/views.py`)
|
||||
|
||||
#### KeywordsFilter
|
||||
```python
|
||||
class KeywordsFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Keywords
|
||||
fields = ['status', 'cluster_id', 'seed_keyword__country', 'seed_keyword_id', 'created_at__gte', 'created_at__lte']
|
||||
```
|
||||
|
||||
#### ClustersFilter
|
||||
```python
|
||||
class ClustersFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Clusters
|
||||
fields = ['status', 'created_at__gte', 'created_at__lte']
|
||||
```
|
||||
|
||||
#### ContentIdeasFilter
|
||||
```python
|
||||
class ContentIdeasFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = ContentIdeas
|
||||
fields = ['status', 'keyword_cluster_id', 'content_type', 'content_structure', 'created_at__gte', 'created_at__lte']
|
||||
```
|
||||
|
||||
### Writer Module (`/backend/igny8_core/modules/writer/views.py`)
|
||||
|
||||
#### TasksFilter
|
||||
```python
|
||||
class TasksFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Tasks
|
||||
fields = ['status', 'cluster_id', 'content_type', 'content_structure', 'created_at__gte', 'created_at__lte']
|
||||
```
|
||||
|
||||
#### ImagesFilter
|
||||
```python
|
||||
class ImagesFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Images
|
||||
fields = ['task_id', 'content_id', 'image_type', 'status', 'created_at__gte', 'created_at__lte']
|
||||
```
|
||||
|
||||
#### ContentFilter
|
||||
```python
|
||||
class ContentFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Content
|
||||
fields = ['cluster_id', 'status', 'content_type', 'content_structure', 'source', 'created_at__gte', 'created_at__lte']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Page-by-Page Filter Implementation
|
||||
|
||||
### 1. Keywords Page (`/planner/keywords`)
|
||||
|
||||
**Config File:** `frontend/src/config/pages/keywords.config.tsx`
|
||||
**Page File:** `frontend/src/pages/Planner/Keywords.tsx`
|
||||
|
||||
| Filter Key | Type | Options | Backend Field |
|
||||
|------------|------|---------|---------------|
|
||||
| `search` | text | - | `search` (SearchFilter) |
|
||||
| `status` | select | `new`, `mapped` | `status` |
|
||||
| `country` | select | `US`, `CA`, `GB`, `AE`, `AU`, `IN`, `PK` | `seed_keyword__country` |
|
||||
| `difficulty` | select | `1-5` (mapped labels) | Custom in `get_queryset()` |
|
||||
| `cluster` | select | Dynamic (cluster list) | `cluster_id` |
|
||||
| `volume` | custom | Min/Max inputs | Custom `volume_min`, `volume_max` |
|
||||
|
||||
**Special Handling:**
|
||||
- Difficulty filter uses 1-5 scale that maps to raw score ranges (0-10, 11-30, 31-50, 51-70, 71-100)
|
||||
- Volume range uses custom dropdown with min/max inputs
|
||||
|
||||
---
|
||||
|
||||
### 2. Clusters Page (`/planner/clusters`)
|
||||
|
||||
**Config File:** `frontend/src/config/pages/clusters.config.tsx`
|
||||
**Page File:** `frontend/src/pages/Planner/Clusters.tsx`
|
||||
|
||||
| Filter Key | Type | Options | Backend Field |
|
||||
|------------|------|---------|---------------|
|
||||
| `search` | text | - | `search` (SearchFilter) |
|
||||
| `status` | select | `new`, `mapped` | `status` |
|
||||
| `difficulty` | select | `1-5` (mapped labels) | Custom filtering |
|
||||
| `volume` | custom | Min/Max inputs | Custom `volume_min`, `volume_max` |
|
||||
|
||||
---
|
||||
|
||||
### 3. Ideas Page (`/planner/ideas`)
|
||||
|
||||
**Config File:** `frontend/src/config/pages/ideas.config.tsx`
|
||||
**Page File:** `frontend/src/pages/Planner/Ideas.tsx`
|
||||
|
||||
| Filter Key | Type | Options | Backend Field |
|
||||
|------------|------|---------|---------------|
|
||||
| `search` | text | - | `search` (SearchFilter) |
|
||||
| `status` | select | `new`, `queued`, `completed` | `status` |
|
||||
| `content_structure` | select | 14 structure types | `content_structure` |
|
||||
| `content_type` | select | `post`, `page`, `product`, `taxonomy` | `content_type` |
|
||||
| `keyword_cluster_id` | select | Dynamic (cluster list) | `keyword_cluster_id` |
|
||||
|
||||
**Structure Options:**
|
||||
- Post: `article`, `guide`, `comparison`, `review`, `listicle`
|
||||
- Page: `landing_page`, `business_page`, `service_page`, `general`, `cluster_hub`
|
||||
- Product: `product_page`
|
||||
- Taxonomy: `category_archive`, `tag_archive`, `attribute_archive`
|
||||
|
||||
---
|
||||
|
||||
### 4. Content Page (`/writer/content`)
|
||||
|
||||
**Config File:** `frontend/src/config/pages/content.config.tsx`
|
||||
**Page File:** `frontend/src/pages/Writer/Content.tsx`
|
||||
|
||||
| Filter Key | Type | Options | Backend Field |
|
||||
|------------|------|---------|---------------|
|
||||
| `search` | text | - | `search` (SearchFilter) |
|
||||
| `status` | select | `draft`, `published` | `status` |
|
||||
| `content_type` | select | `post`, `page`, `product`, `taxonomy` | `content_type` |
|
||||
| `content_structure` | select | 14 structure types | `content_structure` |
|
||||
| `source` | select | `igny8`, `wordpress` | `source` |
|
||||
|
||||
---
|
||||
|
||||
### 5. Review Page (`/writer/review`)
|
||||
|
||||
**Config File:** `frontend/src/config/pages/review.config.tsx`
|
||||
**Page File:** `frontend/src/pages/Writer/Review.tsx`
|
||||
|
||||
| Filter Key | Type | Options | Backend Field |
|
||||
|------------|------|---------|---------------|
|
||||
| `search` | text | - | `search` (SearchFilter) |
|
||||
| `status` | select | `draft`, `review`, `approved`, `published` | `status` |
|
||||
| `site_status` | select | `not_published`, `scheduled`, `publishing`, `published`, `failed` | `site_status` |
|
||||
| `content_type` | select | From `CONTENT_TYPE_OPTIONS` | `content_type` |
|
||||
| `content_structure` | select | From `ALL_CONTENT_STRUCTURES` | `content_structure` |
|
||||
|
||||
---
|
||||
|
||||
### 6. Approved Page (`/writer/approved`)
|
||||
|
||||
**Config File:** `frontend/src/config/pages/approved.config.tsx`
|
||||
**Page File:** `frontend/src/pages/Writer/Approved.tsx`
|
||||
|
||||
| Filter Key | Type | Options | Backend Field |
|
||||
|------------|------|---------|---------------|
|
||||
| `search` | text | - | `search` (SearchFilter) |
|
||||
| `status` | select | `draft`, `review`, `approved`, `published` | `status` |
|
||||
| `site_status` | select | `not_published`, `scheduled`, `publishing`, `published`, `failed` | `site_status` |
|
||||
| `content_type` | select | From `CONTENT_TYPE_OPTIONS` | `content_type` |
|
||||
| `content_structure` | select | From `ALL_CONTENT_STRUCTURES` | `content_structure` |
|
||||
|
||||
---
|
||||
|
||||
## Shared Constants
|
||||
|
||||
**File:** `frontend/src/config/structureMapping.ts`
|
||||
|
||||
```typescript
|
||||
export const CONTENT_TYPE_OPTIONS = [
|
||||
{ value: 'post', label: 'Post' },
|
||||
{ value: 'page', label: 'Page' },
|
||||
{ value: 'product', label: 'Product' },
|
||||
{ value: 'taxonomy', label: 'Taxonomy' },
|
||||
];
|
||||
|
||||
export const ALL_CONTENT_STRUCTURES = [
|
||||
{ value: 'article', label: 'Article' },
|
||||
{ value: 'guide', label: 'Guide' },
|
||||
{ value: 'comparison', label: 'Comparison' },
|
||||
{ value: 'review', label: 'Review' },
|
||||
{ value: 'listicle', label: 'Listicle' },
|
||||
{ value: 'landing_page', label: 'Landing Page' },
|
||||
{ value: 'business_page', label: 'Business Page' },
|
||||
{ value: 'service_page', label: 'Service Page' },
|
||||
{ value: 'general', label: 'General' },
|
||||
{ value: 'cluster_hub', label: 'Cluster Hub' },
|
||||
{ value: 'product_page', label: 'Product Page' },
|
||||
{ value: 'category_archive', label: 'Category Archive' },
|
||||
{ value: 'tag_archive', label: 'Tag Archive' },
|
||||
{ value: 'attribute_archive', label: 'Attribute Archive' },
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Difficulty Mapping
|
||||
|
||||
**File:** `frontend/src/utils/difficulty.ts`
|
||||
|
||||
The difficulty filter uses a 1-5 scale with human-readable labels:
|
||||
|
||||
| Value | Label | Raw Score Range |
|
||||
|-------|-------|-----------------|
|
||||
| 1 | Very Easy | 0-10 |
|
||||
| 2 | Easy | 11-30 |
|
||||
| 3 | Medium | 31-50 |
|
||||
| 4 | Hard | 51-70 |
|
||||
| 5 | Very Hard | 71-100 |
|
||||
|
||||
**Important:** The database stores raw SEO difficulty scores (0-100), but the UI displays and filters using the 1-5 scale. The mapping must be consistent between frontend display and backend filtering.
|
||||
|
||||
---
|
||||
|
||||
## Filter State Management Pattern
|
||||
|
||||
Each page follows this pattern:
|
||||
|
||||
```tsx
|
||||
// 1. Define filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [difficultyFilter, setDifficultyFilter] = useState('');
|
||||
|
||||
// 2. Pass to config function
|
||||
const config = createPageConfig({
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
// ...handlers
|
||||
});
|
||||
|
||||
// 3. Config returns filters array
|
||||
filters: [
|
||||
{ key: 'search', type: 'text', placeholder: '...' },
|
||||
{ key: 'status', type: 'select', options: [...] },
|
||||
]
|
||||
|
||||
// 4. TablePageTemplate renders filters
|
||||
// 5. onFilterChange triggers state update
|
||||
// 6. useEffect with dependencies re-fetches data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Query Parameters
|
||||
|
||||
When filters are applied, the frontend constructs API calls like:
|
||||
|
||||
```
|
||||
GET /api/v1/planner/keywords/?status=new&cluster_id=123&search=term&page=1&page_size=50
|
||||
GET /api/v1/planner/clusters/?status=mapped&difficulty=3&volume_min=100
|
||||
GET /api/v1/planner/ideas/?content_structure=guide&content_type=post
|
||||
GET /api/v1/writer/content/?status=draft&source=igny8
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TablePageTemplate Filter Rendering
|
||||
|
||||
The template handles filter rendering in `renderFiltersRow()`:
|
||||
|
||||
```tsx
|
||||
{filters.map((filter) => {
|
||||
if (filter.type === 'text') {
|
||||
return <Input ... />;
|
||||
}
|
||||
if (filter.type === 'select') {
|
||||
return <SelectDropdown ... />;
|
||||
}
|
||||
if (filter.type === 'custom' && filter.customRender) {
|
||||
return filter.customRender();
|
||||
}
|
||||
})}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current Limitations
|
||||
|
||||
1. **Static Options**: Filter options are hardcoded in config files, not dynamically loaded from backend
|
||||
2. **No Cascading**: Changing one filter doesn't update available options in other filters
|
||||
3. **Difficulty Mapping**: Backend filters by exact match, not by mapped ranges
|
||||
4. **No Filter Persistence**: Filters reset on page navigation
|
||||
|
||||
---
|
||||
|
||||
## Planned Improvements
|
||||
|
||||
1. **Dynamic Filter Options API**: Backend endpoint to return available options based on current data
|
||||
2. **Cascading Filters**: When status is selected, only show clusters/types that exist with that status
|
||||
3. **URL State**: Persist filter state in URL query parameters
|
||||
4. **Filter Presets**: Save commonly used filter combinations
|
||||
|
||||
---
|
||||
|
||||
## File References
|
||||
|
||||
| Component | Path |
|
||||
|-----------|------|
|
||||
| TablePageTemplate | `frontend/src/templates/TablePageTemplate.tsx` |
|
||||
| Keywords Config | `frontend/src/config/pages/keywords.config.tsx` |
|
||||
| Clusters Config | `frontend/src/config/pages/clusters.config.tsx` |
|
||||
| Ideas Config | `frontend/src/config/pages/ideas.config.tsx` |
|
||||
| Content Config | `frontend/src/config/pages/content.config.tsx` |
|
||||
| Review Config | `frontend/src/config/pages/review.config.tsx` |
|
||||
| Approved Config | `frontend/src/config/pages/approved.config.tsx` |
|
||||
| Structure Mapping | `frontend/src/config/structureMapping.ts` |
|
||||
| Difficulty Utils | `frontend/src/utils/difficulty.ts` |
|
||||
| Planner Views | `backend/igny8_core/modules/planner/views.py` |
|
||||
| Writer Views | `backend/igny8_core/modules/writer/views.py` |
|
||||
101
v2/Live Docs on Server/igny8-app-docs/30-FRONTEND/PAGE-AUDIT.md
Normal file
101
v2/Live Docs on Server/igny8-app-docs/30-FRONTEND/PAGE-AUDIT.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Frontend Page Audit (In Progress)
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Goal:** Verify each page’s functions, API usage, and flow consistency.
|
||||
|
||||
---
|
||||
|
||||
## Audit Scope (Current Batch)
|
||||
|
||||
- Auth pages: Sign In, Sign Up, Forgot Password, Reset Password, Verify Email, Unsubscribe
|
||||
- Payment page
|
||||
|
||||
---
|
||||
|
||||
## Auth Pages
|
||||
|
||||
### Sign In
|
||||
|
||||
- **Route:** `/signin`
|
||||
- **File:** `frontend/src/pages/AuthPages/SignIn.tsx`
|
||||
- **Components:** `PageMeta`, `AuthLayout`, `SignInForm`
|
||||
- **API usage:** none in page (handled by `SignInForm`)
|
||||
- **Notes:** Page is a wrapper; all auth logic is inside `SignInForm`.
|
||||
|
||||
### Sign Up
|
||||
|
||||
- **Route:** `/signup`
|
||||
- **File:** `frontend/src/pages/AuthPages/SignUp.tsx`
|
||||
- **Components:** `PageMeta`, `SignUpFormUnified`, `GridShape`
|
||||
- **API usage:** `GET /v1/auth/plans/` (public) for plan list
|
||||
- **Behavior:**
|
||||
- Reads `plan` query param to preselect plan
|
||||
- Defaults to first active plan if no match
|
||||
- Sorts plans by price ascending
|
||||
- **Notes:**
|
||||
- Geo detection removed; country selected in form
|
||||
- Payment selection deferred to `/account/plans`
|
||||
|
||||
### Forgot Password
|
||||
|
||||
- **Route:** `/forgot-password`
|
||||
- **File:** `frontend/src/pages/AuthPages/ForgotPassword.tsx`
|
||||
- **Components:** `PageMeta`, icons, local form
|
||||
- **API usage:** `POST /v1/auth/password-reset/` with `{ email }`
|
||||
- **Behavior:**
|
||||
- Always shows success state to prevent email enumeration
|
||||
|
||||
### Reset Password
|
||||
|
||||
- **Route:** `/reset-password?token=...`
|
||||
- **File:** `frontend/src/pages/AuthPages/ResetPassword.tsx`
|
||||
- **Components:** `PageMeta`, icons, form
|
||||
- **API usage:** `POST /v1/auth/password-reset/confirm/` with `{ token, new_password, new_password_confirm }`
|
||||
- **Behavior:**
|
||||
- If no `token`, redirects to `/forgot-password`
|
||||
- Validates password strength client-side
|
||||
- Handles expired/invalid token states
|
||||
|
||||
### Verify Email
|
||||
|
||||
- **Route:** `/verify-email?token=...`
|
||||
- **File:** `frontend/src/pages/AuthPages/VerifyEmail.tsx`
|
||||
- **Components:** `PageMeta`, icons
|
||||
- **API usage:** `POST /v1/auth/users/verify_email/` with `{ token }`
|
||||
- **Behavior:**
|
||||
- Requires query param `token`
|
||||
- Handles expired/invalid token state
|
||||
|
||||
### Unsubscribe
|
||||
|
||||
- **Route:** `/unsubscribe`
|
||||
- **File:** `frontend/src/pages/AuthPages/Unsubscribe.tsx`
|
||||
- **Components:** `PageMeta`, icons
|
||||
- **Behavior:**
|
||||
- Displays guidance and redirects to `/account/settings?tab=notifications` after 5 seconds
|
||||
- Notes that transactional emails are not unsubscribable
|
||||
- **Potential issue:** redirect requires auth; unauthenticated users will be sent to sign-in flow.
|
||||
|
||||
---
|
||||
|
||||
## Payment Page
|
||||
|
||||
### Payment
|
||||
|
||||
- **Route:** `/payment`
|
||||
- **File:** `frontend/src/pages/Payment.tsx`
|
||||
- **Components:** `InputField`, `TextArea`, `Button`
|
||||
- **Store usage:** `useAuthStore` for user/account plan
|
||||
- **Behavior:**
|
||||
- Reads `plan` from query string or current account plan
|
||||
- Generates mailto for offline payment confirmation
|
||||
- Redirects to `/pricing` if plan or user missing
|
||||
- **Potential issue:** `/pricing` is not defined in `App.tsx` routes (may exist in marketing app).
|
||||
|
||||
---
|
||||
|
||||
## Next Audit Batch
|
||||
|
||||
- Dashboard and core workflow pages
|
||||
- Sites pages (dashboard, settings, sync, deployment)
|
||||
- Planner and Writer pages
|
||||
@@ -0,0 +1,182 @@
|
||||
# Page Requirements - Site & Sector Selectors
|
||||
|
||||
**Last Verified:** December 27, 2025
|
||||
**Version:** 1.2.0
|
||||
|
||||
This document outlines all pages in the application and their requirements for site/sector selectors.
|
||||
|
||||
## Legend
|
||||
- **Site Selector**: Whether the page needs a site selector dropdown
|
||||
- **Sector Selector**: Whether the page needs a sector selector dropdown
|
||||
- **Implementation**: How the selectors should behave on this page
|
||||
- **Next Action**: Recommended workflow guidance for Planner/Writer pages
|
||||
|
||||
---
|
||||
|
||||
## Planner Module (Content Planning)
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Keywords | `/planner/keywords` | ✅ Required | ✅ Required | Filter keywords by site/sector | `{count} selected → Auto-Cluster` OR `{clustered} clustered → Generate Ideas` |
|
||||
| Clusters | `/planner/clusters` | ✅ Required | ✅ Required | Filter clusters by site/sector | `{count} selected → Expand Clusters` OR `{ready} ready → Generate Ideas` |
|
||||
| Cluster Detail | `/planner/clusters/:id` | ✅ Read-only | ✅ Read-only | Display only (inherited from cluster) | `Back to Clusters` OR `Generate Ideas from Cluster` |
|
||||
| Ideas | `/planner/ideas` | ✅ Required | ✅ Required | Filter ideas by site/sector | `{count} selected → Create Tasks` OR `{approved} approved → Create Tasks` |
|
||||
|
||||
---
|
||||
|
||||
## Writer Module (Content Creation)
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Tasks | `/writer/tasks` | ✅ Required | ✅ Required | Filter tasks by site/sector | `{count} selected → Generate Content` OR `{ready} ready → Generate Content` |
|
||||
| Content | `/writer/content` | ✅ Required | ✅ Required | Filter content by site/sector | `{count} selected → Generate Images` OR `{draft} drafts → Add Images` |
|
||||
| Images | `/writer/images` | ✅ Required | ✅ Required | Filter images by site/sector | `{count} selected → Submit for Review` OR `{ready} ready → Submit for Review` |
|
||||
| Review | `/writer/review` | ✅ Required | ✅ Required | Filter review items by site/sector | `{count} selected → Approve Selected` OR `{reviewed} reviewed → Approve All` |
|
||||
| Approved | `/writer/approved` | ✅ Required | ✅ Required | Filter approved items by site/sector | `{count} selected → Sync to WordPress` OR `View All Sites` |
|
||||
|
||||
---
|
||||
|
||||
## Linker Module (Internal Linking)
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Content List | `/linker/content` | ✅ Required | ✅ Optional | Filter content by site/sector | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Optimizer Module (Content Optimization)
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Content Selector | `/optimizer/content` | ✅ Required | ✅ Optional | Filter content for optimization | N/A |
|
||||
| Analysis Preview | `/optimizer/analyze/:id` | ✅ Read-only | ❌ Not needed | Display only (inherited from content) | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Thinker Module (AI Configuration) - Admin Only
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Prompts | `/thinker/prompts` | ❌ Global | ❌ Global | System-wide prompts | N/A |
|
||||
| Author Profiles | `/thinker/author-profiles` | ❌ Global | ❌ Global | System-wide profiles | N/A |
|
||||
| Strategies | `/thinker/strategies` | ❌ Global | ❌ Global | System-wide strategies | N/A |
|
||||
| Image Testing | `/thinker/image-testing` | ❌ Global | ❌ Global | Testing interface | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Sites Module (Site Management)
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Site List | `/sites` | ❌ Shows all | ❌ Not applicable | Lists all sites | N/A |
|
||||
| Site Dashboard | `/sites/:id` | ✅ Read-only | ❌ Not needed | Inherited from route param | N/A |
|
||||
| Site Content | `/sites/:id/content` | ✅ Read-only | ✅ Optional | Filter by sector within site | N/A |
|
||||
| Page Manager | `/sites/:id/pages` | ✅ Read-only | ❌ Not needed | Inherited from route param | N/A |
|
||||
| Site Settings | `/sites/:id/settings` | ✅ Read-only | ❌ Not needed | Inherited from route param | N/A |
|
||||
| Sync Dashboard | `/sites/:id/sync` | ✅ Read-only | ❌ Not needed | Inherited from route param | N/A |
|
||||
| Deployment Panel | `/sites/:id/deploy` | ✅ Read-only | ❌ Not needed | Inherited from route param | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Account & Billing
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Account Settings | `/account/settings` | ❌ Not needed | ❌ Not needed | Account-level settings | N/A |
|
||||
| Plans & Billing | `/account/plans` | ❌ Not needed | ❌ Not needed | Account-level billing | N/A |
|
||||
| Purchase Credits | `/account/purchase-credits` | ❌ Not needed | ❌ Not needed | Account-level purchase | N/A |
|
||||
| Usage Analytics | `/account/usage` | ✅ Optional | ❌ Not needed | Filter usage by site | N/A |
|
||||
| Content Settings | `/account/content-settings` | ❌ Not needed | ❌ Not needed | Account-level settings | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Settings (Admin)
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| General Settings | `/settings` | ❌ Not needed | ❌ Not needed | System-wide settings | N/A |
|
||||
| Users | `/settings/users` | ❌ Not needed | ❌ Not needed | User management | N/A |
|
||||
| Subscriptions | `/settings/subscriptions` | ❌ Not needed | ❌ Not needed | Subscription management | N/A |
|
||||
| System | `/settings/system` | ❌ Not needed | ❌ Not needed | System configuration | N/A |
|
||||
| Account | `/settings/account` | ❌ Not needed | ❌ Not needed | Account settings | N/A |
|
||||
| AI Settings | `/settings/ai` | ❌ Not needed | ❌ Not needed | AI model configuration | N/A |
|
||||
| Plans | `/settings/plans` | ❌ Not needed | ❌ Not needed | Plan management | N/A |
|
||||
| Industries | `/settings/industries` | ❌ Not needed | ❌ Not needed | Industry reference data | N/A |
|
||||
| Integration | `/settings/integration` | ❌ Not needed | ❌ Not needed | API integrations | N/A |
|
||||
| Publishing | `/settings/publishing` | ❌ Not needed | ❌ Not needed | Publishing defaults | N/A |
|
||||
| Sites | `/settings/sites` | ❌ Not needed | ❌ Not needed | Site configuration | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Reference Data
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Seed Keywords | `/reference/seed-keywords` | ✅ Optional | ✅ Optional | Filter reference keywords | N/A |
|
||||
| Industries | `/reference/industries` | ❌ Not needed | ❌ Not needed | Global reference data | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Add Keywords | `/setup/add-keywords` | ✅ Required | ✅ Required | Target site/sector for import | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Other Pages
|
||||
|
||||
| Page | Route | Site Selector | Sector Selector | Implementation | Recommended Next Action |
|
||||
|------|-------|---------------|-----------------|----------------|-------------------------|
|
||||
| Home Dashboard | `/` | ✅ Optional | ❌ Not needed | Overview all sites or filter | N/A |
|
||||
| Help | `/help` | ❌ Not needed | ❌ Not needed | Documentation | N/A |
|
||||
| Components | `/components` | ❌ Not needed | ❌ Not needed | Design system showcase | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Site Selector Behavior
|
||||
- **Required**: User must select a site before content is displayed
|
||||
- **Optional**: Shows all sites by default, can filter to specific site
|
||||
- **Read-only**: Shows the current site but cannot be changed (inherited from route/context)
|
||||
- **Not needed**: Page operates at account/system level
|
||||
|
||||
### Sector Selector Behavior
|
||||
- **Required**: User must select both site and sector
|
||||
- **Optional**: Shows all sectors by default within selected site
|
||||
- **Read-only**: Shows current sector but cannot be changed
|
||||
- **Not needed**: Page doesn't operate at sector level
|
||||
|
||||
### Next Action Patterns (Planner/Writer)
|
||||
The next action button should follow this pattern:
|
||||
1. If items are selected → Action on selected items
|
||||
2. If no selection but ready items exist → Workflow progression action
|
||||
3. If nothing actionable → Hide or disable
|
||||
|
||||
Example:
|
||||
```tsx
|
||||
nextAction={selectedIds.length > 0 ? {
|
||||
label: 'Process Selected',
|
||||
message: `${selectedIds.length} items selected`,
|
||||
onClick: handleBulkAction,
|
||||
} : workflowStats.ready > 0 ? {
|
||||
label: 'Continue to Next Step',
|
||||
href: '/next/page',
|
||||
message: `${workflowStats.ready} items ready`,
|
||||
} : undefined}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow Pipeline (Planner → Writer)
|
||||
|
||||
```
|
||||
Keywords → Clusters → Ideas → Tasks → Content → Images → Review → Approved
|
||||
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
|
||||
Cluster Expand Create Generate Generate Submit Approve Sync to
|
||||
Keywords Ideas Tasks Content Images Review Content WordPress
|
||||
```
|
||||
|
||||
Each page's "next action" guides users through this pipeline.
|
||||
358
v2/Live Docs on Server/igny8-app-docs/30-FRONTEND/PAGES.md
Normal file
358
v2/Live Docs on Server/igny8-app-docs/30-FRONTEND/PAGES.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# Frontend Pages & Routes
|
||||
|
||||
> **Auto-verified against:** `frontend/src/App.tsx`
|
||||
> **Last Verified:** January 20, 2026
|
||||
> **Version:** 1.8.3
|
||||
> **Total Routes:** 100
|
||||
|
||||
---
|
||||
|
||||
## Route Configuration
|
||||
|
||||
Routes defined in `/frontend/src/App.tsx`:
|
||||
|
||||
| Guard | Purpose |
|
||||
|-------|---------|
|
||||
| `ProtectedRoute` | Requires authentication |
|
||||
| `AdminRoute` | Requires staff/admin role |
|
||||
| `AppLayout` | Shared layout (sidebar + header) |
|
||||
|
||||
---
|
||||
|
||||
## Public Routes (No Auth Required)
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/signin` | `AuthPages/SignIn.tsx` | User login |
|
||||
| `/signup` | `AuthPages/SignUp.tsx` | Account registration |
|
||||
| `/signup/pk` | `AuthPages/SignUp.tsx` | Legacy route → main signup |
|
||||
| `/payment` | `Payment.tsx` | Payment page |
|
||||
| `/forgot-password` | `AuthPages/ForgotPassword.tsx` | Request password reset |
|
||||
| `/reset-password` | `AuthPages/ResetPassword.tsx` | Set new password |
|
||||
| `/verify-email` | `AuthPages/VerifyEmail.tsx` | Email verification |
|
||||
| `/unsubscribe` | `AuthPages/Unsubscribe.tsx` | Email unsubscribe |
|
||||
| `/terms` | `legal/Terms.tsx` | Terms of Service |
|
||||
| `/privacy` | `legal/Privacy.tsx` | Privacy Policy |
|
||||
|
||||
---
|
||||
|
||||
## Dashboard
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/` | `Dashboard/Home.tsx` | Main dashboard with workflow widgets |
|
||||
|
||||
---
|
||||
|
||||
## Setup & Keywords Library
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/setup/wizard` | `Setup/SetupWizard.tsx` | Onboarding wizard |
|
||||
| `/keywords-library` | `Setup/IndustriesSectorsKeywords.tsx` | Keywords library |
|
||||
|
||||
**Redirects:**
|
||||
- `/setup/add-keywords` → `/keywords-library`
|
||||
- `/setup/industries-sectors-keywords` → `/keywords-library`
|
||||
|
||||
---
|
||||
|
||||
## Sites Management
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/sites` | `Sites/List.tsx` | Sites list |
|
||||
| `/sites/:id` | `Sites/Dashboard.tsx` | Site dashboard |
|
||||
| `/sites/:id/pages` | `Sites/PageManager.tsx` | Page manager |
|
||||
| `/sites/:id/pages/new` | `Sites/PageManager.tsx` | New page |
|
||||
| `/sites/:id/pages/:pageId/edit` | `Sites/PageManager.tsx` | Edit page |
|
||||
| `/sites/:id/content` | `Sites/Content.tsx` | Site content overview |
|
||||
| `/sites/:id/content/structure` | `Sites/ContentStructure.tsx` | Content structure |
|
||||
| `/sites/:id/settings` | `Sites/Settings.tsx` | Site settings |
|
||||
| `/sites/:id/sync` | `Sites/SyncDashboard.tsx` | Sync dashboard |
|
||||
| `/sites/:id/deploy` | `Sites/DeploymentPanel.tsx` | Deployment panel |
|
||||
| `/sites/:id/posts/:postId` | `Sites/PostEditor.tsx` | Post editor |
|
||||
| `/sites/:id/posts/:postId/edit` | `Sites/PostEditor.tsx` | Post editor |
|
||||
|
||||
**Redirects:**
|
||||
- `/sites/:id/publishing-queue` → `/publisher/content-calendar`
|
||||
|
||||
---
|
||||
|
||||
## Planner
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/planner` | → `/planner/keywords` | Redirect |
|
||||
| `/planner/keywords` | `Planner/Keywords.tsx` | Keyword management |
|
||||
| `/planner/clusters` | `Planner/Clusters.tsx` | Cluster listing |
|
||||
| `/planner/clusters/:id` | `Planner/ClusterDetail.tsx` | Cluster detail |
|
||||
| `/planner/ideas` | `Planner/Ideas.tsx` | Content ideas |
|
||||
|
||||
---
|
||||
|
||||
## Writer
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/writer` | → `/writer/tasks` | Redirect |
|
||||
| `/writer/tasks` | `Writer/Tasks.tsx` | Task queue |
|
||||
| `/writer/content` | `Writer/Content.tsx` | Content list |
|
||||
| `/writer/content/:id` | `Writer/ContentView.tsx` | Content detail |
|
||||
| `/writer/images` | `Writer/Images.tsx` | Images by content |
|
||||
| `/writer/review` | `Writer/Review.tsx` | Review queue |
|
||||
| `/writer/approved` | `Writer/Approved.tsx` | Approved content |
|
||||
|
||||
**Redirects:**
|
||||
- `/writer/drafts` → `/writer/content`
|
||||
- `/writer/published` → `/writer/approved`
|
||||
|
||||
---
|
||||
|
||||
## Automation
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/automation` | → `/automation/overview` | Redirect |
|
||||
| `/automation/overview` | `Automation/AutomationOverview.tsx` | Run history |
|
||||
| `/automation/runs/:runId` | `Automation/AutomationRunDetail.tsx` | Run detail |
|
||||
| `/automation/run` | `Automation/AutomationPage.tsx` | Run execution |
|
||||
|
||||
**Redirects:**
|
||||
- `/automation/settings` → `/sites/settings?tab=automation`
|
||||
|
||||
---
|
||||
|
||||
## Publisher
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/publisher` | → `/publisher/content-calendar` | Redirect |
|
||||
| `/publisher/content-calendar` | `Publisher/ContentCalendar.tsx` | Content calendar |
|
||||
|
||||
---
|
||||
|
||||
## Linker (Optional Module)
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/linker` | → `/linker/content` | Redirect |
|
||||
| `/linker/content` | `Linker/ContentList.tsx` | Linker content list |
|
||||
|
||||
---
|
||||
|
||||
## Optimizer (Optional Module)
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/optimizer` | → `/optimizer/content` | Redirect |
|
||||
| `/optimizer/content` | `Optimizer/ContentSelector.tsx` | Content selector |
|
||||
| `/optimizer/analyze/:id` | `Optimizer/AnalysisPreview.tsx` | Analysis preview |
|
||||
|
||||
---
|
||||
|
||||
## Thinker (Admin Only)
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/thinker` | → `/thinker/prompts` | Redirect |
|
||||
| `/thinker/prompts` | `Thinker/Prompts.tsx` | Prompt management |
|
||||
| `/thinker/author-profiles` | `Thinker/AuthorProfiles.tsx` | Author profiles |
|
||||
| `/thinker/profile` | `Thinker/Profile.tsx` | Profile settings |
|
||||
| `/thinker/strategies` | `Thinker/Strategies.tsx` | Strategies |
|
||||
| `/thinker/image-testing` | `Thinker/ImageTesting.tsx` | Image testing |
|
||||
|
||||
---
|
||||
|
||||
## Billing
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/billing` | → `/billing/overview` | Redirect |
|
||||
| `/billing/overview` | `Settings/CreditsAndBilling.tsx` | Billing overview |
|
||||
| `/billing/credits` | `Billing/Credits.tsx` | Credits listing |
|
||||
| `/billing/transactions` | `Billing/Transactions.tsx` | Transactions |
|
||||
| `/billing/usage` | `Billing/Usage.tsx` | Usage |
|
||||
|
||||
---
|
||||
|
||||
## Account
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/account/notifications` | `account/NotificationsPage.tsx` | Notifications |
|
||||
| `/account/settings` | `account/AccountSettingsPage.tsx` | Account settings |
|
||||
| `/account/settings/profile` | `account/AccountSettingsPage.tsx` | Profile tab |
|
||||
| `/account/settings/team` | `account/AccountSettingsPage.tsx` | Team tab |
|
||||
| `/account/plans` | `account/PlansAndBillingPage.tsx` | Plans & billing |
|
||||
| `/account/plans/upgrade` | `account/PlansAndBillingPage.tsx` | Upgrade tab |
|
||||
| `/account/plans/history` | `account/PlansAndBillingPage.tsx` | History tab |
|
||||
| `/account/usage` | `account/UsageDashboardPage.tsx` | Usage dashboard |
|
||||
| `/account/content-settings` | `account/ContentSettingsPage.tsx` | Content settings |
|
||||
| `/account/content-settings/publishing` | `account/ContentSettingsPage.tsx` | Publishing tab |
|
||||
| `/account/content-settings/images` | `account/ContentSettingsPage.tsx` | Images tab |
|
||||
|
||||
**Redirects:**
|
||||
- `/account/team` → `/account/settings/team`
|
||||
- `/account/purchase-credits` → `/account/plans`
|
||||
- `/account/usage/logs` → `/account/usage`
|
||||
- `/account/usage/credits` → `/account/usage`
|
||||
- `/account/usage/insights` → `/account/usage`
|
||||
- `/account/usage/activity` → `/account/usage`
|
||||
|
||||
---
|
||||
|
||||
## Reference Data
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/reference/seed-keywords` | `Reference/SeedKeywords.tsx` | Seed keywords |
|
||||
| `/reference/industries` | `Reference/Industries.tsx` | Industries |
|
||||
|
||||
---
|
||||
|
||||
## Settings (Admin)
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/settings` | `Settings/General.tsx` | General settings |
|
||||
| `/settings/users` | `Settings/Users.tsx` | Users |
|
||||
| `/settings/subscriptions` | `Settings/Subscriptions.tsx` | Subscriptions |
|
||||
| `/settings/system` | `Settings/System.tsx` | System settings |
|
||||
| `/settings/account` | `Settings/Account.tsx` | Account settings |
|
||||
| `/settings/plans` | `Settings/Plans.tsx` | Plans |
|
||||
| `/settings/industries` | `Settings/Industries.tsx` | Industries |
|
||||
| `/settings/integration` | `Settings/Integration.tsx` | Integrations |
|
||||
| `/settings/publishing` | `Settings/Publishing.tsx` | Publishing settings |
|
||||
| `/settings/sites` | `Settings/Sites.tsx` | Sites settings |
|
||||
|
||||
**Redirects:**
|
||||
- `/settings/profile` → `/account/settings`
|
||||
- `/settings/import-export` → `/`
|
||||
|
||||
---
|
||||
|
||||
## Help
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/help` | `Help/Help.tsx` | Help center |
|
||||
|
||||
---
|
||||
|
||||
## Internal Pages (Dev Only)
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `/components` | `Components.tsx` | Component showcase |
|
||||
| `/ui-elements` | `UIElements.tsx` | Design system reference |
|
||||
|
||||
---
|
||||
|
||||
## Fallback
|
||||
|
||||
| Route | File | Description |
|
||||
|-------|------|-------------|
|
||||
| `*` | `OtherPage/NotFound.tsx` | 404 page |
|
||||
|
||||
---
|
||||
|
||||
## Page Files Directory
|
||||
|
||||
```
|
||||
frontend/src/pages/
|
||||
├── account/
|
||||
│ ├── AccountSettingsPage.tsx
|
||||
│ ├── ContentSettingsPage.tsx
|
||||
│ ├── NotificationsPage.tsx
|
||||
│ ├── PlansAndBillingPage.tsx
|
||||
│ ├── PurchaseCreditsPage.tsx
|
||||
│ ├── UsageAnalyticsPage.tsx
|
||||
│ └── UsageDashboardPage.tsx
|
||||
├── AuthPages/
|
||||
│ ├── AuthPageLayout.tsx
|
||||
│ ├── ForgotPassword.tsx
|
||||
│ ├── ResetPassword.tsx
|
||||
│ ├── SignIn.tsx
|
||||
│ ├── SignUp.tsx
|
||||
│ ├── Unsubscribe.tsx
|
||||
│ └── VerifyEmail.tsx
|
||||
├── Automation/
|
||||
│ ├── AutomationOverview.tsx
|
||||
│ ├── AutomationPage.tsx
|
||||
│ ├── AutomationRunDetail.tsx
|
||||
│ └── PipelineSettings.tsx
|
||||
├── Billing/
|
||||
│ ├── Credits.tsx
|
||||
│ ├── Transactions.tsx
|
||||
│ └── Usage.tsx
|
||||
├── Dashboard/
|
||||
│ └── Home.tsx
|
||||
├── Help/
|
||||
│ └── Help.tsx
|
||||
├── legal/
|
||||
│ ├── Privacy.tsx
|
||||
│ └── Terms.tsx
|
||||
├── Linker/
|
||||
│ └── ContentList.tsx
|
||||
├── Optimizer/
|
||||
│ ├── AnalysisPreview.tsx
|
||||
│ └── ContentSelector.tsx
|
||||
├── OtherPage/
|
||||
│ └── NotFound.tsx
|
||||
├── Planner/
|
||||
│ ├── ClusterDetail.tsx
|
||||
│ ├── Clusters.tsx
|
||||
│ ├── Ideas.tsx
|
||||
│ └── Keywords.tsx
|
||||
├── Publisher/
|
||||
│ ├── ContentCalendar.tsx
|
||||
│ └── PublishSettings.tsx
|
||||
├── Reference/
|
||||
│ ├── Industries.tsx
|
||||
│ └── SeedKeywords.tsx
|
||||
├── Settings/
|
||||
│ ├── Account.tsx
|
||||
│ ├── CreditsAndBilling.tsx
|
||||
│ ├── General.tsx
|
||||
│ ├── Industries.tsx
|
||||
│ ├── Integration.tsx
|
||||
│ ├── Plans.tsx
|
||||
│ ├── Publishing.tsx
|
||||
│ ├── Sites.tsx
|
||||
│ ├── Subscriptions.tsx
|
||||
│ ├── System.tsx
|
||||
│ ├── Users.tsx
|
||||
│ └── WordPressIntegrationDebug.tsx
|
||||
├── Setup/
|
||||
│ ├── IndustriesSectorsKeywords.tsx
|
||||
│ └── SetupWizard.tsx
|
||||
├── Sites/
|
||||
│ ├── AIAutomationSettings.tsx
|
||||
│ ├── Content.tsx
|
||||
│ ├── ContentStructure.tsx
|
||||
│ ├── Dashboard.tsx
|
||||
│ ├── DeploymentPanel.tsx
|
||||
│ ├── List.tsx
|
||||
│ ├── PageManager.tsx
|
||||
│ ├── PostEditor.tsx
|
||||
│ ├── PublishingQueue.tsx
|
||||
│ ├── Settings.tsx
|
||||
│ └── SyncDashboard.tsx
|
||||
├── Thinker/
|
||||
│ ├── AuthorProfiles.tsx
|
||||
│ ├── ImageTesting.tsx
|
||||
│ ├── Profile.tsx
|
||||
│ ├── Prompts.tsx
|
||||
│ └── Strategies.tsx
|
||||
├── Writer/
|
||||
│ ├── Approved.tsx
|
||||
│ ├── Content.tsx
|
||||
│ ├── ContentView.tsx
|
||||
│ ├── Images.tsx
|
||||
│ ├── Review.tsx
|
||||
│ └── Tasks.tsx
|
||||
├── Components.tsx
|
||||
├── Payment.tsx
|
||||
└── UIElements.tsx
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
516
v2/Live Docs on Server/igny8-app-docs/30-FRONTEND/STORES.md
Normal file
516
v2/Live Docs on Server/igny8-app-docs/30-FRONTEND/STORES.md
Normal file
@@ -0,0 +1,516 @@
|
||||
# Zustand State Management
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Framework:** Zustand 4 with persist middleware
|
||||
|
||||
---
|
||||
|
||||
## Store Architecture
|
||||
|
||||
All stores in `/frontend/src/store/` use Zustand with TypeScript.
|
||||
|
||||
**Key Patterns:**
|
||||
- `persist` middleware for localStorage persistence
|
||||
- Async actions for API calls
|
||||
- Selectors for derived state
|
||||
|
||||
**Available Stores (11 total):**
|
||||
| Store | File | Purpose |
|
||||
|-------|------|---------|
|
||||
| Auth | `authStore.ts` | Authentication, user session, account |
|
||||
| Site | `siteStore.ts` | Site selection/management |
|
||||
| Sector | `sectorStore.ts` | Sector management within sites |
|
||||
| Planner | `plannerStore.ts` | Planner module state |
|
||||
| Billing | `billingStore.ts` | Credits, billing info |
|
||||
| Notification | `notificationStore.ts` | App notifications |
|
||||
| Settings | `settingsStore.ts` | User preferences |
|
||||
| Module | `moduleStore.ts` | Module enable/disable state |
|
||||
| Column Visibility | `columnVisibilityStore.ts` | Table column preferences |
|
||||
| Page Size | `pageSizeStore.ts` | Table pagination preferences |
|
||||
| Onboarding | `onboardingStore.ts` | Onboarding wizard state |
|
||||
|
||||
---
|
||||
|
||||
## Auth Store (`authStore.ts`)
|
||||
|
||||
**Purpose:** User authentication and session management
|
||||
|
||||
```typescript
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
account: Account | null;
|
||||
isAuthenticated: boolean;
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface AuthActions {
|
||||
login(email: string, password: string): Promise<void>;
|
||||
logout(): void;
|
||||
register(data: RegisterData): Promise<void>;
|
||||
refreshAccessToken(): Promise<void>;
|
||||
fetchUser(): Promise<void>;
|
||||
updateUser(data: UserUpdate): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Persistence:** `accessToken`, `refreshToken` in localStorage
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
const { user, login, logout, isAuthenticated } = useAuthStore();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Site Store (`siteStore.ts`)
|
||||
|
||||
**Purpose:** Site selection and management
|
||||
|
||||
```typescript
|
||||
interface SiteState {
|
||||
sites: Site[];
|
||||
currentSite: Site | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface SiteActions {
|
||||
fetchSites(): Promise<void>;
|
||||
createSite(data: SiteCreate): Promise<Site>;
|
||||
updateSite(id: string, data: SiteUpdate): Promise<Site>;
|
||||
deleteSite(id: string): Promise<void>;
|
||||
setCurrentSite(site: Site): void;
|
||||
}
|
||||
```
|
||||
|
||||
**Persistence:** `currentSite.id` in localStorage
|
||||
|
||||
**Auto-selection:** If no site selected and sites exist, auto-selects first site
|
||||
|
||||
---
|
||||
|
||||
## Sector Store (`sectorStore.ts`)
|
||||
|
||||
**Purpose:** Sector selection and management within sites
|
||||
|
||||
```typescript
|
||||
interface SectorState {
|
||||
sectors: Sector[];
|
||||
currentSector: Sector | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface SectorActions {
|
||||
fetchSectors(siteId: string): Promise<void>;
|
||||
createSector(data: SectorCreate): Promise<Sector>;
|
||||
updateSector(id: string, data: SectorUpdate): Promise<Sector>;
|
||||
deleteSector(id: string): Promise<void>;
|
||||
setCurrentSector(sector: Sector): void;
|
||||
}
|
||||
```
|
||||
|
||||
**Persistence:** `currentSector.id` in localStorage
|
||||
|
||||
**Dependency:** Reloads when `currentSite` changes
|
||||
|
||||
---
|
||||
|
||||
## Module Store (`moduleStore.ts`)
|
||||
|
||||
**Purpose:** Track which modules are enabled/disabled
|
||||
|
||||
```typescript
|
||||
interface ModuleState {
|
||||
modules: ModuleSettings;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface ModuleSettings {
|
||||
planner_enabled: boolean;
|
||||
writer_enabled: boolean;
|
||||
linker_enabled: boolean;
|
||||
optimizer_enabled: boolean;
|
||||
automation_enabled: boolean;
|
||||
integration_enabled: boolean;
|
||||
publisher_enabled: boolean;
|
||||
}
|
||||
|
||||
interface ModuleActions {
|
||||
fetchModules(): Promise<void>;
|
||||
updateModules(settings: Partial<ModuleSettings>): Promise<void>;
|
||||
isModuleEnabled(module: ModuleName): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
const { isModuleEnabled } = useModuleStore();
|
||||
if (isModuleEnabled('planner')) { /* show planner */ }
|
||||
```
|
||||
|
||||
**Currently used for:** Sidebar visibility only
|
||||
|
||||
---
|
||||
|
||||
## Billing Store (`billingStore.ts`)
|
||||
|
||||
**Purpose:** Credit balance and usage tracking
|
||||
|
||||
```typescript
|
||||
interface BillingState {
|
||||
balance: CreditBalance | null;
|
||||
usage: CreditUsage[];
|
||||
limits: PlanLimits | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface CreditBalance {
|
||||
ideaCredits: number;
|
||||
contentCredits: number;
|
||||
imageCredits: number;
|
||||
optimizationCredits: number;
|
||||
}
|
||||
|
||||
interface BillingActions {
|
||||
fetchBalance(): Promise<void>;
|
||||
fetchUsage(period?: string): Promise<void>;
|
||||
fetchLimits(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Refresh triggers:**
|
||||
- After content generation
|
||||
- After image generation
|
||||
- After optimization (when implemented)
|
||||
|
||||
---
|
||||
|
||||
## Planner Store (`plannerStore.ts`)
|
||||
|
||||
**Purpose:** Keywords, clusters, and content ideas state
|
||||
|
||||
```typescript
|
||||
interface PlannerState {
|
||||
keywords: Keyword[];
|
||||
clusters: Cluster[];
|
||||
ideas: ContentIdea[];
|
||||
selectedKeywords: string[];
|
||||
filters: KeywordFilters;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface PlannerActions {
|
||||
fetchKeywords(siteId: string, sectorId?: string): Promise<void>;
|
||||
createKeyword(data: KeywordCreate): Promise<Keyword>;
|
||||
bulkDeleteKeywords(ids: string[]): Promise<void>;
|
||||
autoCluster(keywordIds: string[]): Promise<void>;
|
||||
generateIdeas(clusterId: string): Promise<void>;
|
||||
setFilters(filters: Partial<KeywordFilters>): void;
|
||||
selectKeywords(ids: string[]): void;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writer Store (`writerStore.ts`)
|
||||
|
||||
**Purpose:** Tasks and content management
|
||||
|
||||
```typescript
|
||||
interface WriterState {
|
||||
tasks: Task[];
|
||||
content: Content[];
|
||||
currentContent: Content | null;
|
||||
filters: TaskFilters;
|
||||
isLoading: boolean;
|
||||
isGenerating: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface WriterActions {
|
||||
fetchTasks(siteId: string, sectorId?: string): Promise<void>;
|
||||
createTask(data: TaskCreate): Promise<Task>;
|
||||
generateContent(taskId: string): Promise<Content>;
|
||||
fetchContent(contentId: string): Promise<Content>;
|
||||
updateContent(id: string, data: ContentUpdate): Promise<Content>;
|
||||
generateImages(contentId: string): Promise<void>;
|
||||
publishToWordPress(contentId: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Generation state:** `isGenerating` tracks active AI operations
|
||||
|
||||
---
|
||||
|
||||
## Automation Store (`automationStore.ts`)
|
||||
|
||||
**Purpose:** Automation pipeline state and control
|
||||
|
||||
```typescript
|
||||
interface AutomationState {
|
||||
config: AutomationConfig | null;
|
||||
currentRun: AutomationRun | null;
|
||||
pipeline: PipelineOverview | null;
|
||||
history: AutomationRun[];
|
||||
logs: AutomationLog[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface AutomationActions {
|
||||
fetchConfig(siteId: string): Promise<void>;
|
||||
updateConfig(data: ConfigUpdate): Promise<void>;
|
||||
startRun(siteId: string): Promise<void>;
|
||||
pauseRun(runId: string): Promise<void>;
|
||||
resumeRun(runId: string): Promise<void>;
|
||||
cancelRun(runId: string): Promise<void>;
|
||||
fetchPipeline(siteId: string): Promise<void>;
|
||||
fetchLogs(runId: string): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Polling:** Active runs trigger status polling every 5 seconds
|
||||
|
||||
---
|
||||
|
||||
## Integration Store (`integrationStore.ts`)
|
||||
|
||||
**Purpose:** WordPress integration management
|
||||
|
||||
```typescript
|
||||
interface IntegrationState {
|
||||
integrations: SiteIntegration[];
|
||||
currentIntegration: SiteIntegration | null;
|
||||
syncStatus: SyncStatus | null;
|
||||
isLoading: boolean;
|
||||
isSyncing: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface IntegrationActions {
|
||||
fetchIntegrations(siteId: string): Promise<void>;
|
||||
createIntegration(data: IntegrationCreate): Promise<SiteIntegration>;
|
||||
testConnection(id: string): Promise<TestResult>;
|
||||
triggerSync(id: string): Promise<void>;
|
||||
fetchSyncStatus(id: string): Promise<SyncStatus>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI Store (`uiStore.ts`)
|
||||
|
||||
**Purpose:** UI state (sidebar, modals, notifications)
|
||||
|
||||
```typescript
|
||||
interface UIState {
|
||||
sidebarOpen: boolean;
|
||||
sidebarCollapsed: boolean;
|
||||
theme: 'light' | 'dark' | 'system';
|
||||
notifications: Notification[];
|
||||
}
|
||||
|
||||
interface UIActions {
|
||||
toggleSidebar(): void;
|
||||
collapseSidebar(): void;
|
||||
setTheme(theme: Theme): void;
|
||||
addNotification(notification: Notification): void;
|
||||
removeNotification(id: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
**Persistence:** `theme`, `sidebarCollapsed` in localStorage
|
||||
|
||||
---
|
||||
|
||||
## Notification Store (`notificationStore.ts`) - v1.2.0
|
||||
|
||||
**Purpose:** In-app notifications for AI task completions and system events
|
||||
|
||||
```typescript
|
||||
type NotificationType = 'success' | 'error' | 'warning' | 'info';
|
||||
type NotificationCategory = 'ai_task' | 'system' | 'info';
|
||||
|
||||
interface Notification {
|
||||
id: string;
|
||||
apiId?: number; // Server ID for synced notifications
|
||||
type: NotificationType;
|
||||
category: NotificationCategory;
|
||||
title: string;
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
read: boolean;
|
||||
actionLabel?: string;
|
||||
actionHref?: string;
|
||||
metadata?: {
|
||||
taskId?: string;
|
||||
functionName?: string;
|
||||
count?: number;
|
||||
credits?: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface NotificationStore {
|
||||
notifications: Notification[];
|
||||
unreadCount: number;
|
||||
isLoading: boolean;
|
||||
lastFetched: Date | null;
|
||||
|
||||
// Actions
|
||||
addNotification(notification: Omit<Notification, 'id' | 'timestamp' | 'read'>): void;
|
||||
markAsRead(id: string): void;
|
||||
markAllAsRead(): void;
|
||||
removeNotification(id: string): void;
|
||||
clearAll(): void;
|
||||
fetchFromAPI(): Promise<void>;
|
||||
syncWithAPI(): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- In-memory queue for optimistic UI updates
|
||||
- API sync for persistent notifications
|
||||
- Auto-dismissal with configurable timeout
|
||||
- Read/unread state tracking
|
||||
- Category-based filtering (ai_task, system, info)
|
||||
|
||||
**API Integration:** Uses `notifications.api.ts` service:
|
||||
- `fetchNotifications()` - List with pagination
|
||||
- `fetchUnreadCount()` - Unread count
|
||||
- `markNotificationRead()` - Mark single as read
|
||||
- `markAllNotificationsRead()` - Mark all as read
|
||||
- `deleteNotification()` - Delete notification
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
const { notifications, unreadCount, addNotification, markAsRead } = useNotificationStore();
|
||||
|
||||
// Add AI task completion notification
|
||||
addNotification({
|
||||
type: 'success',
|
||||
category: 'ai_task',
|
||||
title: 'Content Generated',
|
||||
message: 'Generated 5 articles successfully',
|
||||
metadata: { count: 5, credits: 250 }
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Store Dependencies
|
||||
|
||||
```
|
||||
authStore
|
||||
│
|
||||
└── siteStore (loads after auth)
|
||||
│
|
||||
└── sectorStore (loads when site changes)
|
||||
│
|
||||
├── plannerStore (scoped to site/sector)
|
||||
├── writerStore (scoped to site/sector)
|
||||
├── automationStore (scoped to site)
|
||||
└── integrationStore (scoped to site)
|
||||
|
||||
notificationStore (global, polls/syncs independently)
|
||||
|
||||
moduleStore (global, loads once per account)
|
||||
billingStore (global, loads once per account)
|
||||
onboardingStore (global, syncs with UserSettings backend)
|
||||
uiStore (local only, no API)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Onboarding Store (`onboardingStore.ts`) (v1.3.2)
|
||||
|
||||
**Purpose:** Manages onboarding wizard and guide screen state
|
||||
|
||||
```typescript
|
||||
interface OnboardingState {
|
||||
isGuideDismissed: boolean;
|
||||
isGuideVisible: boolean;
|
||||
isLoading: boolean;
|
||||
lastSyncedAt: Date | null;
|
||||
}
|
||||
|
||||
interface OnboardingActions {
|
||||
dismissGuide(): Promise<void>;
|
||||
showGuide(): void;
|
||||
toggleGuide(): void;
|
||||
loadFromBackend(): Promise<void>;
|
||||
syncToBackend(dismissed: boolean): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
**Persistence:**
|
||||
- Local: `onboarding-storage` in localStorage
|
||||
- Backend: `UserSettings` with key `workflow_guide_dismissed`
|
||||
|
||||
**Features:**
|
||||
- Cross-device sync via backend UserSettings
|
||||
- Rate-limited sync (max every 5 minutes)
|
||||
- Graceful fallback if backend unavailable
|
||||
|
||||
**Usage:**
|
||||
```typescript
|
||||
const { isGuideVisible, dismissGuide, showGuide } = useOnboardingStore();
|
||||
|
||||
// Show onboarding wizard
|
||||
if (isGuideVisible) {
|
||||
return <OnboardingWizard onDismiss={dismissGuide} />;
|
||||
}
|
||||
|
||||
// Re-show guide button
|
||||
<Button onClick={showGuide}>Show Guide</Button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Store Files Location
|
||||
|
||||
```
|
||||
frontend/src/store/
|
||||
├── authStore.ts
|
||||
├── siteStore.ts
|
||||
├── sectorStore.ts
|
||||
├── moduleStore.ts
|
||||
├── billingStore.ts
|
||||
├── notificationStore.ts # v1.2.0
|
||||
├── onboardingStore.ts
|
||||
├── pageSizeStore.ts
|
||||
├── plannerStore.ts
|
||||
├── writerStore.ts
|
||||
├── automationStore.ts
|
||||
├── integrationStore.ts
|
||||
├── uiStore.ts
|
||||
└── index.ts (re-exports)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always scope to site/sector** when fetching module data
|
||||
2. **Check module enabled** before showing features
|
||||
3. **Handle loading states** in UI components
|
||||
4. **Clear store on logout** to prevent data leaks
|
||||
5. **Use selectors** for derived/filtered data
|
||||
|
||||
---
|
||||
|
||||
## Planned Changes
|
||||
|
||||
| Item | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| Add `linkerStore` | State for internal linking results | Medium |
|
||||
| Add `optimizerStore` | State for optimization results | Medium |
|
||||
| Better error typing | Typed error codes per store | Low |
|
||||
| DevTools integration | Zustand devtools middleware | Low |
|
||||
@@ -0,0 +1,403 @@
|
||||
# Automation & Publishing Scheduling System
|
||||
|
||||
> **Last Updated:** January 18, 2026
|
||||
> **System:** IGNY8 Celery Task Scheduling
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
IGNY8 uses **Celery Beat** to schedule two distinct but related systems:
|
||||
|
||||
1. **Automation Pipeline** - Processes content through 7 stages (Keywords → Published)
|
||||
2. **Publishing Scheduler** - Schedules and publishes approved content to WordPress
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ CELERY BEAT SCHEDULER │
|
||||
│ (Persistent Schedule Store) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────────┼─────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Every Hour │ │ Every Hour │ │ Every 5 min │
|
||||
│ at :05 │ │ at :00 │ │ │
|
||||
└───────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ check_ │ │schedule_approved│ │process_scheduled│
|
||||
│ scheduled_ │ │ _content │ │ _publications │
|
||||
│ automations │ │ │ │ │
|
||||
└───────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Automation │ │ Content gets │ │ Publishes to │
|
||||
│ Pipeline │ │ scheduled_ │ │ WordPress via │
|
||||
│ (7 stages) │ │ publish_at set │ │ API │
|
||||
└───────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Automation Scheduling
|
||||
|
||||
### Celery Task Configuration
|
||||
|
||||
| Task Name | Schedule | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `automation.check_scheduled_automations` | Every hour at `:05` | Check if any automation configs should run |
|
||||
| `automation.check_test_triggers` | Every 5 minutes | Check for admin test triggers (exits early if none) |
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ AUTOMATION SCHEDULING FLOW │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Celery Beat AutomationConfig Result
|
||||
│ │ │
|
||||
│ (Every hour at :05) │ │
|
||||
▼ │ │
|
||||
┌─────────────┐ │ │
|
||||
│ 02:05 check │ ─── Hour ────► scheduled_hour == 2? │
|
||||
│ 03:05 check │ ─── Hour ────► scheduled_hour == 3? │
|
||||
│ 04:05 check │ ─── Hour ────► scheduled_hour == 4? │
|
||||
│ ... │ │ │
|
||||
└─────────────┘ │ │
|
||||
▼ │
|
||||
┌────────────────┐ │
|
||||
│ Match Found? │ │
|
||||
└────────────────┘ │
|
||||
│ │ │
|
||||
Yes No │
|
||||
│ │ │
|
||||
▼ └─► Skip │
|
||||
┌────────────────┐ │
|
||||
│ Check Blocks: │ │
|
||||
│ • 23hr block │ │
|
||||
│ • Already │ │
|
||||
│ running? │ │
|
||||
└────────────────┘ │
|
||||
│ │
|
||||
Pass │
|
||||
│ │
|
||||
▼ │
|
||||
┌────────────────┐ │
|
||||
│ Start Run │ ────────────────────────► Run Started
|
||||
│ • Set last_run │
|
||||
│ • Queue task │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
### Hourly Matching Logic
|
||||
|
||||
The scheduler runs at `:05` of every hour and checks if `scheduled_hour == current_hour`:
|
||||
|
||||
| Celery Runs At | Checks Hour | Example Config |
|
||||
|----------------|-------------|----------------|
|
||||
| `02:05` | `2` | `scheduled_time = 02:00` → Matches |
|
||||
| `03:05` | `3` | `scheduled_time = 03:00` → Matches |
|
||||
| `14:05` | `14` | `scheduled_time = 14:00` (2 PM) → Matches |
|
||||
|
||||
**Note:** Users select hour only (1-12 with AM/PM in UI), stored as `HH:00` format. The `:05` offset ensures the check happens after the configured hour begins.
|
||||
|
||||
### Test Mode (Admin Feature)
|
||||
|
||||
Admins can trigger automations without waiting for the hourly schedule:
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ TEST TRIGGER FLOW │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Admin Action Celery (Every Minute) Result
|
||||
│ │ │
|
||||
▼ │ │
|
||||
┌─────────────────┐ │ │
|
||||
│ Enable test mode│ │ │
|
||||
│ Set trigger time│ │ │
|
||||
│ (or "now") │ │ │
|
||||
└─────────────────┘ │ │
|
||||
│ │ │
|
||||
└───────────────────────────────────▼ │
|
||||
┌─────────────────┐ │
|
||||
│check_test_ │ │
|
||||
│triggers task │ │
|
||||
└─────────────────┘ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│test_trigger_at │ │
|
||||
│<= now? │ │
|
||||
└─────────────────┘ │
|
||||
│ │ │
|
||||
Yes No │
|
||||
│ └─► Wait │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│Start test run │──────────────► Run Started
|
||||
│trigger_type= │ (type='test')
|
||||
│'test' │
|
||||
│Clear trigger_at │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
**Test mode fields:**
|
||||
- `test_mode_enabled` - Enable test functionality for this config
|
||||
- `test_trigger_at` - When to trigger (set to `now` for immediate)
|
||||
|
||||
**Admin actions:**
|
||||
- 🧪 **Trigger test run** - Sets both fields and triggers at next minute
|
||||
- 🧹 **Clear test mode** - Disables test mode and clears trigger
|
||||
|
||||
### Frequency Rules
|
||||
|
||||
| Frequency | When It Runs | Additional Condition |
|
||||
|-----------|--------------|----------------------|
|
||||
| `daily` | Every day | `scheduled_hour == current_hour` |
|
||||
| `weekly` | Mondays only | `weekday() == 0` + hour match |
|
||||
| `monthly` | 1st of month only | `day == 1` + hour match |
|
||||
|
||||
### Duplicate Prevention (23-Hour Block)
|
||||
|
||||
After a successful run, the config won't run again for **23 hours**:
|
||||
|
||||
```python
|
||||
if config.last_run_at:
|
||||
time_since_last_run = now - config.last_run_at
|
||||
if time_since_last_run < timedelta(hours=23):
|
||||
# SKIP - already ran recently
|
||||
```
|
||||
|
||||
### Schedule Change Behavior
|
||||
|
||||
When `scheduled_time`, `frequency`, or `is_enabled` changes:
|
||||
- `last_run_at` is reset to `None`
|
||||
- `next_run_at` is recalculated
|
||||
- Automation becomes eligible immediately at the next hourly check
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Current time: 12:30
|
||||
You change scheduled_time from 02:00 → 12:00 (12 PM)
|
||||
Hour 12 was already checked at 12:05
|
||||
→ Automation will run tomorrow at 12:05 (daily) or next valid day (weekly/monthly)
|
||||
```
|
||||
|
||||
**Pro tip:** Use test mode to trigger immediately after schedule changes.
|
||||
|
||||
---
|
||||
|
||||
## 2. Publishing Scheduling
|
||||
|
||||
### Celery Task Configuration
|
||||
|
||||
| Task Name | Schedule | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| `publishing.schedule_approved_content` | Every hour at `:00` | Schedule approved content for future publishing |
|
||||
| `publishing.process_scheduled_publications` | Every 5 min | Publish content where `scheduled_publish_at <= now` |
|
||||
|
||||
### Publishing Flow
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ PUBLISHING SCHEDULING FLOW │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
EVERY HOUR (:00) EVERY 5 MINUTES
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────┐ ┌────────────────────────┐
|
||||
│schedule_approved_content│ │process_scheduled_ │
|
||||
│ │ │publications │
|
||||
└────────────────────────┘ └────────────────────────┘
|
||||
│ │
|
||||
▼ │
|
||||
┌────────────────────────┐ │
|
||||
│ For each Site with │ │
|
||||
│ auto_publish_enabled: │ │
|
||||
│ │ │
|
||||
│ 1. Find approved │ │
|
||||
│ content │ │
|
||||
│ 2. Calculate slots │ │
|
||||
│ based on settings │ │
|
||||
│ 3. Set scheduled_ │ │
|
||||
│ publish_at │ │
|
||||
│ 4. Set site_status= │ │
|
||||
│ 'scheduled' │ │
|
||||
└────────────────────────┘ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ DATABASE │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ Content Table │ │
|
||||
│ │ • status = 'approved' │ │
|
||||
│ │ • site_status = 'scheduled' │ │
|
||||
│ │ • scheduled_publish_at = <datetime> │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ process_scheduled_ │
|
||||
│ publications │
|
||||
│ │
|
||||
│ WHERE: │
|
||||
│ • site_status = │
|
||||
│ 'scheduled' │
|
||||
│ • scheduled_publish_at │
|
||||
│ <= NOW │
|
||||
└────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
│ Publish to WordPress │
|
||||
│ via PublisherService │
|
||||
│ │
|
||||
│ On Success: │
|
||||
│ • site_status = │
|
||||
│ 'published' │
|
||||
│ • Set wp_post_id │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
### Scheduling Modes
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `immediate` | Content scheduled for `now`, picked up within 5 minutes |
|
||||
| `time_slots` | Content scheduled at specific times (e.g., 9am, 2pm, 6pm) |
|
||||
| `stagger` | Content spread evenly across publish hours |
|
||||
|
||||
### Publishing Limits
|
||||
|
||||
| Limit | Description |
|
||||
|-------|-------------|
|
||||
| `daily_publish_limit` | Max posts per day |
|
||||
| `weekly_publish_limit` | Max posts per week |
|
||||
| `monthly_publish_limit` | Max posts per month |
|
||||
| `queue_limit` | Max items to schedule at once (default: 100) |
|
||||
|
||||
---
|
||||
|
||||
## 3. System Relationship
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ COMPLETE CONTENT LIFECYCLE │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
AUTOMATION PIPELINE PUBLISHING PIPELINE
|
||||
(Every 15 min check) (Hourly + Every 5 min)
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│ Stage 1-6 │ │
|
||||
│ Keywords → │ │
|
||||
│ Content Created │ │
|
||||
└─────────────────┘ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│ Stage 7 │ │
|
||||
│ Auto-Approval │ status='approved' │
|
||||
│ (if enabled) │ ─────────────────────────────────► │
|
||||
└─────────────────┘ │
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│schedule_approved_ │
|
||||
│content (hourly) │
|
||||
│ │
|
||||
│ Sets: │
|
||||
│ • scheduled_publish │
|
||||
│ _at │
|
||||
│ • site_status = │
|
||||
│ 'scheduled' │
|
||||
└─────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│process_scheduled_ │
|
||||
│publications (5 min) │
|
||||
│ │
|
||||
│ Publishes to WP │
|
||||
│ • site_status = │
|
||||
│ 'published' │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Celery Beat Schedule Summary
|
||||
|
||||
| Task | Schedule | Crontab |
|
||||
|------|----------|---------|
|
||||
| `automation.check_scheduled_automations` | Every 15 min | `minute='0,15,30,45'` |
|
||||
| `publishing.schedule_approved_content` | Hourly | `minute=0` |
|
||||
| `publishing.process_scheduled_publications` | Every 5 min | `minute='*/5'` |
|
||||
|
||||
---
|
||||
|
||||
## 5. Key Configuration Tables
|
||||
|
||||
### AutomationConfig
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| `is_enabled` | Boolean | Enable/disable scheduling |
|
||||
| `frequency` | Choice | `daily`, `weekly`, `monthly` |
|
||||
| `scheduled_time` | Time | When to run (e.g., `02:00`) |
|
||||
| `last_run_at` | DateTime | Last successful run (23hr block) |
|
||||
| `next_run_at` | DateTime | Calculated next run time |
|
||||
|
||||
### PublishingSettings
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| `auto_publish_enabled` | Boolean | Enable auto-scheduling |
|
||||
| `scheduling_mode` | Choice | `immediate`, `time_slots`, `stagger` |
|
||||
| `publish_days` | Array | `['mon', 'tue', ...]` |
|
||||
| `publish_time_slots` | Array | `['09:00', '14:00', '18:00']` |
|
||||
| `daily_publish_limit` | Integer | Max posts/day |
|
||||
|
||||
---
|
||||
|
||||
## 6. Troubleshooting
|
||||
|
||||
### Automation Didn't Run
|
||||
|
||||
| Check | Solution |
|
||||
|-------|----------|
|
||||
| `is_enabled = False` | Enable the automation |
|
||||
| Time not in current window | Wait for next window or change time |
|
||||
| `last_run_at` within 23hrs | Wait for 23hr block to expire |
|
||||
| Another run in progress | Wait for current run to complete |
|
||||
| Config updated after window | Schedule moved to next occurrence |
|
||||
|
||||
### Content Not Publishing
|
||||
|
||||
| Check | Solution |
|
||||
|-------|----------|
|
||||
| `auto_publish_enabled = False` | Enable in PublishingSettings |
|
||||
| Content `status != 'approved'` | Approve content first |
|
||||
| No `wp_api_key` on Site | Configure WordPress integration |
|
||||
| Daily/weekly limit reached | Wait for limit reset or increase |
|
||||
|
||||
---
|
||||
|
||||
## 7. Logs
|
||||
|
||||
Monitor these logs for scheduling issues:
|
||||
|
||||
```bash
|
||||
# Automation scheduling
|
||||
docker logs igny8_celery_worker 2>&1 | grep "AutomationTask"
|
||||
|
||||
# Publishing scheduling
|
||||
docker logs igny8_celery_worker 2>&1 | grep "schedule_approved_content\|process_scheduled"
|
||||
```
|
||||
@@ -0,0 +1,338 @@
|
||||
# Content Pipeline Workflow
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The IGNY8 content pipeline transforms raw keywords into published WordPress articles through a multi-stage workflow. This can run manually (step-by-step) or automatically via the Automation module.
|
||||
|
||||
---
|
||||
|
||||
## Pipeline Stages
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ CONTENT PIPELINE │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ KEYWORDS │───►│ CLUSTERS │───►│ IDEAS │───►│ TASKS │ │
|
||||
│ │ Stage 1 │ │ Stage 2 │ │ Stage 3 │ │ Stage 4 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ CONTENT │───►│ IMAGES │───►│ REVIEW │───►│ PUBLISH │ │
|
||||
│ │ Stage 5 │ │ Stage 6 │ │ Stage 7 │ │ Stage 8 │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stage 1: Keywords
|
||||
|
||||
**Module:** Planner
|
||||
**Status Values:** `new`, `mapped`
|
||||
|
||||
### Input
|
||||
- Seed keywords (manually added or from SEO tools)
|
||||
- Optional: search volume, difficulty, CPC data
|
||||
|
||||
### Process
|
||||
1. User adds keywords via UI or bulk import
|
||||
2. Keywords validated and deduplicated
|
||||
3. Assigned to site + sector
|
||||
|
||||
### Output
|
||||
- Keyword records in `Keyword` model
|
||||
- Status: `pending`
|
||||
|
||||
### Credit Usage
|
||||
- None (free operation)
|
||||
|
||||
---
|
||||
|
||||
## Stage 2: Clustering
|
||||
|
||||
**Module:** Planner
|
||||
**AI Function:** `AutoClusterKeywords`
|
||||
|
||||
### Input
|
||||
- Selected pending keywords (2-100)
|
||||
|
||||
### Process
|
||||
1. AI analyzes semantic relationships
|
||||
2. Groups keywords by topic/intent
|
||||
3. Creates cluster with name + description
|
||||
|
||||
### Output
|
||||
- `Cluster` records created
|
||||
- Keywords linked to clusters
|
||||
- Keyword status → `clustered`
|
||||
|
||||
### Credit Usage
|
||||
- Credits deducted based on AI tokens used (see AIModelConfig)
|
||||
|
||||
---
|
||||
|
||||
## Stage 3: Ideas
|
||||
|
||||
**Module:** Planner
|
||||
**AI Function:** `GenerateContentIdeas`
|
||||
|
||||
### Input
|
||||
- Cluster with 2+ keywords
|
||||
|
||||
### Process
|
||||
1. AI generates content idea titles
|
||||
2. Creates brief description for each
|
||||
3. Suggests primary + secondary keywords
|
||||
|
||||
### Output
|
||||
- `ContentIdea` records created
|
||||
- Status: `pending`
|
||||
|
||||
### Credit Usage
|
||||
- Credits deducted based on AI tokens used (see AIModelConfig)
|
||||
|
||||
---
|
||||
|
||||
## Stage 4: Tasks
|
||||
|
||||
**Module:** Writer
|
||||
**Status Values:** `pending`, `in_progress`, `completed`, `cancelled`
|
||||
|
||||
### Input
|
||||
- Selected content ideas
|
||||
|
||||
### Process
|
||||
1. Ideas converted to tasks
|
||||
2. Task gets content brief + keywords
|
||||
3. Optional: set due date, assign user
|
||||
|
||||
### Output
|
||||
- `Task` records created
|
||||
- Status: `pending`
|
||||
- Ideas status → `used`
|
||||
|
||||
### Credit Usage
|
||||
- None (free operation)
|
||||
|
||||
---
|
||||
|
||||
## Stage 5: Content Generation
|
||||
|
||||
**Module:** Writer
|
||||
**AI Function:** `GenerateContent`
|
||||
|
||||
### Input
|
||||
- Task with title + keywords + brief
|
||||
|
||||
### Process
|
||||
1. AI generates full article content
|
||||
2. Creates structured HTML output
|
||||
3. Adds meta title + description
|
||||
|
||||
### Output
|
||||
- `Content` record created
|
||||
- Full HTML body
|
||||
- SEO metadata
|
||||
- Task status → `completed`
|
||||
|
||||
### Credit Usage
|
||||
- Credits deducted based on AI tokens used (see AIModelConfig)
|
||||
|
||||
---
|
||||
|
||||
## Stage 6: Image Generation
|
||||
|
||||
**Module:** Writer
|
||||
**AI Function:** `GenerateImages`
|
||||
|
||||
### Input
|
||||
- Content record
|
||||
- Number of images (default: 1-3)
|
||||
|
||||
### Process
|
||||
1. AI analyzes content for image prompts
|
||||
2. Generates images via DALL-E/Runware
|
||||
3. Creates thumbnail + full versions
|
||||
|
||||
### Output
|
||||
- `ContentImage` records created
|
||||
- Image URLs + alt text
|
||||
- Featured image assigned
|
||||
|
||||
### Credit Usage
|
||||
- Credits deducted per image (see AIModelConfig.credits_per_image)
|
||||
|
||||
---
|
||||
|
||||
## Stage 7: Review
|
||||
|
||||
**Module:** Writer
|
||||
**Status Values:** `draft`, `review`, `approved`, `published`
|
||||
|
||||
### Input
|
||||
- Generated content + images
|
||||
|
||||
### Process
|
||||
1. Content displayed in rich editor
|
||||
2. User reviews + edits if needed
|
||||
3. User approves for publishing
|
||||
|
||||
### Output
|
||||
- Content status → `approved`
|
||||
- Any manual edits saved
|
||||
|
||||
### Credit Usage
|
||||
- None (free operation)
|
||||
- Regeneration deducts credits based on AI model used
|
||||
|
||||
---
|
||||
|
||||
## Stage 8: Publishing
|
||||
|
||||
**Module:** Publisher
|
||||
**Integration:** WordPress REST API
|
||||
|
||||
### Input
|
||||
- Approved content
|
||||
- WordPress integration credentials
|
||||
|
||||
### Process
|
||||
1. Content formatted for WordPress
|
||||
2. Images uploaded to WP media
|
||||
3. Post created with categories/tags
|
||||
4. Status set to draft/published
|
||||
|
||||
### Output
|
||||
- `PublishingRecord` created
|
||||
- WordPress post ID stored
|
||||
- Content status → `published`
|
||||
|
||||
### Credit Usage
|
||||
- None (free operation)
|
||||
|
||||
---
|
||||
|
||||
## Automation Mode
|
||||
|
||||
When running via Automation module:
|
||||
|
||||
1. **Configuration** - Set limits per stage
|
||||
2. **Execution** - Pipeline runs automatically
|
||||
3. **Pacing** - Configurable delays between operations
|
||||
4. **Monitoring** - Real-time status updates
|
||||
|
||||
### Automation Config Options
|
||||
|
||||
```
|
||||
Stage Limits:
|
||||
- clustering_limit: Max keywords to cluster
|
||||
- ideas_limit: Max ideas to generate
|
||||
- content_limit: Max content to generate
|
||||
- image_limit: Max images to generate
|
||||
- publish_limit: Max content to publish
|
||||
|
||||
Timing:
|
||||
- delay_between_operations: Seconds between API calls
|
||||
- max_runtime: Maximum run duration
|
||||
|
||||
Behavior:
|
||||
- auto_approve: Skip review stage
|
||||
- auto_publish: Publish immediately
|
||||
- stop_on_error: Halt pipeline on failure
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Diagram
|
||||
|
||||
```
|
||||
Seed Keywords
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Keyword │ status: pending
|
||||
│ Model │ belongs_to: site, sector
|
||||
└────────┬────────┘
|
||||
│ AI: AutoCluster
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Cluster │ keywords: [...]
|
||||
│ Model │ belongs_to: site, sector
|
||||
└────────┬────────┘
|
||||
│ AI: GenerateIdeas
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ ContentIdea │ primary_keyword, secondaries
|
||||
│ Model │ cluster_id, status
|
||||
└────────┬────────┘
|
||||
│ Convert to Task
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Task │ idea_id, brief
|
||||
│ Model │ assigned_to, status
|
||||
└────────┬────────┘
|
||||
│ AI: GenerateContent
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Content │ task_id, body, meta
|
||||
│ Model │ status: draft
|
||||
└────────┬────────┘
|
||||
│ AI: GenerateImages
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ ContentImage │ content_id, url
|
||||
│ Model │ alt_text, is_featured
|
||||
└────────┬────────┘
|
||||
│ User Review
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Content │ status: approved
|
||||
│ (updated) │
|
||||
└────────┬────────┘
|
||||
│ WordPress API
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│PublishingRecord │ content_id, wp_post_id
|
||||
│ Model │ status, published_at
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Stage | Common Errors | Recovery |
|
||||
|-------|---------------|----------|
|
||||
| Clustering | API timeout | Retry with smaller batch |
|
||||
| Ideas | API rate limit | Wait and retry |
|
||||
| Content | Insufficient credits | Add credits, retry |
|
||||
| Images | Image API failure | Skip images, continue |
|
||||
| Publish | WordPress auth fail | Reauth integration |
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Pipeline Stats (Dashboard)
|
||||
|
||||
- Keywords pending clustering
|
||||
- Ideas pending task creation
|
||||
- Tasks pending generation
|
||||
- Content pending review
|
||||
- Content pending publish
|
||||
|
||||
### Automation Logs
|
||||
|
||||
- Run ID + timestamps
|
||||
- Stage + item processed
|
||||
- Success/failure status
|
||||
- Credit deductions
|
||||
- Error messages
|
||||
@@ -0,0 +1,522 @@
|
||||
# Content Publishing Workflow Guide
|
||||
|
||||
**Last Updated**: January 20, 2026
|
||||
**Version**: 1.8.4
|
||||
**Status**: Production
|
||||
**Audience**: Content Editors, Publishers, Site Administrators
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers the complete content publishing workflow, from content creation through multi-platform publishing (WordPress, Shopify, Custom Sites). The system supports both immediate publishing and scheduled publishing with progress tracking and error recovery.
|
||||
|
||||
---
|
||||
|
||||
## Publishing Workflow States
|
||||
|
||||
### Content Status Flow
|
||||
|
||||
```
|
||||
Draft → Review → Approved → Published
|
||||
```
|
||||
|
||||
- **Draft**: Content is being written/edited
|
||||
- **Review**: Content submitted for review by editors
|
||||
- **Approved**: Content approved and ready for publishing
|
||||
- **Published**: Content successfully published to site
|
||||
|
||||
### Site Publishing Status
|
||||
|
||||
- **not_published**: Content has never been published
|
||||
- **scheduled**: Content scheduled for future publishing
|
||||
- **publishing**: Currently being published (in-progress)
|
||||
- **published**: Successfully published to site
|
||||
- **failed**: Publishing attempt failed (needs retry/reschedule)
|
||||
|
||||
---
|
||||
|
||||
## 1. Publishing from Approved Page
|
||||
|
||||
### Single Content Publishing
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to **Writer → Approved**
|
||||
2. Find the content you want to publish
|
||||
3. Click the **3-dot menu** on the content row
|
||||
4. Select **"Publish Now"**
|
||||
5. Publishing progress modal appears with real-time status:
|
||||
- 📄 Preparing content (0-25%)
|
||||
- 🚀 Uploading to site (25-50%)
|
||||
- ⚙️ Processing response (50-75%)
|
||||
- ✓ Finalizing (75-100%)
|
||||
6. On success:
|
||||
- Green checkmark displayed
|
||||
- "View on [Site Name]" button available
|
||||
- Content marked as published
|
||||
7. On failure:
|
||||
- Error message displayed
|
||||
- **Retry** button available
|
||||
- **Close** button to exit
|
||||
|
||||
**Features:**
|
||||
- Real-time progress tracking
|
||||
- Platform-agnostic (works with WordPress, Shopify, Custom sites)
|
||||
- Automatic error recovery
|
||||
- Direct link to published content
|
||||
- No limit on single item publishing
|
||||
|
||||
### Bulk Publishing (Max 5 Items)
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to **Writer → Approved**
|
||||
2. Select 2-5 content items using checkboxes
|
||||
3. Click **"Publish to Site"** button at top
|
||||
4. Bulk publishing modal shows queue with individual progress bars
|
||||
5. Items process sequentially (one at a time)
|
||||
6. Each item shows:
|
||||
- Progress bar (0-100%)
|
||||
- Current status (Preparing/Uploading/Processing/Finalizing)
|
||||
- Success: Green checkmark + published URL
|
||||
- Failure: Red X + error message + Retry button
|
||||
7. Summary at bottom: "X completed, Y failed, Z pending"
|
||||
8. Cannot close modal until all items complete
|
||||
|
||||
**Publishing Limit:**
|
||||
- **Direct bulk publish**: Maximum 5 items at once
|
||||
- **Reason**: Prevents server overload and API rate limiting
|
||||
- **For more items**: Use "Schedule Selected" instead (no limit)
|
||||
|
||||
**If you select 6+ items:**
|
||||
```
|
||||
⚠️ Publishing Limit Exceeded Modal appears:
|
||||
- Shows you selected X items (over 5 limit)
|
||||
- Options:
|
||||
1. Deselect items to publish ≤5
|
||||
2. Use "Schedule Selected" instead (no limit)
|
||||
- Tip: Scheduling is better for large batches
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Scheduling Content
|
||||
|
||||
### Manual Scheduling (Single Item)
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to **Writer → Approved**
|
||||
2. Click **3-dot menu** on content row
|
||||
3. Select **"Schedule"**
|
||||
4. Schedule Content Modal appears:
|
||||
- **Schedule Date**: Pick date from calendar
|
||||
- **Schedule Time**: Set time (HH:MM AM/PM)
|
||||
- **Preview**: Shows "January 15, 2025 at 9:00 AM"
|
||||
5. Click **"Schedule"** to confirm
|
||||
6. Content appears in **Publisher → Content Calendar**
|
||||
7. Celery task will auto-publish at scheduled time (runs every 5 minutes)
|
||||
|
||||
**Default Time**: 9:00 AM on selected date
|
||||
|
||||
### Bulk Scheduling (Unlimited Items)
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to **Writer → Approved**
|
||||
2. Select any number of items (no limit!)
|
||||
3. Click **"Schedule Selected"** button
|
||||
4. Bulk Schedule Preview Modal shows:
|
||||
- Your site's default schedule settings
|
||||
- Start time (e.g., 9:00 AM)
|
||||
- Stagger interval (e.g., 15 minutes between each)
|
||||
- First and last publish times
|
||||
- List of all items with scheduled times
|
||||
5. Options:
|
||||
- **"Change Settings"**: Opens Site Settings → Publishing tab in new tab
|
||||
- **"Confirm Schedule"**: Applies schedule to all items
|
||||
6. All items scheduled and appear in calendar
|
||||
|
||||
**Site Settings Integration:**
|
||||
- Go to **Sites → [Your Site] → Settings → Publishing** tab
|
||||
- Configure:
|
||||
- **Auto-publish Schedule**: Time of day (e.g., 9:00 AM)
|
||||
- **Stagger Interval**: Minutes between each (e.g., 15 min)
|
||||
- **Timezone**: Your site's timezone
|
||||
- **Max Daily Publishes**: Optional limit per day
|
||||
- These defaults apply to bulk scheduling automatically
|
||||
|
||||
**Example Schedule** (10 items, 9 AM start, 15 min stagger):
|
||||
```
|
||||
1. First Article → Jan 17, 9:00 AM
|
||||
2. Second Article → Jan 17, 9:15 AM
|
||||
3. Third Article → Jan 17, 9:30 AM
|
||||
4. Fourth Article → Jan 17, 9:45 AM
|
||||
5. Fifth Article → Jan 17, 10:00 AM
|
||||
...
|
||||
10. Tenth Article → Jan 17, 11:15 AM
|
||||
```
|
||||
|
||||
### Scheduling from Content Calendar
|
||||
|
||||
**Drag-and-Drop Method:**
|
||||
1. Navigate to **Publisher → Content Calendar**
|
||||
2. Approved content appears in left sidebar
|
||||
3. Drag content item to desired date on calendar
|
||||
4. Item scheduled for 9:00 AM on that date automatically
|
||||
5. Edit time if needed (see "Editing Schedules" below)
|
||||
|
||||
---
|
||||
|
||||
## 3. Managing Scheduled Content
|
||||
|
||||
### Viewing Scheduled Content
|
||||
|
||||
**Content Calendar View:**
|
||||
- Navigate to **Publisher → Content Calendar**
|
||||
- Calendar shows all scheduled items by date
|
||||
- Each item displays:
|
||||
- Title (truncated)
|
||||
- Site name
|
||||
- Scheduled time
|
||||
- Status badge (Scheduled/Publishing/Published/Failed)
|
||||
|
||||
**List View Filter:**
|
||||
- Navigate to **Writer → Approved**
|
||||
- Filter by `site_status = 'scheduled'`
|
||||
- Shows all scheduled items in table format
|
||||
|
||||
### Editing Schedules (Rescheduling)
|
||||
|
||||
**From Calendar View:**
|
||||
1. Find scheduled item on calendar
|
||||
2. Click **Pencil icon** (Edit Schedule) on item
|
||||
3. Schedule Content Modal opens with current date/time pre-filled
|
||||
4. Change date and/or time
|
||||
5. Click **"Reschedule"**
|
||||
6. Item moves to new date/time on calendar
|
||||
|
||||
**From Approved Page:**
|
||||
1. Navigate to **Writer → Approved**
|
||||
2. Click **3-dot menu** on scheduled content
|
||||
3. Select **"Reschedule"**
|
||||
4. Change date/time in modal
|
||||
5. Click **"Reschedule"**
|
||||
|
||||
**From 3-Dot Menu:**
|
||||
- **Reschedule**: Change to new date/time
|
||||
- **Unschedule**: Cancel schedule, keep as approved
|
||||
- **Publish Now**: Skip schedule, publish immediately
|
||||
|
||||
### Unscheduling Content
|
||||
|
||||
**Steps:**
|
||||
1. Click **3-dot menu** on scheduled content
|
||||
2. Select **"Unschedule"**
|
||||
3. Confirmation modal appears:
|
||||
```
|
||||
⚠️ Unschedule Content?
|
||||
Current schedule: Jan 15, 2025 9:00 AM
|
||||
[Cancel] [Unschedule]
|
||||
```
|
||||
4. Click **"Unschedule"** to confirm
|
||||
5. Content returns to `not_published` status
|
||||
6. Stays in Approved page, can be rescheduled or published directly
|
||||
|
||||
---
|
||||
|
||||
## 4. Handling Failed Publications
|
||||
|
||||
### Identifying Failed Content
|
||||
|
||||
**Visual Indicators:**
|
||||
- ❌ Red "Failed" badge
|
||||
- 🕐 Shows original scheduled time
|
||||
- 📄 Error message (truncated)
|
||||
- 🔄 Retry options available
|
||||
|
||||
**Where to Find Failed Items:**
|
||||
1. **Content Calendar**: Separate "Failed Scheduled Publications" section at top
|
||||
2. **Approved Page**: Filter by `site_status = 'failed'`
|
||||
3. **Dashboard**: "Failed Publications" widget (if configured)
|
||||
|
||||
### Failed Item Display (Calendar)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ ❌ Failed Scheduled Publications (2) │
|
||||
├──────────────────────────────────────────────────────┤
|
||||
│ ⚠️ Article Title 1 │
|
||||
│ Site: My WordPress Blog │
|
||||
│ Scheduled: Jan 13, 2025 9:00 AM │
|
||||
│ Error: Publishing API error: Invalid credentials │
|
||||
│ [Reschedule] [Publish Now] │
|
||||
└──────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Viewing Error Details
|
||||
|
||||
**Steps:**
|
||||
1. Click **3-dot menu** on failed content
|
||||
2. Select **"View Error Details"**
|
||||
3. Error Details Modal shows:
|
||||
- Content title and site name
|
||||
- Original scheduled time
|
||||
- Failure timestamp
|
||||
- Full error message
|
||||
- Actions: Fix Site Settings / Publish Now / Reschedule
|
||||
|
||||
### Recovering from Failed Publishing
|
||||
|
||||
**Option 1: Publish Now**
|
||||
- Click **"Publish Now"** button
|
||||
- Opens Publishing Progress Modal
|
||||
- Attempts immediate republish
|
||||
- On success: Content marked as published, error cleared
|
||||
- On failure: Error message updated, stays as failed
|
||||
|
||||
**Option 2: Reschedule**
|
||||
- Click **"Reschedule"** button
|
||||
- Opens Schedule Content Modal
|
||||
- Pick new date/time
|
||||
- Content re-queued with status = 'scheduled'
|
||||
- Celery will retry at new scheduled time
|
||||
|
||||
**Option 3: Fix Site Settings**
|
||||
- Click **"Fix Site Settings"** button
|
||||
- Opens Site Settings → Publishing tab
|
||||
- Check API credentials, domain, platform settings
|
||||
- Return to content and retry
|
||||
|
||||
### Common Error Types
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Invalid credentials | API key wrong/expired | Update API key in Site Settings |
|
||||
| 403 Forbidden | Permissions issue | Check user role in site admin |
|
||||
| Network timeout | Site unreachable | Check domain, retry later |
|
||||
| Missing required field | Content incomplete | Edit content, fill required fields |
|
||||
| Rate limit exceeded | Too many requests | Reschedule with longer intervals |
|
||||
| Platform-specific error | WordPress/Shopify API issue | Check error details, consult platform docs |
|
||||
|
||||
---
|
||||
|
||||
## 5. Publishing to Multiple Platform Types
|
||||
|
||||
### Supported Platforms
|
||||
|
||||
1. **WordPress Sites**
|
||||
- Uses WordPress REST API
|
||||
- Requires Application Password
|
||||
- Supports posts, pages, custom post types
|
||||
- Categories and tags automatically synced
|
||||
|
||||
2. **Shopify Sites**
|
||||
- Uses Shopify Admin API
|
||||
- Requires API key and password
|
||||
- Supports products, blog posts, pages
|
||||
- Collections automatically assigned
|
||||
|
||||
3. **Custom Sites**
|
||||
- Uses custom REST API endpoint
|
||||
- Flexible authentication
|
||||
- Configurable field mapping
|
||||
- Platform-agnostic error handling
|
||||
|
||||
### Platform Configuration
|
||||
|
||||
**Go to**: Sites → [Your Site] → Settings
|
||||
|
||||
**Required Fields:**
|
||||
- **Platform Type**: Select WordPress / Shopify / Custom
|
||||
- **Domain**: Your site URL (e.g., https://mysite.com)
|
||||
- **API Key**: Platform-specific authentication key
|
||||
- **Additional Settings**: Varies by platform
|
||||
|
||||
**WordPress Example:**
|
||||
```
|
||||
Platform Type: WordPress
|
||||
Domain: https://myblog.com
|
||||
API Key: xxxx xxxx xxxx xxxx xxxx xxxx
|
||||
Username: admin
|
||||
```
|
||||
|
||||
**Shopify Example:**
|
||||
```
|
||||
Platform Type: Shopify
|
||||
Domain: https://mystore.myshopify.com
|
||||
API Key: shpat_xxxxxxxxxxxx
|
||||
Password: shppa_xxxxxxxxxxxx
|
||||
```
|
||||
|
||||
### Publishing Behavior by Platform
|
||||
|
||||
All platforms use the same unified publishing interface:
|
||||
- Same progress modal
|
||||
- Same error handling
|
||||
- Same scheduling system
|
||||
- Platform differences handled automatically in backend
|
||||
|
||||
---
|
||||
|
||||
## 6. Review Page Workflow (Approval Only)
|
||||
|
||||
### Important: No Publishing from Review
|
||||
|
||||
**Review Page Purpose**: Content approval workflow only
|
||||
|
||||
**Available Actions:**
|
||||
- ✅ Approve (single or bulk)
|
||||
- ✅ View content
|
||||
- ✅ Edit content
|
||||
- ✅ Delete content
|
||||
- ❌ ~~Publish to Site~~ (removed)
|
||||
|
||||
**Why?**
|
||||
- Ensures content goes through proper approval workflow
|
||||
- Prevents accidental publishing of unreviewed content
|
||||
- Clear separation: Review → Approve → Publish
|
||||
|
||||
**Workflow:**
|
||||
```
|
||||
1. Writer creates content (Draft)
|
||||
2. Writer submits for review (Review)
|
||||
3. Editor reviews and approves (Approved)
|
||||
4. Publisher publishes from Approved page (Published)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Best Practices
|
||||
|
||||
### When to Publish Immediately
|
||||
|
||||
- ✅ Time-sensitive content (news, announcements)
|
||||
- ✅ Small batches (1-5 items)
|
||||
- ✅ Testing new content types
|
||||
- ✅ Urgent fixes or updates
|
||||
|
||||
### When to Use Scheduling
|
||||
|
||||
- ✅ Large batches (6+ items)
|
||||
- ✅ Content with planned publish dates
|
||||
- ✅ Avoiding rate limits
|
||||
- ✅ Publishing during optimal times
|
||||
- ✅ Automated publishing workflows
|
||||
- ✅ Content calendar planning
|
||||
|
||||
### Scheduling Tips
|
||||
|
||||
1. **Use Site Defaults**: Let system handle timing automatically
|
||||
2. **Stagger Publications**: 15-30 minute intervals reduce load
|
||||
3. **Check Timezone**: Ensure site timezone matches your expectations
|
||||
4. **Plan Ahead**: Schedule content days/weeks in advance
|
||||
5. **Monitor Failures**: Check failed items daily, fix issues promptly
|
||||
6. **Test First**: Publish 1-2 items manually before bulk scheduling
|
||||
|
||||
### Error Prevention
|
||||
|
||||
1. **Verify Credentials**: Test site connection before bulk operations
|
||||
2. **Check Content Quality**: Ensure all required fields filled
|
||||
3. **Validate Images**: Confirm images uploaded and accessible
|
||||
4. **Review Platform Requirements**: WordPress categories, Shopify collections, etc.
|
||||
5. **Monitor Rate Limits**: Don't schedule too many items at once
|
||||
6. **Backup Content**: Export content before large publishing operations
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
### Problem: Publishing is slow
|
||||
|
||||
**Solution:**
|
||||
- Check network connection
|
||||
- Verify site is responsive
|
||||
- Use scheduling instead of bulk publish
|
||||
- Check site server performance
|
||||
|
||||
### Problem: All items failing with same error
|
||||
|
||||
**Solution:**
|
||||
- Check Site Settings → API credentials
|
||||
- Verify domain is correct and accessible
|
||||
- Test site connection manually
|
||||
- Check platform API status (WordPress.org, Shopify status page)
|
||||
|
||||
### Problem: Items stuck in "publishing" status
|
||||
|
||||
**Solution:**
|
||||
- Backend may be processing
|
||||
- Wait 5-10 minutes (Celery runs every 5 min)
|
||||
- Check backend logs for errors
|
||||
- Contact system administrator if persists
|
||||
|
||||
### Problem: Schedule times are wrong timezone
|
||||
|
||||
**Solution:**
|
||||
- Go to Site Settings → Publishing
|
||||
- Set correct timezone for your site
|
||||
- Reschedule affected items
|
||||
- Future schedules will use correct timezone
|
||||
|
||||
### Problem: Site selector not updating calendar content
|
||||
|
||||
**Solution:**
|
||||
- Refresh your browser (Ctrl+F5 or Cmd+Shift+R)
|
||||
- This issue was fixed in latest update (Jan 2026)
|
||||
- Calendar now automatically reloads when you change sites
|
||||
- Check browser console for site change logs
|
||||
- If problem persists, clear browser cache
|
||||
|
||||
**What was fixed:**
|
||||
- Frontend: Fixed useEffect dependency in ContentCalendar.tsx
|
||||
- Backend: Added site_id and site_status to ContentFilter
|
||||
- All API queries now properly filter by selected site
|
||||
|
||||
### Problem: Cannot see published content on site
|
||||
|
||||
**Solution:**
|
||||
- Click "View on [Site Name]" link to verify URL
|
||||
- Check site visibility settings (not private/draft)
|
||||
- Clear site cache if using caching plugin
|
||||
- Verify content published to correct site
|
||||
- Check platform-specific visibility settings
|
||||
|
||||
### Problem: Bulk publish limit blocking my workflow
|
||||
|
||||
**Solution:**
|
||||
- Use "Schedule Selected" instead (no limit)
|
||||
- Set up site default schedule for automation
|
||||
- Or select ≤5 items at a time for immediate publishing
|
||||
|
||||
---
|
||||
|
||||
## 9. Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Space` | Select/deselect item in table |
|
||||
| `Shift + Click` | Select range of items |
|
||||
| `Ctrl/Cmd + Click` | Select multiple non-contiguous items |
|
||||
| `Esc` | Close modal (when not publishing) |
|
||||
| `Enter` | Confirm action in modal |
|
||||
|
||||
---
|
||||
|
||||
## 10. Support and Resources
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Documentation**: `docs/40-WORKFLOWS/` (this guide)
|
||||
- **API Reference**: `docs/20-API/PUBLISHER.md`
|
||||
- **Developer Docs**: `docs/30-FRONTEND/PUBLISHING-MODALS.md`
|
||||
- **System Status**: Check backend logs at `/admin/system/logs/`
|
||||
|
||||
### Contact
|
||||
|
||||
- **Technical Issues**: Contact system administrator
|
||||
- **Content Questions**: Contact editorial team
|
||||
- **Platform Setup**: Contact site owner/administrator
|
||||
|
||||
---
|
||||
|
||||
**Document Version**: 1.0
|
||||
**Last Updated**: January 2026
|
||||
**Status**: Production Ready
|
||||
@@ -0,0 +1,521 @@
|
||||
# Credit System
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** ✅ Complete (v1.8.3 - Two-Pool Credit System)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
IGNY8 uses a **two-pool credit system** (v1.8.3):
|
||||
- **Plan Credits** (`account.credits`): From subscription, reset on renewal
|
||||
- **Bonus Credits** (`account.bonus_credits`): Purchased, **NEVER expire**
|
||||
|
||||
**Usage Priority:** Plan credits consumed first, bonus credits only when plan = 0.
|
||||
|
||||
---
|
||||
|
||||
## Credit Flow (Verified Architecture)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CREDIT FLOW │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Plan.included_credits = Monthly allocation (e.g., 10,000) │
|
||||
│ ↓ (Added on subscription renewal/approval) │
|
||||
│ Account.credits = Current balance (real-time, decremented) │
|
||||
│ ↓ (Decremented on each AI operation) │
|
||||
│ CreditTransaction = Log of all credit changes │
|
||||
│ CreditUsageLog = Detailed operation tracking │
|
||||
│ │
|
||||
│ THESE ARE NOT PARALLEL - They serve different purposes: │
|
||||
│ • Plan.included_credits = "How many credits per month" │
|
||||
│ • Account.credits = "How many credits you have RIGHT NOW" │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Where credits are added to Account.credits:**
|
||||
1. `billing/views.py` - When manual payment is approved
|
||||
2. `payment_service.py` - When credit package purchased
|
||||
3. `credit_service.py` - Generic `add_credits()` method
|
||||
|
||||
---
|
||||
|
||||
## Simplified Limits (v1.5.0)
|
||||
|
||||
### Hard Limits (Never Reset)
|
||||
|
||||
| Limit | Plan Field | Account Field | Description |
|
||||
|-------|------------|---------------|-------------|
|
||||
| Sites | `max_sites` | (count of Site objects) | Maximum sites per account |
|
||||
| Users | `max_users` | (count of User objects) | Maximum team members |
|
||||
| Keywords | `max_keywords` | (count of Keyword objects) | Total keywords allowed |
|
||||
|
||||
### Monthly Limits (Reset on Billing Cycle)
|
||||
|
||||
| Limit | Plan Field | Account Field | Description |
|
||||
|-------|------------|---------------|-------------|
|
||||
| Ahrefs Queries | `max_ahrefs_queries` | `usage_ahrefs_queries` | Live Ahrefs API queries per month |
|
||||
|
||||
### Removed Limits (Now Credit-Based)
|
||||
|
||||
The following limits were removed in v1.5.0 - credits handle these:
|
||||
- ~~max_clusters~~ → Credits
|
||||
- ~~max_content_ideas~~ → Credits
|
||||
- ~~max_content_words~~ → Credits
|
||||
- ~~max_images_basic~~ → Credits
|
||||
- ~~max_images_premium~~ → Credits
|
||||
- ~~max_image_prompts~~ → Credits
|
||||
|
||||
---
|
||||
|
||||
## Plan Tiers
|
||||
|
||||
| Plan | Credits/Month | Sites | Users | Keywords | Ahrefs Queries |
|
||||
|------|---------------|-------|-------|----------|----------------|
|
||||
| Free | 500 | 1 | 1 | 100 | 0 |
|
||||
| Starter | 5,000 | 3 | 2 | 500 | 50 |
|
||||
| Growth | 15,000 | 10 | 5 | 2,000 | 200 |
|
||||
| Scale | 50,000 | Unlimited | 10 | 10,000 | 500 |
|
||||
|
||||
---
|
||||
|
||||
## Credit Operations
|
||||
|
||||
### Token-Based Operations (Text AI)
|
||||
|
||||
Credits calculated from actual token usage:
|
||||
- `credits = ceil(total_tokens / tokens_per_credit)`
|
||||
- `tokens_per_credit` defined per model in `AIModelConfig`
|
||||
|
||||
| Operation | Model Example | tokens_per_credit |
|
||||
|-----------|---------------|-------------------|
|
||||
| Keyword Clustering | gpt-4o-mini | 10,000 |
|
||||
| Idea Generation | gpt-4o-mini | 10,000 |
|
||||
| Content Generation | gpt-4o | 1,000 |
|
||||
| Content Optimization | gpt-4o-mini | 10,000 |
|
||||
|
||||
### Fixed-Cost Operations (Image AI) - v1.7.1 Complete
|
||||
|
||||
Credits per image based on quality tier:
|
||||
|
||||
| Quality Tier | Model Example | Credits/Image |
|
||||
|--------------|---------------|---------------|
|
||||
| Basic | runware:97@1 | 1 |
|
||||
| Quality | dall-e-3 | 5 |
|
||||
| Premium | google:4@2 | 15 |
|
||||
|
||||
**Image Generation Credit Flow (v1.7.1):**
|
||||
1. Pre-check: `CreditService.check_credits_for_image()` verifies sufficient credits
|
||||
2. Generation: Images generated via `process_image_generation_queue` Celery task
|
||||
3. Post-deduct: `CreditService.deduct_credits_for_image()` called per successful image
|
||||
4. Logging: `CreditUsageLog` + `CreditTransaction` + `AITaskLog` entries created
|
||||
5. Notifications: `NotificationService.notify_images_complete/failed()` called
|
||||
|
||||
### Free Operations
|
||||
|
||||
| Operation | Cost |
|
||||
|-----------|------|
|
||||
| Add keyword (manual) | 0 |
|
||||
| Create content task | 0 |
|
||||
| Edit content | 0 |
|
||||
| Publish to WordPress | 0 |
|
||||
| Sync from WordPress | 0 |
|
||||
|
||||
---
|
||||
|
||||
## Database Models
|
||||
|
||||
### Account (Credit Balance)
|
||||
|
||||
```python
|
||||
class Account(models.Model):
|
||||
credits = models.IntegerField(default=0) # Current balance
|
||||
usage_ahrefs_queries = models.IntegerField(default=0) # Monthly Ahrefs usage
|
||||
```
|
||||
|
||||
### Plan (Allocations)
|
||||
|
||||
```python
|
||||
class Plan(models.Model):
|
||||
included_credits = models.IntegerField(default=0) # Monthly allocation
|
||||
max_sites = models.IntegerField(default=1)
|
||||
max_users = models.IntegerField(default=1)
|
||||
max_keywords = models.IntegerField(default=100)
|
||||
max_ahrefs_queries = models.IntegerField(default=0) # Monthly Ahrefs limit
|
||||
```
|
||||
|
||||
### CreditTransaction (Ledger)
|
||||
|
||||
```python
|
||||
class CreditTransaction(models.Model):
|
||||
account = models.ForeignKey(Account)
|
||||
transaction_type = models.CharField() # purchase/subscription/refund/deduction
|
||||
amount = models.DecimalField() # Positive (add) or negative (deduct)
|
||||
balance_after = models.DecimalField()
|
||||
description = models.CharField()
|
||||
created_at = models.DateTimeField()
|
||||
```
|
||||
|
||||
### CreditUsageLog (Analytics)
|
||||
|
||||
```python
|
||||
class CreditUsageLog(models.Model):
|
||||
account = models.ForeignKey(Account)
|
||||
operation_type = models.CharField() # clustering/content_generation/image_generation
|
||||
credits_used = models.DecimalField()
|
||||
model_used = models.CharField()
|
||||
tokens_input = models.IntegerField()
|
||||
tokens_output = models.IntegerField()
|
||||
created_at = models.DateTimeField()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Business Logic
|
||||
|
||||
### CreditService
|
||||
|
||||
Location: `backend/igny8_core/business/billing/services/credit_service.py`
|
||||
|
||||
**Key Methods:**
|
||||
|
||||
```python
|
||||
class CreditService:
|
||||
@staticmethod
|
||||
def check_credits(account, required_credits):
|
||||
"""Check if sufficient credits available, raises InsufficientCreditsError if not"""
|
||||
|
||||
@staticmethod
|
||||
def deduct_credits_for_operation(account, operation_type, model, tokens_in, tokens_out, metadata=None):
|
||||
"""Deduct credits and log usage after AI operation"""
|
||||
|
||||
@staticmethod
|
||||
def add_credits(account, amount, transaction_type, description):
|
||||
"""Add credits (admin/purchase/subscription)"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_credits_from_tokens(operation_type, tokens_in, tokens_out, model=None):
|
||||
"""Calculate credits based on token usage and model"""
|
||||
```
|
||||
|
||||
### LimitService
|
||||
|
||||
Location: `backend/igny8_core/business/billing/services/limit_service.py`
|
||||
|
||||
**Key Methods:**
|
||||
|
||||
```python
|
||||
class LimitService:
|
||||
HARD_LIMIT_MAPPINGS = {
|
||||
'sites': {...},
|
||||
'users': {...},
|
||||
'keywords': {...},
|
||||
}
|
||||
|
||||
MONTHLY_LIMIT_MAPPINGS = {
|
||||
'ahrefs_queries': {...},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def check_hard_limit(cls, account, limit_name, additional_count=1):
|
||||
"""Check if adding items would exceed hard limit"""
|
||||
|
||||
@classmethod
|
||||
def check_monthly_limit(cls, account, limit_name, additional_count=1):
|
||||
"""Check if operation would exceed monthly limit"""
|
||||
|
||||
@classmethod
|
||||
def increment_monthly_usage(cls, account, limit_name, count=1):
|
||||
"""Increment monthly usage counter"""
|
||||
```
|
||||
|
||||
### Usage in AI Operations
|
||||
|
||||
```python
|
||||
# In content generation service
|
||||
def generate_content(task, user):
|
||||
account = task.site.account
|
||||
|
||||
# 1. Pre-check credits (estimated)
|
||||
estimated_credits = 50 # Estimate for content generation
|
||||
CreditService.check_credits(account, estimated_credits)
|
||||
|
||||
# 2. Execute AI function
|
||||
content, usage = ai_engine.generate_content(task)
|
||||
|
||||
# 3. Deduct actual credits based on token usage
|
||||
CreditService.deduct_credits_for_operation(
|
||||
account=account,
|
||||
operation_type='content_generation',
|
||||
model=usage.model,
|
||||
tokens_in=usage.input_tokens,
|
||||
tokens_out=usage.output_tokens,
|
||||
metadata={'content_id': content.id}
|
||||
)
|
||||
|
||||
return content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Responses
|
||||
|
||||
### Successful Operation
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { ... },
|
||||
"credits_used": 15,
|
||||
"balance": 9985
|
||||
}
|
||||
```
|
||||
|
||||
### Insufficient Credits
|
||||
|
||||
```json
|
||||
HTTP 402 Payment Required
|
||||
|
||||
{
|
||||
"success": false,
|
||||
"error": "Insufficient credits",
|
||||
"code": "INSUFFICIENT_CREDITS",
|
||||
"required": 50,
|
||||
"available": 25
|
||||
}
|
||||
```
|
||||
|
||||
### Limit Exceeded
|
||||
|
||||
```json
|
||||
HTTP 402 Payment Required
|
||||
|
||||
{
|
||||
"success": false,
|
||||
"error": "Keyword limit reached",
|
||||
"code": "HARD_LIMIT_EXCEEDED",
|
||||
"limit": "keywords",
|
||||
"current": 500,
|
||||
"max": 500
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Handling
|
||||
|
||||
### Credit Balance Display
|
||||
|
||||
- Header shows current credit balance
|
||||
- Updates after each operation
|
||||
- Warning at low balance (< 10%)
|
||||
|
||||
### Pre-Operation Check
|
||||
|
||||
```typescript
|
||||
import { checkCreditsBeforeOperation } from '@/utils/creditCheck';
|
||||
import { useInsufficientCreditsModal } from '@/components/billing/InsufficientCreditsModal';
|
||||
|
||||
function ContentGenerator() {
|
||||
const { showModal } = useInsufficientCreditsModal();
|
||||
|
||||
const handleGenerate = async () => {
|
||||
// Check credits before operation
|
||||
const check = await checkCreditsBeforeOperation(50); // estimated cost
|
||||
|
||||
if (!check.hasEnoughCredits) {
|
||||
showModal({
|
||||
requiredCredits: check.requiredCredits,
|
||||
availableCredits: check.availableCredits,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Proceed with generation
|
||||
await generateContent();
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Credit Balance
|
||||
|
||||
```
|
||||
GET /api/v1/billing/balance/
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"credits": 9500,
|
||||
"plan_credits_per_month": 10000,
|
||||
"credits_used_this_month": 500,
|
||||
"credits_remaining": 9500
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Limits
|
||||
|
||||
```
|
||||
GET /api/v1/billing/usage/limits/
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"limits": {
|
||||
"sites": { "current": 2, "limit": 5, "type": "hard" },
|
||||
"users": { "current": 2, "limit": 3, "type": "hard" },
|
||||
"keywords": { "current": 847, "limit": 1000, "type": "hard" },
|
||||
"ahrefs_queries": { "current": 23, "limit": 50, "type": "monthly" }
|
||||
},
|
||||
"days_until_reset": 18
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Analytics
|
||||
|
||||
```
|
||||
GET /api/v1/account/usage/analytics/?days=30
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"period_days": 30,
|
||||
"start_date": "2025-12-06",
|
||||
"end_date": "2026-01-05",
|
||||
"current_balance": 9500,
|
||||
"total_usage": 500,
|
||||
"total_purchases": 0,
|
||||
"usage_by_type": [
|
||||
{ "transaction_type": "content_generation", "total": -350, "count": 15 },
|
||||
{ "transaction_type": "image_generation", "total": -100, "count": 20 },
|
||||
{ "transaction_type": "clustering", "total": -50, "count": 10 }
|
||||
],
|
||||
"daily_usage": [
|
||||
{ "date": "2026-01-05", "usage": 25, "purchases": 0, "net": -25 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Credit Allocation
|
||||
|
||||
Credits are added to `Account.credits` when:
|
||||
|
||||
1. **Subscription Renewal** - `Plan.included_credits` added monthly
|
||||
2. **Payment Approval** - Manual payments approved by admin
|
||||
3. **Credit Purchase** - Credit packages bought by user
|
||||
4. **Admin Adjustment** - Manual credit grants/adjustments
|
||||
|
||||
### Monthly Reset
|
||||
|
||||
Monthly limits (Ahrefs queries) reset on billing cycle:
|
||||
|
||||
```python
|
||||
# In Account model
|
||||
def reset_monthly_usage(self):
|
||||
"""Reset monthly usage counters (called on billing cycle renewal)"""
|
||||
self.usage_ahrefs_queries = 0
|
||||
self.save(update_fields=['usage_ahrefs_queries'])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin Operations
|
||||
|
||||
### Manual Credit Adjustment
|
||||
|
||||
Via Django Admin or API:
|
||||
|
||||
```python
|
||||
from igny8_core.business.billing.services.credit_service import CreditService
|
||||
|
||||
# Add credits
|
||||
CreditService.add_credits(
|
||||
account=account,
|
||||
amount=1000,
|
||||
transaction_type='adjustment',
|
||||
description='Customer support adjustment'
|
||||
)
|
||||
```
|
||||
|
||||
### Usage Audit
|
||||
|
||||
All credit changes logged in `CreditTransaction` with:
|
||||
- Timestamp
|
||||
- Transaction type
|
||||
- Amount (positive or negative)
|
||||
- Balance after transaction
|
||||
- Description
|
||||
|
||||
All AI operations logged in `CreditUsageLog` with:
|
||||
- Operation type
|
||||
- Credits used
|
||||
- Model used
|
||||
- Token counts
|
||||
- Related object metadata
|
||||
|
||||
---
|
||||
|
||||
## Image Generation Credit System (v1.7.1)
|
||||
|
||||
### Implementation Details
|
||||
|
||||
**Files:**
|
||||
- `CreditService.check_credits_for_image()` - [credit_service.py:307-335](../backend/igny8_core/business/billing/services/credit_service.py#L307-L335)
|
||||
- `process_image_generation_queue` credit check - [tasks.py:290-319](../backend/igny8_core/ai/tasks.py#L290-L319)
|
||||
- `deduct_credits_for_image()` - [tasks.py:745-770](../backend/igny8_core/ai/tasks.py#L745-L770)
|
||||
- AITaskLog logging - [tasks.py:838-875](../backend/igny8_core/ai/tasks.py#L838-L875)
|
||||
- Notifications - [tasks.py:877-895](../backend/igny8_core/ai/tasks.py#L877-L895)
|
||||
|
||||
### Credit Flow for Image Generation
|
||||
|
||||
```
|
||||
1. User triggers image generation
|
||||
↓
|
||||
2. CreditService.check_credits_for_image(account, model, num_images)
|
||||
- Calculates: credits_per_image × num_images
|
||||
- Raises InsufficientCreditsError if balance < required
|
||||
↓
|
||||
3. process_image_generation_queue() processes each image
|
||||
↓
|
||||
4. For each successful image:
|
||||
CreditService.deduct_credits_for_image()
|
||||
- Creates CreditUsageLog entry
|
||||
- Creates CreditTransaction entry
|
||||
- Updates account.credits balance
|
||||
↓
|
||||
5. After all images processed:
|
||||
- AITaskLog entry created
|
||||
- Notification created (success or failure)
|
||||
```
|
||||
|
||||
### Logging Locations
|
||||
|
||||
| Table | What's Logged | When |
|
||||
|-------|---------------|------|
|
||||
| CreditTransaction | Credit deduction (financial ledger) | Per image |
|
||||
| CreditUsageLog | Usage details (model, cost, credits) | Per image |
|
||||
| AITaskLog | Task execution summary | After batch |
|
||||
| Notification | User notification | After batch |
|
||||
|
||||
### Automation Compatibility
|
||||
|
||||
Image generation credits work identically for:
|
||||
- Manual image generation (from UI)
|
||||
- Automation Stage 6 (scheduled/manual automation runs)
|
||||
|
||||
Both call `process_image_generation_queue` which handles:
|
||||
- Credit checking before generation
|
||||
- Credit deduction after each successful image
|
||||
- Proper logging to all tables
|
||||
@@ -0,0 +1,596 @@
|
||||
# Scheduled Content Publishing Workflow
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Module:** Publishing / Automation
|
||||
**Status:** ✅ Site filtering fixed and verified
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
IGNY8 provides automated content publishing to WordPress, Shopify, and custom sites. Content goes through a scheduling process before being published at the designated time.
|
||||
|
||||
**Current System (v2.0):**
|
||||
- Site credentials stored directly on `Site` model (`api_key`, `domain`, `platform_type`)
|
||||
- Multi-platform support: WordPress, Shopify, Custom APIs
|
||||
- No `SiteIntegration` model required
|
||||
- Publishing via `PublisherService` (not legacy Celery tasks)
|
||||
- API endpoint: `POST /api/v1/publisher/publish`
|
||||
- **Site Filtering:** All content queries filtered by `site_id` (fixed Jan 2026)
|
||||
|
||||
---
|
||||
|
||||
## Content Lifecycle for Publishing
|
||||
|
||||
### Understanding Content.status vs Content.site_status
|
||||
|
||||
Content has **TWO separate status fields**:
|
||||
|
||||
1. **`status`** - Editorial workflow status
|
||||
- `draft` - Being created/edited
|
||||
- `review` - Submitted for review
|
||||
- `approved` - Ready for publishing
|
||||
- `published` - Legacy (not used for external publishing)
|
||||
|
||||
2. **`site_status`** - External site publishing status (platform-agnostic)
|
||||
- `not_published` - Not yet published to any site
|
||||
- `scheduled` - Has a scheduled_publish_at time
|
||||
- `publishing` - Currently being published
|
||||
- `published` - Successfully published to site (WordPress, Shopify, or Custom)
|
||||
- `failed` - Publishing failed
|
||||
|
||||
**Note:** `site_status` works across all platforms (WordPress, Shopify, Custom) and is filtered by `site_id` to show only content for the selected site.
|
||||
|
||||
### Publishing Flow
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ DRAFT │ ← Content is being created/edited
|
||||
│ status: draft │
|
||||
└────────┬────────┘
|
||||
│ User approves content
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ APPROVED │ ← Content is ready for publishing
|
||||
│ status: approved│ status='approved', site_status='not_published'
|
||||
│ site_status: │
|
||||
│ not_published │
|
||||
└────────┬────────┘
|
||||
│ Hourly: schedule_approved_content task
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ SCHEDULED │ ← Content has a scheduled_publish_at time
|
||||
│ status: approved│ site_status='scheduled'
|
||||
│ site_status: │ scheduled_publish_at set to future datetime
|
||||
│ scheduled │
|
||||
└────────┬────────┘
|
||||
│ Every 5 min: process_scheduled_publications task
|
||||
│ (when scheduled_publish_at <= now)
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ PUBLISHING │ ← WordPress API call in progress
|
||||
│ status: approved│ site_status='publishing'
|
||||
│ site_status: │
|
||||
│ publishing │
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌────┴────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────┐ ┌────────┐
|
||||
│PUBLISHED│ │ FAILED │
|
||||
│status: │ │status: │
|
||||
│approved │ │approved│
|
||||
│site_ │ │site_ │
|
||||
│status: │ │status: │
|
||||
│published│ │failed │
|
||||
└─────────┘ └────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### Site Selector & Content Filtering
|
||||
|
||||
**Content Calendar** (`frontend/src/pages/Publisher/ContentCalendar.tsx`):
|
||||
- Automatically filters all content by selected site
|
||||
- Reloads data when site selector changes
|
||||
- Shows scheduled, publishing, published, and failed content for active site only
|
||||
|
||||
**Critical Implementation Details:**
|
||||
- All API queries include `site_id: activeSite.id` parameter
|
||||
- Backend `ContentFilter` includes `site_id` in filterable fields
|
||||
- useEffect hook reacts to `activeSite?.id` changes to trigger reload
|
||||
- Content cleared when no site selected
|
||||
|
||||
**Site Selector Fix (Jan 2026):**
|
||||
- Fixed circular dependency in useEffect (lines 285-294)
|
||||
- Only depends on `activeSite?.id`, not on callback functions
|
||||
- Added console logging for debugging site changes
|
||||
- Pattern follows Dashboard and Approved pages
|
||||
|
||||
**Backend Filter Configuration:**
|
||||
```python
|
||||
# backend/igny8_core/modules/writer/views.py
|
||||
class ContentFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Content
|
||||
fields = [
|
||||
'cluster_id',
|
||||
'site_id', # Required for site filtering
|
||||
'status',
|
||||
'site_status', # Required for status filtering
|
||||
'content_type',
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Celery Tasks
|
||||
|
||||
### 1. `schedule_approved_content`
|
||||
|
||||
**Schedule:** Every hour at :00
|
||||
**Task Name:** `publishing.schedule_approved_content`
|
||||
**File:** `backend/igny8_core/tasks/publishing_scheduler.py`
|
||||
|
||||
#### What It Does:
|
||||
1. Finds all sites with `PublishingSettings.auto_publish_enabled = True`
|
||||
2. Gets approved content (`status='approved'`, `site_status='not_published'`, `scheduled_publish_at=null`)
|
||||
3. Calculates available publishing slots based on:
|
||||
- `publish_days` - which days are allowed (e.g., Mon-Fri)
|
||||
- `publish_time_slots` - which times are allowed (e.g., 09:00, 14:00, 18:00)
|
||||
- `daily_publish_limit` - max posts per day
|
||||
- `weekly_publish_limit` - max posts per week
|
||||
- `monthly_publish_limit` - max posts per month
|
||||
4. Assigns `scheduled_publish_at` datetime and sets `site_status='scheduled'`
|
||||
|
||||
#### Configuration Location:
|
||||
`PublishingSettings` model linked to each Site. Configurable via:
|
||||
- Admin: `/admin/integration/publishingsettings/`
|
||||
- API: `/api/v1/sites/{site_id}/publishing-settings/`
|
||||
|
||||
---
|
||||
|
||||
### 2. `process_scheduled_publications`
|
||||
|
||||
**Schedule:** Every 5 minutes
|
||||
**Task Name:** `publishing.process_scheduled_publications`
|
||||
**File:** `backend/igny8_core/tasks/publishing_scheduler.py`
|
||||
|
||||
#### What It Does:
|
||||
1. Finds all content where:
|
||||
- `site_status='scheduled'`
|
||||
- `scheduled_publish_at <= now`
|
||||
2. For each content item:
|
||||
- Validates WordPress configuration exists on Site (`wp_api_key`, `domain`)
|
||||
- Updates `site_status='publishing'`
|
||||
- Calls `PublisherService.publish_content()` directly (synchronous)
|
||||
- Updates `site_status='published'` on success or `'failed'` on error
|
||||
3. Logs results and any errors
|
||||
|
||||
**Current Implementation (v2.0):**
|
||||
- Uses `PublisherService` (current system)
|
||||
- Gets config from `Site.wp_api_key` and `Site.domain`
|
||||
- No `SiteIntegration` required
|
||||
- Synchronous publishing (not queued to Celery)
|
||||
|
||||
---
|
||||
|
||||
### 3. `PublisherService.publish_content`
|
||||
|
||||
**Type:** Service method (called by scheduler)
|
||||
**File:** `backend/igny8_core/business/publishing/services/publisher_service.py`
|
||||
|
||||
#### What It Does:
|
||||
1. **Load Content** - Gets content by ID
|
||||
2. **Get WordPress Config** - Reads from `Site.wp_api_key` and `Site.domain`
|
||||
3. **Call Adapter** - Uses `WordPressAdapter` to handle API communication
|
||||
4. **WordPress API Call** - POSTs to `{domain}/wp-json/igny8/v1/publish`
|
||||
5. **Update Content** - Sets `external_id`, `external_url`, `site_status='published'`
|
||||
6. **Create Record** - Logs in `PublishingRecord` model
|
||||
|
||||
#### WordPress Connection:
|
||||
- Uses the IGNY8 WordPress Bridge plugin installed on the site
|
||||
- API endpoint: `{site.domain}/wp-json/igny8/v1/publish`
|
||||
- Authentication: API key from `Site.wp_api_key`
|
||||
- **No SiteIntegration model needed**
|
||||
|
||||
---
|
||||
|
||||
## Database Models
|
||||
|
||||
### Content Fields (Publishing Related)
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `status` | CharField | **Editorial workflow**: `draft`, `review`, `approved` |
|
||||
| `site_status` | CharField | **WordPress publishing status**: `not_published`, `scheduled`, `publishing`, `published`, `failed` |
|
||||
| `site_status_updated_at` | DateTimeField | When site_status was last changed |
|
||||
| `scheduled_publish_at` | DateTimeField | When content should be published (null if not scheduled) |
|
||||
| `external_id` | CharField | WordPress post ID after publishing |
|
||||
| `external_url` | URLField | WordPress post URL after publishing |
|
||||
|
||||
**Important:** These are separate concerns:
|
||||
- `status` tracks editorial approval
|
||||
- `site_status` tracks external publishing
|
||||
- Content typically has `status='approved'` AND `site_status='not_published'` before scheduling
|
||||
|
||||
### PublishingSettings Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `site` | ForeignKey | The site these settings apply to |
|
||||
| `auto_publish_enabled` | BooleanField | Whether automatic scheduling is enabled |
|
||||
| `publish_days` | JSONField | List of allowed days: `['mon', 'tue', 'wed', 'thu', 'fri']` |
|
||||
| `publish_time_slots` | JSONField | List of times: `['09:00', '14:00', '18:00']` |
|
||||
| `daily_publish_limit` | IntegerField | Max posts per day (null = unlimited) |
|
||||
| `weekly_publish_limit` | IntegerField | Max posts per week (null = unlimited) |
|
||||
| `monthly_publish_limit` | IntegerField | Max posts per month (null = unlimited) |
|
||||
|
||||
---
|
||||
|
||||
## Celery Beat Schedule
|
||||
|
||||
From `backend/igny8_core/celery.py`:
|
||||
|
||||
```python
|
||||
app.conf.beat_schedule = {
|
||||
# ...
|
||||
'schedule-approved-content': {
|
||||
'task': 'publishing.schedule_approved_content',
|
||||
'schedule': crontab(minute=0), # Every hour at :00
|
||||
},
|
||||
'process-scheduled-publications': {
|
||||
'task': 'publishing.process_scheduled_publications',
|
||||
'schedule': crontab(minute='*/5'), # Every 5 minutes
|
||||
},
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manual Publishing
|
||||
|
||||
Content can be published immediately or rescheduled via API:
|
||||
|
||||
### Publish Now
|
||||
```
|
||||
POST /api/v1/publisher/publish
|
||||
{
|
||||
"content_id": 123,
|
||||
"destinations": ["wordpress"]
|
||||
}
|
||||
```
|
||||
|
||||
### Schedule for Later
|
||||
```
|
||||
POST /api/v1/writer/content/{id}/schedule/
|
||||
{
|
||||
"scheduled_at": "2026-01-20T14:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Reschedule Failed Content
|
||||
```
|
||||
POST /api/v1/writer/content/{id}/reschedule/
|
||||
{
|
||||
"scheduled_at": "2026-01-20T15:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** Reschedule works from any `site_status` (failed, published, scheduled, etc.)
|
||||
|
||||
### Unschedule
|
||||
```
|
||||
POST /api/v1/writer/content/{id}/unschedule/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Debugging
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
**Browser Console Logs:**
|
||||
When changing sites in Content Calendar, you should see:
|
||||
```
|
||||
[ContentCalendar] Site changed to: 45 My Site Name
|
||||
[ContentCalendar] Triggering loadQueue...
|
||||
```
|
||||
|
||||
**Check Site Filtering:**
|
||||
1. Open browser DevTools → Network tab
|
||||
2. Change site in site selector
|
||||
3. Look for API calls to `/api/v1/writer/content/`
|
||||
4. Verify `site_id` parameter is included in query string
|
||||
5. Verify count matches database for that site
|
||||
|
||||
**Common Issues:**
|
||||
- No console logs when changing sites → useEffect not triggering (refresh page)
|
||||
- API calls missing `site_id` parameter → backend filter not working
|
||||
- Wrong count displayed → database query issue or cache problem
|
||||
|
||||
### Backend Log Files
|
||||
- **Publish Logs:** `backend/logs/publish-sync-logs/`
|
||||
- **API Logs:** `backend/logs/wordpress_api.log`
|
||||
|
||||
### Check Celery Status
|
||||
```bash
|
||||
docker compose -f docker-compose.app.yml -p igny8-app logs igny8_celery_worker
|
||||
docker compose -f docker-compose.app.yml -p igny8-app logs igny8_celery_beat
|
||||
```
|
||||
|
||||
### Check Scheduled Content
|
||||
```python
|
||||
# Django shell
|
||||
from igny8_core.business.content.models import Content
|
||||
from django.utils import timezone
|
||||
|
||||
# Past due content (should have been published)
|
||||
Content.objects.filter(
|
||||
site_status='scheduled',
|
||||
scheduled_publish_at__lt=timezone.now()
|
||||
).count()
|
||||
|
||||
# Upcoming scheduled content
|
||||
Content.objects.filter(
|
||||
site_status='scheduled',
|
||||
scheduled_publish_at__gt=timezone.now()
|
||||
).order_by('scheduled_publish_at')[:10]
|
||||
|
||||
# Check scheduled content for specific site
|
||||
site_id = 45
|
||||
Content.objects.filter(
|
||||
site_id=site_id,
|
||||
site_status='scheduled'
|
||||
).count()
|
||||
|
||||
# Compare with frontend display
|
||||
# Should match count shown in Content Calendar for that site
|
||||
```
|
||||
|
||||
### Manual Task Execution
|
||||
```python
|
||||
# Django shell
|
||||
from igny8_core.tasks.publishing_scheduler import (
|
||||
schedule_approved_content,
|
||||
process_scheduled_publications
|
||||
)
|
||||
|
||||
# Run scheduling task
|
||||
schedule_approved_content()
|
||||
|
||||
# Process due publications
|
||||
process_scheduled_publications()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Failure Reasons
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| No WordPress API key | Site.wp_api_key is empty | Configure API key in Site settings |
|
||||
| No domain configured | Site.domain is empty | Set domain in Site settings |
|
||||
| API key invalid/expired | WordPress API key issue | Regenerate API key in WordPress plugin |
|
||||
| Connection timeout | WordPress site unreachable | Check site availability |
|
||||
| Plugin not active | IGNY8 Bridge plugin disabled | Enable plugin in WordPress |
|
||||
| Content already published | Duplicate publish attempt | Use reschedule to republish |
|
||||
|
||||
### Retry Policy
|
||||
- Failed content marked with `site_status='failed'`
|
||||
- Use the `reschedule` action to retry publishing
|
||||
- Can reschedule from any status (failed, published, etc.)
|
||||
|
||||
### Rescheduling Failed Content
|
||||
|
||||
```python
|
||||
# Via API
|
||||
POST /api/v1/writer/content/{id}/reschedule/
|
||||
{
|
||||
"scheduled_at": "2026-01-20T15:00:00Z"
|
||||
}
|
||||
|
||||
# Via Django shell
|
||||
from igny8_core.business.content.models import Content
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
failed_content = Content.objects.filter(site_status='failed')
|
||||
for content in failed_content:
|
||||
# Reschedule for 1 hour from now
|
||||
content.site_status = 'scheduled'
|
||||
content.scheduled_publish_at = timezone.now() + timedelta(hours=1)
|
||||
content.site_status_updated_at = timezone.now()
|
||||
content.save(update_fields=['site_status', 'scheduled_publish_at', 'site_status_updated_at'])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Site Selector Not Updating Content Calendar
|
||||
|
||||
**Symptoms:**
|
||||
- Changing sites doesn't reload calendar content
|
||||
- Count shows wrong number of scheduled items
|
||||
- Content from wrong site displayed
|
||||
|
||||
**Solution:**
|
||||
1. **Hard refresh browser:** Ctrl+F5 (Windows/Linux) or Cmd+Shift+R (Mac)
|
||||
2. **Clear browser cache**
|
||||
3. **Check console logs:** Should see `[ContentCalendar] Site changed to: ...`
|
||||
4. **Verify API calls:** Check Network tab for `site_id` parameter
|
||||
|
||||
**What Was Fixed (Jan 2026):**
|
||||
- Frontend: Fixed useEffect circular dependency in ContentCalendar.tsx
|
||||
- Backend: Added `site_id` and `site_status` to ContentFilter fields
|
||||
- All API queries now properly filter by `site_id: activeSite.id`
|
||||
- Content clears when no site selected
|
||||
|
||||
**If Problem Persists:**
|
||||
```bash
|
||||
# Check backend filter configuration
|
||||
grep -n "site_id" backend/igny8_core/modules/writer/views.py
|
||||
|
||||
# Should show site_id in ContentFilter.Meta.fields
|
||||
```
|
||||
|
||||
### Content Not Being Scheduled
|
||||
|
||||
1. Check `PublishingSettings.auto_publish_enabled` is `True`
|
||||
2. Verify content has `status='approved'` and `site_status='not_published'`
|
||||
3. Check `scheduled_publish_at` is null (already scheduled content won't reschedule)
|
||||
4. Verify publish limits haven't been reached
|
||||
5. **Verify correct site selected** in site selector
|
||||
|
||||
### Content Not Publishing
|
||||
|
||||
1. Check Celery Beat is running: `docker compose logs igny8_celery_beat`
|
||||
2. Check Celery Worker is running: `docker compose logs igny8_celery_worker`
|
||||
3. Look for errors in worker logs
|
||||
4. Verify Site has `wp_api_key` configured
|
||||
5. Verify Site has `domain` configured
|
||||
6. Test WordPress API connectivity
|
||||
|
||||
### Resetting Failed Content
|
||||
|
||||
Use the reschedule API endpoint:
|
||||
|
||||
```bash
|
||||
# Reschedule single content
|
||||
curl -X POST https://api.igny8.com/api/v1/writer/content/344/reschedule/ \
|
||||
-H "Authorization: Api-Key YOUR_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"scheduled_at": "2026-01-20T15:00:00Z"}'
|
||||
```
|
||||
|
||||
Or via Django shell:
|
||||
|
||||
```python
|
||||
# Reset all failed content to reschedule in 1 hour
|
||||
from igny8_core.business.content.models import Content
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
failed_content = Content.objects.filter(site_status='failed')
|
||||
for content in failed_content:
|
||||
content.site_status = 'scheduled'
|
||||
content.scheduled_publish_at = timezone.now() + timedelta(hours=1)
|
||||
content.site_status_updated_at = timezone.now()
|
||||
content.save(update_fields=['site_status', 'scheduled_publish_at', 'site_status_updated_at'])
|
||||
print(f"Rescheduled content {content.id}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ IGNY8 Backend │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────Validate Site.wp_api_key & domain │ │
|
||||
│ │ - Call PublisherService.publish_content() │ │
|
||||
│ │ │ │
|
||||
│ │ 3. PublisherService │ │
|
||||
│ │ - Get config from Site model │ │
|
||||
│ │ - Call WordPressAdapter │ │
|
||||
│ │ - Update content status │ │
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└───────────────────────────────────┼─────────────────────────────┘
|
||||
│
|
||||
▼ HTTPS
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ WordPress Site │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ IGNY8 Bridge Plugin │ │
|
||||
│ │ │ │
|
||||
│ │ /wp-json/igny8/v1/publish │ │
|
||||
│ │ - Receives content payload │ │
|
||||
│ │ - Creates/updates WordPress post │ │
|
||||
│ │ - Handles images, categories, tags │ │
|
||||
│ │ - Returns post ID and URL │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### Schedule Content
|
||||
```http
|
||||
POST /api/v1/writer/content/{id}/schedule/
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scheduled_at": "2026-01-20T14:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Reschedule Content (Failed or Any Status)
|
||||
```http
|
||||
POST /api/v1/writer/content/{id}/reschedule/
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"scheduled_at": "2026-01-20T15:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Unschedule Content
|
||||
```http
|
||||
POST /api/v1/writer/content/{id}/unschedule/
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
```
|
||||
|
||||
### Publish Immediately
|
||||
```http
|
||||
POST /api/v1/publisher/publish
|
||||
Authorization: Api-Key YOUR_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"content_id": 123,
|
||||
"destinations": ["wordpress"]
|
||||
}│
|
||||
│ └────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
└───────────────────────────────────┼─────────────────────────────┘
|
||||
│
|
||||
▼ HTTPS
|
||||
┌───────────────────────────────────────────────────────────────┐
|
||||
│ WordPress Site │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ IGNY8 Bridge Plugin │ │
|
||||
│ │ │ │
|
||||
│ │ /wp-json/igny8-bridge/v1/publish │ │
|
||||
│ │ - Receives content payload │ │
|
||||
│ │ - Creates/updates WordPress post │ │
|
||||
│ │ - Handles images, categories, tags │ │
|
||||
│ │ - Returns post ID and URL │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Publisher Module](../10-MODULES/PUBLISHER.md)
|
||||
- [WordPress Integration](../60-PLUGINS/WORDPRESS-INTEGRATION.md)
|
||||
- [Content Pipeline](CONTENT-PIPELINE.md)
|
||||
@@ -0,0 +1,61 @@
|
||||
# Migration Notes
|
||||
|
||||
## Purpose
|
||||
Track significant schema or data migrations, with references to the scripts present in the repo that have been used to adjust data or structure.
|
||||
|
||||
## Code Locations (exact paths)
|
||||
- Migration/utility scripts (repo root `backend/`):
|
||||
- `verify_migrations.py`, `verify_status_fixes.py`, `verify_taxonomy.py`
|
||||
- `fix_*` scripts (e.g., `fix_cluster_status.py`, `fix_content_types.py`, `fix_integration_site_url.py`, `fix_sync.py`, `fix_taxonomy_relationships.py`)
|
||||
- `rename_fields_migration.sql`
|
||||
- `inject_test_data.py`, `sync_idea_status.py`, `check_recent_keywords.py`, `check_api_response.py`, `diagnose_generate_content.py`
|
||||
- Django migrations: `backend/igny8_core/**/migrations/` (per app)
|
||||
|
||||
## High-Level Responsibilities
|
||||
- Capture when ad-hoc fixes or manual SQL were required so future migrations can account for existing state.
|
||||
- Provide guidance on verifying migrations after code changes.
|
||||
|
||||
## Detailed Behavior
|
||||
- Python fix/verify scripts operate against live DBs to patch or validate data (names indicate target domain: clusters/content/integration/taxonomy).
|
||||
- `rename_fields_migration.sql` suggests a manual SQL rename was performed; ensure corresponding Django migration exists or is applied.
|
||||
- `verify_*` scripts check consistency after migrations or data changes.
|
||||
- These scripts are not automatically executed; they are run manually as needed.
|
||||
|
||||
## Execution Flow (Recommended)
|
||||
- Prefer standard Django migrations first: `python manage.py makemigrations && python manage.py migrate`.
|
||||
- For data issues covered by fix scripts:
|
||||
- Review script purpose and run in controlled environment (staging) before production.
|
||||
- Take DB backup before executing manual SQL or fix scripts.
|
||||
- After applying migrations/fixes, run verify scripts to confirm expected state.
|
||||
|
||||
## Cross-Module Interactions
|
||||
- Scripts touch planner/writer/integration data; impacts automation and billing indirectly if content/status changes alter usage.
|
||||
|
||||
## State Transitions (if applicable)
|
||||
- DB schema changes via migrations; data patches via scripts.
|
||||
|
||||
## Error Handling
|
||||
- Scripts log/print outputs; failures will surface during execution; no centralized handler.
|
||||
|
||||
## Tenancy Rules
|
||||
- All scripts should respect account/site scoping; review code before running to ensure filters exist or add them.
|
||||
|
||||
## Billing Rules (if applicable)
|
||||
- None directly; data corrections may affect content/automation counts but not credit logs.
|
||||
|
||||
## Background Tasks / Schedulers (if applicable)
|
||||
- None; scripts are on-demand.
|
||||
|
||||
## Key Design Considerations
|
||||
- Keep fix/verify scripts version-controlled but treat them as one-off tools; remove or archive obsolete scripts.
|
||||
- Ensure Django migrations are the source of truth for schema; manual SQL should be mirrored in migrations.
|
||||
|
||||
## How Developers Should Work With This Module
|
||||
- Before running any fix/verify script, read it and constrain to target account/site if possible.
|
||||
- Add notes here when new manual interventions occur, including date/purpose and scripts used.
|
||||
|
||||
## Recent Hardening (Dec 2025)
|
||||
- Throttling bypass for authenticated users to prevent user-facing 429s.
|
||||
- AI keys fallback: OpenAI/Runware pulled from system account (`aws-admin`/`default-account`/`default`) or Django settings if tenant key absent.
|
||||
- Integration settings restricted to system account or developer (`IsSystemAccountOrDeveloper` backend guard, `AdminGuard` frontend).
|
||||
- DRF default permissions tightened to authenticated + tenant access (`IsAuthenticatedAndActive`, `HasTenantAccess`); public endpoints must override explicitly (e.g., AuthViewSet).
|
||||
@@ -0,0 +1,303 @@
|
||||
# DevOps Operations Guide
|
||||
|
||||
**Purpose:** Complete operational procedures for managing IGNY8 in production
|
||||
**Version:** 1.0
|
||||
**Last Updated:** January 20, 2026
|
||||
|
||||
---
|
||||
|
||||
## 📋 Executive Summary
|
||||
|
||||
This document provides a complete structure for:
|
||||
1. **Automated Backups** - Regular database + config backups
|
||||
2. **Environment Management** - Dev vs Staging vs Production
|
||||
3. **Health Monitoring** - Automated health checks & alerts
|
||||
4. **Disaster Recovery** - Quick recovery procedures
|
||||
5. **Change Management** - Safe deployment workflow
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Directory Structure (To Be Implemented)
|
||||
|
||||
```
|
||||
/data/
|
||||
├── app/
|
||||
│ └── igny8/ # Application code
|
||||
│ ├── docker-compose.app.yml # Production compose ✅
|
||||
│ ├── docker-compose.staging.yml # Staging compose ⚠️ TO CREATE
|
||||
│ ├── .env # Production env
|
||||
│ ├── .env.staging # Staging env ⚠️ TO CREATE
|
||||
│ └── scripts/
|
||||
│ └── ops/ # ⚠️ TO CREATE
|
||||
│ ├── backup-db.sh # Database backup
|
||||
│ ├── backup-full.sh # Full backup (db + code + config)
|
||||
│ ├── restore-db.sh # Database restore
|
||||
│ ├── deploy-staging.sh # Deploy to staging
|
||||
│ ├── deploy-production.sh# Deploy to production
|
||||
│ ├── rollback.sh # Rollback deployment
|
||||
│ ├── health-check.sh # System health check
|
||||
│ ├── sync-prod-to-staging.sh # Sync data
|
||||
│ └── log-rotate.sh # Log rotation
|
||||
│
|
||||
├── backups/ # Backup storage
|
||||
│ ├── daily/ # Daily automated backups
|
||||
│ │ └── YYYYMMDD/
|
||||
│ │ ├── db_igny8_YYYYMMDD_HHMMSS.sql.gz
|
||||
│ │ └── config_YYYYMMDD.tar.gz
|
||||
│ ├── weekly/ # Weekly backups (kept 4 weeks)
|
||||
│ ├── monthly/ # Monthly backups (kept 12 months)
|
||||
│ └── pre-deploy/ # Pre-deployment snapshots
|
||||
│ └── YYYYMMDD_HHMMSS/
|
||||
│
|
||||
├── logs/ # Centralized logs
|
||||
│ ├── production/
|
||||
│ │ ├── backend.log
|
||||
│ │ ├── celery-worker.log
|
||||
│ │ ├── celery-beat.log
|
||||
│ │ └── access.log
|
||||
│ ├── staging/
|
||||
│ └── caddy/
|
||||
│
|
||||
└── stack/ # Infrastructure stack
|
||||
└── igny8-stack/ # (Future - not yet separated)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Automated Backup System
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
| Type | Frequency | Retention | Content |
|
||||
|------|-----------|-----------|---------|
|
||||
| **Daily** | 1:00 AM | 7 days | Database + configs |
|
||||
| **Weekly** | Sunday 2:00 AM | 4 weeks | Full backup |
|
||||
| **Monthly** | 1st of month | 12 months | Full backup |
|
||||
| **Pre-Deploy** | Before each deploy | 5 most recent | Database snapshot |
|
||||
|
||||
### Cron Schedule
|
||||
|
||||
```bash
|
||||
# /etc/cron.d/igny8-backup
|
||||
|
||||
# Daily database backup at 1:00 AM
|
||||
0 1 * * * root /data/app/igny8/scripts/ops/backup-db.sh daily >> /data/logs/backup.log 2>&1
|
||||
|
||||
# Weekly full backup on Sunday at 2:00 AM
|
||||
0 2 * * 0 root /data/app/igny8/scripts/ops/backup-full.sh weekly >> /data/logs/backup.log 2>&1
|
||||
|
||||
# Monthly full backup on 1st at 3:00 AM
|
||||
0 3 1 * * root /data/app/igny8/scripts/ops/backup-full.sh monthly >> /data/logs/backup.log 2>&1
|
||||
|
||||
# Health check every 5 minutes
|
||||
*/5 * * * * root /data/app/igny8/scripts/ops/health-check.sh >> /data/logs/health.log 2>&1
|
||||
|
||||
# Log rotation daily at midnight
|
||||
0 0 * * * root /data/app/igny8/scripts/ops/log-rotate.sh >> /data/logs/maintenance.log 2>&1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Environment Management
|
||||
|
||||
### Environment Comparison
|
||||
|
||||
| Aspect | Development | Staging | Production |
|
||||
|--------|-------------|---------|------------|
|
||||
| **Domain** | localhost:5173 | staging.igny8.com | app.igny8.com |
|
||||
| **API** | localhost:8010 | staging-api.igny8.com | api.igny8.com |
|
||||
| **Database** | igny8_dev_db | igny8_staging_db | igny8_db |
|
||||
| **Redis DB** | 2 | 1 | 0 |
|
||||
| **Debug** | True | False | False |
|
||||
| **AI Keys** | Test/Limited | Test/Limited | Production |
|
||||
| **Payments** | Sandbox | Sandbox | Live |
|
||||
| **Compose File** | docker-compose.dev.yml | docker-compose.staging.yml | docker-compose.app.yml |
|
||||
| **Project Name** | igny8-dev | igny8-staging | igny8-app |
|
||||
|
||||
### Port Allocation
|
||||
|
||||
| Service | Dev | Staging | Production |
|
||||
|---------|-----|---------|------------|
|
||||
| Backend | 8010 | 8012 | 8011 |
|
||||
| Frontend | 5173 | 8024 | 8021 |
|
||||
| Marketing | 5174 | 8026 | 8023 |
|
||||
| Flower | - | 5556 | 5555 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Workflow
|
||||
|
||||
### Safe Deployment Checklist
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DEPLOYMENT CHECKLIST │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ PRE-DEPLOYMENT │
|
||||
│ □ All tests passing on staging? │
|
||||
│ □ Database migrations reviewed? │
|
||||
│ □ Backup created? │
|
||||
│ □ Rollback plan ready? │
|
||||
│ □ Team notified? │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ DEPLOYMENT │
|
||||
│ □ Create pre-deploy backup │
|
||||
│ □ Tag current images for rollback │
|
||||
│ □ Pull latest code │
|
||||
│ □ Build new images │
|
||||
│ □ Apply migrations │
|
||||
│ □ Restart containers │
|
||||
│ □ Verify health check │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ POST-DEPLOYMENT │
|
||||
│ □ Monitor logs for 10 minutes │
|
||||
│ □ Test critical paths (login, API, AI functions) │
|
||||
│ □ Check error rates │
|
||||
│ □ If issues → ROLLBACK │
|
||||
│ □ Update changelog │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Git Branch Strategy
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ main │ ← Production deployments
|
||||
└────▲─────┘
|
||||
│ merge (after staging approval)
|
||||
┌────┴─────┐
|
||||
│ staging │ ← Staging deployments
|
||||
└────▲─────┘
|
||||
│ merge
|
||||
┌────────────────┼────────────────┐
|
||||
│ │ │
|
||||
┌───────┴───────┐ ┌──────┴──────┐ ┌───────┴───────┐
|
||||
│feature/xyz │ │feature/abc │ │hotfix/urgent │
|
||||
└───────────────┘ └─────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏥 Health Monitoring
|
||||
|
||||
### Health Check Endpoints
|
||||
|
||||
| Endpoint | Purpose | Expected Response |
|
||||
|----------|---------|-------------------|
|
||||
| `/api/v1/system/status/` | Overall system status | `{"status": "healthy"}` |
|
||||
| `/api/v1/system/health/` | Detailed component health | JSON with all components |
|
||||
|
||||
### Monitoring Targets
|
||||
|
||||
1. **Backend API** - Response time < 500ms
|
||||
2. **Database** - Connection pool healthy
|
||||
3. **Redis** - Connection alive
|
||||
4. **Celery Workers** - Queue length < 100
|
||||
5. **Celery Beat** - Scheduler running
|
||||
6. **Disk Space** - > 20% free
|
||||
7. **Memory** - < 80% used
|
||||
|
||||
### Alert Thresholds
|
||||
|
||||
| Metric | Warning | Critical |
|
||||
|--------|---------|----------|
|
||||
| API Response Time | > 1s | > 5s |
|
||||
| Error Rate | > 1% | > 5% |
|
||||
| CPU Usage | > 70% | > 90% |
|
||||
| Memory Usage | > 70% | > 90% |
|
||||
| Disk Usage | > 70% | > 90% |
|
||||
| Celery Queue | > 50 | > 200 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Common Operations
|
||||
|
||||
### Daily Operations
|
||||
|
||||
```bash
|
||||
# Check system health
|
||||
/data/app/igny8/scripts/ops/health-check.sh
|
||||
|
||||
# View logs
|
||||
tail -f /data/logs/production/backend.log
|
||||
|
||||
# Check container status
|
||||
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app ps
|
||||
```
|
||||
|
||||
### Weekly Operations
|
||||
|
||||
```bash
|
||||
# Review backup status
|
||||
ls -la /data/backups/daily/
|
||||
du -sh /data/backups/*
|
||||
|
||||
# Check disk space
|
||||
df -h
|
||||
|
||||
# Review error logs
|
||||
grep -i error /data/logs/production/backend.log | tail -50
|
||||
```
|
||||
|
||||
### Emergency Procedures
|
||||
|
||||
```bash
|
||||
# Immediate rollback
|
||||
/data/app/igny8/scripts/ops/rollback.sh
|
||||
|
||||
# Emergency restart
|
||||
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app restart
|
||||
|
||||
# Emergency database restore
|
||||
/data/app/igny8/scripts/ops/restore-db.sh /data/backups/latest.sql.gz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 What's Missing (Action Items)
|
||||
|
||||
### Priority 1 - Critical (Before Go-Live)
|
||||
|
||||
| Item | Status | Action |
|
||||
|------|--------|--------|
|
||||
| `docker-compose.staging.yml` | ❌ Missing | Create from documentation |
|
||||
| `.env.staging` | ❌ Missing | Create from example |
|
||||
| Deployment scripts | ❌ Missing | Create all ops scripts |
|
||||
| Automated backup cron | ❌ Missing | Set up cron jobs |
|
||||
| Pre-deploy backup | ❌ Missing | Add to deploy script |
|
||||
|
||||
### Priority 2 - Important (First Week)
|
||||
|
||||
| Item | Status | Action |
|
||||
|------|--------|--------|
|
||||
| Health check automation | ❌ Missing | Create monitoring |
|
||||
| Log rotation | ❌ Missing | Set up logrotate |
|
||||
| Staging DNS | ❌ Unknown | Configure if needed |
|
||||
| Caddyfile staging routes | ❌ Unknown | Add staging domains |
|
||||
|
||||
### Priority 3 - Nice to Have (First Month)
|
||||
|
||||
| Item | Status | Action |
|
||||
|------|--------|--------|
|
||||
| CI/CD pipeline | ❌ Not set | Optional automation |
|
||||
| External monitoring | ❌ Not set | UptimeRobot/Datadog |
|
||||
| Alerting system | ❌ Not set | Email/Slack alerts |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Create ops scripts directory**: `/data/app/igny8/scripts/ops/`
|
||||
2. **Create all deployment scripts** (see STAGING-SETUP-GUIDE.md)
|
||||
3. **Create staging compose file** (copy from documentation)
|
||||
4. **Set up automated backups**
|
||||
5. **Test complete deployment cycle** on staging
|
||||
6. **Go live with confidence**
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [STAGING-SETUP-GUIDE.md](final-clean-best-deployment-plan/STAGING-SETUP-GUIDE.md) - Detailed staging setup
|
||||
- [TWO-REPO-ARCHITECTURE.md](final-clean-best-deployment-plan/TWO-REPO-ARCHITECTURE.md) - Architecture overview
|
||||
- [INFRASTRUCTURE-STACK.md](final-clean-best-deployment-plan/INFRASTRUCTURE-STACK.md) - Stack details
|
||||
@@ -0,0 +1,74 @@
|
||||
# Deployment Guide
|
||||
|
||||
## Purpose
|
||||
Describe how to deploy the IGNY8 stack using the provided Dockerfiles and `docker-compose.app.yml`, including service wiring and required external dependencies.
|
||||
|
||||
## Code Locations (exact paths)
|
||||
- App compose stack: `docker-compose.app.yml`
|
||||
- Backend image: `backend/Dockerfile`
|
||||
- Frontend image: `frontend/Dockerfile`
|
||||
- Backend settings/env: `backend/igny8_core/settings.py`
|
||||
|
||||
## High-Level Responsibilities
|
||||
- Build images for backend, frontend, marketing, and sites renderer.
|
||||
- Bring up the app stack (backend, frontend, marketing, sites, Celery worker, Celery beat) on the shared external network.
|
||||
- Rely on external infra services (Postgres, Redis) defined outside this repo (referenced in compose comments).
|
||||
|
||||
## Detailed Behavior
|
||||
- Backend container:
|
||||
- Image `igny8-backend:latest` from `backend/Dockerfile` (Python 3.11 slim, installs `requirements.txt`, runs Gunicorn on 8010).
|
||||
- Mounted volumes: `/data/app/igny8/backend` (code), `/data/app/igny8` (shared), `/data/app/logs` (logs).
|
||||
- Env vars for DB/Redis and security flags (USE_SECURE_COOKIES/PROXY, DEBUG, SECRET_KEY).
|
||||
- Healthcheck hits `http://localhost:8010/api/v1/system/status/`.
|
||||
- Frontend container:
|
||||
- Image `igny8-frontend-dev:latest` from `frontend/Dockerfile.dev` (built separately; serves via Vite dev server on 5173 exposed as 8021).
|
||||
- Env `VITE_BACKEND_URL`.
|
||||
- Marketing dev and Sites renderer containers: images `igny8-marketing-dev:latest` and `igny8-sites-dev:latest`, ports 8023→5174 and 8024→5176; Sites mounts `/data/app/igny8/sites` and `/data/app/sites-data`.
|
||||
- Celery worker/beat:
|
||||
- Use `igny8-backend:latest`, commands `celery -A igny8_core worker` and `celery -A igny8_core beat`.
|
||||
- Share same DB/Redis env and code volumes.
|
||||
- Network: `igny8_net` marked `external: true`; compose expects Postgres and Redis running in another stack (`/data/app/docker-compose.yml` per comment).
|
||||
- Ports: backend 8011→8010, frontend 8021→5173, marketing 8023→5174, sites 8024→5176.
|
||||
|
||||
## Data Structures / Models Involved (no code)
|
||||
- Not model-specific; relies on runtime env (Postgres DB, Redis broker).
|
||||
|
||||
## Execution Flow
|
||||
- Build images:
|
||||
- `docker build -t igny8-backend:latest -f backend/Dockerfile backend`
|
||||
- `docker build -t igny8-frontend-dev:latest -f frontend/Dockerfile.dev frontend`
|
||||
- `docker build -t igny8-marketing-dev:latest -f frontend/Dockerfile.marketing.dev frontend`
|
||||
- `docker build -t igny8-sites-dev:latest -f sites/Dockerfile.dev sites`
|
||||
- Ensure external infra stack (Postgres, Redis, network `igny8_net`) is up.
|
||||
- Run: `docker compose -f docker-compose.app.yml -p igny8-app up -d`.
|
||||
- Healthcheck will mark backend healthy before frontend depends_on proceeds.
|
||||
|
||||
## Cross-Module Interactions
|
||||
- Backend depends on Postgres/Redis; Celery worker/beat rely on same env to process automation/AI tasks.
|
||||
- Frontend depends on backend health; sites renderer reads deployed sites from `/data/app/sites-data`.
|
||||
|
||||
## State Transitions (if applicable)
|
||||
- Container lifecycle managed by Docker restart policy (`restart: always`).
|
||||
|
||||
## Error Handling
|
||||
- Backend healthcheck fails if status endpoint not reachable; container marked unhealthy causing depends_on wait.
|
||||
- Gunicorn exit surfaces via Docker logs; no auto-restart beyond Docker restart policy.
|
||||
|
||||
## Tenancy Rules
|
||||
- Enforced in application layer via middleware; deployment does not alter tenancy.
|
||||
|
||||
## Billing Rules (if applicable)
|
||||
- None at deployment layer.
|
||||
|
||||
## Background Tasks / Schedulers (if applicable)
|
||||
- Celery beat runs schedules (e.g., automation scheduler) using same image and env.
|
||||
|
||||
## Key Design Considerations
|
||||
- Compose uses images (not builds) to avoid accidental rebuilds in Portainer; images must exist beforehand.
|
||||
- External network requirement means infra stack must pre-create `igny8_net` and services.
|
||||
- Backend served by Gunicorn with 4 workers per compose command; adjust via compose if scaling container horizontally.
|
||||
|
||||
## How Developers Should Work With This Module
|
||||
- When changing env vars, update `docker-compose.app.yml` and keep parity with `settings.py`.
|
||||
- For new services (e.g., monitoring), add to compose and attach to `igny8_net`.
|
||||
- Keep healthcheck endpoint stable (`/api/v1/system/status/`) or update compose accordingly.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Environment Setup
|
||||
|
||||
## Purpose
|
||||
Outline required runtime dependencies, environment variables, and local setup steps derived from the codebase configuration.
|
||||
|
||||
## Code Locations (exact paths)
|
||||
- Django settings/env: `backend/igny8_core/settings.py`
|
||||
- Backend dependencies: `backend/requirements.txt`
|
||||
- Backend image provisioning: `backend/Dockerfile`
|
||||
- Frontend env/build: `frontend/Dockerfile`, `frontend/package.json`, `frontend/vite.config.ts`
|
||||
- Compose stack env: `docker-compose.app.yml`
|
||||
|
||||
## High-Level Responsibilities
|
||||
- Provide prerequisites for backend (Python 3.11, Postgres, Redis) and frontend (Node 18).
|
||||
- Enumerate environment variables consumed by backend and compose files.
|
||||
- Describe local or containerized setup flows.
|
||||
|
||||
## Detailed Behavior
|
||||
- Backend settings require:
|
||||
- `SECRET_KEY`, `DEBUG`, `USE_SECURE_COOKIES`, `USE_SECURE_PROXY_HEADER`.
|
||||
- Database: `DATABASE_URL` or `DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`, `DB_PORT`; falls back to SQLite in DEBUG if none provided.
|
||||
- Redis/Celery: `CELERY_BROKER_URL`, `CELERY_RESULT_BACKEND` default to `redis://{REDIS_HOST}:{REDIS_PORT}/0`.
|
||||
- JWT: `JWT_SECRET_KEY`, expiry defaults (15m access, 30d refresh).
|
||||
- CORS: allowed origins include local ports (5173/5174/5176/8024) and `app.igny8.com`.
|
||||
- Stripe/PayPal keys optional (`STRIPE_PUBLIC_KEY`, `STRIPE_SECRET_KEY`, `PAYPAL_*`).
|
||||
- Backend Dockerfile installs system deps (gcc, libpq-dev) and pip installs `requirements.txt`; runs `collectstatic` (best-effort).
|
||||
- Frontend expects `VITE_BACKEND_URL` (compose sets to `https://api.igny8.com/api`); build via `npm install` then `npm run build` (Dockerfile).
|
||||
- Compose injects DB/Redis env to backend/worker/beat and secure cookie/proxy flags for production use.
|
||||
|
||||
## Data Structures / Models Involved (no code)
|
||||
- Not model-specific; environment affects DB connections, auth, CORS, Celery, billing keys.
|
||||
|
||||
## Execution Flow
|
||||
- Local (backend):
|
||||
- `python -m venv .venv && source .venv/bin/activate`
|
||||
- `pip install -r backend/requirements.txt`
|
||||
- Set env vars (DB/REDIS/JWT/CORS/SECRET_KEY).
|
||||
- `python backend/manage.py migrate` then `python backend/manage.py runserver 8010`.
|
||||
- Local (frontend):
|
||||
- `npm install` in `frontend/`
|
||||
- Set `VITE_BACKEND_URL`
|
||||
- `npm run dev -- --host --port 5173`
|
||||
- Containerized:
|
||||
- Build images per Dockerfiles; run `docker compose -f docker-compose.app.yml up -d` with external Postgres/Redis and network `igny8_net`.
|
||||
|
||||
## Cross-Module Interactions
|
||||
- Celery uses same Redis host/port as defined in env; automation tasks rely on this.
|
||||
- CORS/secure cookie flags must align with frontend host to allow auth.
|
||||
|
||||
## State Transitions (if applicable)
|
||||
- `DEBUG` toggles throttling bypass (`IGNY8_DEBUG_THROTTLE`) and SQLite fallback.
|
||||
|
||||
## Error Handling
|
||||
- Missing DB env → falls back to SQLite in DEBUG; in prod must set Postgres vars.
|
||||
- Healthcheck in compose will fail if env misconfigured and backend cannot start.
|
||||
|
||||
## Tenancy Rules
|
||||
- Unchanged by env; account context enforced in middleware once app runs.
|
||||
|
||||
## Billing Rules (if applicable)
|
||||
- Stripe/PayPal keys optional; without them payment flows are disabled/pending.
|
||||
|
||||
## Background Tasks / Schedulers (if applicable)
|
||||
- Celery broker/backend must be reachable; worker/beat require the same env set.
|
||||
|
||||
## Key Design Considerations
|
||||
- Prefer Postgres in all shared/test/prod; SQLite only for local development.
|
||||
- Keep SECRET_KEY/JWT keys distinct and secret in production.
|
||||
|
||||
## How Developers Should Work With This Module
|
||||
- Add new env variables in `settings.py` with safe defaults; document in this file.
|
||||
- Mirror envs in compose and deployment systems (Portainer/CI) to avoid drift.
|
||||
@@ -0,0 +1,974 @@
|
||||
# WordPress Integration & Publishing Flow - Complete Technical Documentation
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** Production Active
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [System Overview](#1-system-overview)
|
||||
2. [Integration Setup Flow](#2-integration-setup-flow)
|
||||
3. [Manual Publishing Flow](#3-manual-publishing-flow)
|
||||
4. [Automation Publishing Flow](#4-automation-publishing-flow)
|
||||
5. [Webhook Sync Flow (WordPress → IGNY8)](#5-webhook-sync-flow-wordpress--igny8)
|
||||
6. [Metadata Sync Flow](#6-metadata-sync-flow)
|
||||
7. [Data Models & Storage](#7-data-models--storage)
|
||||
8. [Plugin Distribution System](#8-plugin-distribution-system)
|
||||
9. [Current Implementation](#9-current-implementation)
|
||||
10. [Flow Diagrams](#10-flow-diagrams)
|
||||
|
||||
---
|
||||
|
||||
## 1. System Overview
|
||||
|
||||
### Architecture Summary
|
||||
|
||||
IGNY8 integrates with WordPress sites through a **custom WordPress plugin** (`igny8-wp-bridge`) that:
|
||||
- Receives content from IGNY8 via a custom REST endpoint (`/wp-json/igny8/v1/publish`)
|
||||
- Sends status updates back to IGNY8 via webhooks
|
||||
- **Authenticates using API key ONLY** (stored in Site.wp_api_key - single source of truth)
|
||||
- Auto-updates via IGNY8 plugin distribution system (v1.7.0+)
|
||||
- Supports advanced template rendering with image layouts
|
||||
- **No WordPress admin credentials required** (username/password authentication deprecated)
|
||||
|
||||
### Communication Pattern
|
||||
|
||||
```
|
||||
IGNY8 App ←→ WordPress Site
|
||||
│ │
|
||||
│ HTTP POST │
|
||||
├─────────────→│ Publish content via /wp-json/igny8/v1/publish
|
||||
│ │
|
||||
│ HTTP POST │
|
||||
│←─────────────┤ Webhook status updates via /api/v1/integration/webhooks/wordpress/status/
|
||||
│ │
|
||||
│ HTTP GET │
|
||||
├─────────────→│ Check plugin updates via /wp-json/igny8/v1/check-update
|
||||
│ │
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| SiteIntegration Model | `business/integration/models.py` | Stores WordPress credentials & config |
|
||||
| SyncEvent Model | `business/integration/models.py` | Logs all sync operations |
|
||||
| Plugin Models | `plugins/models.py` | Plugin versioning and distribution |
|
||||
| Celery Task | `tasks/wordpress_publishing.py` | Background publishing worker |
|
||||
| Webhook Handler | `modules/integration/webhooks.py` | Receives WordPress status updates |
|
||||
| Frontend Form | `components/sites/WordPressIntegrationForm.tsx` | User setup UI |
|
||||
|
||||
### Recent Updates (v1.7.0)
|
||||
|
||||
**Plugin Distribution System:**
|
||||
- ✅ Automated plugin distribution and updates
|
||||
- ✅ WordPress plugin v1.3.3 with template improvements
|
||||
- ✅ Image layout fixes (square/landscape positioning)
|
||||
- ✅ Auto-update mechanism via WordPress hooks
|
||||
- ✅ Health check and monitoring endpoints
|
||||
|
||||
---
|
||||
|
||||
## 2. Integration Setup Flow
|
||||
|
||||
### 2.1 Pre-requisites (WordPress Side)
|
||||
|
||||
1. WordPress 5.6+ with REST API enabled
|
||||
2. Pretty permalinks enabled (Settings → Permalinks)
|
||||
3. IGNY8 WordPress Bridge plugin v1.3.0+ installed and activated
|
||||
4. No security plugins blocking REST API
|
||||
|
||||
### 2.2 Plugin Installation (Updated v1.7.0)
|
||||
|
||||
**Option 1: Manual Download & Install**
|
||||
- User downloads latest plugin from IGNY8 app
|
||||
- Frontend: Site Settings → WordPress Integration → Download Plugin
|
||||
- Download endpoint: `https://api.igny8.com/api/plugins/igny8-wp-bridge/download/`
|
||||
- Installs in WordPress: Plugins → Add New → Upload ZIP
|
||||
|
||||
**Option 2: Direct URL Install**
|
||||
- WordPress admin → Plugins → Add New → Upload Plugin
|
||||
- Paste ZIP URL with signed download token
|
||||
- Plugin installs and auto-registers with IGNY8
|
||||
|
||||
**Plugin Auto-Update:**
|
||||
- WordPress checks for updates every 12 hours
|
||||
- Plugin hooks into `pre_set_site_transient_update_plugins`
|
||||
- Compares current version with latest from IGNY8 API
|
||||
- Notifies admin if update available
|
||||
- Can be updated via WordPress admin (one-click)
|
||||
|
||||
### 2.3 Setup Steps (User Flow)
|
||||
|
||||
**Step 1: User navigates to Site Settings**
|
||||
- Frontend: `/sites/{id}/settings` → WordPress Integration section
|
||||
- Component: `WordPressIntegrationForm.tsx`
|
||||
|
||||
**Step 2: User clicks "Generate API Key"**
|
||||
- Frontend calls: `POST /v1/integration/integrations/generate-api-key/`
|
||||
- Body: `{ "site_id": 123 }`
|
||||
- Backend stores API key in `Site.wp_api_key` field (SINGLE source of truth)
|
||||
- Creates/updates `SiteIntegration` record with empty credentials_json
|
||||
|
||||
**Step 3: User configures WordPress plugin**
|
||||
- Configures plugin with:
|
||||
- IGNY8 API URL: `https://api.igny8.com`
|
||||
- Site API Key: (copied from IGNY8)
|
||||
- Site ID: (shown in IGNY8)
|
||||
- **Note:** No WordPress admin credentials needed
|
||||
|
||||
**Step 4: Test Connection**
|
||||
- Plugin calls: `POST https://api.igny8.com/api/v1/integration/integrations/test-connection/`
|
||||
- Headers: `Authorization: Bearer {api_key}`
|
||||
- Body: `{ "site_id": 123, "api_key": "...", "site_url": "https://..." }`
|
||||
- Backend validates API key against `Site.wp_api_key`
|
||||
- Success: SiteIntegration created with empty credentials_json, plugin registers installation
|
||||
- Failure: Error message displayed
|
||||
|
||||
### 2.4 Data Created During Setup
|
||||
|
||||
**SiteIntegration Record:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"site_id": 123,
|
||||
"account_id": 456,
|
||||
"platform": "wordpress",
|
||||
"platform_type": "cms",
|
||||
"config_json": {
|
||||
"site_url": "https://example.com"
|
||||
},
|
||||
"credentials_json": {
|
||||
"plugin_version": "1.3.4",
|
||||
"debug_enabled": false
|
||||
},
|
||||
"is_active": true,
|
||||
"sync_enabled": true,
|
||||
"sync_status": "pending",
|
||||
"_note": "API key stored in Site.wp_api_key, not in credentials_json"
|
||||
}
|
||||
```
|
||||
|
||||
**Site Model (API Key Storage):**
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"name": "Example Site",
|
||||
"url": "https://example.com",
|
||||
"wp_api_key": "igny8_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"hosting_type": "wordpress"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2.5 Authentication Architecture (v1.3.4+)
|
||||
|
||||
### API Key as Single Source of Truth
|
||||
|
||||
**Storage:**
|
||||
- API key stored in `Site.wp_api_key` field (Django backend)
|
||||
- Plugin stores same API key in WordPress options table: `igny8_api_key`
|
||||
- SiteIntegration.credentials_json does NOT contain API key
|
||||
|
||||
**Authentication Flow (IGNY8 → WordPress):**
|
||||
```python
|
||||
# Backend: publisher_service.py
|
||||
destination_config = {
|
||||
'site_url': integration.config_json.get('site_url'),
|
||||
'api_key': integration.site.wp_api_key # From Site model
|
||||
}
|
||||
|
||||
# WordPress Adapter: wordpress_adapter.py
|
||||
headers = {
|
||||
'Authorization': f'Bearer {api_key}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
requests.post(f"{site_url}/wp-json/igny8/v1/publish", headers=headers, json=payload)
|
||||
```
|
||||
|
||||
**Authentication Flow (WordPress → IGNY8):**
|
||||
```php
|
||||
// Plugin: class-igny8-api.php
|
||||
$api_key = get_option('igny8_api_key');
|
||||
$headers = array(
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
'Content-Type' => 'application/json'
|
||||
);
|
||||
wp_remote_post('https://api.igny8.com/api/v1/...', array('headers' => $headers));
|
||||
```
|
||||
|
||||
**Validation (WordPress Side):**
|
||||
```php
|
||||
// Plugin: class-igny8-rest-api.php
|
||||
public function check_permission($request) {
|
||||
// Check X-IGNY8-API-KEY header
|
||||
$header_api_key = $request->get_header('x-igny8-api-key');
|
||||
|
||||
// Check Authorization Bearer header
|
||||
$auth_header = $request->get_header('Authorization');
|
||||
|
||||
$stored_api_key = get_option('igny8_api_key');
|
||||
|
||||
if (hash_equals($stored_api_key, $header_api_key) ||
|
||||
strpos($auth_header, 'Bearer ' . $stored_api_key) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error('rest_forbidden', 'Invalid API key', array('status' => 401));
|
||||
}
|
||||
```
|
||||
|
||||
### Deprecated Authentication Methods
|
||||
|
||||
**No Longer Supported (removed in v1.3.4):**
|
||||
- ❌ Username/password authentication
|
||||
- ❌ App passwords via WordPress REST API
|
||||
- ❌ OAuth/token exchange
|
||||
- ❌ Webhook signature validation (webhooks deprecated)
|
||||
- ❌ Storing API key in SiteIntegration.credentials_json
|
||||
|
||||
**Legacy Fields (do not use):**
|
||||
- `Site.wp_username` - deprecated
|
||||
- `Site.wp_app_password` - deprecated
|
||||
- `Site.wp_url` - deprecated (use SiteIntegration.config_json.site_url)
|
||||
|
||||
---
|
||||
|
||||
## 3. Manual Publishing Flow
|
||||
|
||||
### 3.1 Trigger Points
|
||||
|
||||
1. **Content Review Page** - "Publish to WordPress" button
|
||||
2. **Content Approved Page** - Publish action in context menu
|
||||
3. **Bulk Actions** - Select multiple, publish all
|
||||
|
||||
### 3.2 Detailed Flow
|
||||
|
||||
**Step 1: User clicks "Publish to WordPress"**
|
||||
- Frontend: `ContentViewSet.publish` action called
|
||||
- Endpoint: `POST /api/v1/writer/content/{id}/publish/`
|
||||
- Optional body: `{ "site_integration_id": 123 }`
|
||||
|
||||
**Step 2: Backend validates content**
|
||||
- Checks content exists and belongs to user's site
|
||||
- Checks content not already published (`external_id` must be null)
|
||||
- Finds active WordPress integration for the site
|
||||
- Error if no integration found
|
||||
|
||||
**Step 3: Optimistic status update**
|
||||
- Content status changed to `published` immediately
|
||||
- User sees success message
|
||||
- Actual WordPress publishing happens async
|
||||
|
||||
**Step 4: Celery task queued**
|
||||
- Task: `publish_content_to_wordpress.delay(content_id, site_integration_id)`
|
||||
- Task name: `igny8_core.tasks.wordpress_publishing`
|
||||
|
||||
**Step 5: Celery task execution (Background)**
|
||||
|
||||
The task performs these steps:
|
||||
|
||||
1. **Load data** - Get Content and SiteIntegration from database
|
||||
2. **Check if already published** - Skip if `external_id` exists
|
||||
3. **Generate excerpt** - Strip HTML, take first 150 chars
|
||||
4. **Load taxonomy terms** - Categories from `taxonomy_terms` M2M field, fallback to cluster name
|
||||
5. **Load tags** - From `taxonomy_terms` + primary/secondary keywords
|
||||
6. **Load images** - Featured image and gallery images, convert paths to URLs
|
||||
7. **Build payload:**
|
||||
```json
|
||||
{
|
||||
"content_id": 123,
|
||||
"title": "Post Title",
|
||||
"content_html": "<p>Full HTML content...</p>",
|
||||
"excerpt": "Short excerpt...",
|
||||
"status": "publish",
|
||||
"seo_title": "SEO Title",
|
||||
"seo_description": "Meta description",
|
||||
"primary_keyword": "main keyword",
|
||||
"secondary_keywords": ["kw1", "kw2"],
|
||||
"featured_image_url": "https://app.igny8.com/images/...",
|
||||
"gallery_images": [{ "url": "...", "alt": "", "caption": "" }],
|
||||
"categories": ["Category Name"],
|
||||
"tags": ["tag1", "tag2"]
|
||||
}
|
||||
```
|
||||
8. **Send to WordPress:**
|
||||
- URL: `{site_url}/wp-json/igny8/v1/publish`
|
||||
- Headers: `X-IGNY8-API-Key: {api_key}`
|
||||
- Method: POST
|
||||
- Timeout: 30 seconds
|
||||
|
||||
**Step 6: Process WordPress response**
|
||||
|
||||
| HTTP Code | Meaning | Action |
|
||||
|-----------|---------|--------|
|
||||
| 201 | Created | Update content with `external_id`, `external_url`, log success |
|
||||
| 409 | Already exists | Update content with existing WordPress post data |
|
||||
| 4xx/5xx | Error | Log failure, retry up to 3 times with exponential backoff |
|
||||
|
||||
**Step 7: Update IGNY8 content record**
|
||||
```python
|
||||
content.external_id = str(wp_post_id)
|
||||
content.external_url = wp_post_url
|
||||
content.status = 'published'
|
||||
content.metadata['wordpress_status'] = 'publish'
|
||||
content.save()
|
||||
```
|
||||
|
||||
**Step 8: Create SyncEvent record**
|
||||
```python
|
||||
SyncEvent.objects.create(
|
||||
integration=site_integration,
|
||||
site=content.site,
|
||||
account=content.account,
|
||||
event_type='publish',
|
||||
action='content_publish',
|
||||
description=f"Published content '{title}' to WordPress",
|
||||
success=True,
|
||||
content_id=content.id,
|
||||
external_id=external_id,
|
||||
details={...}
|
||||
)
|
||||
```
|
||||
|
||||
### 3.3 Error Handling
|
||||
|
||||
- **Timeout**: Retry after 60 seconds (first attempt), then exponentially
|
||||
- **Connection Error**: Same retry logic
|
||||
- **Max Retries**: 3 attempts total
|
||||
- **Final Failure**: SyncEvent created with `success=False`, error logged
|
||||
|
||||
---
|
||||
|
||||
## 4. Automation Publishing Flow
|
||||
|
||||
### 4.1 Automation Stage 7: Review → Published
|
||||
|
||||
Automation Stage 7 handles auto-approval and publishing:
|
||||
|
||||
**Current Implementation:**
|
||||
- Content in `review` status is changed to `published` status
|
||||
- Status change only - **NO automatic WordPress push currently implemented**
|
||||
- Lock released, automation run marked complete
|
||||
|
||||
**What DOES NOT happen automatically:**
|
||||
- No `publish_content_to_wordpress` task is triggered
|
||||
- Content sits in `published` status waiting for manual publish or scheduled task
|
||||
|
||||
### 4.2 Scheduled Publishing Task (NOT CURRENTLY SCHEDULED)
|
||||
|
||||
There exists a task `process_pending_wordpress_publications()` that:
|
||||
- Finds content with `status='published'` and `external_id=NULL`
|
||||
- Queues each item to `publish_content_to_wordpress` task
|
||||
- Processes max 50 items per run
|
||||
|
||||
**CURRENT STATUS: This task is NOT in Celery Beat schedule!**
|
||||
|
||||
Looking at `celery.py`, the beat schedule includes:
|
||||
- `check-scheduled-automations` (hourly)
|
||||
- `replenish-monthly-credits` (monthly)
|
||||
- Various maintenance tasks
|
||||
|
||||
**Missing:**
|
||||
- `process_pending_wordpress_publications` is NOT scheduled
|
||||
|
||||
### 4.3 To Enable Auto-Publishing After Automation
|
||||
|
||||
Two options:
|
||||
|
||||
**Option A: Add to Celery Beat Schedule**
|
||||
```python
|
||||
'process-pending-wordpress-publications': {
|
||||
'task': 'igny8_core.tasks.wordpress_publishing.process_pending_wordpress_publications',
|
||||
'schedule': crontab(minute='*/5'), # Every 5 minutes
|
||||
},
|
||||
```
|
||||
|
||||
**Option B: Call directly from Stage 7**
|
||||
Modify `run_stage_7()` to queue publish tasks for each approved content item.
|
||||
|
||||
---
|
||||
|
||||
## 5. Webhook Sync Flow (WordPress → IGNY8)
|
||||
|
||||
### 5.1 Purpose
|
||||
|
||||
Keeps IGNY8 in sync when content is modified directly in WordPress:
|
||||
- Post status changed (published → draft, etc.)
|
||||
- Post deleted/trashed
|
||||
- Post metadata updated
|
||||
|
||||
### 5.2 Webhook Endpoints
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `POST /api/v1/integration/webhooks/wordpress/status/` | Status changes |
|
||||
| `POST /api/v1/integration/webhooks/wordpress/metadata/` | Metadata updates |
|
||||
|
||||
### 5.3 Status Webhook Flow
|
||||
|
||||
**Step 1: WordPress plugin detects status change**
|
||||
- Hooks into `transition_post_status` action
|
||||
- Collects: post_id, content_id, new_status, post_url
|
||||
|
||||
**Step 2: Plugin sends webhook to IGNY8**
|
||||
```
|
||||
POST https://app.igny8.com/api/v1/integration/webhooks/wordpress/status/
|
||||
|
||||
Headers:
|
||||
X-IGNY8-API-KEY: {api_key}
|
||||
|
||||
Body:
|
||||
{
|
||||
"post_id": 123,
|
||||
"content_id": 456,
|
||||
"post_status": "publish",
|
||||
"post_url": "https://example.com/my-post/",
|
||||
"post_title": "My Post Title",
|
||||
"site_url": "https://example.com"
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: IGNY8 validates and processes**
|
||||
|
||||
1. Extract API key from header
|
||||
2. Find Content by `content_id`
|
||||
3. Find SiteIntegration by site_url + platform
|
||||
4. Verify API key matches stored key
|
||||
5. Map WordPress status to IGNY8 status:
|
||||
| WordPress | IGNY8 |
|
||||
|-----------|-------|
|
||||
| publish | published |
|
||||
| draft | draft |
|
||||
| pending | review |
|
||||
| private | published |
|
||||
| trash | draft |
|
||||
| future | review |
|
||||
6. Update Content record:
|
||||
```python
|
||||
content.external_id = str(post_id)
|
||||
content.external_url = post_url
|
||||
content.status = mapped_status
|
||||
content.metadata['wordpress_status'] = post_status
|
||||
content.metadata['last_wp_sync'] = now
|
||||
content.save()
|
||||
```
|
||||
7. Create SyncEvent log
|
||||
|
||||
### 5.4 Metadata Webhook Flow
|
||||
|
||||
Similar to status webhook but updates `content.metadata['wp_metadata']` with:
|
||||
- Categories
|
||||
- Tags
|
||||
- Author info
|
||||
- Modified date
|
||||
|
||||
---
|
||||
|
||||
## 6. Metadata Sync Flow
|
||||
|
||||
### 6.1 Manual Sync (User-Initiated)
|
||||
|
||||
**Trigger:** User clicks "Sync Now" in Site Settings
|
||||
|
||||
**Endpoint:** `POST /api/v1/integration/integrations/{id}/sync/`
|
||||
|
||||
**What it does:**
|
||||
- Calls `SyncMetadataService.sync_wordpress_structure()`
|
||||
- Fetches from WordPress:
|
||||
- Post types and counts
|
||||
- Categories list
|
||||
- Tags list
|
||||
- Site metadata
|
||||
- Updates integration's `last_sync_at`
|
||||
- Does NOT push/pull content
|
||||
|
||||
### 6.2 Structure Update
|
||||
|
||||
**Endpoint:** `POST /api/v1/integration/integrations/{id}/update_structure/`
|
||||
|
||||
Refreshes understanding of WordPress site:
|
||||
- Available post types
|
||||
- Taxonomy structures
|
||||
- Capabilities
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Models & Storage
|
||||
|
||||
### 7.1 SiteIntegration
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| id | AutoField | Primary key |
|
||||
| account | FK(Account) | Owner account |
|
||||
| site | FK(Site) | IGNY8 site (contains wp_api_key) |
|
||||
| platform | CharField | 'wordpress' |
|
||||
| platform_type | CharField | 'cms' |
|
||||
| config_json | JSONField | `{ "site_url": "https://..." }` |
|
||||
| credentials_json | JSONField | `{ "plugin_version": "1.3.4", "debug_enabled": false }` |
|
||||
| is_active | Boolean | Connection enabled |
|
||||
| sync_enabled | Boolean | Two-way sync enabled |
|
||||
| last_sync_at | DateTime | Last successful sync |
|
||||
| sync_status | CharField | pending/success/failed/syncing |
|
||||
| sync_error | TextField | Last error message |
|
||||
|
||||
**Note:** `credentials_json` no longer stores API key. API key is stored in `Site.wp_api_key` (single source of truth).
|
||||
|
||||
### 7.1a Site Model (API Key Storage)
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|-------|
|
||||
| id | AutoField | Primary key |
|
||||
| account | FK(Account) | Owner account |
|
||||
| name | CharField | Site display name |
|
||||
| url | URLField | WordPress site URL |
|
||||
| wp_api_key | CharField | **API key for WordPress integration (SINGLE source of truth)** |
|
||||
| wp_url | URLField | Legacy field (deprecated) |
|
||||
| wp_username | CharField | Legacy field (deprecated) |
|
||||
| wp_app_password | CharField | Legacy field (deprecated) |
|
||||
| hosting_type | CharField | 'wordpress', 'shopify', 'igny8_sites', 'multi' |
|
||||
|
||||
### 7.2 Plugin Models
|
||||
|
||||
#### Plugin
|
||||
|
||||
Core plugin registry (platform-agnostic).
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|-------|
|
||||
| id | AutoField | Primary key |
|
||||
| name | CharField | Plugin display name (e.g., "IGNY8 WordPress Bridge") |
|
||||
| slug | SlugField | URL-safe identifier (e.g., "igny8-wp-bridge") |
|
||||
| platform | CharField | Target platform ('wordpress', 'shopify', etc.) |
|
||||
| description | TextField | Plugin description |
|
||||
| author | CharField | Plugin author |
|
||||
| author_url | URLField | Author website |
|
||||
| plugin_url | URLField | Plugin homepage |
|
||||
| icon_url | URLField | Plugin icon (256x256) |
|
||||
| banner_url | URLField | Plugin banner (772x250) |
|
||||
| is_active | Boolean | Whether plugin is available for download |
|
||||
| created_at | DateTime | Record creation |
|
||||
| updated_at | DateTime | Last modified |
|
||||
|
||||
**Current WordPress Plugin:**
|
||||
- Name: "IGNY8 WordPress Bridge"
|
||||
- Slug: "igny8-wp-bridge"
|
||||
- Platform: "wordpress"
|
||||
- Description: "Connect your WordPress site to IGNY8 for AI-powered content publishing, SEO optimization, and seamless automation."
|
||||
- Author: "IGNY8 Team"
|
||||
|
||||
#### PluginVersion
|
||||
|
||||
Version tracking with distribution files.
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|-------|
|
||||
| id | AutoField | Primary key |
|
||||
| plugin | FK(Plugin) | Parent plugin |
|
||||
| version | CharField | Semantic version (e.g., "1.3.4") |
|
||||
| status | CharField | 'development', 'beta', 'released', 'deprecated' |
|
||||
| release_notes | TextField | Changelog/release notes |
|
||||
| file_path | CharField | Path to ZIP file in /plugins/{platform}/dist/ |
|
||||
| file_size | BigInteger | ZIP file size in bytes |
|
||||
| checksum_md5 | CharField | MD5 hash for verification |
|
||||
| checksum_sha256 | CharField | SHA256 hash for verification |
|
||||
| requires_version | CharField | Minimum platform version (e.g., WP 5.6+) |
|
||||
| tested_version | CharField | Tested up to version |
|
||||
| is_latest | Boolean | Whether this is the latest stable version |
|
||||
| download_count | Integer | Number of downloads |
|
||||
| released_at | DateTime | Public release date |
|
||||
| created_at | DateTime | Record creation |
|
||||
|
||||
**Current Latest Version (1.3.4):**
|
||||
- Status: "released"
|
||||
- File: `/plugins/wordpress/dist/igny8-wp-bridge-1.3.4.zip`
|
||||
- Requires: WordPress 5.6+
|
||||
- Tested: WordPress 6.4
|
||||
- Features: API key authentication only, template improvements, image layout fixes
|
||||
|
||||
#### PluginInstallation
|
||||
|
||||
Tracks plugin installations per site.
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|-------|
|
||||
| id | AutoField | Primary key |
|
||||
| site | FK(Site) | Site where plugin is installed |
|
||||
| plugin_version | FK(PluginVersion) | Installed version |
|
||||
| installed_at | DateTime | Installation timestamp |
|
||||
| last_seen | DateTime | Last health check |
|
||||
| status | CharField | 'active', 'inactive', 'error' |
|
||||
| metadata | JSONField | Installation-specific data (PHP version, WP version, etc.) |
|
||||
|
||||
#### PluginDownload
|
||||
|
||||
Download analytics.
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|-------|
|
||||
| id | AutoField | Primary key |
|
||||
| plugin_version | FK(PluginVersion) | Downloaded version |
|
||||
| site | FK(Site, null=True) | Site that downloaded (if authenticated) |
|
||||
| ip_address | GenericIPAddressField | Downloader IP |
|
||||
| user_agent | TextField | Browser/client info |
|
||||
| downloaded_at | DateTime | Download timestamp |
|
||||
|
||||
### 7.3 SyncEvent
|
||||
|
||||
| Field | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| id | AutoField | Primary key |
|
||||
| integration | FK(SiteIntegration) | Parent integration |
|
||||
| site | FK(Site) | Related site |
|
||||
| account | FK(Account) | Owner account |
|
||||
| event_type | CharField | publish/sync/error/webhook/metadata_sync |
|
||||
| action | CharField | content_publish/status_update/etc |
|
||||
| description | TextField | Human-readable event description |
|
||||
| success | Boolean | Event outcome |
|
||||
| content_id | Integer | IGNY8 content ID |
|
||||
| external_id | CharField | WordPress post ID |
|
||||
| error_message | TextField | Error details if failed |
|
||||
| details | JSONField | Additional event data |
|
||||
| duration_ms | Integer | Operation duration |
|
||||
| created_at | DateTime | Event timestamp |
|
||||
|
||||
### 7.3 Content Fields (Publishing Related)
|
||||
|
||||
| Field | Purpose |
|
||||
|-------|---------|
|
||||
| external_id | WordPress post ID (string) |
|
||||
| external_url | WordPress post URL |
|
||||
| status | IGNY8 status (draft/review/published) |
|
||||
| metadata['wordpress_status'] | WordPress status (publish/draft/etc) |
|
||||
| metadata['last_wp_sync'] | Last webhook sync timestamp |
|
||||
|
||||
---
|
||||
|
||||
## 8. Current Implementation Gaps
|
||||
|
||||
### 8.1 Missing: Scheduled Auto-Publishing
|
||||
|
||||
**Problem:** Content approved by Automation Stage 7 is not automatically pushed to WordPress.
|
||||
|
||||
**Current State:**
|
||||
- `process_pending_wordpress_publications()` task exists
|
||||
- Task is NOT in Celery Beat schedule
|
||||
- Content remains in `published` status with `external_id=NULL`
|
||||
|
||||
**Impact:** Users must manually publish each content item even after automation completes.
|
||||
|
||||
### 8.2 Missing: Pull Sync (WordPress → IGNY8 Content)
|
||||
|
||||
**Problem:** No way to import existing WordPress posts into IGNY8.
|
||||
|
||||
**Current State:**
|
||||
- Webhooks only handle status updates for content that originated from IGNY8
|
||||
- No "Import from WordPress" feature
|
||||
- No scheduled pull sync
|
||||
|
||||
### 8.3 Missing: Content Update Sync
|
||||
|
||||
**Problem:** Editing content in IGNY8 after publishing doesn't update WordPress.
|
||||
|
||||
**Current State:**
|
||||
- Only initial publish is supported
|
||||
- No "republish" or "update" functionality
|
||||
- `external_id` check prevents re-publishing
|
||||
|
||||
### 8.4 Missing: Image Upload to WordPress
|
||||
|
||||
**Problem:** Featured images are passed as URLs, not uploaded to WordPress media library.
|
||||
|
||||
**Current State:**
|
||||
- Image URL is sent in payload
|
||||
- WordPress plugin must download and create attachment
|
||||
- If plugin doesn't handle this, no featured image
|
||||
|
||||
---
|
||||
|
||||
## 8. Plugin Distribution System
|
||||
|
||||
### 8.1 Architecture (v1.7.0)
|
||||
|
||||
**Plugin Distribution Components:**
|
||||
- `Plugin` model - Multi-platform plugin registry
|
||||
- `PluginVersion` model - Version tracking with files and checksums
|
||||
- `PluginInstallation` model - Installation tracking per site
|
||||
- `PluginDownload` model - Download analytics
|
||||
|
||||
**Distribution Endpoints:**
|
||||
```
|
||||
GET /api/plugins/igny8-wp-bridge/download/ - Download latest ZIP
|
||||
POST /api/plugins/igny8-wp-bridge/check-update/ - Check for updates
|
||||
GET /api/plugins/igny8-wp-bridge/info/ - Plugin metadata
|
||||
POST /api/plugins/igny8-wp-bridge/register/ - Register installation
|
||||
POST /api/plugins/igny8-wp-bridge/health-check/ - Health monitoring
|
||||
```
|
||||
|
||||
### 8.2 Auto-Update Mechanism
|
||||
|
||||
**WordPress Side:**
|
||||
1. Plugin hooks into `pre_set_site_transient_update_plugins`
|
||||
2. Calls `/api/plugins/igny8-wp-bridge/check-update/` with current version
|
||||
3. Receives update info if newer version available
|
||||
4. WordPress displays update notification
|
||||
5. User clicks "Update Now" (or auto-update runs)
|
||||
6. WordPress downloads ZIP from `/download/` endpoint
|
||||
7. Installs and activates new version
|
||||
|
||||
**IGNY8 Side:**
|
||||
1. Developer updates plugin source code
|
||||
2. Creates new `PluginVersion` in Django admin
|
||||
3. Changes status to "released"
|
||||
4. Signal automatically builds ZIP with checksums
|
||||
5. Files stored in `/plugins/wordpress/dist/`
|
||||
6. WordPress sites can now download/update
|
||||
|
||||
### 8.3 Version History (Recent)
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|| 1.3.4 | Jan 12, 2026 | **API key authentication only** (removed username/password support), webhooks deprecated, Bearer token auth, simplified integration || 1.3.3 | Jan 10, 2026 | Template design: Square image grid fixes, landscape positioning, direct styling for images without captions |
|
||||
| 1.3.2 | Jan 9, 2026 | Template rendering improvements, image layout enhancements |
|
||||
| 1.3.1 | Jan 9, 2026 | Plugin versioning updates |
|
||||
| 1.3.0 | Jan 8, 2026 | Distribution system release, auto-update mechanism |
|
||||
|
||||
### 8.4 Security Features
|
||||
|
||||
- **Signed URLs:** Download links expire after configurable time
|
||||
- **Checksums:** MD5 and SHA256 verification
|
||||
- **Rate Limiting:** Per IP/site download limits
|
||||
- **API Authentication:** Required for sensitive operations
|
||||
- **Version Verification:** WordPress validates plugin before update
|
||||
|
||||
### 8.5 Monitoring
|
||||
|
||||
**Installation Tracking:**
|
||||
- Total installations per version
|
||||
- Active installations by site
|
||||
- Version distribution analytics
|
||||
|
||||
**Download Analytics:**
|
||||
- Download counts per version
|
||||
- Geographic distribution
|
||||
- Failed download attempts
|
||||
|
||||
**Health Checks:**
|
||||
- Plugin health status per installation
|
||||
- Error reporting from WordPress sites
|
||||
- Version compatibility tracking
|
||||
|
||||
---
|
||||
|
||||
## 9. Current Implementation
|
||||
|
||||
### 9.1 Production Status (v1.7.0)
|
||||
|
||||
**✅ Fully Operational:**
|
||||
- WordPress plugin distribution system
|
||||
- Auto-update mechanism
|
||||
- Template rendering with advanced layouts
|
||||
- Image positioning (square/landscape)
|
||||
- Content publishing (manual + automation)
|
||||
- Webhook status sync
|
||||
- API authentication
|
||||
- Health monitoring
|
||||
|
||||
**✅ Template Features (v1.3.3):**
|
||||
- Square images: Side-by-side layout (left/right aligned)
|
||||
- Landscape images: Full-width with max 1024px
|
||||
- Images appear after first paragraph in sections
|
||||
- Direct border-radius/shadow on images without captions
|
||||
- Responsive design for mobile/tablet
|
||||
|
||||
### 9.2 Known Limitations
|
||||
|
||||
**Missing Features:**
|
||||
- Bi-directional content sync (WordPress → IGNY8 editing)
|
||||
- Conflict resolution for dual edits
|
||||
- Advanced metadata sync beyond basics
|
||||
- Multi-site WordPress network support
|
||||
- Custom post type publishing
|
||||
|
||||
### 9.3 Future Enhancements
|
||||
|
||||
**Planned:**
|
||||
- Image regeneration feature (Phase 9)
|
||||
- Advanced template customization
|
||||
- Custom field mapping
|
||||
- Bulk publishing operations
|
||||
- Real-time sync notifications
|
||||
|
||||
---
|
||||
|
||||
## 10. Flow Diagrams
|
||||
|
||||
### 10.1 Integration Setup (API Key Authentication)
|
||||
|
||||
```
|
||||
┌──────────┐ ┌─────────────────┐ ┌───────────────┐
|
||||
│ User │ │ IGNY8 API │ │ WordPress │
|
||||
│ │ │ (Backend) │ │ Site │
|
||||
└────┬─────┘ └────────┬────────┘ └───────┬───────┘
|
||||
│ │ │
|
||||
│ 1. Generate API Key (Site Settings) │
|
||||
├───────────────────>│ │
|
||||
│ │ Store in Site.wp_api_key
|
||||
│<───────────────────┤ (SINGLE source) │
|
||||
│ 2. API Key: igny8_live_xxxxx │
|
||||
│ │ │
|
||||
│ 3. Download Plugin ZIP │
|
||||
├───────────────────>│ │
|
||||
│ │ GET /api/plugins/ │
|
||||
│ │ igny8-wp-bridge/ │
|
||||
│ │ download/ │
|
||||
│<───────────────────┤ │
|
||||
│ 4. igny8-wp-bridge-1.3.4.zip │
|
||||
│ │ │
|
||||
│ 5. Install & Activate Plugin──────────────┼────────>
|
||||
│ │ │
|
||||
│ 6. Enter API Key + Site ID in WP Settings─┼────────>
|
||||
│ │ │
|
||||
│ 7. Click "Test Connection" in Plugin──────┼────────>
|
||||
│ │ │
|
||||
│ │ 8. POST /api/v1/ │
|
||||
│ │ integration/ │
|
||||
│ │ integrations/ │
|
||||
│ │ test-connection/ │
|
||||
│ │<─────────────────────┤
|
||||
│ │ Headers: │
|
||||
│ │ Authorization: │
|
||||
│ │ Bearer {api_key} │
|
||||
│ │ │
|
||||
│ │ Validate against │
|
||||
│ │ Site.wp_api_key │
|
||||
│ │ │
|
||||
│ │ Create/Update │
|
||||
│ │ SiteIntegration │
|
||||
│ │ (credentials_json={})│
|
||||
│ │ │
|
||||
│ │ 9. 200 OK │
|
||||
│ ├─────────────────────>│
|
||||
│ │ {success: true} │
|
||||
│ │ │
|
||||
│ 10. Success Message in Plugin─────────────┼────────>
|
||||
│ │ │
|
||||
│ │ 11. POST /register/ │
|
||||
│ │<─────────────────────┤
|
||||
│ │ Store PluginInstallation
|
||||
│ │ │
|
||||
```
|
||||
|
||||
**Key Changes in v1.3.4:**
|
||||
- ✅ API key stored in `Site.wp_api_key` (not in SiteIntegration)
|
||||
- ✅ `credentials_json` is empty (only stores plugin_version, debug_enabled)
|
||||
- ✅ Authentication via `Authorization: Bearer {api_key}` header
|
||||
- ✅ No WordPress admin username/password needed
|
||||
- ✅ Simplified setup - single API key for all communication
|
||||
|
||||
### 10.2 Manual Publishing
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌───────────────┐
|
||||
│ User │ │ IGNY8 API │ │ Celery │ │ WordPress │
|
||||
└────┬─────┘ └──────┬───────┘ └────┬─────┘ └───────┬───────┘
|
||||
│ │ │ │
|
||||
│ 1. Click Publish │ │ │
|
||||
├─────────────────>│ │ │
|
||||
│ │ │ │
|
||||
│ │ 2. Validate │ │
|
||||
│ │ 3. Update status │ │
|
||||
│ │ 4. Queue task │ │
|
||||
│ ├─────────────────>│ │
|
||||
│<─────────────────┤ │ │
|
||||
│ 5. "Publishing..." │ │
|
||||
│ │ │ │
|
||||
│ │ │ 6. POST /publish │
|
||||
│ │ ├──────────────────>│
|
||||
│ │ │ │
|
||||
│ │ │<──────────────────┤
|
||||
│ │ │ 7. 201 Created │
|
||||
│ │ │ │
|
||||
│ │ 8. Update Content│ │
|
||||
│ │<─────────────────┤ │
|
||||
│ │ 9. Create SyncEvent │
|
||||
│ │ │ │
|
||||
```
|
||||
|
||||
### 10.3 Plugin Auto-Update Flow
|
||||
|
||||
```
|
||||
┌───────────────┐ ┌──────────────┐
|
||||
│ WordPress │ │ IGNY8 API │
|
||||
└───────┬───────┘ └──────┬───────┘
|
||||
│ │
|
||||
│ 1. Check for updates (cron)
|
||||
│ POST /check-update/│
|
||||
├───────────────────>│
|
||||
│ (current: 1.3.2) │
|
||||
│ │
|
||||
│<───────────────────┤
|
||||
│ 2. Update available│
|
||||
│ (latest: 1.3.3) │
|
||||
│ │
|
||||
│ 3. User clicks │
|
||||
│ "Update Now" │
|
||||
│ │
|
||||
│ GET /download/ │
|
||||
├───────────────────>│
|
||||
│ │
|
||||
│<───────────────────┤
|
||||
│ 4. ZIP file │
|
||||
│ │
|
||||
│ 5. Install & Activate
|
||||
│ │
|
||||
│ POST /register/ │
|
||||
├───────────────────>│
|
||||
│ (new version info) │
|
||||
│ │
|
||||
│<───────────────────┤
|
||||
│ 6. Registration OK │
|
||||
│ │
|
||||
```
|
||||
|
||||
### 10.4 Webhook Status Sync
|
||||
|
||||
```
|
||||
┌───────────────┐ ┌──────────────┐
|
||||
│ WordPress │ │ IGNY8 API │
|
||||
└───────┬───────┘ └──────┬───────┘
|
||||
│ │
|
||||
│ 1. User changes status in WP
|
||||
│ │
|
||||
│ 2. POST /webhooks/wordpress/status/
|
||||
├───────────────────>│
|
||||
│ │
|
||||
│ │ 3. Validate API key
|
||||
│ │ 4. Find Content
|
||||
│ │ 5. Map status
|
||||
│ │ 6. Update Content
|
||||
│ │ 7. Create SyncEvent
|
||||
│ │
|
||||
│<───────────────────┤
|
||||
│ 8. 200 OK │
|
||||
│ │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Flow | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Integration Setup | ✅ Working | API key based |
|
||||
| Manual Publish | ✅ Working | Via Celery task |
|
||||
| Automation Publish | ⚠️ Partial | Stage 7 sets status but doesn't trigger WordPress push |
|
||||
| Webhook Status Sync | ✅ Working | WordPress → IGNY8 status updates |
|
||||
| Webhook Metadata Sync | ✅ Working | WordPress → IGNY8 metadata |
|
||||
| Scheduled Auto-Publish | ❌ Not Active | Task exists but not scheduled |
|
||||
| Content Pull Sync | ❌ Not Implemented | No import from WordPress |
|
||||
| Content Update Sync | ❌ Not Implemented | No republish capability |
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
# IGNY8 Application Repository
|
||||
|
||||
**Repository Name:** `igny8-app`
|
||||
**Purpose:** Custom application code for the IGNY8 Content AI Platform
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This repository contains ONLY the custom business logic for IGNY8. It does not contain:
|
||||
- Dockerfiles (in stack repo)
|
||||
- package.json / requirements.txt (in stack repo)
|
||||
- Vite/TypeScript configs (in stack repo)
|
||||
- Docker compose files (in stack repo)
|
||||
|
||||
---
|
||||
|
||||
## Complete Folder Structure
|
||||
|
||||
```
|
||||
igny8-app/
|
||||
│
|
||||
├── README.md # App overview
|
||||
├── CHANGELOG.md # Version history
|
||||
├── .gitignore # Git ignore rules
|
||||
├── .env.example # Environment template
|
||||
│
|
||||
├── backend/
|
||||
│ └── igny8_core/ # Django application
|
||||
│ ├── __init__.py
|
||||
│ ├── settings.py # Django settings
|
||||
│ ├── urls.py # Root URL routing
|
||||
│ ├── celery.py # Celery configuration
|
||||
│ ├── wsgi.py # WSGI entry point
|
||||
│ ├── asgi.py # ASGI entry point
|
||||
│ ├── tasks.py # Root Celery tasks
|
||||
│ │
|
||||
│ ├── auth/ # Authentication module
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── models.py # User, Account, Site, Sector, Plan
|
||||
│ │ ├── views.py # Login, register, password reset
|
||||
│ │ ├── serializers.py # DRF serializers
|
||||
│ │ ├── middleware.py # AccountContextMiddleware
|
||||
│ │ ├── urls.py # Auth URL routes
|
||||
│ │ └── migrations/ # Database migrations
|
||||
│ │
|
||||
│ ├── api/ # API infrastructure
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base.py # Base ViewSets (AccountModelViewSet)
|
||||
│ │ ├── authentication.py # JWT, API key auth
|
||||
│ │ ├── pagination.py # Unified pagination
|
||||
│ │ └── tests/ # API tests
|
||||
│ │
|
||||
│ ├── ai/ # AI engine
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── engine.py # AIEngine orchestrator
|
||||
│ │ ├── registry.py # Function registry
|
||||
│ │ ├── model_registry.py # AI model configuration
|
||||
│ │ ├── progress.py # Progress tracking
|
||||
│ │ └── functions/ # AI function implementations
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── auto_cluster.py
|
||||
│ │ ├── generate_ideas.py
|
||||
│ │ ├── generate_content.py
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── modules/ # Feature modules (API layer)
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── planner/ # Keywords, Clusters, Ideas
|
||||
│ │ │ ├── models.py
|
||||
│ │ │ ├── views.py
|
||||
│ │ │ ├── serializers.py
|
||||
│ │ │ ├── urls.py
|
||||
│ │ │ └── migrations/
|
||||
│ │ ├── writer/ # Tasks, Content, Images
|
||||
│ │ ├── billing/ # Credits, usage, transactions
|
||||
│ │ ├── integration/ # WordPress integration
|
||||
│ │ ├── system/ # Settings, prompts, AI config
|
||||
│ │ ├── linker/ # Internal linking
|
||||
│ │ ├── optimizer/ # Content optimization
|
||||
│ │ └── publisher/ # Publishing pipeline
|
||||
│ │
|
||||
│ ├── business/ # Business logic (services)
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── automation/ # 7-stage automation pipeline
|
||||
│ │ ├── billing/ # Credit service
|
||||
│ │ ├── content/ # Content generation
|
||||
│ │ ├── integration/ # Sync services
|
||||
│ │ ├── linking/ # Link processing
|
||||
│ │ ├── notifications/ # Notification system
|
||||
│ │ ├── optimization/ # Content optimization
|
||||
│ │ ├── planning/ # Clustering, ideas
|
||||
│ │ └── publishing/ # Publishing orchestration
|
||||
│ │
|
||||
│ ├── middleware/ # Custom middleware
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── request_id.py # X-Request-ID header
|
||||
│ │ └── resource_tracker.py # Resource tracking
|
||||
│ │
|
||||
│ ├── tasks/ # Celery background tasks
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── *.py
|
||||
│ │
|
||||
│ ├── admin/ # Django admin customization
|
||||
│ ├── common/ # Shared utilities
|
||||
│ ├── management/ # Django management commands
|
||||
│ │ └── commands/
|
||||
│ ├── utils/ # Helper functions
|
||||
│ ├── urls/ # URL routing modules
|
||||
│ ├── static/ # App static files
|
||||
│ └── templates/ # Django templates
|
||||
│
|
||||
├── frontend/
|
||||
│ └── src/ # React application
|
||||
│ ├── main.tsx # Entry point
|
||||
│ ├── App.tsx # Root component, routing
|
||||
│ ├── index.css # Global styles, Tailwind
|
||||
│ ├── vite-env.d.ts # Vite type definitions
|
||||
│ ├── svg.d.ts # SVG type definitions
|
||||
│ │
|
||||
│ ├── api/ # API client modules
|
||||
│ │ ├── linker.api.ts
|
||||
│ │ ├── optimizer.api.ts
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── services/ # Core services
|
||||
│ │ ├── api.ts # Main API service
|
||||
│ │ └── notifications.api.ts # Notification API
|
||||
│ │
|
||||
│ ├── store/ # Zustand state stores
|
||||
│ │ ├── authStore.ts # Authentication state
|
||||
│ │ ├── siteStore.ts # Active site
|
||||
│ │ ├── sectorStore.ts # Active sector
|
||||
│ │ ├── billingStore.ts # Billing state
|
||||
│ │ ├── moduleStore.ts # Module enable/disable
|
||||
│ │ ├── notificationStore.ts # Notifications
|
||||
│ │ └── ...
|
||||
│ │
|
||||
│ ├── pages/ # Route pages
|
||||
│ │ ├── Dashboard/
|
||||
│ │ ├── Planner/
|
||||
│ │ ├── Writer/
|
||||
│ │ ├── Automation/
|
||||
│ │ ├── Linker/
|
||||
│ │ ├── Optimizer/
|
||||
│ │ ├── Settings/
|
||||
│ │ ├── Billing/
|
||||
│ │ └── Auth/
|
||||
│ │
|
||||
│ ├── components/ # Reusable components
|
||||
│ │ ├── common/ # Shared UI components
|
||||
│ │ ├── dashboard/ # Dashboard widgets
|
||||
│ │ ├── header/ # Header components
|
||||
│ │ └── shared/ # Cross-feature components
|
||||
│ │
|
||||
│ ├── layout/ # Layout components
|
||||
│ │ ├── AppLayout.tsx
|
||||
│ │ ├── AppHeader.tsx
|
||||
│ │ └── AppSidebar.tsx
|
||||
│ │
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── context/ # React contexts
|
||||
│ ├── config/ # App configuration
|
||||
│ ├── icons/ # Icon components
|
||||
│ ├── styles/ # CSS modules/styles
|
||||
│ ├── utils/ # Utility functions
|
||||
│ ├── types/ # TypeScript types
|
||||
│ ├── templates/ # Content templates
|
||||
│ ├── modules/ # Feature modules
|
||||
│ ├── marketing/ # Marketing site
|
||||
│ │ ├── index.tsx
|
||||
│ │ ├── MarketingApp.tsx
|
||||
│ │ ├── config/
|
||||
│ │ ├── components/
|
||||
│ │ ├── layout/
|
||||
│ │ ├── pages/
|
||||
│ │ ├── images/
|
||||
│ │ └── styles/
|
||||
│ │
|
||||
│ └── __tests__/ # Test files
|
||||
│
|
||||
├── public/ # Public static assets
|
||||
│ ├── favicon.ico
|
||||
│ ├── logo.svg
|
||||
│ └── images/
|
||||
│
|
||||
└── docs/ # Documentation
|
||||
├── 00-SYSTEM/
|
||||
│ └── ARCHITECTURE.md
|
||||
├── 10-MODULES/
|
||||
├── 20-API/
|
||||
├── 30-FRONTEND/
|
||||
├── 40-WORKFLOWS/
|
||||
├── 50-DEPLOYMENT/
|
||||
│ ├── TWO-REPO-ARCHITECTURE.md
|
||||
│ ├── INFRASTRUCTURE-STACK.md
|
||||
│ ├── IGNY8-APP-STRUCTURE.md # This file
|
||||
│ ├── DOCKER-DEPLOYMENT.md
|
||||
│ └── ENVIRONMENT-SETUP.md
|
||||
├── 90-REFERENCE/
|
||||
└── plans/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment to New Server
|
||||
|
||||
### Prerequisites
|
||||
- Stack already installed (see INFRASTRUCTURE-STACK.md)
|
||||
- Server has `/data/stack/igny8-stack/` ready
|
||||
- Docker network `igny8_net` exists
|
||||
|
||||
### Step-by-Step Deployment
|
||||
|
||||
```
|
||||
1. Copy app code to server
|
||||
|
||||
Option A: Git clone
|
||||
cd /data/app
|
||||
git clone https://github.com/yourorg/igny8-app.git igny8
|
||||
|
||||
Option B: Rsync from existing server
|
||||
rsync -avz --exclude='.git' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='staticfiles' \
|
||||
--exclude='dist' \
|
||||
old-server:/data/app/igny8/ /data/app/igny8/
|
||||
|
||||
2. Create symlinks to stack
|
||||
cd /data/stack/igny8-stack
|
||||
./scripts/link-app.sh igny8
|
||||
|
||||
3. Configure environment
|
||||
cd /data/app/igny8
|
||||
cp .env.example .env
|
||||
nano .env # Set your secrets
|
||||
|
||||
4. Install frontend dependencies
|
||||
docker exec igny8_frontend npm install
|
||||
|
||||
5. Run database migrations
|
||||
docker exec igny8_backend python manage.py migrate
|
||||
|
||||
6. Create superuser (first time only)
|
||||
docker exec -it igny8_backend python manage.py createsuperuser
|
||||
|
||||
7. Collect static files
|
||||
docker exec igny8_backend python manage.py collectstatic --noinput
|
||||
|
||||
8. Start all services
|
||||
docker compose -f docker-compose.app.yml up -d
|
||||
|
||||
9. Verify
|
||||
curl https://api.igny8.com/api/v1/system/status/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Gets Copied vs Symlinked
|
||||
|
||||
### Copied (Your Code)
|
||||
|
||||
| Path | Content |
|
||||
|------|---------|
|
||||
| `backend/igny8_core/` | All Django app code |
|
||||
| `frontend/src/` | All React code |
|
||||
| `public/` | Static assets |
|
||||
| `docs/` | Documentation |
|
||||
| `.env` | Environment config |
|
||||
|
||||
### Symlinked (From Stack)
|
||||
|
||||
| App Path | Stack Path |
|
||||
|----------|------------|
|
||||
| `backend/Dockerfile` | `stack/backend/Dockerfile` |
|
||||
| `backend/requirements.txt` | `stack/backend/requirements.txt` |
|
||||
| `backend/manage.py` | `stack/backend/manage.py` |
|
||||
| `frontend/Dockerfile.dev` | `stack/frontend/Dockerfile.dev` |
|
||||
| `frontend/package.json` | `stack/frontend/package.json` |
|
||||
| `frontend/vite.config.ts` | `stack/frontend/vite.config.ts` |
|
||||
| `frontend/tsconfig*.json` | `stack/frontend/tsconfig*.json` |
|
||||
| `docker-compose.app.yml` | `stack/docker/docker-compose.app.yml` |
|
||||
|
||||
---
|
||||
|
||||
## Updating App on Existing Server
|
||||
|
||||
### Code Update (Most Common)
|
||||
|
||||
```
|
||||
cd /data/app/igny8
|
||||
git pull # Get latest code
|
||||
|
||||
# If migrations changed:
|
||||
docker exec igny8_backend python manage.py migrate
|
||||
|
||||
# If frontend deps changed:
|
||||
docker exec igny8_frontend npm install
|
||||
|
||||
# Restart to apply:
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
### Full Rebuild (After Major Changes)
|
||||
|
||||
```
|
||||
cd /data/app/igny8
|
||||
docker compose down
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backup Before Migration
|
||||
|
||||
### What to Backup
|
||||
|
||||
| Item | Command/Method |
|
||||
|------|----------------|
|
||||
| Database | `pg_dump igny8_db > backup.sql` |
|
||||
| App code | `git push` or `rsync` |
|
||||
| Uploads | Copy `/data/app/igny8/backend/media/` |
|
||||
| Environment | Copy `.env` file |
|
||||
|
||||
### Full Backup Script
|
||||
|
||||
```
|
||||
# On old server
|
||||
DATE=$(date +%Y%m%d)
|
||||
mkdir -p /data/backups/$DATE
|
||||
|
||||
# Database
|
||||
docker exec igny8_postgres pg_dump -U igny8 igny8_db > /data/backups/$DATE/db.sql
|
||||
|
||||
# App code (excluding generated files)
|
||||
tar -czf /data/backups/$DATE/app.tar.gz \
|
||||
--exclude='node_modules' \
|
||||
--exclude='__pycache__' \
|
||||
--exclude='staticfiles' \
|
||||
--exclude='dist' \
|
||||
--exclude='.git' \
|
||||
/data/app/igny8/
|
||||
|
||||
# Environment
|
||||
cp /data/app/igny8/.env /data/backups/$DATE/
|
||||
|
||||
echo "Backup complete: /data/backups/$DATE/"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [TWO-REPO-ARCHITECTURE.md](TWO-REPO-ARCHITECTURE.md) - Why this structure
|
||||
- [INFRASTRUCTURE-STACK.md](INFRASTRUCTURE-STACK.md) - Stack repo details
|
||||
- [DOCKER-DEPLOYMENT.md](DOCKER-DEPLOYMENT.md) - Container details
|
||||
- [ARCHITECTURE.md](../00-SYSTEM/ARCHITECTURE.md) - System architecture
|
||||
@@ -0,0 +1,219 @@
|
||||
# Infrastructure Stack Repository
|
||||
|
||||
**Repository Name:** `igny8-stack`
|
||||
**Purpose:** Reusable tech stack for Django + React applications
|
||||
|
||||
---
|
||||
|
||||
## Complete Folder Structure
|
||||
|
||||
```
|
||||
igny8-stack/
|
||||
│
|
||||
├── README.md # Stack overview and quick start
|
||||
├── install.sh # Main installation script
|
||||
├── uninstall.sh # Cleanup script
|
||||
│
|
||||
├── config/
|
||||
│ ├── .env.example # Environment variables template
|
||||
│ │
|
||||
│ └── caddy/
|
||||
│ └── Caddyfile # Reverse proxy configuration
|
||||
│
|
||||
├── docker/
|
||||
│ ├── docker-compose.infra.yml # Shared services (Postgres, Redis, Caddy)
|
||||
│ └── docker-compose.app.yml # App services template
|
||||
│
|
||||
├── backend/
|
||||
│ ├── Dockerfile # Python 3.11 slim + dependencies
|
||||
│ ├── requirements.txt # Django, DRF, Celery, Gunicorn, etc.
|
||||
│ ├── manage.py # Django CLI entry point
|
||||
│ ├── container_startup.sh # Container entrypoint script
|
||||
│ └── create_groups.py # Initial permission groups setup
|
||||
│
|
||||
├── frontend/
|
||||
│ ├── Dockerfile.dev # Vite dev server with HMR
|
||||
│ ├── Dockerfile.prod # Production build with Caddy
|
||||
│ ├── Dockerfile.marketing.dev # Marketing site dev server
|
||||
│ ├── package.json # React, Vite, Tailwind, Zustand, etc.
|
||||
│ ├── package-lock.json # Locked dependency versions
|
||||
│ ├── vite.config.ts # Vite configuration
|
||||
│ ├── tsconfig.json # TypeScript base config
|
||||
│ ├── tsconfig.app.json # App TypeScript config
|
||||
│ ├── tsconfig.node.json # Node TypeScript config
|
||||
│ ├── postcss.config.js # PostCSS for Tailwind
|
||||
│ ├── eslint.config.js # ESLint rules
|
||||
│ ├── index.html # Main app HTML template
|
||||
│ └── marketing.html # Marketing site HTML template
|
||||
│
|
||||
└── scripts/
|
||||
├── build-images.sh # Build all Docker images
|
||||
├── migrate.sh # Run Django migrations
|
||||
├── backup-db.sh # Database backup utility
|
||||
├── restore-db.sh # Database restore utility
|
||||
└── health-check.sh # System health check
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Purposes
|
||||
|
||||
### Root Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `install.sh` | Creates directories, symlinks, builds images, sets up network |
|
||||
| `uninstall.sh` | Removes symlinks and cleans up (keeps data) |
|
||||
|
||||
### Config Folder
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `.env.example` | Template for environment variables (DB passwords, secrets, domains) |
|
||||
| `caddy/Caddyfile` | Routes domains to containers, handles SSL, WebSocket proxying |
|
||||
|
||||
### Docker Folder
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `docker-compose.infra.yml` | Postgres, Redis, Caddy, PgAdmin, FileBrowser, Portainer |
|
||||
| `docker-compose.app.yml` | Backend, Frontend, Celery Worker, Celery Beat, Flower |
|
||||
|
||||
### Backend Folder
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile` | Python 3.11 slim, installs requirements, runs Gunicorn |
|
||||
| `requirements.txt` | All Python dependencies (Django 5.x, DRF, Celery, etc.) |
|
||||
| `manage.py` | Django management command entry point |
|
||||
| `container_startup.sh` | Logs startup, runs migrations if needed |
|
||||
| `create_groups.py` | Creates initial Django permission groups |
|
||||
|
||||
### Frontend Folder
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `Dockerfile.dev` | Node 18, Vite dev server with hot reload |
|
||||
| `Dockerfile.prod` | Multi-stage build, Caddy serves static files |
|
||||
| `Dockerfile.marketing.dev` | Marketing site dev server (port 5174) |
|
||||
| `package.json` | All npm dependencies |
|
||||
| `vite.config.ts` | Build config, HMR settings, code splitting |
|
||||
| `tsconfig*.json` | TypeScript compiler settings |
|
||||
| `postcss.config.js` | Tailwind CSS processing |
|
||||
| `eslint.config.js` | Code linting rules |
|
||||
| `index.html` | Main app entry HTML |
|
||||
| `marketing.html` | Marketing site entry HTML |
|
||||
|
||||
### Scripts Folder
|
||||
|
||||
| Script | Purpose |
|
||||
|--------|---------|
|
||||
| `build-images.sh` | Builds all Docker images with proper tags |
|
||||
| `migrate.sh` | Runs Django migrations inside container |
|
||||
| `backup-db.sh` | Dumps PostgreSQL database to backup file |
|
||||
| `restore-db.sh` | Restores database from backup file |
|
||||
| `health-check.sh` | Checks all services are running |
|
||||
|
||||
---
|
||||
|
||||
## New Server Installation
|
||||
|
||||
### Prerequisites
|
||||
- Ubuntu 22.04+ or Debian 12+
|
||||
- Docker and Docker Compose installed
|
||||
- Git installed
|
||||
- Domain DNS pointing to server IP
|
||||
|
||||
### Step-by-Step Installation
|
||||
|
||||
```
|
||||
1. Create directories
|
||||
mkdir -p /data/stack /data/app /data/logs
|
||||
|
||||
2. Clone stack repository
|
||||
cd /data/stack
|
||||
git clone https://github.com/yourorg/igny8-stack.git
|
||||
|
||||
3. Run installation script
|
||||
cd igny8-stack
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
|
||||
4. Verify installation
|
||||
docker images # Should show app-backend, app-frontend images
|
||||
docker network ls # Should show igny8_net
|
||||
ls -la /data/app/ # Should show prepared structure
|
||||
```
|
||||
|
||||
### What install.sh Does
|
||||
|
||||
1. Creates Docker network (`igny8_net`)
|
||||
2. Creates directory structure under `/data/app/`
|
||||
3. Builds Docker images:
|
||||
- `app-backend:latest`
|
||||
- `app-frontend-dev:latest`
|
||||
- `app-marketing-dev:latest`
|
||||
4. Starts infrastructure services (Postgres, Redis, Caddy)
|
||||
5. Creates symlinks for app folder structure
|
||||
6. Prints next steps
|
||||
|
||||
---
|
||||
|
||||
## Updating Stack on Existing Server
|
||||
|
||||
```
|
||||
1. Pull latest changes
|
||||
cd /data/stack/igny8-stack
|
||||
git pull
|
||||
|
||||
2. Rebuild images if Dockerfile changed
|
||||
./scripts/build-images.sh
|
||||
|
||||
3. Restart containers to use new images
|
||||
cd /data/app/igny8
|
||||
docker compose -f docker-compose.app.yml down
|
||||
docker compose -f docker-compose.app.yml up -d
|
||||
|
||||
4. Verify
|
||||
docker ps
|
||||
./scripts/health-check.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Customizing for Different Apps
|
||||
|
||||
The stack is designed to run any Django + React application. To use it for a different app (not igny8):
|
||||
|
||||
1. Clone stack to new server
|
||||
2. Run `install.sh`
|
||||
3. Clone your app repo to `/data/app/yourapp/`
|
||||
4. Update `.env` with your app's settings
|
||||
5. Update Caddyfile with your domains
|
||||
6. Start containers
|
||||
|
||||
The stack doesn't contain any igny8-specific logic - it's a generic Django+React runtime environment.
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Key variables to set in `.env`:
|
||||
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| `DB_HOST` | PostgreSQL host | `postgres` |
|
||||
| `DB_NAME` | Database name | `igny8_db` |
|
||||
| `DB_USER` | Database user | `igny8` |
|
||||
| `DB_PASSWORD` | Database password | `secure_password` |
|
||||
| `REDIS_HOST` | Redis host | `redis` |
|
||||
| `SECRET_KEY` | Django secret key | `random_50_char_string` |
|
||||
| `DOMAIN` | Primary domain | `igny8.com` |
|
||||
| `DEBUG` | Debug mode | `False` |
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [TWO-REPO-ARCHITECTURE.md](TWO-REPO-ARCHITECTURE.md) - Why two repos
|
||||
- [IGNY8-APP-STRUCTURE.md](IGNY8-APP-STRUCTURE.md) - App-specific code structure
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
# Two-Repository Architecture
|
||||
|
||||
**Purpose:** Separate tech stack infrastructure from custom application code for easy server migration and deployment.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Instead of one monolithic repository, split into two:
|
||||
|
||||
| Repository | Contains | Changes | Deployment |
|
||||
|------------|----------|---------|------------|
|
||||
| `igny8-stack` | Docker, configs, dependencies | Rarely | Clone once per server |
|
||||
| `igny8-app` | Your custom business logic | Frequently | Copy to deploy |
|
||||
|
||||
---
|
||||
|
||||
## Why This Architecture?
|
||||
|
||||
### Current Problem
|
||||
- Everything mixed together (Dockerfiles, configs, your code)
|
||||
- To deploy to new server, must copy everything
|
||||
- Hard to distinguish "what's mine" vs "what's framework"
|
||||
|
||||
### Solution Benefits
|
||||
- **Clean separation**: Stack vs App code
|
||||
- **Easy migration**: Just copy app folder after stack is installed
|
||||
- **Version independence**: Update stack without touching app
|
||||
- **Reusable stack**: Same stack can run different apps on different servers
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
### Server Structure After Setup
|
||||
|
||||
```
|
||||
/data/
|
||||
├── stack/
|
||||
│ └── igny8-stack/ # Cloned from stack repo (read-only)
|
||||
│
|
||||
├── app/
|
||||
│ └── igny8/ # Your app code + symlinks to stack
|
||||
│ ├── backend/
|
||||
│ │ ├── Dockerfile → symlink to stack
|
||||
│ │ ├── requirements.txt → symlink to stack
|
||||
│ │ ├── manage.py → symlink to stack
|
||||
│ │ └── igny8_core/ # YOUR CODE
|
||||
│ │
|
||||
│ └── frontend/
|
||||
│ ├── package.json → symlink to stack
|
||||
│ ├── vite.config.ts → symlink to stack
|
||||
│ └── src/ # YOUR CODE
|
||||
│
|
||||
└── logs/
|
||||
```
|
||||
|
||||
### Symlinks Explained
|
||||
|
||||
The `install.sh` script creates symbolic links from your app folder to the stack folder. This means:
|
||||
- Stack files (Dockerfile, package.json, etc.) live in one place
|
||||
- Your app folder references them via symlinks
|
||||
- Docker sees a complete project structure
|
||||
- You only manage your custom code
|
||||
|
||||
---
|
||||
|
||||
## Migration Workflow
|
||||
|
||||
### On New Server
|
||||
|
||||
```
|
||||
Step 1: Install Stack (one-time)
|
||||
────────────────────────────────
|
||||
cd /data/stack
|
||||
git clone https://your-repo/igny8-stack.git
|
||||
cd igny8-stack
|
||||
./install.sh
|
||||
|
||||
Step 2: Deploy Your App
|
||||
────────────────────────────────
|
||||
cd /data/app
|
||||
git clone https://your-repo/igny8-app.git igny8
|
||||
# OR: rsync/scp from old server
|
||||
|
||||
Step 3: Configure
|
||||
────────────────────────────────
|
||||
cp .env.example .env
|
||||
nano .env # Set secrets, domains
|
||||
|
||||
Step 4: Start
|
||||
────────────────────────────────
|
||||
docker compose -f docker-compose.app.yml up -d
|
||||
```
|
||||
|
||||
### Updating Existing Server
|
||||
|
||||
**Update app code only:**
|
||||
```
|
||||
cd /data/app/igny8
|
||||
git pull
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
**Update stack (rare):**
|
||||
```
|
||||
cd /data/stack/igny8-stack
|
||||
git pull
|
||||
./scripts/build-images.sh
|
||||
docker compose -f /data/app/igny8/docker-compose.app.yml up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What Goes Where?
|
||||
|
||||
### Stack Repo (igny8-stack)
|
||||
|
||||
Things that are **same for any Django+React app**:
|
||||
- Dockerfiles
|
||||
- Base requirements.txt / package.json
|
||||
- Vite config, TypeScript config
|
||||
- Docker compose templates
|
||||
- Caddy config templates
|
||||
- Utility scripts
|
||||
|
||||
### App Repo (igny8-app)
|
||||
|
||||
Things that are **specific to your application**:
|
||||
- Django app code (models, views, serializers, business logic)
|
||||
- React components, pages, stores
|
||||
- App-specific static assets
|
||||
- Documentation
|
||||
- Environment config templates
|
||||
|
||||
---
|
||||
|
||||
## Key Principle
|
||||
|
||||
> If you deleted your app code and replaced it with a different Django+React app,
|
||||
> would this file still make sense?
|
||||
> - YES → Stack repo
|
||||
> - NO → App repo
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [INFRASTRUCTURE-STACK.md](INFRASTRUCTURE-STACK.md) - Complete stack repo structure
|
||||
- [IGNY8-APP-STRUCTURE.md](IGNY8-APP-STRUCTURE.md) - App repo structure
|
||||
142
v2/Live Docs on Server/igny8-app-docs/60-PLUGINS/INDEX.md
Normal file
142
v2/Live Docs on Server/igny8-app-docs/60-PLUGINS/INDEX.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# Plugin Management Documentation
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** Production
|
||||
|
||||
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
|
||||
|
||||
## Overview
|
||||
|
||||
The IGNY8 plugin distribution system provides a comprehensive infrastructure for managing, distributing, and updating plugins across multiple platforms (WordPress, Shopify, custom sites). The system includes automated versioning, security features, update mechanisms, and monitoring.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### API Endpoints (Production)
|
||||
|
||||
| Endpoint | Purpose | Method |
|
||||
|----------|---------|--------|
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/download/` | Download latest plugin ZIP | GET |
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/` | Check for updates (called by WP plugin) | POST |
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/info/` | Get plugin metadata | GET |
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/register/` | Register new installation | POST |
|
||||
| `https://api.igny8.com/api/plugins/igny8-wp-bridge/health-check/` | Plugin health check | POST |
|
||||
|
||||
### Key Directories
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| `/data/app/igny8/plugins/wordpress/source/igny8-wp-bridge/` | WordPress plugin source code |
|
||||
| `/data/app/igny8/plugins/wordpress/dist/` | Distribution ZIP files |
|
||||
| `/data/app/igny8/plugins/shopify/` | Shopify integration (planned) |
|
||||
| `/data/app/igny8/plugins/custom-site/` | Custom site integration (planned) |
|
||||
| `/data/app/igny8/backend/igny8_core/plugins/` | Django plugin management app |
|
||||
|
||||
### Database Models
|
||||
|
||||
- `Plugin` - Plugin registry (name, slug, platform, status)
|
||||
- `PluginVersion` - Version tracking with file info, checksums, changelog
|
||||
- `PluginInstallation` - Track installations per site with version info
|
||||
- `PluginDownload` - Download analytics and tracking
|
||||
|
||||
### Current Plugin Status
|
||||
|
||||
| Platform | Plugin Name | Current Version | Status |
|
||||
|----------|-------------|-----------------|--------|
|
||||
| WordPress | IGNY8 WP Bridge | 1.5.1 | Production |
|
||||
| Shopify | IGNY8 Shopify | - | Infrastructure Ready |
|
||||
| Custom | IGNY8 Bridge | - | Infrastructure Ready |
|
||||
|
||||
### WordPress Plugin Admin Pages (v1.5.1)
|
||||
|
||||
| Page | Purpose |
|
||||
|------|---------|
|
||||
| Dashboard | Connection status, Site ID, masked API key, content stats |
|
||||
| Settings | Post types, default post status, taxonomies, sync toggle |
|
||||
| Logs | Webhook activity with timestamps, event types, status |
|
||||
|
||||
## System Features
|
||||
|
||||
### Distribution System
|
||||
- Automated ZIP package generation
|
||||
- Version-based file management
|
||||
- MD5 and SHA256 checksums
|
||||
- Signed download URLs
|
||||
- Rate limiting and security
|
||||
|
||||
### Update Mechanism
|
||||
- WordPress auto-update via `pre_set_site_transient_update_plugins` hook
|
||||
- Version comparison and update notifications
|
||||
- Changelog display
|
||||
- Compatibility checks
|
||||
|
||||
### Monitoring & Analytics
|
||||
- Installation tracking per site
|
||||
- Download counts and analytics
|
||||
- Health check endpoints
|
||||
- Version distribution tracking
|
||||
- Error logging and monitoring
|
||||
|
||||
### Security Features
|
||||
- Signed URLs with expiration
|
||||
- Checksum verification (MD5/SHA256)
|
||||
- Rate limiting per IP/site
|
||||
- API authentication for sensitive endpoints
|
||||
- Production environment protection
|
||||
|
||||
## Integration Flow
|
||||
|
||||
```
|
||||
Developer → Update Source Code → Create PluginVersion in Admin
|
||||
↓
|
||||
Automatic: Build ZIP, Calculate Checksums, Store in /dist/
|
||||
↓
|
||||
WordPress Site → Check for Updates → Download via API
|
||||
↓
|
||||
Install/Update → Register Installation → Health Check
|
||||
```
|
||||
|
||||
## Version Progression (Last 20 Commits)
|
||||
|
||||
**WordPress Plugin Versions:**
|
||||
- v1.5.1 - Admin UI consolidation (6→3 pages), header fixes
|
||||
- v1.5.0 - Authentication simplification (Site.wp_api_key as single source)
|
||||
- v1.3.3 - Template design improvements, image layout fixes
|
||||
- v1.3.2 - Template fixes in app and plugin
|
||||
- v1.3.1 - WordPress plugin updates
|
||||
- v1.3.0 - Initial distribution system release
|
||||
|
||||
**Infrastructure Updates:**
|
||||
- Plugin distribution system implemented (v1.7.0)
|
||||
- Authentication simplified to Site.wp_api_key (single source of truth)
|
||||
- SiteIntegration model retained for sync tracking (not auth)
|
||||
- Database models for multi-platform support
|
||||
- API endpoints for lifecycle management
|
||||
- Automated packaging and versioning
|
||||
- Security and monitoring features
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### App-Side Documentation (This Folder)
|
||||
- Plugin distribution infrastructure
|
||||
- API endpoint documentation
|
||||
- Database models and admin interface
|
||||
- Integration workflows
|
||||
- Security and monitoring
|
||||
|
||||
### Plugin-Side Documentation
|
||||
See `/plugins/wordpress/source/igny8-wp-bridge/docs/` for:
|
||||
- Plugin PHP code and functions
|
||||
- WordPress hooks and filters
|
||||
- Template system
|
||||
- Content publishing flow
|
||||
- Plugin-specific configuration
|
||||
|
||||
---
|
||||
|
||||
**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/`.
|
||||
@@ -0,0 +1,692 @@
|
||||
# Plugin Update Workflow
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Status:** Production
|
||||
**Scope:** How to release plugin updates and what happens automatically vs manually
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Start: Simplified Release Process
|
||||
|
||||
The plugin release process has been **simplified** to require only 3 fields:
|
||||
|
||||
1. **Version** (e.g., 1.3.3)
|
||||
2. **Changelog** (what's new)
|
||||
3. **Status** (draft → released)
|
||||
|
||||
All other fields are either:
|
||||
- ✅ Auto-filled from previous version
|
||||
- ✅ Auto-generated on release (file path, size, checksum)
|
||||
- ✅ Auto-calculated (version code)
|
||||
|
||||
**Release in 3 clicks:**
|
||||
```
|
||||
Django Admin → Add Plugin Version → Enter 3 fields → Save → Change status to 'released'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recent Release History (v1.7.0)
|
||||
|
||||
### WordPress Plugin Progression
|
||||
|
||||
| Version | Date | Type | Key Changes |
|
||||
|---------|------|------|-------------|
|
||||
| 1.5.1 | Jan 10, 2026 | Minor | Admin UI consolidation: 6→3 pages (Dashboard, Settings, Logs), header alignment fixes |
|
||||
| 1.5.0 | Jan 10, 2026 | Minor | Authentication simplification: Site.wp_api_key as single source of truth |
|
||||
| 1.3.3 | Jan 10, 2026 | Patch | Template design: Square image grid fixes, landscape image positioning, direct border-radius/shadow on images without captions |
|
||||
| 1.3.2 | Jan 9, 2026 | Patch | Template fixes in app and plugin, image layout improvements |
|
||||
| 1.3.1 | Jan 9, 2026 | Patch | WordPress plugin versioning updates |
|
||||
| 1.3.0 | Jan 8, 2026 | Minor | Initial distribution system release, automated updates |
|
||||
|
||||
### Distribution System Status
|
||||
|
||||
- ✅ Automated ZIP generation: Operational
|
||||
- ✅ Checksum calculation: MD5 + SHA256
|
||||
- ✅ WordPress auto-update: Active
|
||||
- ✅ Health checks: Implemented
|
||||
- ✅ Installation tracking: Complete
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
9. [Recent Updates & Lessons Learned](#9-recent-updates--lessons-learned)
|
||||
|
||||
---
|
||||
|
||||
## 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: just enter version, changelog, status |
|
||||
| **Verify After Release** | After status change | Run verification checklist |
|
||||
| **Mark Old Version Deprecated** | After successful release | Change old version status |
|
||||
|
||||
**Note:** The form is simplified - most fields auto-fill from previous version or are auto-generated.
|
||||
|
||||
### 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
|
||||
|
||||
### The Simplified Process
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Simplified Version Release (3 Required Fields) │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Django Admin → Add Plugin Version │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Plugin: [IGNY8 WordPress Bridge ▼] ← Select plugin │ │
|
||||
│ │ Version: [1.2.0] ← New version │ │
|
||||
│ │ Status: [draft ▼] ← Start as draft │ │
|
||||
│ │ Changelog: [Bug fixes and improvements] ← What's new │ │
|
||||
│ │ │ │
|
||||
│ │ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ │
|
||||
│ │ Everything below is AUTO-FILLED or AUTO-GENERATED: │ │
|
||||
│ │ │ │
|
||||
│ │ Min API Version: 1.0 (from previous version) │ │
|
||||
│ │ Min PHP Version: 7.4 (from previous version) │ │
|
||||
│ │ File Path: (empty) → Auto-generated on release │ │
|
||||
│ │ File Size: (empty) → Auto-calculated on release │ │
|
||||
│ │ Checksum: (empty) → Auto-calculated on release │ │
|
||||
│ │ Version Code: (empty) → Auto-calculated from version │ │
|
||||
│ └────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Click [Save] → Status changes to 'released' → ZIP builds automatically │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 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 **ONLY** these required fields:
|
||||
- **Plugin:** IGNY8 WordPress Bridge
|
||||
- **Version:** 1.2.0
|
||||
- **Status:** draft (initially)
|
||||
- **Changelog:** Describe changes
|
||||
5. Click **Save**
|
||||
|
||||
**Note:** All other fields are auto-filled:
|
||||
- `min_api_version`, `min_platform_version`, `min_php_version` → Copied from previous version
|
||||
- `file_path`, `file_size`, `checksum` → Auto-generated when you release
|
||||
- `version_code` → Auto-calculated from version number
|
||||
|
||||
### Step 4: Release the Version
|
||||
|
||||
**Option A: Release via Status Change**
|
||||
|
||||
1. Edit the PluginVersion you just created
|
||||
2. Change **Status** to `released`
|
||||
3. Click **Save**
|
||||
|
||||
**Option B: Release via Bulk Action**
|
||||
|
||||
1. Select the version(s) in the list
|
||||
2. Choose **Actions** → **✅ Release selected versions**
|
||||
3. Click **Go**
|
||||
|
||||
**What happens:** Signal triggers auto-build → ZIP created → database updated with file info
|
||||
|
||||
**Note:** You can also use the action **📢 Mark as update ready** to immediately notify WordPress sites.
|
||||
|
||||
### 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
|
||||
|
||||
### Quick Verification Script
|
||||
|
||||
Run this single command to verify everything:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Complete Plugin Release Verification Script
|
||||
# Usage: Save as verify-plugin.sh and run: bash verify-plugin.sh 1.1.2
|
||||
|
||||
VERSION=${1:-"latest"}
|
||||
PLUGIN_SLUG="igny8-wp-bridge"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Plugin Release Verification: v${VERSION}"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 1. Check ZIP file exists
|
||||
echo "1. Checking ZIP file..."
|
||||
if [ "$VERSION" = "latest" ]; then
|
||||
ls -lh /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip 2>/dev/null && echo " ✓ Latest ZIP exists" || echo " ✗ Latest ZIP not found"
|
||||
else
|
||||
ls -lh /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-v${VERSION}.zip 2>/dev/null && echo " ✓ ZIP v${VERSION} exists" || echo " ✗ ZIP v${VERSION} not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Check symlink
|
||||
echo "2. Checking symlink..."
|
||||
ls -la /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip | grep -q "^l" && echo " ✓ Symlink valid" || echo " ✗ Symlink missing"
|
||||
echo ""
|
||||
|
||||
# 3. Test download endpoint
|
||||
echo "3. Testing download endpoint..."
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/download/)
|
||||
FILE_SIZE=$(curl -s -o /dev/null -w "%{size_download}" https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/download/)
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo " ✓ Download works: ${HTTP_CODE} - ${FILE_SIZE} bytes ($(( FILE_SIZE / 1024 )) KB)"
|
||||
else
|
||||
echo " ✗ Download failed: HTTP ${HTTP_CODE}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 4. Test check-update endpoint
|
||||
echo "4. Testing check-update endpoint..."
|
||||
UPDATE_RESPONSE=$(curl -s "https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/check-update/?current_version=1.0.0")
|
||||
LATEST_VERSION=$(echo "$UPDATE_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('latest_version', 'N/A'))" 2>/dev/null)
|
||||
if [ -n "$LATEST_VERSION" ] && [ "$LATEST_VERSION" != "N/A" ]; then
|
||||
echo " ✓ Check-update works: Latest version = $LATEST_VERSION"
|
||||
else
|
||||
echo " ✗ Check-update failed"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 5. Test info endpoint
|
||||
echo "5. Testing info endpoint..."
|
||||
INFO_RESPONSE=$(curl -s "https://api.igny8.com/api/plugins/${PLUGIN_SLUG}/info/")
|
||||
PLUGIN_NAME=$(echo "$INFO_RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin).get('name', 'N/A'))" 2>/dev/null)
|
||||
if [ -n "$PLUGIN_NAME" ] && [ "$PLUGIN_NAME" != "N/A" ]; then
|
||||
echo " ✓ Info endpoint works: $PLUGIN_NAME"
|
||||
else
|
||||
echo " ✗ Info endpoint failed"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 6. Verify version in ZIP
|
||||
echo "6. Verifying version in ZIP..."
|
||||
if [ "$VERSION" != "latest" ]; then
|
||||
ZIP_VERSION=$(unzip -p /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-v${VERSION}.zip ${PLUGIN_SLUG}/igny8-bridge.php 2>/dev/null | grep -E "Version:" | head -1 | awk '{print $3}')
|
||||
if [ "$ZIP_VERSION" = "$VERSION" ]; then
|
||||
echo " ✓ ZIP version matches: $ZIP_VERSION"
|
||||
else
|
||||
echo " ✗ ZIP version mismatch: expected $VERSION, got $ZIP_VERSION"
|
||||
fi
|
||||
else
|
||||
echo " - Skipped (use specific version to check)"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 7. Verify API URL
|
||||
echo "7. Verifying API URL in ZIP..."
|
||||
API_COUNT=$(unzip -p /data/app/igny8/plugins/wordpress/dist/${PLUGIN_SLUG}-latest.zip ${PLUGIN_SLUG}/igny8-bridge.php 2>/dev/null | grep -c "api.igny8.com")
|
||||
if [ "$API_COUNT" -gt 0 ]; then
|
||||
echo " ✓ API URL correct: api.igny8.com found (${API_COUNT} occurrences)"
|
||||
else
|
||||
echo " ✗ API URL incorrect: api.igny8.com not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 8. Check database
|
||||
echo "8. Checking database..."
|
||||
docker exec igny8_backend python manage.py shell -c "
|
||||
from igny8_core.plugins.models import Plugin, PluginVersion
|
||||
try:
|
||||
p = Plugin.objects.get(slug='${PLUGIN_SLUG}')
|
||||
v = p.get_latest_version()
|
||||
if v:
|
||||
print(f' ✓ Latest version: {v.version}')
|
||||
print(f' ✓ Status: {v.status}')
|
||||
print(f' ✓ File: {v.file_path}')
|
||||
print(f' ✓ Size: {v.file_size:,} bytes ({v.file_size/1024:.1f} KB)')
|
||||
print(f' ✓ Checksum: {v.checksum[:32]}...')
|
||||
else:
|
||||
print(' ✗ No released version found')
|
||||
except Exception as e:
|
||||
print(f' ✗ Error: {e}')
|
||||
" 2>/dev/null | grep -E "✓|✗"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "Verification Complete"
|
||||
echo "=========================================="
|
||||
```
|
||||
|
||||
**Quick run (copy-paste):**
|
||||
|
||||
```bash
|
||||
# Verify latest version
|
||||
curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/info/" | python3 -m json.tool && \
|
||||
curl -s "https://api.igny8.com/api/plugins/igny8-wp-bridge/check-update/?current_version=1.0.0" | python3 -m json.tool && \
|
||||
ls -lh /data/app/igny8/plugins/wordpress/dist/igny8-wp-bridge-latest.zip && \
|
||||
echo "✓ All endpoints working"
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Recent Updates & Lessons Learned
|
||||
|
||||
### v1.3.3 Release (Jan 10, 2026)
|
||||
|
||||
**Changes:**
|
||||
- Fixed square image grid layout (side-by-side display)
|
||||
- Fixed landscape image positioning in sections 4+
|
||||
- Removed card wrapper for images without captions
|
||||
- Applied border-radius/shadow directly to images
|
||||
- Landscape images now appear after first paragraph
|
||||
|
||||
**Process:**
|
||||
- Source code updated in plugin templates and CSS
|
||||
- Version bumped in `igny8-bridge.php` (header + constant)
|
||||
- Django admin: Created v1.3.3 with changelog
|
||||
- Status changed to "released" → ZIP auto-generated
|
||||
- Verified download endpoint and file contents
|
||||
- Template changes tested on staging WordPress site
|
||||
|
||||
**Lessons:**
|
||||
- CSS changes require thorough cross-browser testing
|
||||
- Image layout fixes need responsive design verification
|
||||
- Template changes should be tested with multiple content types
|
||||
|
||||
### v1.3.2 Release (Jan 9, 2026)
|
||||
|
||||
**Changes:**
|
||||
- Template rendering improvements
|
||||
- Image layout enhancements
|
||||
- Content section fixes
|
||||
|
||||
**Process:**
|
||||
- Standard release workflow
|
||||
- Auto-build successful
|
||||
- No rollback needed
|
||||
|
||||
### v1.3.0 Release (Jan 8, 2026)
|
||||
|
||||
**Changes:**
|
||||
- Initial distribution system implementation
|
||||
- WordPress auto-update mechanism
|
||||
- Base template system
|
||||
|
||||
**Process:**
|
||||
- Major release with new infrastructure
|
||||
- Extensive testing required
|
||||
- First use of automated packaging
|
||||
|
||||
### Distribution System Milestones
|
||||
|
||||
**v1.7.0 Infrastructure:**
|
||||
- ✅ Complete plugin distribution system
|
||||
- ✅ Multi-platform support architecture
|
||||
- ✅ Automated versioning and packaging
|
||||
- ✅ Security features (checksums, signed URLs)
|
||||
- ✅ Monitoring and analytics
|
||||
|
||||
**Best Practices Established:**
|
||||
1. Always test download endpoint after release
|
||||
2. Verify ZIP contents match source
|
||||
3. Check version number in extracted files
|
||||
4. Test update notification in WordPress
|
||||
5. Monitor download analytics
|
||||
|
||||
**Common Issues Resolved:**
|
||||
- ZIP generation timing → Now synchronous in signals
|
||||
- Checksum mismatches → Auto-calculated reliably
|
||||
- Version comparison → Semantic versioning logic fixed
|
||||
- File size tracking → Automatic and accurate
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
### Release New Version (Simplified)
|
||||
|
||||
```
|
||||
1. Edit source code in /plugins/wordpress/source/igny8-wp-bridge/
|
||||
2. Update version in igny8-bridge.php (header + constant)
|
||||
3. Django Admin → Add Plugin Version:
|
||||
- Plugin: IGNY8 WordPress Bridge
|
||||
- Version: 1.3.3 (or next version)
|
||||
- Changelog: Describe changes
|
||||
- Status: draft
|
||||
- (All other fields auto-fill)
|
||||
4. Change status to "released" (or use bulk action) → Auto-build triggers
|
||||
5. Run verification checklist
|
||||
6. Optionally: Mark old version as deprecated
|
||||
```
|
||||
|
||||
**Admin Bulk Actions:**
|
||||
- **✅ Release selected versions** - Builds ZIP and marks as released
|
||||
- **📢 Mark as update ready** - Notifies WordPress sites
|
||||
- **🗑️ Mark as deprecated** - Deprecates old versions
|
||||
|
||||
### 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
|
||||
@@ -0,0 +1,785 @@
|
||||
# WordPress Integration Management
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**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. [Recent Updates (v1.7.0)](#9-recent-updates-v170)
|
||||
10. [Troubleshooting](#10-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
|
||||
- Plugin distribution infrastructure (v1.7.0)
|
||||
- Automated versioning and packaging system
|
||||
|
||||
### 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
|
||||
|
||||
### Current Status (v1.7.0)
|
||||
|
||||
- ✅ WordPress Plugin: v1.3.3 (Production)
|
||||
- ✅ Distribution System: Fully operational
|
||||
- ✅ Auto-update Mechanism: Active
|
||||
- ✅ Template Design: Updated with image layout fixes
|
||||
- ✅ API Endpoints: All operational
|
||||
- ✅ Security Features: Checksums, signed URLs, rate limiting
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
│ │ │ ├── class-admin.php # Menu registration
|
||||
│ │ │ ├── layout-header.php # Layout wrapper + sidebar
|
||||
│ │ │ ├── layout-footer.php # Footer
|
||||
│ │ │ ├── settings.php # Settings handler
|
||||
│ │ │ └── pages/ # Admin pages (3 pages)
|
||||
│ │ │ ├── connection.php # Dashboard (connection + stats)
|
||||
│ │ │ ├── settings.php # Post types, taxonomies, sync
|
||||
│ │ │ └── logs.php # Webhook activity logs
|
||||
│ │ ├── sync/ # Sync functionality
|
||||
│ │ ├── templates/ # Frontend templates
|
||||
│ │ └── docs/ # Plugin-internal docs
|
||||
│ └── dist/ # Distribution files
|
||||
│ ├── igny8-wp-bridge-v1.x.x.zip # Version ZIP
|
||||
│ ├── igny8-wp-bridge-v1.x.x.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
|
||||
```
|
||||
|
||||
### Plugin Admin Pages
|
||||
|
||||
The WordPress plugin provides 3 admin pages:
|
||||
|
||||
| Page | File | Purpose |
|
||||
|------|------|---------|
|
||||
| Dashboard | `connection.php` | Connection status, Site ID, API key, content stats |
|
||||
| Settings | `settings.php` | Post types, default post status, taxonomies, sync toggle |
|
||||
| Logs | `logs.php` | Webhook activity with timestamps, event types, status |
|
||||
|
||||
**Layout:**
|
||||
- Dashboard: 2-column grid (Connection Status + Content Stats)
|
||||
- Settings: 3-column top row (Post Types + Default Post Status + IGNY8 Sync) + dynamic taxonomy cards
|
||||
- Logs: Full-width table with search
|
||||
|
||||
### 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.
|
||||
|
||||
**Required Fields Only:**
|
||||
- Plugin
|
||||
- Version (e.g., 1.2.0)
|
||||
- Changelog
|
||||
- Status
|
||||
|
||||
**Auto-Filled Fields:**
|
||||
- Min API/Platform/PHP versions (copied from previous version)
|
||||
- File path, size, checksum (generated on release)
|
||||
- Version code (calculated from version number)
|
||||
|
||||
**To Release a New Version:**
|
||||
|
||||
1. Click **Add Plugin Version**
|
||||
2. Select plugin
|
||||
3. Enter version number (e.g., 1.2.0)
|
||||
4. Write changelog
|
||||
5. Set status to `draft`
|
||||
6. Save
|
||||
7. Change status to `released` (or use bulk action "✅ Release selected versions")
|
||||
8. ZIP is automatically built with all file info populated
|
||||
|
||||
**Bulk Actions:**
|
||||
- **✅ Release selected versions** - Builds ZIP and releases
|
||||
- **📢 Mark as update ready** - Notifies WordPress sites
|
||||
- **🗑️ Mark as deprecated** - Deprecates old versions
|
||||
|
||||
#### 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
|
||||
|
||||
**Authentication (Source of Truth):**
|
||||
|
||||
```python
|
||||
# Site model (in auth/models.py) - SINGLE source of truth for API auth
|
||||
|
||||
class Site(models.Model):
|
||||
wp_api_key = models.CharField(max_length=255) # API key for WP authentication
|
||||
# Used by: PublisherService, test connection, plugin verification
|
||||
```
|
||||
|
||||
**Sync Tracking (SiteIntegration):**
|
||||
|
||||
```python
|
||||
# SiteIntegration model (in auth/models.py) - Tracks sync status, NOT auth
|
||||
|
||||
class SiteIntegration(models.Model):
|
||||
site = models.ForeignKey(Site, ...) # IGNY8 site
|
||||
platform = models.CharField(...) # "wordpress", "shopify"
|
||||
external_site_url = models.URLField() # WordPress URL
|
||||
sync_enabled = models.BooleanField()
|
||||
sync_status = models.CharField(...) # "pending", "syncing", "completed"
|
||||
last_sync_at = models.DateTimeField()
|
||||
sync_error = models.TextField(null=True)
|
||||
connection_status = models.CharField(...) # "connected", "error"
|
||||
```
|
||||
|
||||
**Key Architecture Points:**
|
||||
- `Site.wp_api_key` is the **SINGLE source of truth** for API authentication
|
||||
- `SiteIntegration` provides sync tracking but NOT authentication
|
||||
- Publishing uses `Site.wp_api_key` via `X-IGNY8-API-KEY` header
|
||||
- SiteIntegration kept for future multi-platform support (Shopify, custom)
|
||||
|
||||
---
|
||||
|
||||
## 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/igny8/backend/logs/` |
|
||||
| Plugin API Logs | Check Django logs for `/api/plugins/` requests |
|
||||
| WordPress Plugin Logs | WP site's debug.log if WP_DEBUG enabled |
|
||||
|
||||
---
|
||||
|
||||
## 9. Recent Updates (v1.7.0)
|
||||
|
||||
### Plugin Distribution System Implemented
|
||||
|
||||
**Infrastructure:**
|
||||
- ✅ Complete plugin distribution system operational
|
||||
- ✅ Multi-platform support (WordPress, Shopify, Custom sites)
|
||||
- ✅ Automated ZIP packaging with versioning
|
||||
- ✅ Security features: checksums (MD5/SHA256), signed URLs, rate limiting
|
||||
- ✅ Monitoring and analytics dashboard
|
||||
|
||||
**Database Models:**
|
||||
- `Plugin` - Platform-agnostic plugin registry
|
||||
- `PluginVersion` - Full version lifecycle management
|
||||
- `PluginInstallation` - Per-site installation tracking
|
||||
- `PluginDownload` - Download analytics and monitoring
|
||||
|
||||
**API Endpoints:**
|
||||
- All 5 endpoints operational and tested
|
||||
- `/download/` - Serve plugin ZIP files
|
||||
- `/check-update/` - WordPress update mechanism
|
||||
- `/info/` - Plugin metadata
|
||||
- `/register/` - Installation registration
|
||||
- `/health-check/` - Plugin health monitoring
|
||||
|
||||
### WordPress Plugin Updates (v1.3.0 → v1.5.1)
|
||||
|
||||
**v1.5.1 - Admin UI Consolidation:**
|
||||
- Reduced admin pages from 6 to 3: Dashboard, Settings, Logs
|
||||
- Renamed Connection page to Dashboard with content stats
|
||||
- Removed redundant pages: Data, Sync, old Dashboard
|
||||
- Settings redesigned with 3-column layout (Post Types + Default Post Status + IGNY8 Sync)
|
||||
- Fixed header alignment across all pages with proper width styling
|
||||
- Cleaned up orphaned code and functions
|
||||
|
||||
**v1.5.0 - Authentication Simplification:**
|
||||
- Simplified authentication to use Site.wp_api_key as single source of truth
|
||||
- Removed redundant access_token handling
|
||||
- Improved test connection flow using authenticated /verify-key endpoint
|
||||
|
||||
**v1.3.3 - Template Design Improvements:**
|
||||
- Fixed square images displaying in 2 rows (now side-by-side)
|
||||
- Fixed landscape images in sections 4+ displaying incorrectly
|
||||
- Removed card wrapper for images without descriptions
|
||||
- Applied border-radius and shadow directly to images
|
||||
- Landscape images now appear after first paragraph
|
||||
- Consistent CSS classes for all image types
|
||||
|
||||
**v1.3.2 - Template Fixes:**
|
||||
- Template rendering improvements in app and plugin
|
||||
- Image layout enhancements
|
||||
- Content section fixes
|
||||
|
||||
**v1.3.1 - Plugin Updates:**
|
||||
- Versioning system improvements
|
||||
- WordPress plugin compatibility updates
|
||||
|
||||
**v1.3.0 - Initial Distribution System:**
|
||||
- First release with automated distribution
|
||||
- Update mechanism implementation
|
||||
- Base template system
|
||||
|
||||
### Version Progression Timeline
|
||||
|
||||
```
|
||||
v1.7.0 (Jan 10, 2026) - App infrastructure + plugin v1.5.1
|
||||
├── Plugin admin UI consolidation (6→3 pages)
|
||||
├── Authentication simplification
|
||||
├── Plugin distribution system complete
|
||||
├── Template design overhaul
|
||||
└── Pre-launch cleanup complete
|
||||
|
||||
v1.6.2 (Jan 8, 2026) - Design refinements
|
||||
└── Marketing site updates
|
||||
|
||||
v1.6.0 (Jan 8, 2026) - Payment system refactor
|
||||
└── Stripe, PayPal, Bank Transfer
|
||||
|
||||
v1.4.0 (Jan 5, 2026) - AI model architecture overhaul
|
||||
└── IntegrationProvider, AIModelConfig
|
||||
```
|
||||
|
||||
### Infrastructure Improvements
|
||||
|
||||
**Security:**
|
||||
- Signed URLs with expiration for downloads
|
||||
- Checksum verification (MD5 + SHA256)
|
||||
- Rate limiting per IP/site
|
||||
- API authentication for sensitive operations
|
||||
- Production environment protection
|
||||
|
||||
**Automation:**
|
||||
- Auto-build on version status change
|
||||
- Automatic checksum calculation
|
||||
- File size tracking
|
||||
- Version number updates in ZIP contents
|
||||
- Released timestamp tracking
|
||||
|
||||
**Monitoring:**
|
||||
- Installation tracking per site
|
||||
- Download analytics
|
||||
- Health check endpoints
|
||||
- Version distribution monitoring
|
||||
- Error logging and alerts
|
||||
|
||||
### Documentation Updates
|
||||
|
||||
- Added comprehensive plugin distribution plan
|
||||
- Updated API endpoint documentation
|
||||
- Enhanced troubleshooting guides
|
||||
- Added version progression tracking
|
||||
- Updated architecture diagrams
|
||||
|
||||
---
|
||||
|
||||
## 10. Troubleshooting
|
||||
|-----|----------|
|
||||
| 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
|
||||
@@ -0,0 +1,526 @@
|
||||
# AI Functions Reference
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
IGNY8's AI engine provides functions for content planning and generation. Located in `backend/igny8_core/ai/`.
|
||||
|
||||
**Providers (v1.4.0+):**
|
||||
- **OpenAI** - GPT-4 for text, DALL-E 3 for images (via `IntegrationProvider`)
|
||||
- **Anthropic** - Claude models for text (via `IntegrationProvider`)
|
||||
- **Runware** - Alternative image generation (via `IntegrationProvider`)
|
||||
- **Bria** - Additional image generation option
|
||||
|
||||
**Key Features:**
|
||||
- Provider API keys stored in `IntegrationProvider` model
|
||||
- Model configurations stored in `AIModelConfig` model
|
||||
- System defaults stored in `SystemAISettings` singleton
|
||||
- Credit-based pricing per model via `AIModelConfig.tokens_per_credit` / `credits_per_image`
|
||||
|
||||
---
|
||||
|
||||
## Architecture (Updated v1.4.0)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ AI ENGINE │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ AIEngine │───►│ Function │───►│ Provider │ │
|
||||
│ │ (router) │ │ (logic) │ │ (API) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │ ▲ │
|
||||
│ │ │ │
|
||||
│ ▼ │ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ModelRegistry │───►│AIModelConfig │───►│Integration │ │
|
||||
│ │ (service) │ │ (database) │ │ Provider │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ Functions: │
|
||||
│ • AutoClusterKeywords │
|
||||
│ • GenerateContentIdeas │
|
||||
│ • GenerateContent │
|
||||
│ • GenerateImages │
|
||||
│ • GenerateImagePrompts │
|
||||
│ • OptimizeContent (pending) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Model Registry (NEW v1.4.0)
|
||||
|
||||
**Location:** `backend/igny8_core/ai/model_registry.py`
|
||||
|
||||
Central registry for AI model configurations with caching.
|
||||
|
||||
```python
|
||||
from igny8_core.ai.model_registry import ModelRegistry
|
||||
|
||||
# Get model configuration
|
||||
model = ModelRegistry.get_model('gpt-4o-mini')
|
||||
|
||||
# Get pricing rate
|
||||
input_rate = ModelRegistry.get_rate('gpt-4o-mini', 'input')
|
||||
|
||||
# Calculate cost
|
||||
cost = ModelRegistry.calculate_cost('gpt-4o-mini', input_tokens=1000, output_tokens=500)
|
||||
|
||||
# Get API key for provider
|
||||
api_key = ModelRegistry.get_api_key('openai')
|
||||
|
||||
# Get default model
|
||||
default_text = ModelRegistry.get_default_model('text')
|
||||
default_image = ModelRegistry.get_default_model('image')
|
||||
|
||||
# List available models
|
||||
text_models = ModelRegistry.list_models(model_type='text')
|
||||
image_models = ModelRegistry.list_models(model_type='image', provider='runware')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AIEngine
|
||||
|
||||
**Location:** `backend/igny8_core/ai/engine.py`
|
||||
|
||||
Main orchestrator for AI operations.
|
||||
|
||||
```python
|
||||
class AIEngine:
|
||||
def __init__(self, account: Account):
|
||||
self.account = account
|
||||
self.settings = self._load_settings() # Uses SystemAISettings + AccountSettings
|
||||
self.text_provider = self._init_text_provider() # Uses ModelRegistry
|
||||
self.image_provider = self._init_image_provider() # Uses ModelRegistry
|
||||
|
||||
def auto_cluster(self, keywords: List[Keyword]) -> List[Cluster]:
|
||||
"""Cluster keywords by topic"""
|
||||
|
||||
def generate_ideas(self, cluster: Cluster) -> List[ContentIdea]:
|
||||
"""Generate content ideas for cluster"""
|
||||
|
||||
def generate_content(self, task: Task) -> Content:
|
||||
"""Generate full article content"""
|
||||
|
||||
def generate_images(self, content: Content, count: int = 1, quality_tier: str = 'quality') -> List[ContentImage]:
|
||||
"""Generate images for content with quality tier selection (v1.4.0)"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Function: AutoClusterKeywords
|
||||
|
||||
**Purpose:** Group semantically related keywords into clusters
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"keywords": [
|
||||
{"id": "...", "keyword": "python tutorial"},
|
||||
{"id": "...", "keyword": "learn python"},
|
||||
{"id": "...", "keyword": "python basics"},
|
||||
...
|
||||
],
|
||||
"site_context": {
|
||||
"name": "Tech Blog",
|
||||
"industry": "Technology"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"clusters": [
|
||||
{
|
||||
"name": "Python Learning Resources",
|
||||
"description": "Tutorials and guides for learning Python",
|
||||
"keywords": ["python tutorial", "learn python", "python basics"]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `auto_cluster`
|
||||
|
||||
**Model:** Default text model (from `AIModelConfig.is_default`)
|
||||
|
||||
**Credit Cost:** Based on `AIModelConfig.tokens_per_credit` for model used
|
||||
|
||||
---
|
||||
|
||||
## Function: GenerateContentIdeas
|
||||
|
||||
**Purpose:** Create content ideas from a keyword cluster
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"cluster": {
|
||||
"name": "Python Learning Resources",
|
||||
"description": "...",
|
||||
"keywords": [...]
|
||||
},
|
||||
"site_context": {
|
||||
"name": "Tech Blog",
|
||||
"industry": "Technology"
|
||||
},
|
||||
"count": 5 # Number of ideas to generate
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"ideas": [
|
||||
{
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"description": "A comprehensive guide covering...",
|
||||
"primary_keyword": "python tutorial",
|
||||
"secondary_keywords": ["learn python", "python basics"]
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `generate_ideas`
|
||||
|
||||
**Model:** Default text model (from `AIModelConfig.is_default`)
|
||||
|
||||
**Credit Cost:** Based on `AIModelConfig.tokens_per_credit` for model used
|
||||
|
||||
---
|
||||
|
||||
## Function: GenerateContent
|
||||
|
||||
**Purpose:** Create full article from task brief
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"task": {
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"brief": "Write a comprehensive guide...",
|
||||
"primary_keyword": "python tutorial",
|
||||
"secondary_keywords": ["learn python", "python basics"]
|
||||
},
|
||||
"site_context": {
|
||||
"name": "Tech Blog",
|
||||
"industry": "Technology",
|
||||
"tone": "Professional but approachable"
|
||||
},
|
||||
"options": {
|
||||
"target_word_count": 2000,
|
||||
"include_headings": True,
|
||||
"include_lists": True,
|
||||
"include_code_blocks": True,
|
||||
"temperature": 0.7, # From SystemAISettings or AccountSettings override
|
||||
"max_tokens": 8192 # From SystemAISettings or AccountSettings override
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"body": "<h2>Introduction</h2><p>...</p>...", # Full HTML
|
||||
"excerpt": "Learn the essential Python tips...",
|
||||
"meta_title": "10 Python Tips for Beginners | Tech Blog",
|
||||
"meta_description": "Master Python with these 10 essential tips...",
|
||||
"word_count": 2150
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `generate_content`
|
||||
|
||||
**Model:** Default text model (from `AIModelConfig.is_default`)
|
||||
|
||||
**Credit Cost:** Based on `AIModelConfig.tokens_per_credit` for model used
|
||||
|
||||
---
|
||||
|
||||
## Function: GenerateImages (Updated v1.4.0)
|
||||
|
||||
**Purpose:** Create images for article content with quality tier selection
|
||||
|
||||
**Input:**
|
||||
```python
|
||||
{
|
||||
"content": {
|
||||
"title": "10 Essential Python Tips for Beginners",
|
||||
"body": "<html>...</html>",
|
||||
"primary_keyword": "python tutorial"
|
||||
},
|
||||
"options": {
|
||||
"count": 3,
|
||||
"quality_tier": "quality", # basic (1 credit), quality (5 credits), premium (15 credits)
|
||||
"style": "photorealistic", # From SystemAISettings or override
|
||||
"size": "1024x1024" # From SystemAISettings or override
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Quality Tiers (v1.4.0):**
|
||||
|
||||
| Tier | Model Example | credits_per_image | Description |
|
||||
|------|---------------|-------------------|-------------|
|
||||
| basic | runware:97@1 | 1 | Fast generation, simple images |
|
||||
| quality | dall-e-3 | 5 | Balanced quality and speed |
|
||||
| premium | google:4@2 | 15 | Best quality, slower |
|
||||
|
||||
**Process:**
|
||||
1. Get quality tier model from `AIModelConfig.get_image_models_by_tier()`
|
||||
2. Analyze content to identify image opportunities
|
||||
3. Generate prompts for each image (via `GenerateImagePrompts`)
|
||||
4. Call image provider API (using `IntegrationProvider.get_api_key()`)
|
||||
5. Store images and generate thumbnails
|
||||
|
||||
**Output:**
|
||||
```python
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"url": "https://storage.../image1.png",
|
||||
"thumbnail_url": "https://storage.../image1_thumb.png",
|
||||
"alt_text": "Python code example showing...",
|
||||
"caption": "Example of Python list comprehension",
|
||||
"prompt": "A clean code editor showing Python syntax...",
|
||||
"is_featured": True,
|
||||
"model_used": "dall-e-3",
|
||||
"quality_tier": "quality",
|
||||
"credits_used": 5
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Prompt Template:** `generate_image_prompts`
|
||||
|
||||
**Model:** Selected by quality tier from `AIModelConfig`
|
||||
|
||||
**Credit Cost:** `AIModelConfig.credits_per_image` for selected model
|
||||
|
||||
---
|
||||
|
||||
## Function: OptimizeContent (Pending)
|
||||
|
||||
**Status:** ⏸️ Not yet implemented
|
||||
|
||||
**Purpose:** SEO optimize existing content
|
||||
|
||||
**Planned Input:**
|
||||
```python
|
||||
{
|
||||
"content": {
|
||||
"title": "...",
|
||||
"body": "...",
|
||||
"target_keyword": "..."
|
||||
},
|
||||
"optimization_type": "seo" | "readability" | "both"
|
||||
}
|
||||
```
|
||||
|
||||
**Planned Output:**
|
||||
```python
|
||||
{
|
||||
"optimized_title": "...",
|
||||
"optimized_body": "...",
|
||||
"changes": [
|
||||
{"type": "keyword_density", "description": "..."},
|
||||
{"type": "heading_structure", "description": "..."},
|
||||
...
|
||||
],
|
||||
"score_before": 65,
|
||||
"score_after": 85
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Templates
|
||||
|
||||
### System Prompts
|
||||
|
||||
Stored in `PromptTemplate` model or defaults in code.
|
||||
|
||||
| Template | Purpose |
|
||||
|----------|---------|
|
||||
| `auto_cluster` | Keywords → Clusters |
|
||||
| `generate_ideas` | Cluster → Ideas |
|
||||
| `generate_content` | Task → Article |
|
||||
| `generate_image_prompts` | Content → Image prompts |
|
||||
| `optimize_content` | Content → Optimized (pending) |
|
||||
|
||||
### Template Variables
|
||||
|
||||
```python
|
||||
{
|
||||
"site_name": "Tech Blog",
|
||||
"site_industry": "Technology",
|
||||
"site_tone": "Professional",
|
||||
"keyword": "python tutorial",
|
||||
"keywords": ["python", "tutorial", "learn"],
|
||||
"cluster_name": "Python Learning",
|
||||
"task_title": "10 Python Tips",
|
||||
"task_brief": "Write a guide...",
|
||||
"target_word_count": 2000
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Prompts
|
||||
|
||||
Users can customize prompts via Settings → Prompts:
|
||||
|
||||
```python
|
||||
# API
|
||||
GET /api/v1/system/prompts/{type}/
|
||||
PUT /api/v1/system/prompts/{type}/
|
||||
POST /api/v1/system/prompts/{type}/reset/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider: OpenAI
|
||||
|
||||
**Location:** `backend/igny8_core/ai/providers/openai_provider.py`
|
||||
|
||||
### Text Generation
|
||||
|
||||
```python
|
||||
class OpenAITextProvider:
|
||||
def complete(self, prompt: str, options: dict) -> str:
|
||||
response = openai.ChatCompletion.create(
|
||||
model=options.get('model', 'gpt-4'),
|
||||
messages=[
|
||||
{"role": "system", "content": self.system_prompt},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=options.get('temperature', 0.7),
|
||||
max_tokens=options.get('max_tokens', 4000)
|
||||
)
|
||||
return response.choices[0].message.content
|
||||
```
|
||||
|
||||
### Image Generation
|
||||
|
||||
```python
|
||||
class OpenAIImageProvider:
|
||||
def generate(self, prompt: str, options: dict) -> str:
|
||||
response = openai.Image.create(
|
||||
model="dall-e-3",
|
||||
prompt=prompt,
|
||||
size=options.get('size', '1024x1024'),
|
||||
quality=options.get('quality', 'standard'),
|
||||
n=1
|
||||
)
|
||||
return response.data[0].url
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Provider: Runware
|
||||
|
||||
**Location:** `backend/igny8_core/ai/providers/runware_provider.py`
|
||||
|
||||
Alternative image generation via Runware API.
|
||||
|
||||
```python
|
||||
class RunwareImageProvider:
|
||||
def generate(self, prompt: str, options: dict) -> str:
|
||||
response = self.client.generate(
|
||||
prompt=prompt,
|
||||
width=options.get('width', 1024),
|
||||
height=options.get('height', 1024),
|
||||
model=options.get('model', 'default')
|
||||
)
|
||||
return response.image_url
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Code | Handling |
|
||||
|-------|------|----------|
|
||||
| Rate limit | `rate_limit_exceeded` | Retry with backoff |
|
||||
| Context too long | `context_length_exceeded` | Truncate input |
|
||||
| Content filter | `content_policy_violation` | Return error to user |
|
||||
| API unavailable | `api_error` | Retry or fail |
|
||||
|
||||
### Retry Strategy
|
||||
|
||||
```python
|
||||
class AIEngine:
|
||||
MAX_RETRIES = 3
|
||||
BASE_DELAY = 1 # seconds
|
||||
|
||||
def _call_with_retry(self, func, *args, **kwargs):
|
||||
for attempt in range(self.MAX_RETRIES):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except RateLimitError:
|
||||
delay = self.BASE_DELAY * (2 ** attempt)
|
||||
time.sleep(delay)
|
||||
except ContentPolicyError:
|
||||
raise # Don't retry policy violations
|
||||
raise MaxRetriesExceeded()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Per-Account Settings
|
||||
|
||||
```python
|
||||
# In AIIntegrationSettings
|
||||
openai_api_key = "sk-..."
|
||||
openai_model = "gpt-4" # or gpt-4-turbo
|
||||
image_provider = "dalle" # or "runware"
|
||||
dalle_api_key = "sk-..."
|
||||
runware_api_key = "..."
|
||||
```
|
||||
|
||||
### Model Options
|
||||
|
||||
| Model | Use Case | Token Limit |
|
||||
|-------|----------|-------------|
|
||||
| `gpt-4` | High quality content | 8,192 |
|
||||
| `gpt-4-turbo` | Faster, larger context | 128,000 |
|
||||
| `gpt-3.5-turbo` | Budget option | 4,096 |
|
||||
|
||||
---
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Logging
|
||||
|
||||
All AI operations logged with:
|
||||
- Input parameters (sanitized)
|
||||
- Output summary
|
||||
- Token usage
|
||||
- Latency
|
||||
- Error details
|
||||
|
||||
### Metrics
|
||||
|
||||
Tracked via internal logging:
|
||||
- Operations per day
|
||||
- Average latency
|
||||
- Error rate
|
||||
- Token consumption
|
||||
- Credit usage
|
||||
@@ -0,0 +1,662 @@
|
||||
# Phase 6: Data Backup & Cleanup Guide
|
||||
|
||||
**Version:** 1.0
|
||||
**Created:** January 9, 2026
|
||||
**Purpose:** Pre-V1.0 Launch Database Preparation
|
||||
|
||||
---
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [What Was Created](#what-was-created)
|
||||
3. [When to Use](#when-to-use)
|
||||
4. [Pre-Execution Checklist](#pre-execution-checklist)
|
||||
5. [Command 1: Export System Config](#command-1-export-system-config)
|
||||
6. [Command 2: Cleanup User Data](#command-2-cleanup-user-data)
|
||||
7. [Complete Workflow](#complete-workflow)
|
||||
8. [Safety Measures](#safety-measures)
|
||||
9. [Rollback Procedures](#rollback-procedures)
|
||||
10. [FAQ](#faq)
|
||||
|
||||
---
|
||||
|
||||
## 📖 Overview
|
||||
|
||||
Phase 6 provides two Django management commands to safely prepare your IGNY8 database for V1.0 production launch:
|
||||
|
||||
1. **Export System Configuration** - Backs up all system settings to JSON files
|
||||
2. **Cleanup User Data** - Removes all test/development user data while preserving system configuration
|
||||
|
||||
### Why These Commands?
|
||||
|
||||
- **Clean Start**: Launch V1.0 with a pristine database
|
||||
- **Configuration Preservation**: Keep all your carefully configured settings
|
||||
- **Safety First**: Multiple safety checks and dry-run options
|
||||
- **Audit Trail**: Complete metadata and logging
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ What Was Created
|
||||
|
||||
### File Locations
|
||||
|
||||
```
|
||||
backend/igny8_core/management/commands/
|
||||
├── export_system_config.py # System configuration backup
|
||||
└── cleanup_user_data.py # User data cleanup
|
||||
```
|
||||
|
||||
### Command 1: `export_system_config.py`
|
||||
|
||||
**Purpose**: Exports all system configuration to JSON files for backup and version control.
|
||||
|
||||
**What it exports:**
|
||||
- ✅ Subscription Plans (Starter, Growth, Scale)
|
||||
- ✅ Credit Cost Configurations
|
||||
- ✅ AI Model Settings (OpenAI, Anthropic, etc.)
|
||||
- ✅ Global Integration Settings
|
||||
- ✅ Industries and Sectors
|
||||
- ✅ Seed Keywords (reference data)
|
||||
- ✅ Author Profiles
|
||||
- ✅ AI Prompts and Variables
|
||||
|
||||
**What it creates:**
|
||||
- Individual JSON files for each data type
|
||||
- `export_metadata.json` with timestamp and statistics
|
||||
- Organized folder structure in `backups/config/`
|
||||
|
||||
### Command 2: `cleanup_user_data.py`
|
||||
|
||||
**Purpose**: Safely removes all user-generated test data before production launch.
|
||||
|
||||
**What it deletes:**
|
||||
- 🗑️ Sites and Site Settings
|
||||
- 🗑️ Keywords, Clusters, Ideas
|
||||
- 🗑️ Tasks, Content, Images
|
||||
- 🗑️ Publishing Records
|
||||
- 🗑️ WordPress Sync Events
|
||||
- 🗑️ Credit Transactions and Usage Logs
|
||||
- 🗑️ Automation Runs
|
||||
- 🗑️ Notifications
|
||||
- 🗑️ Orders
|
||||
|
||||
**What it preserves:**
|
||||
- ✅ User Accounts (admin users)
|
||||
- ✅ System Configuration (all settings from export)
|
||||
- ✅ Plans and Pricing
|
||||
- ✅ AI Models and Prompts
|
||||
- ✅ Industries and Sectors
|
||||
|
||||
---
|
||||
|
||||
## ⏰ When to Use
|
||||
|
||||
### Correct Timing
|
||||
|
||||
✅ **Use these commands when:**
|
||||
- You're preparing for V1.0 production launch
|
||||
- You've completed all testing and configuration
|
||||
- You want to start production with clean data
|
||||
- All system settings (Plans, AI models, prompts) are finalized
|
||||
|
||||
❌ **Do NOT use these commands when:**
|
||||
- You're still in active development
|
||||
- You haven't backed up your configurations
|
||||
- You're unsure about your system settings
|
||||
- You're in production with live users
|
||||
|
||||
### Recommended Timeline
|
||||
|
||||
```
|
||||
Day -7: Final configuration review
|
||||
Day -5: Export system config (first backup)
|
||||
Day -3: Test commands in staging
|
||||
Day -2: Export system config (final backup)
|
||||
Day -1: Cleanup user data in staging
|
||||
Day 0: Launch day - cleanup in production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Pre-Execution Checklist
|
||||
|
||||
Before running ANY Phase 6 command, complete this checklist:
|
||||
|
||||
### Environment Verification
|
||||
|
||||
- [ ] Confirm you're in the correct environment (staging vs production)
|
||||
- [ ] Check `ENVIRONMENT` setting in Django settings
|
||||
- [ ] Verify database connection is correct
|
||||
- [ ] Ensure you have full database backup
|
||||
|
||||
### System State
|
||||
|
||||
- [ ] All Plans configured and tested
|
||||
- [ ] All AI prompts finalized
|
||||
- [ ] All credit costs verified
|
||||
- [ ] All industries/sectors populated
|
||||
- [ ] Seed keywords imported
|
||||
|
||||
### Safety Backups
|
||||
|
||||
- [ ] Full database dump exists
|
||||
- [ ] Previous export exists (if available)
|
||||
- [ ] Media files backed up
|
||||
- [ ] Environment variables documented
|
||||
|
||||
### Access & Permissions
|
||||
|
||||
- [ ] You have Django shell access
|
||||
- [ ] You have database backup access
|
||||
- [ ] You have rollback permissions
|
||||
- [ ] Stakeholders notified
|
||||
|
||||
---
|
||||
|
||||
## 📤 Command 1: Export System Config
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py export_system_config
|
||||
```
|
||||
|
||||
### With Custom Output Directory
|
||||
|
||||
```bash
|
||||
python manage.py export_system_config --output-dir=/path/to/backup
|
||||
```
|
||||
|
||||
### Step-by-Step Execution
|
||||
|
||||
#### Step 1: Navigate to Backend
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
```
|
||||
|
||||
#### Step 2: Run Export
|
||||
|
||||
```bash
|
||||
python manage.py export_system_config --output-dir=../backups/config/$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
#### Step 3: Verify Output
|
||||
|
||||
```bash
|
||||
ls -la ../backups/config/$(date +%Y%m%d)/
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
plans.json # Subscription plans
|
||||
credit_costs.json # Credit cost configurations
|
||||
ai_models.json # AI model settings
|
||||
global_integrations.json # Integration settings
|
||||
industries.json # Industry master data
|
||||
sectors.json # Sector master data
|
||||
seed_keywords.json # Reference keywords
|
||||
author_profiles.json # Writing style profiles
|
||||
prompts.json # AI prompts
|
||||
prompt_variables.json # Prompt variables
|
||||
export_metadata.json # Export timestamp & stats
|
||||
```
|
||||
|
||||
#### Step 4: Verify Data
|
||||
|
||||
Check one of the exports:
|
||||
```bash
|
||||
cat ../backups/config/$(date +%Y%m%d)/plans.json | head -20
|
||||
```
|
||||
|
||||
#### Step 5: Commit to Version Control
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
git add backups/config/
|
||||
git commit -m "Backup: V1.0 system configuration export"
|
||||
git push
|
||||
```
|
||||
|
||||
### What The Output Looks Like
|
||||
|
||||
```
|
||||
Exporting system configuration to: /data/app/igny8/backups/config/20260109
|
||||
|
||||
✓ Exported 3 Subscription Plans → plans.json
|
||||
✓ Exported 12 Credit Cost Configurations → credit_costs.json
|
||||
✓ Exported 4 AI Model Configurations → ai_models.json
|
||||
✓ Exported 1 Global Integration Settings → global_integrations.json
|
||||
✓ Exported 15 Industries → industries.json
|
||||
✓ Exported 47 Sectors → sectors.json
|
||||
✓ Exported 523 Seed Keywords → seed_keywords.json
|
||||
✓ Exported 3 Author Profiles → author_profiles.json
|
||||
✓ Exported 8 AI Prompts → prompts.json
|
||||
✓ Exported 12 Prompt Variables → prompt_variables.json
|
||||
|
||||
✓ Metadata saved to export_metadata.json
|
||||
|
||||
======================================================================
|
||||
System Configuration Export Complete!
|
||||
|
||||
Successful: 10 exports
|
||||
Failed: 0 exports
|
||||
Location: /data/app/igny8/backups/config/20260109
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Problem**: "No module named 'django'"
|
||||
```bash
|
||||
# Solution: Activate virtual environment or use Docker
|
||||
docker-compose exec backend python manage.py export_system_config
|
||||
```
|
||||
|
||||
**Problem**: "Permission denied" when writing files
|
||||
```bash
|
||||
# Solution: Check directory permissions
|
||||
mkdir -p ../backups/config
|
||||
chmod 755 ../backups/config
|
||||
```
|
||||
|
||||
**Problem**: Empty JSON files
|
||||
```bash
|
||||
# Solution: Verify data exists in database
|
||||
python manage.py shell
|
||||
>>> from igny8_core.modules.billing.models import Plan
|
||||
>>> Plan.objects.count()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗑️ Command 2: Cleanup User Data
|
||||
|
||||
### ⚠️ CRITICAL WARNING
|
||||
|
||||
**THIS COMMAND PERMANENTLY DELETES DATA**
|
||||
|
||||
- Cannot be undone without database restore
|
||||
- Removes ALL user-generated content
|
||||
- Should ONLY be run before production launch
|
||||
- ALWAYS run `--dry-run` first
|
||||
|
||||
### Safety Features
|
||||
|
||||
1. **Dry-Run Mode**: Preview deletions without actually deleting
|
||||
2. **Confirmation Prompt**: Must type "DELETE ALL DATA" to proceed
|
||||
3. **Production Protection**: Blocked in production environment (unless explicitly allowed)
|
||||
4. **Transaction Safety**: All deletions in atomic transaction
|
||||
5. **Detailed Logging**: Shows exactly what was deleted
|
||||
|
||||
### Usage: Dry Run (Always First!)
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py cleanup_user_data --dry-run
|
||||
```
|
||||
|
||||
### Dry Run Output Example
|
||||
|
||||
```
|
||||
======================================================================
|
||||
DRY RUN - No data will be deleted
|
||||
======================================================================
|
||||
|
||||
✓ Would delete 1,234 Notifications
|
||||
✓ Would delete 5,678 Credit Usage Logs
|
||||
✓ Would delete 456 Credit Transactions
|
||||
✓ Would delete 23 Orders
|
||||
✓ Would delete 8,901 WordPress Sync Events
|
||||
✓ Would delete 234 Publishing Records
|
||||
✓ Would delete 45 Automation Runs
|
||||
✓ Would delete 3,456 Images
|
||||
✓ Would delete 2,345 Content
|
||||
✓ Would delete 4,567 Tasks
|
||||
✓ Would delete 5,678 Content Ideas
|
||||
✓ Would delete 1,234 Clusters
|
||||
✓ Would delete 9,876 Keywords
|
||||
✓ Would delete 12 Sites
|
||||
|
||||
→ Keeping 3 Users (not deleted)
|
||||
|
||||
Total records to delete: 43,739
|
||||
|
||||
======================================================================
|
||||
To proceed with actual deletion, run:
|
||||
python manage.py cleanup_user_data --confirm
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Usage: Actual Cleanup
|
||||
|
||||
```bash
|
||||
python manage.py cleanup_user_data --confirm
|
||||
```
|
||||
|
||||
**You will be prompted:**
|
||||
```
|
||||
======================================================================
|
||||
⚠️ DELETING ALL USER DATA - THIS CANNOT BE UNDONE!
|
||||
======================================================================
|
||||
|
||||
Type "DELETE ALL DATA" to proceed:
|
||||
```
|
||||
|
||||
**Type exactly:** `DELETE ALL DATA`
|
||||
|
||||
### Actual Cleanup Output
|
||||
|
||||
```
|
||||
Proceeding with deletion...
|
||||
|
||||
✓ Deleted 1,234 Notifications
|
||||
✓ Deleted 5,678 Credit Usage Logs
|
||||
✓ Deleted 456 Credit Transactions
|
||||
✓ Deleted 23 Orders
|
||||
✓ Deleted 8,901 WordPress Sync Events
|
||||
✓ Deleted 234 Publishing Records
|
||||
✓ Deleted 45 Automation Runs
|
||||
✓ Deleted 3,456 Images
|
||||
✓ Deleted 2,345 Content
|
||||
✓ Deleted 4,567 Tasks
|
||||
✓ Deleted 5,678 Content Ideas
|
||||
✓ Deleted 1,234 Clusters
|
||||
✓ Deleted 9,876 Keywords
|
||||
✓ Deleted 12 Sites
|
||||
|
||||
======================================================================
|
||||
User Data Cleanup Complete!
|
||||
|
||||
Total records deleted: 43,739
|
||||
Failed deletions: 0
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Production Environment Protection
|
||||
|
||||
If you try to run cleanup in production:
|
||||
|
||||
```
|
||||
⚠️ BLOCKED: Cannot run cleanup in PRODUCTION environment!
|
||||
|
||||
To allow this, temporarily set ENVIRONMENT to "staging" in settings.
|
||||
```
|
||||
|
||||
To override (ONLY if absolutely necessary):
|
||||
|
||||
```python
|
||||
# In settings.py - TEMPORARY
|
||||
ENVIRONMENT = 'staging' # Change back after cleanup!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Complete Workflow
|
||||
|
||||
### Full Pre-Launch Procedure
|
||||
|
||||
```bash
|
||||
# ========================================
|
||||
# STEP 1: FULL DATABASE BACKUP
|
||||
# ========================================
|
||||
cd /data/app/igny8/backend
|
||||
pg_dump -h localhost -U postgres igny8_db > ../backups/$(date +%Y%m%d)_pre_v1_full_backup.sql
|
||||
|
||||
# Verify backup exists and has content
|
||||
ls -lh ../backups/$(date +%Y%m%d)_pre_v1_full_backup.sql
|
||||
head -50 ../backups/$(date +%Y%m%d)_pre_v1_full_backup.sql
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 2: EXPORT SYSTEM CONFIGURATION
|
||||
# ========================================
|
||||
python manage.py export_system_config --output-dir=../backups/config/$(date +%Y%m%d)
|
||||
|
||||
# Verify exports
|
||||
ls -la ../backups/config/$(date +%Y%m%d)/
|
||||
|
||||
# Review critical configs
|
||||
cat ../backups/config/$(date +%Y%m%d)/plans.json | python -m json.tool | head -30
|
||||
cat ../backups/config/$(date +%Y%m%d)/credit_costs.json | python -m json.tool | head -30
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 3: COMMIT CONFIGS TO GIT
|
||||
# ========================================
|
||||
cd /data/app/igny8
|
||||
git add backups/config/
|
||||
git commit -m "Pre-V1.0: System configuration backup $(date +%Y%m%d)"
|
||||
git push
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 4: BACKUP MEDIA FILES
|
||||
# ========================================
|
||||
cd /data/app/igny8
|
||||
tar -czf backups/$(date +%Y%m%d)_media_backup.tar.gz backend/media/
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 5: DRY RUN CLEANUP (REVIEW CAREFULLY)
|
||||
# ========================================
|
||||
cd backend
|
||||
python manage.py cleanup_user_data --dry-run
|
||||
|
||||
# Review the counts - make sure they're expected
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 6: ACTUAL CLEANUP (POINT OF NO RETURN)
|
||||
# ========================================
|
||||
python manage.py cleanup_user_data --confirm
|
||||
# Type: DELETE ALL DATA
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 7: VERIFY CLEANUP
|
||||
# ========================================
|
||||
python manage.py shell << 'EOF'
|
||||
from igny8_core.auth.models import Site, CustomUser
|
||||
from igny8_core.business.planning.models import Keywords
|
||||
from igny8_core.modules.billing.models import Plan
|
||||
|
||||
print(f"Sites: {Site.objects.count()} (should be 0)")
|
||||
print(f"Keywords: {Keywords.objects.count()} (should be 0)")
|
||||
print(f"Users: {CustomUser.objects.count()} (admins preserved)")
|
||||
print(f"Plans: {Plan.objects.count()} (should have your plans)")
|
||||
EOF
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 8: TEST APPLICATION
|
||||
# ========================================
|
||||
python manage.py runserver 0.0.0.0:8000 &
|
||||
# Visit app and verify:
|
||||
# - Can login as admin
|
||||
# - Dashboard loads (empty state)
|
||||
# - Plans visible in settings
|
||||
# - Can create new user account
|
||||
|
||||
|
||||
# ========================================
|
||||
# STEP 9: TAG RELEASE
|
||||
# ========================================
|
||||
cd /data/app/igny8
|
||||
git tag -a v1.0.0-clean -m "V1.0.0 - Clean database ready for launch"
|
||||
git push origin v1.0.0-clean
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Safety Measures
|
||||
|
||||
### Built-in Protections
|
||||
|
||||
1. **Atomic Transactions**: All deletions in single transaction - all or nothing
|
||||
2. **Production Check**: Requires explicit override in production
|
||||
3. **Confirmation Prompt**: Must type exact phrase
|
||||
4. **Dry Run**: See exactly what will be deleted
|
||||
5. **Detailed Logging**: Know what was deleted and any failures
|
||||
|
||||
### Manual Safety Checklist
|
||||
|
||||
Before running cleanup:
|
||||
|
||||
- [ ] **Full database backup** exists and verified
|
||||
- [ ] **System config export** completed and committed to git
|
||||
- [ ] **Media files** backed up
|
||||
- [ ] **Dry run reviewed** and counts are expected
|
||||
- [ ] **Stakeholders notified** of pending cleanup
|
||||
- [ ] **Rollback plan** documented and tested
|
||||
- [ ] **Off-hours execution** scheduled (if production)
|
||||
- [ ] **Monitoring ready** to catch any issues
|
||||
|
||||
### Additional Recommendations
|
||||
|
||||
1. **Staging First**: Always test in staging environment first
|
||||
2. **Screenshot Evidence**: Take screenshots of dry-run output
|
||||
3. **Communication**: Notify team before and after
|
||||
4. **Timing**: Run during low-traffic hours
|
||||
5. **Verification**: Test application immediately after
|
||||
|
||||
---
|
||||
|
||||
## 🔙 Rollback Procedures
|
||||
|
||||
### If Something Goes Wrong
|
||||
|
||||
#### During Cleanup (Transaction Failed)
|
||||
|
||||
No action needed - atomic transaction will automatically rollback.
|
||||
|
||||
#### After Cleanup (Need to Restore)
|
||||
|
||||
```bash
|
||||
# OPTION 1: Restore from PostgreSQL backup
|
||||
cd /data/app/igny8
|
||||
psql -U postgres -d igny8_db < backups/20260109_pre_v1_full_backup.sql
|
||||
|
||||
# OPTION 2: Restore specific tables (if partial restore needed)
|
||||
pg_restore -U postgres -d igny8_db -t "specific_table" backups/20260109_pre_v1_full_backup.sql
|
||||
|
||||
# OPTION 3: Restore from Docker backup (if using Docker)
|
||||
docker-compose exec -T db psql -U postgres igny8_db < backups/20260109_pre_v1_full_backup.sql
|
||||
```
|
||||
|
||||
#### Restore Media Files
|
||||
|
||||
```bash
|
||||
cd /data/app/igny8
|
||||
tar -xzf backups/20260109_media_backup.tar.gz -C backend/
|
||||
```
|
||||
|
||||
#### Verify Restore
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python manage.py shell << 'EOF'
|
||||
from igny8_core.auth.models import Site
|
||||
from igny8_core.business.planning.models import Keywords
|
||||
print(f"Sites restored: {Site.objects.count()}")
|
||||
print(f"Keywords restored: {Keywords.objects.count()}")
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
### Q: Can I run these commands multiple times?
|
||||
|
||||
**A:**
|
||||
- **Export Config**: Yes, safe to run multiple times. Creates timestamped backups.
|
||||
- **Cleanup**: Yes, but after first cleanup there's nothing left to delete (idempotent).
|
||||
|
||||
### Q: What if I only want to delete some data?
|
||||
|
||||
**A:** These commands are all-or-nothing by design for safety. To delete specific data, use Django admin or write a custom management command.
|
||||
|
||||
### Q: Can I restore individual items from the export?
|
||||
|
||||
**A:** Yes! The JSON files use Django's standard serialization format. Use `python manage.py loaddata <file>.json` to restore.
|
||||
|
||||
### Q: Will this affect my development environment?
|
||||
|
||||
**A:** Only if you run it there. These commands work on whatever database your Django settings point to.
|
||||
|
||||
### Q: How long does cleanup take?
|
||||
|
||||
**A:** Depends on data volume. Typical ranges:
|
||||
- Small (< 10k records): 1-5 seconds
|
||||
- Medium (10k-100k): 5-30 seconds
|
||||
- Large (> 100k): 30-120 seconds
|
||||
|
||||
### Q: What if cleanup fails halfway?
|
||||
|
||||
**A:** Can't happen - it's wrapped in an atomic transaction. Either everything deletes or nothing does.
|
||||
|
||||
### Q: Do I need to stop the application?
|
||||
|
||||
**A:** Recommended but not required. Stopping the app prevents race conditions during cleanup.
|
||||
|
||||
### Q: Can I schedule these as cron jobs?
|
||||
|
||||
**A:**
|
||||
- **Export**: Yes, great for automated backups
|
||||
- **Cleanup**: No, should only be run manually with explicit confirmation
|
||||
|
||||
### Q: What about Django migrations?
|
||||
|
||||
**A:** Cleanup only deletes data, not schema. All tables and migrations remain intact.
|
||||
|
||||
### Q: How do I know if my system config is complete?
|
||||
|
||||
**A:** Run the export and review the counts in `export_metadata.json`. Compare with your documentation.
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### If You Need Help
|
||||
|
||||
1. **Check this guide** thoroughly first
|
||||
2. **Review error messages** carefully
|
||||
3. **Test in staging** before production
|
||||
4. **Contact team** if unsure about any step
|
||||
|
||||
### Emergency Contacts
|
||||
|
||||
- **Database Issues**: DBA team
|
||||
- **Application Issues**: Backend team
|
||||
- **Configuration Questions**: System admin
|
||||
- **Rollback Needed**: All hands on deck!
|
||||
|
||||
---
|
||||
|
||||
## ✅ Success Criteria
|
||||
|
||||
After completing Phase 6, you should have:
|
||||
|
||||
- ✅ Multiple timestamped config exports in `backups/config/`
|
||||
- ✅ Full database SQL backup in `backups/`
|
||||
- ✅ Media files backup in `backups/`
|
||||
- ✅ Zero user-generated data in database
|
||||
- ✅ All system configurations intact
|
||||
- ✅ Application starts and loads empty state
|
||||
- ✅ Admin can log in
|
||||
- ✅ New users can sign up
|
||||
- ✅ Plans visible and functional
|
||||
- ✅ Git tag created for v1.0.0-clean
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** January 9, 2026
|
||||
**Next Review:** After V1.0 Launch
|
||||
|
||||
---
|
||||
|
||||
*This guide is part of the IGNY8 Pre-Launch Preparation (Phase 6)*
|
||||
@@ -0,0 +1,114 @@
|
||||
# IGNY8 Billing System Master Document
|
||||
|
||||
**Last Updated:** 2026-01-20
|
||||
|
||||
> **Primary Reference:** For complete billing documentation, see [BILLING-PAYMENTS-COMPLETE.md](../10-MODULES/BILLING-PAYMENTS-COMPLETE.md)
|
||||
|
||||
This document provides a summary of the billing system implementation.
|
||||
|
||||
---
|
||||
|
||||
## 1) Core Principles
|
||||
|
||||
- **Two-Pool Credit System:**
|
||||
- `account.credits` = Plan credits (reset on renewal)
|
||||
- `account.bonus_credits` = Purchased credits (NEVER expire, NEVER reset)
|
||||
- **Credit Usage Priority:** Plan credits used FIRST, bonus credits only when plan credits = 0
|
||||
- **No hardcoded products**: Plans, credit packages, and future add-ons are data-driven.
|
||||
- **Explicit invoice type**: `subscription`, `credit_package`, `addon`, `custom`.
|
||||
- **Correct crediting**:
|
||||
- Subscription: reset plan credits to **full plan amount** on payment (bonus untouched)
|
||||
- Credit package: add to **bonus_credits** only (never plan credits)
|
||||
- **Renewal Grace Period:** 7 days
|
||||
- **Credit Reset on Non-Payment:** 24 hours after renewal (Day +1), plan credits → 0
|
||||
- **Auditability**: Every credit change is recorded in `CreditTransaction`.
|
||||
|
||||
---
|
||||
|
||||
## 2) Two-Pool Credit System
|
||||
|
||||
| Pool | Field | Source | Behavior |
|
||||
|------|-------|--------|----------|
|
||||
| Plan Credits | `account.credits` | Subscription plan | Reset to plan amount on renewal payment, reset to 0 if unpaid after 24h |
|
||||
| Bonus Credits | `account.bonus_credits` | Credit packages | NEVER expire, NEVER reset, only deducted after plan credits = 0 |
|
||||
|
||||
### Credit Deduction Priority
|
||||
1. Deduct from `credits` (plan) first
|
||||
2. Only when `credits = 0`, deduct remainder from `bonus_credits`
|
||||
|
||||
---
|
||||
|
||||
## 3) Renewal Workflow (Simplified)
|
||||
|
||||
### Stripe/PayPal (Auto-Pay) - Industry Standard
|
||||
- **No advance notice** (like Netflix, Spotify)
|
||||
- Day 0: Auto-charge attempt
|
||||
- If success: Receipt email + credits reset to plan amount
|
||||
- If failed: Retry notification, Stripe retries 4x over 7 days
|
||||
- Day +7: Expired if all retries fail
|
||||
|
||||
### Bank Transfer (Manual)
|
||||
- **Day -3:** Invoice created + Email sent
|
||||
- **Day 0:** Renewal day reminder (if unpaid)
|
||||
- **Day +1:** Urgent reminder + credits reset to 0
|
||||
- **Day +7:** Subscription expired
|
||||
|
||||
---
|
||||
|
||||
## 4) Scheduled Tasks (Updated)
|
||||
|
||||
| Task | Purpose | Schedule |
|
||||
|------|---------|----------|
|
||||
| `create_bank_transfer_invoices` | Invoice 3 days before (bank transfer only) | Daily 09:00 |
|
||||
| `process_subscription_renewals` | Auto-pay renewals (Stripe/PayPal) | Daily 00:05 |
|
||||
| `send_renewal_day_reminders` | Day 0 reminder (bank transfer) | Daily 10:00 |
|
||||
| `send_day_after_reminders` | Day +1 urgent reminder + credit reset | Daily 09:15 |
|
||||
| `check_expired_renewals` | Expire after 7-day grace | Daily 00:15 |
|
||||
| `send_credit_invoice_expiry_reminders` | Credit invoice reminder | Daily 09:30 |
|
||||
| `void_expired_credit_invoices` | Auto-void credit invoices (48h) | Daily 00:45 |
|
||||
|
||||
---
|
||||
|
||||
## 5) Invoice Types and Fulfillment
|
||||
|
||||
| Invoice Type | Credits Action | Account Status |
|
||||
|--------------|----------------|----------------|
|
||||
| `subscription` | Reset plan credits to plan amount | Activate account + subscription |
|
||||
| `credit_package` | Add to **bonus_credits** | No status change |
|
||||
| `addon` | Provision entitlement | No status change |
|
||||
| `custom` | As specified | No status change |
|
||||
|
||||
---
|
||||
|
||||
## 6) Key Implementation Rules
|
||||
|
||||
1. **Two pools:** `credits` (plan) + `bonus_credits` (purchased)
|
||||
2. **Deduction order:** Plan credits first, then bonus credits
|
||||
3. **Subscription payment:** Reset plan credits to full amount (bonus untouched)
|
||||
4. **Credit package payment:** Add to bonus_credits only
|
||||
5. **No payment 24h:** Plan credits → 0, bonus credits unchanged
|
||||
6. **Late payment:** Plan credits restored to full amount
|
||||
|
||||
---
|
||||
|
||||
## 7) Quick Reference
|
||||
|
||||
### Payment Method by Country
|
||||
| Country | Stripe | PayPal | Bank Transfer |
|
||||
|---------|--------|--------|---------------|
|
||||
| Pakistan (PK) | ✅ | ❌ | ✅ |
|
||||
| Others | ✅ | ✅ | ❌ |
|
||||
|
||||
### Credit Reset Summary
|
||||
| Event | Plan Credits | Bonus Credits |
|
||||
|-------|--------------|---------------|
|
||||
| Payment success | Reset to plan amount | No change |
|
||||
| No payment (24h) | Reset to 0 | No change |
|
||||
| Late payment | Reset to plan amount | No change |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Complete Documentation: [BILLING-PAYMENTS-COMPLETE.md](../10-MODULES/BILLING-PAYMENTS-COMPLETE.md)
|
||||
- Payment Gateways: [PAYMENT-SYSTEM.md](PAYMENT-SYSTEM.md)
|
||||
@@ -0,0 +1,921 @@
|
||||
# Django Admin Access Guide - Payment & Email Integration Settings
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Purpose:** Guide to configure Stripe, PayPal, and Resend credentials via Django Admin
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Accessing Django Admin](#1-accessing-django-admin)
|
||||
2. [Integration Providers Settings](#2-integration-providers-settings)
|
||||
3. [Stripe Configuration](#3-stripe-configuration)
|
||||
4. [PayPal Configuration](#4-paypal-configuration)
|
||||
5. [Resend Configuration](#5-resend-configuration)
|
||||
6. [Plan & Pricing Configuration](#6-plan--pricing-configuration)
|
||||
7. [Credit Packages Configuration](#7-credit-packages-configuration)
|
||||
8. [Testing Checklist](#8-testing-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 1. Accessing Django Admin
|
||||
|
||||
### 1.1 URL Access
|
||||
|
||||
**Local Development:**
|
||||
```
|
||||
http://localhost:8000/admin/
|
||||
```
|
||||
|
||||
**Staging/Production:**
|
||||
```
|
||||
https://api.igny8.com/admin/
|
||||
```
|
||||
|
||||
### 1.2 Login
|
||||
|
||||
Use your superuser credentials:
|
||||
- **Username:** (your admin username)
|
||||
- **Password:** (your admin password)
|
||||
|
||||
**Create Superuser (if needed):**
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Integration Providers Settings
|
||||
|
||||
### 2.1 Navigating to Integration Providers
|
||||
|
||||
1. Log in to Django Admin
|
||||
2. Look for **"MODULES"** section (or similar grouping)
|
||||
3. Click on **"System" → "Integration providers"**
|
||||
|
||||
**Direct URL Path:**
|
||||
```
|
||||
/admin/system/integrationprovider/
|
||||
```
|
||||
|
||||
### 2.2 Pre-seeded Providers
|
||||
|
||||
You should see these providers already created:
|
||||
|
||||
| Provider ID | Display Name | Type | Status |
|
||||
|-------------|--------------|------|--------|
|
||||
| `stripe` | Stripe | payment | Active (sandbox) |
|
||||
| `paypal` | PayPal | payment | Active (sandbox) |
|
||||
| `resend` | Resend | email | Active |
|
||||
| `openai` | OpenAI | ai | Active |
|
||||
| `anthropic` | Anthropic | ai | Active |
|
||||
| `google` | Google | ai | Active |
|
||||
| `runware` | Runware | ai | Active |
|
||||
| `cloudflare_r2` | Cloudflare R2 | storage | Active |
|
||||
|
||||
---
|
||||
|
||||
## 3. Stripe Configuration
|
||||
|
||||
### 3.1 Getting Stripe Credentials
|
||||
|
||||
#### Step 1: Login to Stripe Dashboard
|
||||
Go to [dashboard.stripe.com](https://dashboard.stripe.com)
|
||||
|
||||
#### Step 2: Get API Keys
|
||||
|
||||
**Test Mode (Sandbox):**
|
||||
1. Toggle to "Test mode" in top-right
|
||||
2. Go to **Developers → API keys**
|
||||
3. Copy:
|
||||
- **Publishable key** (starts with `pk_test_...`)
|
||||
- **Secret key** (starts with `sk_test_...`)
|
||||
|
||||
**Live Mode (Production):**
|
||||
1. Toggle to "Live mode"
|
||||
2. Go to **Developers → API keys**
|
||||
3. Copy:
|
||||
- **Publishable key** (starts with `pk_live_...`)
|
||||
- **Secret key** (starts with `sk_live_...`)
|
||||
|
||||
#### Step 3: Configure Webhook
|
||||
|
||||
1. Go to **Developers → Webhooks**
|
||||
2. Click **"Add endpoint"**
|
||||
3. Enter endpoint URL:
|
||||
```
|
||||
Test: https://api-staging.igny8.com/api/v1/billing/webhooks/stripe/
|
||||
Live: https://api.igny8.com/api/v1/billing/webhooks/stripe/
|
||||
```
|
||||
4. Select events to listen for:
|
||||
- `checkout.session.completed`
|
||||
- `invoice.paid`
|
||||
- `invoice.payment_failed`
|
||||
- `customer.subscription.updated`
|
||||
- `customer.subscription.deleted`
|
||||
5. Click **"Add endpoint"**
|
||||
6. Copy the **Signing secret** (starts with `whsec_...`)
|
||||
|
||||
#### Step 4: Create Products and Prices
|
||||
|
||||
1. Go to **Products → Add product**
|
||||
2. Create these products:
|
||||
|
||||
**Starter Plan**
|
||||
- Name: `Starter Plan`
|
||||
- Description: `Basic plan for small projects`
|
||||
- Pricing: `$99.00 / month`
|
||||
- Copy the **Price ID** (starts with `price_...`)
|
||||
|
||||
**Growth Plan**
|
||||
- Name: `Growth Plan`
|
||||
- Description: `For growing businesses`
|
||||
- Pricing: `$199.00 / month`
|
||||
- Copy the **Price ID**
|
||||
|
||||
**Scale Plan**
|
||||
- Name: `Scale Plan`
|
||||
- Description: `For large enterprises`
|
||||
- Pricing: `$299.00 / month`
|
||||
- Copy the **Price ID**
|
||||
|
||||
### 3.2 Adding to Django Admin
|
||||
|
||||
1. Go to Django Admin → **System → Integration providers**
|
||||
2. Click on **"stripe"**
|
||||
3. Fill in the fields:
|
||||
|
||||
```
|
||||
Provider ID: stripe (already set)
|
||||
Display name: Stripe (already set)
|
||||
Provider type: payment (already set)
|
||||
|
||||
API key: pk_test_xxxxxxxxxxxxx (or pk_live_ for production)
|
||||
API secret: sk_test_xxxxxxxxxxxxx (or sk_live_ for production)
|
||||
Webhook secret: whsec_xxxxxxxxxxxxx
|
||||
|
||||
API endpoint: [leave empty - uses default]
|
||||
|
||||
Config (JSON):
|
||||
{
|
||||
"currency": "usd",
|
||||
"payment_methods": ["card"],
|
||||
"billing_portal_enabled": true
|
||||
}
|
||||
|
||||
✅ Is active: Checked
|
||||
✅ Is sandbox: Checked (for test mode) / Unchecked (for live mode)
|
||||
```
|
||||
|
||||
4. Click **"Save"**
|
||||
|
||||
### 3.3 Update Plan Models with Stripe Price IDs
|
||||
|
||||
1. Go to Django Admin → **Auth → Plans**
|
||||
2. Edit each plan:
|
||||
|
||||
**Starter Plan:**
|
||||
- Stripe price id: `price_xxxxxxxxxxxxx` (from Stripe dashboard)
|
||||
- Stripe product id: `prod_xxxxxxxxxxxxx` (optional)
|
||||
|
||||
**Growth Plan:**
|
||||
- Stripe price id: `price_xxxxxxxxxxxxx`
|
||||
|
||||
**Scale Plan:**
|
||||
- Stripe price id: `price_xxxxxxxxxxxxx`
|
||||
|
||||
3. Save each plan
|
||||
|
||||
---
|
||||
|
||||
## 4. PayPal Configuration
|
||||
|
||||
### 4.1 Getting PayPal Credentials
|
||||
|
||||
#### Step 1: Login to PayPal Developer Dashboard
|
||||
Go to [developer.paypal.com](https://developer.paypal.com)
|
||||
|
||||
#### Step 2: Create an App
|
||||
|
||||
1. Go to **My Apps & Credentials**
|
||||
2. Select **Sandbox** (for testing) or **Live** (for production)
|
||||
3. Click **"Create App"**
|
||||
4. Enter app name: `IGNY8 Payment Integration`
|
||||
5. Click **"Create App"**
|
||||
6. Copy:
|
||||
- **Client ID** (starts with `AY...` or similar)
|
||||
- **Secret** (click "Show" to reveal)
|
||||
|
||||
#### Step 3: Configure Webhooks
|
||||
|
||||
1. In your app settings, scroll to **"WEBHOOKS"**
|
||||
2. Click **"Add Webhook"**
|
||||
3. Enter webhook URL:
|
||||
```
|
||||
Sandbox: https://api-staging.igny8.com/api/v1/billing/webhooks/paypal/
|
||||
Live: https://api.igny8.com/api/v1/billing/webhooks/paypal/
|
||||
```
|
||||
4. Select event types:
|
||||
- `CHECKOUT.ORDER.APPROVED`
|
||||
- `PAYMENT.CAPTURE.COMPLETED`
|
||||
- `PAYMENT.CAPTURE.DENIED`
|
||||
- `BILLING.SUBSCRIPTION.ACTIVATED`
|
||||
- `BILLING.SUBSCRIPTION.CANCELLED`
|
||||
5. Click **"Save"**
|
||||
6. Copy the **Webhook ID** (starts with `WH-...`)
|
||||
|
||||
#### Step 4: Create Subscription Plans (Required for Subscriptions)
|
||||
|
||||
PayPal subscriptions require creating **Products** and **Plans** in the PayPal dashboard. This is a manual process (not done via API in our implementation).
|
||||
|
||||
##### 4.4.1 Create a PayPal Product
|
||||
|
||||
1. Go to [sandbox.paypal.com/billing/plans](https://www.sandbox.paypal.com/billing/plans) (Sandbox) or [paypal.com/billing/plans](https://www.paypal.com/billing/plans) (Live)
|
||||
2. Click **"Create product"** (or go to Products tab first)
|
||||
3. Fill in:
|
||||
- **Product name**: `IGNY8 Subscription`
|
||||
- **Product type**: `Service`
|
||||
- **Product ID**: `IGNY8-SUB` (or auto-generate)
|
||||
- **Description**: `IGNY8 subscription plans`
|
||||
4. Click **"Create product"**
|
||||
5. Note the **Product ID** (e.g., `PROD-xxxxxxxxxxxxx`)
|
||||
|
||||
##### 4.4.2 Create PayPal Plans (One Per IGNY8 Plan)
|
||||
|
||||
For each plan in your system (Starter, Growth, Scale), create a PayPal plan:
|
||||
|
||||
1. In PayPal dashboard, click **"Create plan"**
|
||||
2. Select the product you just created
|
||||
3. Fill in plan details:
|
||||
|
||||
**Starter Plan:**
|
||||
- **Plan name**: `Starter Plan - Monthly`
|
||||
- **Description**: `Basic plan for small projects`
|
||||
- **Pricing**:
|
||||
- Billing cycle: `Monthly`
|
||||
- Price: `$99.00 USD`
|
||||
- Total cycles: `0` (infinite)
|
||||
- **Setup fee**: `$0.00` (optional)
|
||||
4. Click **"Create plan"**
|
||||
5. **Copy the Plan ID** (starts with `P-...`, e.g., `P-5ML4271244454362WXXX`)
|
||||
|
||||
Repeat for Growth ($199/month) and Scale ($299/month) plans.
|
||||
|
||||
##### 4.4.3 Map PayPal Plan IDs to Django Plans
|
||||
|
||||
1. Go to Django Admin → **Auth → Plans**
|
||||
2. Edit **Starter Plan**:
|
||||
- Scroll to **"PayPal Integration"** section
|
||||
- **Paypal plan id**: Paste `P-xxxxxxxxxxxxx`
|
||||
3. Click **"Save"**
|
||||
4. Repeat for Growth and Scale plans
|
||||
|
||||
**Note:** Without `paypal_plan_id` set, the subscription creation API will return an error.
|
||||
|
||||
### 4.2 Adding to Django Admin
|
||||
|
||||
1. Go to Django Admin → **System → Integration providers**
|
||||
2. Click on **"paypal"**
|
||||
3. Fill in the fields:
|
||||
|
||||
```
|
||||
Provider ID: paypal (already set)
|
||||
Display name: PayPal (already set)
|
||||
Provider type: payment (already set)
|
||||
|
||||
API key: AYxxxxxxxxxxx (Client ID)
|
||||
API secret: ELxxxxxxxxxxx (Secret)
|
||||
Webhook secret: [leave empty - not used by PayPal]
|
||||
|
||||
API endpoint:
|
||||
Sandbox: https://api-m.sandbox.paypal.com
|
||||
Live: https://api-m.paypal.com
|
||||
|
||||
Config (JSON):
|
||||
{
|
||||
"currency": "USD",
|
||||
"webhook_id": "WH-xxxxxxxxxxxxx",
|
||||
"return_url": "https://app.igny8.com/account/plans?paypal=success",
|
||||
"cancel_url": "https://app.igny8.com/account/plans?paypal=cancel"
|
||||
}
|
||||
|
||||
✅ Is active: Checked
|
||||
✅ Is sandbox: Checked (for sandbox) / Unchecked (for live)
|
||||
```
|
||||
|
||||
4. Click **"Save"**
|
||||
|
||||
### 4.3 Live PayPal Payment Enablement (Production)
|
||||
|
||||
Use this section when switching from sandbox to live PayPal payments.
|
||||
|
||||
#### Step 1: Create/Select a Live App
|
||||
1. Go to [developer.paypal.com](https://developer.paypal.com)
|
||||
2. Select **Live** (top toggle)
|
||||
3. Create a new app or select your existing live app
|
||||
4. Copy the **Live Client ID** and **Live Secret**
|
||||
|
||||
#### Step 2: Configure Live Webhook
|
||||
1. In your live app settings, add a webhook:
|
||||
```
|
||||
https://api.igny8.com/api/v1/billing/webhooks/paypal/
|
||||
```
|
||||
2. Select events:
|
||||
- `CHECKOUT.ORDER.APPROVED`
|
||||
- `PAYMENT.CAPTURE.COMPLETED`
|
||||
- `PAYMENT.CAPTURE.DENIED`
|
||||
- `BILLING.SUBSCRIPTION.ACTIVATED`
|
||||
- `BILLING.SUBSCRIPTION.CANCELLED`
|
||||
3. Copy the **Live Webhook ID**
|
||||
|
||||
#### Step 3: Update Django Admin Provider (Live)
|
||||
1. Go to **System → Integration providers → paypal**
|
||||
2. Update fields:
|
||||
- **API key**: Live Client ID
|
||||
- **API secret**: Live Secret
|
||||
- **API endpoint**: `https://api-m.paypal.com`
|
||||
- **Config (JSON)**: set `webhook_id` to the live webhook ID
|
||||
3. Set:
|
||||
- ✅ `is_active` = True
|
||||
- ✅ `is_sandbox` = False
|
||||
4. Click **"Save"**
|
||||
|
||||
#### Step 3.1: Map PayPal Plan IDs in Django
|
||||
PayPal subscription webhooks only work if your plans are mapped.
|
||||
|
||||
1. Go to Django Admin → **Auth → Plans**
|
||||
2. For each plan, set:
|
||||
- **Paypal plan id**: Live PayPal Plan ID (starts with `P-...`)
|
||||
3. Save each plan
|
||||
|
||||
#### Step 4: Validate Live Payment Flow
|
||||
1. Open frontend: `/account/usage`
|
||||
2. Select **PayPal** and complete a real payment
|
||||
3. Confirm:
|
||||
- Order is captured
|
||||
- Credits are added
|
||||
- Payment email is sent
|
||||
|
||||
#### Step 5: Validate PayPal Subscriptions (If Enabled)
|
||||
1. Open frontend: `/account/plans`
|
||||
2. Select **PayPal** and subscribe to a plan
|
||||
3. Confirm:
|
||||
- Subscription is activated
|
||||
- Webhook events are processed
|
||||
- Account plan is updated
|
||||
|
||||
---
|
||||
|
||||
## 5. Resend Configuration
|
||||
|
||||
### 5.1 Getting Resend API Key
|
||||
|
||||
#### Step 1: Login to Resend
|
||||
Go to [resend.com](https://resend.com)
|
||||
|
||||
#### Step 2: Create API Key
|
||||
|
||||
1. Go to **API Keys**
|
||||
2. Click **"Create API Key"**
|
||||
3. Enter name: `IGNY8 Production` (or `IGNY8 Development`)
|
||||
4. Select permission: **"Sending access"**
|
||||
5. Click **"Add"**
|
||||
6. Copy the API key (starts with `re_...`)
|
||||
7. **Save it securely** - you won't see it again!
|
||||
|
||||
#### Step 3: Verify Your Domain
|
||||
|
||||
1. Go to **Domains**
|
||||
2. Click **"Add Domain"**
|
||||
3. Enter your domain: `igny8.com`
|
||||
4. Follow instructions to add DNS records:
|
||||
- **DKIM Record** (TXT)
|
||||
- **SPF Record** (TXT)
|
||||
- **DMARC Record** (TXT)
|
||||
5. Click **"Verify"**
|
||||
6. Wait for verification (can take a few minutes to 24 hours)
|
||||
|
||||
### 5.2 Adding to Django Admin
|
||||
|
||||
1. Go to Django Admin → **System → Integration providers**
|
||||
2. Click on **"resend"**
|
||||
3. Fill in the fields:
|
||||
|
||||
```
|
||||
Provider ID: resend (already set)
|
||||
Display name: Resend (already set)
|
||||
Provider type: email (already set)
|
||||
|
||||
API key: re_xxxxxxxxxxxxx
|
||||
API secret: [leave empty]
|
||||
Webhook secret: [leave empty]
|
||||
API endpoint: [leave empty - uses default]
|
||||
|
||||
Config (JSON):
|
||||
{
|
||||
"from_email": "noreply@igny8.com",
|
||||
"from_name": "IGNY8",
|
||||
"reply_to": "support@igny8.com"
|
||||
}
|
||||
|
||||
✅ Is active: Checked
|
||||
✅ Is sandbox: Unchecked (Resend doesn't have sandbox mode)
|
||||
```
|
||||
|
||||
4. Click **"Save"**
|
||||
|
||||
### 5.3 Email Settings Management
|
||||
|
||||
IGNY8 provides a dedicated **Email Settings** navigation group in Django Admin:
|
||||
|
||||
| Menu Item | URL | Purpose |
|
||||
|-----------|-----|---------|
|
||||
| Email Configuration | `/admin/system/emailsettings/` | Global email defaults (from, reply-to, feature flags) |
|
||||
| Email Templates | `/admin/system/emailtemplate/` | Manage/test email templates |
|
||||
| Email Logs | `/admin/system/emaillog/` | View sent email history |
|
||||
| Resend Provider | `/admin/system/integrationprovider/resend/change/` | API key & config |
|
||||
|
||||
**Email Configuration Settings:**
|
||||
- `from_email` - Default sender (must be verified in Resend)
|
||||
- `from_name` - Display name for sender
|
||||
- `reply_to_email` - Reply-to address
|
||||
- `send_welcome_emails` - Toggle welcome emails on/off
|
||||
- `send_billing_emails` - Toggle payment/invoice emails
|
||||
- `send_subscription_emails` - Toggle renewal reminders
|
||||
- `low_credit_threshold` - Credits level to trigger warning email
|
||||
|
||||
### 5.4 Testing Email Delivery
|
||||
|
||||
**Method 1: Django Admin UI (Recommended)**
|
||||
|
||||
1. Go to **Email Settings → Email Templates**
|
||||
2. Click the **"Test"** button next to any template
|
||||
3. Enter recipient email and customize context JSON
|
||||
4. Click **"Send Test Email"**
|
||||
5. Check **Email Logs** to verify delivery
|
||||
|
||||
**Method 2: Command Line (Docker)**
|
||||
|
||||
```bash
|
||||
docker exec -it igny8_backend python manage.py shell -c "
|
||||
from igny8_core.business.billing.services.email_service import get_email_service
|
||||
|
||||
service = get_email_service()
|
||||
result = service.send_transactional(
|
||||
to='your-email@example.com',
|
||||
subject='Test Email from IGNY8',
|
||||
html='<h1>Test Email</h1><p>If you receive this, Resend is configured correctly!</p>',
|
||||
text='Test Email. If you receive this, Resend is configured correctly!'
|
||||
)
|
||||
print('Result:', result)
|
||||
"
|
||||
```
|
||||
|
||||
**Expected successful response:**
|
||||
```python
|
||||
{'success': True, 'id': '81193754-6f27-4b1a-9c36-d83ae18f6a9a', 'provider': 'resend'}
|
||||
```
|
||||
|
||||
**Method 3: Test with Template**
|
||||
|
||||
```bash
|
||||
docker exec -it igny8_backend python manage.py shell -c "
|
||||
from igny8_core.business.billing.services.email_service import get_email_service
|
||||
|
||||
service = get_email_service()
|
||||
result = service.send_transactional(
|
||||
to='your-email@example.com',
|
||||
subject='Welcome Test',
|
||||
template='emails/welcome.html',
|
||||
context={
|
||||
'user_name': 'Test User',
|
||||
'account_name': 'Test Account',
|
||||
'login_url': 'https://app.igny8.com/login',
|
||||
'frontend_url': 'https://app.igny8.com',
|
||||
},
|
||||
tags=['test']
|
||||
)
|
||||
print('Result:', result)
|
||||
"
|
||||
```
|
||||
|
||||
### 5.5 Available Email Templates
|
||||
|
||||
| Template | Type | Trigger |
|
||||
|----------|------|---------|
|
||||
| `welcome` | Auth | User registration |
|
||||
| `password_reset` | Auth | Password reset request |
|
||||
| `email_verification` | Auth | Email verification |
|
||||
| `payment_confirmation` | Billing | Manual payment submitted |
|
||||
| `payment_approved` | Billing | Payment approved |
|
||||
| `payment_rejected` | Billing | Payment declined |
|
||||
| `payment_failed` | Billing | Auto-payment failed |
|
||||
| `subscription_activated` | Billing | Subscription activated |
|
||||
| `subscription_renewal` | Billing | Renewal reminder |
|
||||
| `refund_notification` | Billing | Refund processed |
|
||||
| `low_credits` | Notification | Credits below threshold |
|
||||
|
||||
### 5.6 Email Service API Reference
|
||||
|
||||
```python
|
||||
send_transactional(
|
||||
to: str | List[str], # Required: recipient email(s)
|
||||
subject: str, # Required: email subject
|
||||
html: str = None, # HTML content
|
||||
text: str = None, # Plain text content
|
||||
template: str = None, # Template path (e.g., 'emails/welcome.html')
|
||||
context: dict = None, # Template context variables
|
||||
from_email: str = None, # Override sender email
|
||||
from_name: str = None, # Override sender name
|
||||
reply_to: str = None, # Reply-to address
|
||||
attachments: List = None, # File attachments
|
||||
tags: List[str] = None # Email tags for tracking
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Plan & Pricing Configuration
|
||||
|
||||
### 6.1 Viewing Plans
|
||||
|
||||
1. Go to Django Admin → **Auth → Plans**
|
||||
2. You should see existing plans:
|
||||
- Free Plan
|
||||
- Starter Plan
|
||||
- Growth Plan
|
||||
- Scale Plan
|
||||
- Enterprise Plan
|
||||
|
||||
### 6.2 Editing Plan Details
|
||||
|
||||
For each plan:
|
||||
|
||||
```
|
||||
Name: Starter Plan
|
||||
Description: Perfect for small projects
|
||||
Price: 99.00
|
||||
Billing period: monthly
|
||||
Included credits: 5000
|
||||
Is active: ✅
|
||||
|
||||
Stripe price id: price_xxxxxxxxxxxxx (from Stripe dashboard)
|
||||
Stripe product id: prod_xxxxxxxxxxxxx (optional)
|
||||
Paypal plan id: P-xxxxxxxxxxxxx (if using PayPal subscriptions)
|
||||
|
||||
Feature limits:
|
||||
Max keywords: 50
|
||||
Max articles per month: 100
|
||||
Max team members: 3
|
||||
Max websites: 1
|
||||
```
|
||||
|
||||
### 6.3 Creating Custom Plans
|
||||
|
||||
1. Click **"Add plan"**
|
||||
2. Fill in all fields
|
||||
3. Make sure to set:
|
||||
- ✅ `is_active` = True (to show to users)
|
||||
- Stripe price ID (from Stripe dashboard)
|
||||
- Included credits (monthly allocation)
|
||||
4. Click **"Save"**
|
||||
|
||||
---
|
||||
|
||||
## 7. Credit Packages Configuration
|
||||
|
||||
### 7.1 Viewing Credit Packages
|
||||
|
||||
1. Go to Django Admin → **Billing → Credit packages**
|
||||
2. You should see existing packages:
|
||||
- Starter: 500 credits @ $9.99
|
||||
- Value: 2,000 credits @ $29.99
|
||||
- Pro: 5,000 credits @ $59.99
|
||||
- Enterprise: 15,000 credits @ $149.99
|
||||
|
||||
### 7.2 Editing Credit Packages
|
||||
|
||||
For each package:
|
||||
|
||||
```
|
||||
Name: Value Package
|
||||
Description: Best value for money
|
||||
Credits: 2000
|
||||
Price: 29.99
|
||||
Display order: 2
|
||||
|
||||
✅ Is active: Checked
|
||||
✅ Is featured: Checked (to highlight on UI)
|
||||
|
||||
Stripe product id: prod_xxxxxxxxxxxxx (optional - for tracking)
|
||||
Paypal product id: (optional)
|
||||
```
|
||||
|
||||
### 7.3 Creating Custom Credit Packages
|
||||
|
||||
1. Click **"Add credit package"**
|
||||
2. Fill in:
|
||||
- Name: e.g., "Black Friday Special"
|
||||
- Credits: e.g., 10000
|
||||
- Price: e.g., 79.99
|
||||
- Description: "Limited time offer!"
|
||||
3. Check ✅ `is_active`
|
||||
4. Check ✅ `is_featured` (optional)
|
||||
5. Click **"Save"**
|
||||
|
||||
---
|
||||
|
||||
## 8. Testing Checklist
|
||||
|
||||
### 8.1 Verify Integration Provider Settings
|
||||
|
||||
Go to Admin → **System → Integration providers**
|
||||
|
||||
- [ ] **Stripe** - API keys added, webhook secret added, is_active=True
|
||||
- [ ] **PayPal** - Client ID/Secret added, webhook ID in config, is_active=True
|
||||
- [ ] **Resend** - API key added, domain verified, is_active=True
|
||||
|
||||
### 8.2 Test Stripe Integration
|
||||
|
||||
1. **Get Config Endpoint:**
|
||||
```bash
|
||||
curl -X GET https://api.igny8.com/api/v1/billing/stripe/config/ \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
Should return publishable key and sandbox status
|
||||
|
||||
2. **Test Checkout Session:**
|
||||
- Go to frontend: `/account/plans`
|
||||
- Select "Stripe" as payment method
|
||||
- Click "Subscribe" on a plan
|
||||
- Should redirect to Stripe Checkout
|
||||
- Complete test payment with card: `4242 4242 4242 4242`
|
||||
- Should receive webhook and activate subscription
|
||||
|
||||
3. **Test Billing Portal:**
|
||||
- Click "Manage Subscription" button
|
||||
- Should redirect to Stripe Billing Portal
|
||||
- Can cancel/update subscription
|
||||
|
||||
### 8.3 Test PayPal Integration
|
||||
|
||||
#### 8.3.1 Verify PayPal Provider Config
|
||||
|
||||
```bash
|
||||
curl -X GET https://api.igny8.com/api/v1/billing/paypal/config/ \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
Should return `client_id` and `sandbox: true`.
|
||||
|
||||
#### 8.3.2 Test One-Time Credit Purchase (Orders API)
|
||||
|
||||
1. Go to frontend: `/account/usage`
|
||||
2. Select **PayPal** as payment method
|
||||
3. Click **"Buy Credits"** on a package
|
||||
4. Should redirect to PayPal sandbox
|
||||
5. Login with sandbox buyer account:
|
||||
- Email: `sb-buyer@personal.example.com` (from PayPal sandbox accounts)
|
||||
- Password: (your sandbox password)
|
||||
6. Complete payment
|
||||
7. **Verify:**
|
||||
- Order is captured (check webhook logs)
|
||||
- Credits are added to account
|
||||
- Payment email is sent
|
||||
|
||||
#### 8.3.3 Test PayPal Subscriptions (Subscriptions API)
|
||||
|
||||
**Prerequisites:**
|
||||
- [ ] PayPal Plans created in dashboard (see Section 4, Step 4)
|
||||
- [ ] `paypal_plan_id` set on each Django Plan (Auth → Plans)
|
||||
|
||||
**Test Steps:**
|
||||
|
||||
1. **Verify Plan Configuration:**
|
||||
```bash
|
||||
# Check if plan has paypal_plan_id
|
||||
docker exec -it igny8_backend python manage.py shell -c "
|
||||
from igny8_core.auth.models import Plan
|
||||
for p in Plan.objects.filter(is_active=True):
|
||||
print(f'{p.name}: paypal_plan_id={p.paypal_plan_id}')
|
||||
"
|
||||
```
|
||||
All paid plans should show a `P-xxxxx` ID.
|
||||
|
||||
2. **Create PayPal Subscription:**
|
||||
- Go to frontend: `/account/plans`
|
||||
- Select **PayPal** as payment method
|
||||
- Click **"Subscribe"** on Starter/Growth/Scale plan
|
||||
- Redirects to PayPal for approval
|
||||
- Login with sandbox buyer account
|
||||
- Approve the subscription
|
||||
|
||||
3. **Verify Subscription Activation:**
|
||||
- Check webhook logs: `BILLING.SUBSCRIPTION.ACTIVATED` should fire
|
||||
- Account plan should be updated
|
||||
- `Subscription` record created with `external_payment_id` = PayPal subscription ID
|
||||
- Credits added based on plan's `included_credits`
|
||||
|
||||
4. **Verify in Django Admin:**
|
||||
- Go to **Auth → Subscriptions**
|
||||
- Find the new subscription
|
||||
- Confirm:
|
||||
- `status` = `active`
|
||||
- `external_payment_id` = `I-xxxxx` (PayPal subscription ID)
|
||||
- `plan` = correct plan
|
||||
|
||||
5. **Test Subscription Cancellation:**
|
||||
- In PayPal sandbox, go to **Pay & Get Paid → Subscriptions**
|
||||
- Cancel the test subscription
|
||||
- `BILLING.SUBSCRIPTION.CANCELLED` webhook should fire
|
||||
- Subscription status should update to `canceled`
|
||||
|
||||
**Sandbox Test Accounts:**
|
||||
|
||||
Create sandbox accounts at [developer.paypal.com/dashboard/accounts](https://developer.paypal.com/dashboard/accounts):
|
||||
- **Business account** - receives payments (seller)
|
||||
- **Personal account** - makes payments (buyer)
|
||||
|
||||
Use the personal account credentials when approving payments/subscriptions.
|
||||
|
||||
### 8.4 Test Email Service
|
||||
|
||||
1. **Test Welcome Email:**
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
python manage.py shell
|
||||
```
|
||||
```python
|
||||
from igny8_core.auth.models import User, Account
|
||||
from igny8_core.business.billing.services.email_service import send_welcome_email
|
||||
|
||||
user = User.objects.first()
|
||||
account = user.account
|
||||
send_welcome_email(user, account)
|
||||
```
|
||||
Check your inbox for welcome email
|
||||
|
||||
2. **Test Payment Confirmation:**
|
||||
- Complete a test payment (Stripe or PayPal)
|
||||
- Should receive payment confirmation email
|
||||
- Check email content and formatting
|
||||
|
||||
### 8.5 Test Webhooks
|
||||
|
||||
1. **Check Webhook Logs:**
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
tail -f logs/django.log | grep webhook
|
||||
```
|
||||
|
||||
2. **Trigger Webhook Events:**
|
||||
- **Stripe:** Complete test checkout, then check webhook logs
|
||||
- **PayPal:** Complete test payment, then check webhook logs
|
||||
|
||||
3. **Verify Webhook Processing:**
|
||||
- Subscription should be activated
|
||||
- Credits should be added
|
||||
- Email notifications should be sent
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Admin URLs
|
||||
|
||||
```
|
||||
# Main sections
|
||||
/admin/system/integrationprovider/ # All integration providers
|
||||
/admin/auth/plan/ # Plans and pricing
|
||||
/admin/billing/creditpackage/ # Credit packages
|
||||
/admin/billing/payment/ # Payment history
|
||||
/admin/billing/invoice/ # Invoices
|
||||
/admin/auth/subscription/ # Active subscriptions
|
||||
/admin/billing/credittransaction/ # Credit transaction history
|
||||
|
||||
# Specific provider configs
|
||||
/admin/system/integrationprovider/stripe/change/
|
||||
/admin/system/integrationprovider/paypal/change/
|
||||
/admin/system/integrationprovider/resend/change/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Never Commit API Keys
|
||||
- ❌ Don't add API keys to code
|
||||
- ❌ Don't commit `.env` files
|
||||
- ✅ Use Django Admin to store credentials
|
||||
- ✅ Use IntegrationProvider model (encrypted in DB)
|
||||
|
||||
### Use Environment-Specific Keys
|
||||
- **Development:** Use Stripe test mode, PayPal sandbox
|
||||
- **Staging:** Use separate test credentials
|
||||
- **Production:** Use live credentials ONLY in production
|
||||
|
||||
### Regular Key Rotation
|
||||
- Rotate API keys every 90 days
|
||||
- Rotate webhook secrets if compromised
|
||||
- Keep backup of old keys during rotation
|
||||
|
||||
### Monitor Webhook Security
|
||||
- Verify webhook signatures always
|
||||
- Log all webhook attempts
|
||||
- Alert on failed signature verification
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Stripe Issues
|
||||
|
||||
**"No such customer"**
|
||||
- Check if `stripe_customer_id` is set on Account model
|
||||
- Clear the field and let system recreate customer
|
||||
|
||||
**"Invalid API Key"**
|
||||
- Verify API key in IntegrationProvider
|
||||
- Check if using test key in live mode (or vice versa)
|
||||
|
||||
**Webhook not working**
|
||||
- Check webhook URL in Stripe dashboard
|
||||
- Verify webhook secret in IntegrationProvider
|
||||
- Check server logs for errors
|
||||
|
||||
### PayPal Issues
|
||||
|
||||
**"Invalid client credentials"**
|
||||
- Verify Client ID and Secret in IntegrationProvider
|
||||
- Make sure using sandbox credentials for sandbox mode
|
||||
|
||||
**"Webhook verification failed"**
|
||||
- Check webhook_id in config JSON
|
||||
- Verify webhook URL in PayPal dashboard
|
||||
|
||||
**Order capture fails**
|
||||
- Check order status (must be APPROVED)
|
||||
- Verify order hasn't already been captured
|
||||
|
||||
**"PayPal plan ID not configured for this plan"**
|
||||
- The Plan model is missing `paypal_plan_id`
|
||||
- Go to Django Admin → **Auth → Plans**
|
||||
- Edit the plan and add the PayPal Plan ID (starts with `P-...`)
|
||||
- Create the plan in PayPal dashboard first if not done
|
||||
|
||||
**"No plan found with paypal_plan_id=..."**
|
||||
- Webhook received but no matching plan in Django
|
||||
- Verify the `paypal_plan_id` in Django matches exactly what's in PayPal
|
||||
- Check for typos or extra whitespace
|
||||
|
||||
**Subscription not activating after approval**
|
||||
- Check webhook logs for `BILLING.SUBSCRIPTION.ACTIVATED` event
|
||||
- Verify webhook URL is correctly configured in PayPal app
|
||||
- Check that `webhook_id` in config JSON matches PayPal dashboard
|
||||
- Ensure sandbox/live environment matches between app and PayPal
|
||||
|
||||
**PayPal subscription appears but no credits added**
|
||||
- Check `included_credits` field on the Plan model
|
||||
- Verify subscription webhook handler completed successfully
|
||||
- Look for errors in Django logs during webhook processing
|
||||
|
||||
### Resend Issues
|
||||
|
||||
**"Invalid API key"**
|
||||
- Verify API key starts with `re_`
|
||||
- Create new API key if needed
|
||||
|
||||
**"Domain not verified"**
|
||||
- Check DNS records in domain provider
|
||||
- Wait up to 24 hours for DNS propagation
|
||||
- Use Resend dashboard to verify domain status
|
||||
|
||||
**Emails not delivered**
|
||||
- Check Resend dashboard logs
|
||||
- Verify from_email domain is verified
|
||||
- Check spam folder
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After configuring all providers:
|
||||
|
||||
1. ✅ Test all payment flows in sandbox mode
|
||||
2. ✅ Test email delivery
|
||||
3. ✅ Verify webhook processing
|
||||
4. ✅ Test frontend payment gateway selection
|
||||
5. ✅ Switch to production credentials when ready to go live
|
||||
|
||||
For production deployment, update:
|
||||
- `is_sandbox` = False for Stripe
|
||||
- `is_sandbox` = False for PayPal
|
||||
- `api_endpoint` = production URLs
|
||||
- Use live API keys for all providers
|
||||
|
||||
---
|
||||
|
||||
**Support:** If you encounter issues, check Django logs:
|
||||
```bash
|
||||
cd /data/app/igny8/backend
|
||||
tail -f logs/django.log
|
||||
```
|
||||
553
v2/Live Docs on Server/igny8-app-docs/90-REFERENCE/FIXES-KB.md
Normal file
553
v2/Live Docs on Server/igny8-app-docs/90-REFERENCE/FIXES-KB.md
Normal file
@@ -0,0 +1,553 @@
|
||||
# Architecture Knowledge Base
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Purpose:** Critical architectural patterns, common issues, and solutions reference
|
||||
|
||||
---
|
||||
|
||||
## 🔥 CRITICAL FIXES - December 2025
|
||||
|
||||
### PERMANENT FIX: Django Admin Custom Sidebar Not Showing on Subpages
|
||||
**ROOT CAUSE**: Django's `ModelAdmin` view methods (`changelist_view`, `change_view`, etc.) do not call `AdminSite.each_context()`, so custom sidebar logic defined in `site.py` was bypassed on model list/detail/edit pages.
|
||||
|
||||
**SOLUTION IMPLEMENTED**:
|
||||
1. ✅ Created `Igny8ModelAdmin` base class extending `UnfoldModelAdmin`
|
||||
2. ✅ Overrides all view methods to inject `extra_context` with custom sidebar
|
||||
3. ✅ Applied to 46+ admin classes across all modules
|
||||
4. ✅ Sidebar now consistent on homepage, app index, and ALL model pages
|
||||
|
||||
**Files Modified**: `backend/igny8_core/admin/base.py`, all `*/admin.py` files
|
||||
|
||||
### PERMANENT FIX: User Swapping / Random Logout Issue
|
||||
**ROOT CAUSE**: Django's database-backed sessions with in-memory user caching caused cross-request contamination at the process level.
|
||||
|
||||
**SOLUTION IMPLEMENTED**:
|
||||
1. ✅ Redis-backed sessions (`SESSION_ENGINE = 'django.contrib.sessions.backends.cache'`)
|
||||
2. ✅ Custom authentication backend without caching (`NoCacheModelBackend`)
|
||||
3. ✅ Session integrity validation (stores and verifies account_id/user_id on every request)
|
||||
4. ✅ Middleware never mutates `request.user` (uses Django's set value directly)
|
||||
|
||||
**See**: `CRITICAL-BUG-FIXES-DEC-2025.md` for complete details.
|
||||
|
||||
### PERMANENT FIX: useNavigate / useLocation Errors During HMR
|
||||
**ROOT CAUSE**: Individual Suspense boundaries per route lost React Router context during Hot Module Replacement.
|
||||
|
||||
**SOLUTION IMPLEMENTED**:
|
||||
1. ✅ Single top-level Suspense boundary around entire `<Routes>` component
|
||||
2. ✅ Removed 100+ individual Suspense wrappers from route elements
|
||||
3. ✅ Router context now persists through HMR automatically
|
||||
|
||||
**See**: `CRITICAL-BUG-FIXES-DEC-2025.md` for complete details.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Authentication & Session Management](#authentication--session-management)
|
||||
2. [Site/Sector Architecture](#sitesector-architecture)
|
||||
3. [State Management & Race Conditions](#state-management--race-conditions)
|
||||
4. [Permission System](#permission-system)
|
||||
5. [Frontend Component Dependencies](#frontend-component-dependencies)
|
||||
6. [Common Pitfalls & Solutions](#common-pitfalls--solutions)
|
||||
|
||||
---
|
||||
|
||||
## Authentication & Session Management
|
||||
|
||||
### Token Persistence Architecture
|
||||
|
||||
**Problem Pattern:**
|
||||
- Zustand persist middleware writes to localStorage asynchronously
|
||||
- API calls can happen before tokens are persisted
|
||||
- Results in 403 "Authentication credentials were not provided" errors
|
||||
|
||||
**Solution Implemented:**
|
||||
```typescript
|
||||
// In authStore.ts login/register functions
|
||||
// CRITICAL: Immediately persist tokens synchronously after setting state
|
||||
const authState = {
|
||||
state: { user, token, refreshToken, isAuthenticated: true },
|
||||
version: 0
|
||||
};
|
||||
localStorage.setItem('auth-storage', JSON.stringify(authState));
|
||||
```
|
||||
|
||||
**Key Principle:** Always write tokens to localStorage synchronously in auth actions, don't rely solely on persist middleware.
|
||||
|
||||
---
|
||||
|
||||
### Logout & State Cleanup
|
||||
|
||||
**WRONG APPROACH (causes race conditions):**
|
||||
```typescript
|
||||
logout: () => {
|
||||
localStorage.clear(); // ❌ BREAKS EVERYTHING
|
||||
set({ user: null, token: null });
|
||||
}
|
||||
```
|
||||
|
||||
**CORRECT APPROACH:**
|
||||
```typescript
|
||||
logout: () => {
|
||||
// ✅ Selective removal - only auth-related keys
|
||||
const authKeys = ['auth-storage', 'site-storage', 'sector-storage', 'billing-storage'];
|
||||
authKeys.forEach(key => localStorage.removeItem(key));
|
||||
|
||||
// ✅ Reset dependent stores explicitly
|
||||
useSiteStore.setState({ activeSite: null });
|
||||
useSectorStore.setState({ activeSector: null, sectors: [] });
|
||||
|
||||
set({ user: null, token: null, isAuthenticated: false });
|
||||
}
|
||||
```
|
||||
|
||||
**Key Principle:** Never use `localStorage.clear()` - it breaks Zustand persist middleware initialization. Always selectively remove keys.
|
||||
|
||||
---
|
||||
|
||||
### 403 Error Handling
|
||||
|
||||
**Problem Pattern:**
|
||||
- 403 errors thrown before checking if it's an auth error
|
||||
- Token validation code becomes unreachable
|
||||
- Invalid tokens persist in localStorage
|
||||
|
||||
**WRONG ORDER:**
|
||||
```typescript
|
||||
// In api.ts
|
||||
if (response.status === 403) {
|
||||
throw new Error(response.statusText); // ❌ Thrown immediately
|
||||
}
|
||||
|
||||
// This code NEVER runs (unreachable):
|
||||
if (errorData?.detail?.includes('Authentication credentials')) {
|
||||
logout(); // Never called!
|
||||
}
|
||||
```
|
||||
|
||||
**CORRECT ORDER:**
|
||||
```typescript
|
||||
// Check for auth errors FIRST, then throw
|
||||
if (response.status === 403) {
|
||||
const errorData = JSON.parse(text);
|
||||
|
||||
// ✅ Check authentication BEFORE throwing
|
||||
if (errorData?.detail?.includes('Authentication credentials')) {
|
||||
const authState = useAuthStore.getState();
|
||||
if (authState?.isAuthenticated) {
|
||||
authState.logout();
|
||||
window.location.href = '/signin';
|
||||
}
|
||||
}
|
||||
|
||||
// Now throw the error
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
```
|
||||
|
||||
**Key Principle:** Handle authentication errors before throwing. Order matters in error handling logic.
|
||||
|
||||
---
|
||||
|
||||
## Site/Sector Architecture
|
||||
|
||||
### Data Hierarchy
|
||||
```
|
||||
Account (Tenant)
|
||||
└── Site (e.g., myblog.com)
|
||||
└── Sector (e.g., Technology, Health)
|
||||
└── Keywords
|
||||
└── Clusters
|
||||
└── Ideas
|
||||
└── Content
|
||||
```
|
||||
|
||||
### Where Sectors Are Used (Global Context)
|
||||
|
||||
**USES SECTORS (requires site/sector selection):**
|
||||
- ✅ Planner Module (Keywords, Clusters, Ideas)
|
||||
- ✅ Writer Module (Tasks, Content, Drafts, Published)
|
||||
- ✅ Linker Module (Internal linking)
|
||||
- ✅ Optimizer Module (Content optimization)
|
||||
- ✅ Setup/Add Keywords page
|
||||
- ✅ Seed Keywords reference data
|
||||
|
||||
**DOES NOT USE SECTORS (account-level only):**
|
||||
- ❌ Billing/Plans pages (`/account/*`)
|
||||
- ❌ Account Settings
|
||||
- ❌ Team Management
|
||||
- ❌ User Profile
|
||||
- ❌ Admin Dashboard
|
||||
- ❌ System Settings
|
||||
|
||||
### Sector Loading Pattern
|
||||
|
||||
**Architecture Decision:**
|
||||
- Sectors loaded by **PageHeader component** (not AppLayout)
|
||||
- Only loads when `hideSiteSector={false}` prop is set
|
||||
- Account/billing pages pass `hideSiteSector={true}` to skip loading
|
||||
|
||||
**Implementation:**
|
||||
```typescript
|
||||
// PageHeader.tsx
|
||||
useEffect(() => {
|
||||
if (hideSiteSector) return; // Skip for account pages
|
||||
|
||||
const currentSiteId = activeSite?.id ?? null;
|
||||
if (currentSiteId && activeSite?.is_active) {
|
||||
loadSectorsForSite(currentSiteId);
|
||||
}
|
||||
}, [activeSite?.id, hideSiteSector]);
|
||||
```
|
||||
|
||||
**Key Principle:** Lazy-load sectors only when components need them. Don't load globally for all pages.
|
||||
|
||||
---
|
||||
|
||||
### Site/Sector Store Persistence
|
||||
|
||||
**Storage Keys:**
|
||||
- `site-storage` - Active site selection
|
||||
- `sector-storage` - Active sector selection
|
||||
|
||||
**Reset Pattern:**
|
||||
```typescript
|
||||
// When site changes, reset sector if it belongs to different site
|
||||
if (currentSector && currentSector.site_id !== newSiteId) {
|
||||
set({ activeSector: null });
|
||||
localStorage.setItem('sector-storage', JSON.stringify({
|
||||
state: { activeSector: null },
|
||||
version: 0
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
**Key Principle:** Sector selection is site-scoped. Always validate sector belongs to active site.
|
||||
|
||||
---
|
||||
|
||||
## State Management & Race Conditions
|
||||
|
||||
### Common Race Condition Patterns
|
||||
|
||||
#### 1. User Switching
|
||||
**Problem:** Rapid logout → login leaves stale state in stores
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
logout: () => {
|
||||
// Reset ALL dependent stores explicitly
|
||||
import('./siteStore').then(({ useSiteStore }) => {
|
||||
useSiteStore.setState({ activeSite: null, loading: false, error: null });
|
||||
});
|
||||
import('./sectorStore').then(({ useSectorStore }) => {
|
||||
useSectorStore.setState({ activeSector: null, sectors: [], loading: false, error: null });
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. API Calls Before Token Persistence
|
||||
**Problem:** API calls happen before Zustand persist writes token
|
||||
|
||||
**Solution:** Synchronous localStorage write immediately after state update (see Authentication section)
|
||||
|
||||
#### 3. Module Loading Failures
|
||||
**Problem:** 404 errors during page navigation cause module loading to fail
|
||||
|
||||
**Solution:** Ensure API endpoints exist before pages try to load them. Use conditional rendering based on route.
|
||||
|
||||
---
|
||||
|
||||
### Zustand Persist Middleware Gotchas
|
||||
|
||||
**Issue 1: Version Mismatch**
|
||||
```typescript
|
||||
// Stored format
|
||||
{ state: { user, token }, version: 0 }
|
||||
|
||||
// If version changes, persist middleware clears state
|
||||
```
|
||||
|
||||
**Issue 2: Async Hydration**
|
||||
- State rehydration from localStorage is async
|
||||
- Can cause brief flash of "no user" state
|
||||
|
||||
**Solution:** Use loading states or check both store AND localStorage:
|
||||
```typescript
|
||||
const getAuthToken = (): string | null => {
|
||||
// Try Zustand store first
|
||||
const authState = useAuthStore.getState();
|
||||
if (authState?.token) return authState.token;
|
||||
|
||||
// Fallback to localStorage
|
||||
const stored = localStorage.getItem('auth-storage');
|
||||
return JSON.parse(stored)?.state?.token || null;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Permission System
|
||||
|
||||
### Superuser/Developer Bypass Pattern
|
||||
|
||||
**Critical Locations for Bypass:**
|
||||
1. Middleware - `auth/middleware.py`
|
||||
2. Permission Classes - `api/permissions.py`
|
||||
3. ViewSet Querysets - `api/base.py`
|
||||
4. Validation Functions - `auth/utils.py`
|
||||
|
||||
**Standard Bypass Check:**
|
||||
```python
|
||||
def check_bypass(user):
|
||||
return (
|
||||
user.is_superuser or
|
||||
user.role == 'developer' or
|
||||
is_system_account_user(user)
|
||||
)
|
||||
```
|
||||
|
||||
**Apply at ALL levels:**
|
||||
- Middleware request validation
|
||||
- DRF permission `has_permission()`
|
||||
- ViewSet `get_queryset()` filtering
|
||||
- Custom validation functions
|
||||
|
||||
**Key Principle:** Bypass checks must be consistent across all permission layers. Missing one layer breaks superuser access.
|
||||
|
||||
---
|
||||
|
||||
### System Account Pattern
|
||||
|
||||
**Reserved Accounts:**
|
||||
- `aws-admin` - System automation account
|
||||
- `default-account` - Default tenant fallback
|
||||
|
||||
**Check Function:**
|
||||
```python
|
||||
def is_system_account_user(user):
|
||||
if not user or not user.account:
|
||||
return False
|
||||
return user.account.slug in ['aws-admin', 'default-account']
|
||||
```
|
||||
|
||||
**Usage:** Always include in bypass checks alongside superuser/developer.
|
||||
|
||||
---
|
||||
|
||||
## Frontend Component Dependencies
|
||||
|
||||
### PageHeader Component
|
||||
**Dependencies:**
|
||||
- `useSiteStore` - Active site
|
||||
- `useSectorStore` - Active sector
|
||||
- `SiteAndSectorSelector` - Dropdown component
|
||||
|
||||
**Props:**
|
||||
- `hideSiteSector: boolean` - Skip site/sector display and loading
|
||||
- `title: string` - Page title
|
||||
- `navigation: ReactNode` - Optional module tabs
|
||||
|
||||
**Used By:**
|
||||
- All Planner pages
|
||||
- All Writer pages
|
||||
- All Optimizer pages
|
||||
- Setup pages
|
||||
- Seed Keywords page
|
||||
|
||||
**NOT Used By:**
|
||||
- Account/billing pages (use plain headers instead)
|
||||
|
||||
---
|
||||
|
||||
### Module Navigation Pattern
|
||||
|
||||
**Component:** `ModuleNavigationTabs.tsx`
|
||||
|
||||
**CRITICAL:** Must be wrapped in `<Router>` context
|
||||
- Uses `useLocation()` and `useNavigate()` hooks
|
||||
- Cannot be used outside `<Routes>` tree
|
||||
|
||||
**Common Error:**
|
||||
```
|
||||
Error: useLocation() may be used only in the context of a <Router> component
|
||||
```
|
||||
|
||||
**Cause:** Component rendered outside React Router context
|
||||
|
||||
**Solution:** Ensure component is within `<Route>` element in App.tsx
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls & Solutions
|
||||
|
||||
### Pitfall 1: Frontend 403 Errors After User Switch
|
||||
|
||||
**Symptoms:**
|
||||
- "Authentication credentials were not provided"
|
||||
- User appears logged in but API calls fail
|
||||
- Manually clearing cache fixes it
|
||||
|
||||
**Root Cause:** Invalid tokens persisting in localStorage after logout
|
||||
|
||||
**Solution:**
|
||||
1. Check 403 handler runs BEFORE throwing error
|
||||
2. Ensure logout clears specific auth keys (not `localStorage.clear()`)
|
||||
3. Add immediate token persistence after login
|
||||
|
||||
**Prevention:** See "Authentication & Session Management" section
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 2: Sector 404 Errors on Billing Pages
|
||||
|
||||
**Symptoms:**
|
||||
- `GET /v1/auth/sites/{id}/sectors/` returns 404
|
||||
- "Failed to fetch dynamically imported module" error
|
||||
- Billing pages don't load
|
||||
|
||||
**Root Cause:** AppLayout loading sectors for ALL pages globally
|
||||
|
||||
**Solution:** Move sector loading to PageHeader component (lazy loading)
|
||||
|
||||
**Prevention:** Only load data when components that need it are mounted
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 3: Module Loading Failures After Git Commits
|
||||
|
||||
**Symptoms:**
|
||||
- React Router context errors
|
||||
- "useLocation() may be used only in context of <Router>" errors
|
||||
- Pages work after rebuild but fail after git push
|
||||
|
||||
**Root Cause:** Docker build cache not invalidated properly
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Force clean rebuild
|
||||
docker compose -f docker-compose.app.yml down
|
||||
docker compose -f docker-compose.app.yml build --no-cache igny8_frontend
|
||||
docker compose -f docker-compose.app.yml up -d
|
||||
```
|
||||
|
||||
**Prevention:** Use `--no-cache` flag when rebuilding after major changes
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 4: Plan Selection Issues in Pricing Page
|
||||
|
||||
**Symptoms:**
|
||||
- Monthly/Annual toggle missing
|
||||
- Pre-selected plan not highlighted
|
||||
- Discount calculation wrong
|
||||
|
||||
**Root Cause:**
|
||||
1. PricingTable component missing `showToggle` prop
|
||||
2. Backend missing `is_featured` and `annual_discount_percent` fields
|
||||
3. Frontend not calculating annual price from discount
|
||||
|
||||
**Solution:**
|
||||
1. Add fields to Plan model with migration
|
||||
2. Pass `annualDiscountPercent` to PricingTable
|
||||
3. Calculate: `annualPrice = monthlyPrice * 12 * (1 - discount/100)`
|
||||
|
||||
**Files Modified:**
|
||||
- `backend/igny8_core/auth/models.py`
|
||||
- `backend/igny8_core/auth/serializers.py`
|
||||
- `frontend/src/services/billing.api.ts`
|
||||
- `frontend/src/components/ui/pricing-table/PricingTable.tsx`
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 5: Adjacent JSX Elements Error
|
||||
|
||||
**Symptoms:**
|
||||
- "Adjacent JSX elements must be wrapped in an enclosing tag"
|
||||
- Build fails but line numbers don't help
|
||||
|
||||
**Root Cause:** Mismatched opening/closing tags (usually missing `</div>`)
|
||||
|
||||
**Debugging Strategy:**
|
||||
1. Use TypeScript compiler: `npx tsc --noEmit <file>`
|
||||
2. Count opening vs closing tags: `grep -c "<div" vs grep -c "</div>"`
|
||||
3. Check conditionals have matching closing parens/braces
|
||||
|
||||
**Common Pattern:**
|
||||
```tsx
|
||||
{condition && (
|
||||
<div>
|
||||
{/* Content */}
|
||||
</div>
|
||||
{/* Missing closing parenthesis causes "adjacent elements" error */}
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:** Ensure every opening bracket has matching close bracket
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### State Management
|
||||
✅ **DO:** Immediately persist auth tokens synchronously
|
||||
✅ **DO:** Selectively remove localStorage keys
|
||||
✅ **DO:** Reset dependent stores on logout
|
||||
❌ **DON'T:** Use `localStorage.clear()`
|
||||
❌ **DON'T:** Rely solely on Zustand persist middleware timing
|
||||
|
||||
### Error Handling
|
||||
✅ **DO:** Check authentication errors BEFORE throwing
|
||||
✅ **DO:** Force logout on invalid tokens
|
||||
✅ **DO:** Redirect to login after logout
|
||||
❌ **DON'T:** Throw errors before checking auth status
|
||||
❌ **DON'T:** Leave invalid tokens in storage
|
||||
|
||||
### Component Architecture
|
||||
✅ **DO:** Lazy-load data at component level
|
||||
✅ **DO:** Skip unnecessary data loading (hideSiteSector pattern)
|
||||
✅ **DO:** Keep components in Router context
|
||||
❌ **DON'T:** Load data globally in AppLayout
|
||||
❌ **DON'T:** Use Router hooks outside Router context
|
||||
|
||||
### Permission System
|
||||
✅ **DO:** Implement bypass at ALL permission layers
|
||||
✅ **DO:** Include system accounts in bypass checks
|
||||
✅ **DO:** Use consistent bypass logic everywhere
|
||||
❌ **DON'T:** Forget middleware layer bypass
|
||||
❌ **DON'T:** Mix permission approaches
|
||||
|
||||
### Docker Builds
|
||||
✅ **DO:** Use `--no-cache` after major changes
|
||||
✅ **DO:** Restart containers after rebuilds
|
||||
✅ **DO:** Check logs for module loading errors
|
||||
❌ **DON'T:** Trust build cache after git commits
|
||||
❌ **DON'T:** Deploy without testing fresh build
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: File Locations
|
||||
|
||||
### Authentication
|
||||
- Token handling: `frontend/src/services/api.ts`
|
||||
- Auth store: `frontend/src/store/authStore.ts`
|
||||
- Middleware: `backend/igny8_core/auth/middleware.py`
|
||||
|
||||
### Permissions
|
||||
- Permission classes: `backend/igny8_core/api/permissions.py`
|
||||
- Base viewsets: `backend/igny8_core/api/base.py`
|
||||
- Validation utils: `backend/igny8_core/auth/utils.py`
|
||||
|
||||
### Site/Sector
|
||||
- Site store: `frontend/src/store/siteStore.ts`
|
||||
- Sector store: `frontend/src/store/sectorStore.ts`
|
||||
- PageHeader: `frontend/src/components/common/PageHeader.tsx`
|
||||
|
||||
### Billing
|
||||
- Billing API: `frontend/src/services/billing.api.ts`
|
||||
- Plans page: `frontend/src/pages/account/PlansAndBillingPage.tsx`
|
||||
- Plan model: `backend/igny8_core/auth/models.py`
|
||||
|
||||
---
|
||||
|
||||
**End of Knowledge Base**
|
||||
*Update this document when architectural patterns change or new common issues are discovered.*
|
||||
@@ -0,0 +1,146 @@
|
||||
# Managed Add-on Plans (Marketing + App)
|
||||
|
||||
**Date:** 2026-01-20
|
||||
|
||||
## Goals
|
||||
- Offer managed services as an optional add-on per site.
|
||||
- Keep core SaaS plans unchanged while enabling add-on selection at pricing, signup, and billing.
|
||||
- Provide clear separation between **Core Plans** and **Managed Add-ons** in backend, frontend, and billing UI.
|
||||
|
||||
---
|
||||
|
||||
## Proposed Managed Add-on Tiers
|
||||
- **Managed Lite** — **$100/site/month**
|
||||
- **Managed Pro** — **$399/site/month**
|
||||
|
||||
### Managed Features (shared)
|
||||
- Onboarding & setup: site integration, automation schedule, content settings.
|
||||
- Monthly SEO content plan: keyword import from library/clustering, topic strategy, content calendar.
|
||||
- Content QA & optimization: review queue checks, SEO meta validation, internal link suggestions.
|
||||
- Publishing ops: scheduled publishing, status monitoring, retry/failure handling.
|
||||
- Reporting: monthly performance + usage summary (credits, content velocity, publishing outcomes).
|
||||
- Support & tuning: strategy optimization/tweaks, automation adjustments, issue triage.
|
||||
|
||||
### Pro extras
|
||||
- Proactive monitoring and escalation.
|
||||
- Priority response.
|
||||
- Expanded strategy iteration (more frequent adjustments).
|
||||
|
||||
---
|
||||
|
||||
## Marketing Site (https://igny8.com/pricing)
|
||||
|
||||
### Layout changes
|
||||
1. **Keep Core Plans section unchanged**.
|
||||
2. Add a **big + icon** directly below the pricing table.
|
||||
3. Add **one single horizontal card** for Managed Add-on:
|
||||
- Visible badge: **COMING SOON**
|
||||
- Card title: “Managed Add-on (Per Site)”
|
||||
- Short summary of major features (1 line)
|
||||
- **Toggle switch** inside the card for **Managed Lite / Managed Pro**
|
||||
- Show price per site for the selected toggle
|
||||
4. No other sections or FAQs added.
|
||||
|
||||
### Suggested UX copy
|
||||
- “Managed Add-on (Per Site) — Coming Soon”
|
||||
- “Choose Lite or Pro”
|
||||
|
||||
---
|
||||
|
||||
## Signup Page (https://app.igny8.com/signup)
|
||||
|
||||
### Layout changes
|
||||
Add **Step 2: Managed Add-ons (Optional)** after plan selection.
|
||||
- Toggle per site: “Add managed services to selected site(s)”
|
||||
- If user selects a plan with multiple sites:
|
||||
- Show checkboxes for each site slot.
|
||||
- Default: none selected.
|
||||
- Inline price calculator:
|
||||
- “Managed Lite x N sites = $X/mo”
|
||||
- “Managed Pro x N sites = $X/mo”
|
||||
|
||||
### UX notes
|
||||
- Keep signup friction low.
|
||||
- If user skips add-on, allow adding later from Billing.
|
||||
|
||||
---
|
||||
|
||||
## App Billing & Plans (Account → Plans & Billing)
|
||||
|
||||
### New UI sections
|
||||
1. **Current Plan** remains unchanged.
|
||||
2. Add **“Managed Add-ons”** section:
|
||||
- Show current add-on tier (if any) and assigned sites.
|
||||
- Show monthly add-on price and next renewal date.
|
||||
3. Add **“Upgrade Add-ons”** tab or sub-panel:
|
||||
- Choose Managed Lite/Pro.
|
||||
- Assign to site(s).
|
||||
- Update monthly total.
|
||||
|
||||
### Existing users
|
||||
- If a user already subscribed to a managed add-on:
|
||||
- Display in **Plan** tab summary.
|
||||
- Include in billing history and invoice breakdown.
|
||||
|
||||
---
|
||||
|
||||
## Backend Model Changes
|
||||
|
||||
### Option A (Minimal changes in Plan model)
|
||||
Add fields to `Plan`:
|
||||
- `plan_type` (choices: `core`, `managed`) — distinguishes SaaS vs add-on.
|
||||
- `per_site` (bool, default false) — marks managed add-ons.
|
||||
- `managed_tier` (optional slug: `lite`, `pro`).
|
||||
|
||||
Add optional relation to `Account` or `Site`:
|
||||
- New model `SiteManagementAddon`:
|
||||
- `site` (FK)
|
||||
- `plan` (FK to Plan where `plan_type=managed`)
|
||||
- `status`, `current_period_start`, `current_period_end`
|
||||
- `external_subscription_id`
|
||||
|
||||
### Option B (Separate ManagedPlan model)
|
||||
Create `ManagedPlan` model (clone of Plan fields needed for pricing + name).
|
||||
Keep `Plan` for core SaaS only.
|
||||
|
||||
**Recommendation:** Option A (fewer tables, uses existing pricing pipeline).
|
||||
|
||||
---
|
||||
|
||||
## Backend Billing Logic
|
||||
- Managed add-ons are **per site**.
|
||||
- Create separate Stripe subscription items per site, or a single subscription with quantity = number of managed sites.
|
||||
- Billing summary should show:
|
||||
- Core plan price
|
||||
- Managed add-on subtotal (N sites x price)
|
||||
- Total monthly
|
||||
|
||||
---
|
||||
|
||||
## Frontend Data Contracts
|
||||
|
||||
### API additions
|
||||
- `GET /api/v1/auth/plans/?type=core` (core plans only)
|
||||
- `GET /api/v1/auth/plans/?type=managed` (managed add-ons)
|
||||
- `GET /api/v1/account/managed-addons/` (current user add-ons + site assignments)
|
||||
- `POST /api/v1/account/managed-addons/` (assign add-on to site(s))
|
||||
- `PUT /api/v1/account/managed-addons/{id}/` (upgrade/downgrade add-on tier)
|
||||
|
||||
---
|
||||
|
||||
## Pricing Copy (Core Plans)
|
||||
Suggested renames to keep consistency:
|
||||
- Starter → **Launch**
|
||||
- Growth → **Growth** (keep)
|
||||
- Scale → **Scale** (keep)
|
||||
|
||||
---
|
||||
|
||||
## Rollout Checklist
|
||||
- Add plan_type + per_site fields + migration.
|
||||
- Add managed add-on seed data (Lite/Pro).
|
||||
- Add managed add-on endpoints + serializer filtering.
|
||||
- Update pricing page layout (marketing).
|
||||
- Update signup flow (managed add-on step).
|
||||
- Update billing page (Managed Add-ons section).
|
||||
- Update invoices to show core + managed breakdown.
|
||||
851
v2/Live Docs on Server/igny8-app-docs/90-REFERENCE/MODELS.md
Normal file
851
v2/Live Docs on Server/igny8-app-docs/90-REFERENCE/MODELS.md
Normal file
@@ -0,0 +1,851 @@
|
||||
# Database Models Reference
|
||||
|
||||
**Last Verified:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
**Total Models:** 52+
|
||||
|
||||
---
|
||||
|
||||
## Data Scoping Overview
|
||||
|
||||
| Scope | Models | Base Class | Filter By |
|
||||
|-------|--------|------------|-----------|
|
||||
| **Global** | `IntegrationProvider`, `AIModelConfig`, `SystemAISettings`, `GlobalAIPrompt`, `GlobalAuthorProfile`, `GlobalStrategy`, `GlobalModuleSettings`, `Industry`, `IndustrySector`, `SeedKeyword` | `models.Model` | None (platform-wide) |
|
||||
| **Account** | `Account`, `User`, `Plan`, `Subscription`, `AccountSettings`, `ModuleEnableSettings`, `AISettings`, `AIPrompt`, `AuthorProfile`, `CreditBalance`, `PasswordResetToken` | `AccountBaseModel` | `account` |
|
||||
| **Site** | `Site`, `PublishingSettings`, `AutomationConfig`, `DefaultAutomationConfig`, `AutomationRun`, `SiteIntegration`, `SiteUserAccess` | `AccountBaseModel` | `account`, `site` |
|
||||
| **Site+Sector** | `Keywords`, `Clusters`, `ContentIdeas`, `Tasks`, `Content`, `Images`, `ContentTaxonomyRelation` | `SiteSectorBaseModel` | `site`, `sector` |
|
||||
| **Billing** | `CreditCostConfig`, `BillingConfiguration`, `CreditPackage`, `PaymentMethodConfig`, `WebhookEvent` | `models.Model` | varies |
|
||||
| **System** | `SystemSettings`, `UserSettings`, `EmailSettings`, `EmailTemplate`, `EmailLog` | `models.Model` | varies |
|
||||
| **Plugins** | `Plugin`, `PluginVersion`, `PluginInstallation`, `PluginDownload` | `models.Model` | varies |
|
||||
|
||||
---
|
||||
|
||||
## Model Count by Location
|
||||
|
||||
| Location | Count | Models |
|
||||
|----------|-------|--------|
|
||||
| `auth/models.py` | 10 | Account, User, Plan, Subscription, Industry, IndustrySector, SeedKeyword, Site, SiteUserAccess, PasswordResetToken |
|
||||
| `modules/system/` | 10 | IntegrationProvider, SystemAISettings, SystemSettings, UserSettings, EmailSettings, EmailTemplate, EmailLog, GlobalAIPrompt, GlobalAuthorProfile, GlobalStrategy |
|
||||
| `business/automation/` | 3 | DefaultAutomationConfig, AutomationConfig, AutomationRun |
|
||||
| `business/billing/` | 6 | CreditCostConfig, BillingConfiguration, CreditPackage, PaymentMethodConfig, AIModelConfig, WebhookEvent |
|
||||
| `business/content/` | 1 | ContentTaxonomyRelation |
|
||||
| `plugins/` | 4 | Plugin, PluginVersion, PluginInstallation, PluginDownload |
|
||||
| `modules/planner/` | 3 | Keywords, Clusters, ContentIdeas |
|
||||
| `modules/writer/` | 3 | Tasks, Content, Images |
|
||||
|
||||
---
|
||||
|
||||
## System Models (v1.4.0+) (`igny8_core/modules/system/`)
|
||||
|
||||
**Purpose:** Centralized AI configuration, provider API keys, and system-wide defaults.
|
||||
|
||||
### IntegrationProvider (NEW v1.4.0)
|
||||
|
||||
Centralized storage for ALL external service API keys. Admin-only.
|
||||
|
||||
```python
|
||||
class IntegrationProvider(models.Model):
|
||||
"""Per final-model-schemas.md - Centralized API key storage"""
|
||||
provider_id = CharField(max_length=50, primary_key=True) # openai, runware, stripe, etc.
|
||||
display_name = CharField(max_length=100)
|
||||
provider_type = CharField(max_length=20) # ai, payment, email, storage
|
||||
|
||||
# Authentication
|
||||
api_key = CharField(max_length=500, blank=True)
|
||||
api_secret = CharField(max_length=500, blank=True)
|
||||
webhook_secret = CharField(max_length=500, blank=True)
|
||||
api_endpoint = URLField(blank=True)
|
||||
|
||||
# Configuration
|
||||
config = JSONField(default=dict)
|
||||
is_active = BooleanField(default=True)
|
||||
is_sandbox = BooleanField(default=False)
|
||||
|
||||
# Audit
|
||||
updated_by = ForeignKey(User, null=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Seeded Providers:**
|
||||
- `openai` - AI (text + DALL-E)
|
||||
- `runware` - AI (images)
|
||||
- `anthropic` - AI (future)
|
||||
- `stripe` - Payment
|
||||
- `paypal` - Payment
|
||||
- `resend` - Email
|
||||
|
||||
**Helper Methods:**
|
||||
- `IntegrationProvider.get_provider(provider_id)` - Get active provider
|
||||
- `IntegrationProvider.get_api_key(provider_id)` - Get API key for provider
|
||||
- `IntegrationProvider.get_providers_by_type(type)` - List providers by type
|
||||
|
||||
---
|
||||
|
||||
### AIModelConfig (NEW v1.4.0)
|
||||
|
||||
Single Source of Truth for all AI models with pricing and credit configuration.
|
||||
|
||||
```python
|
||||
class AIModelConfig(models.Model):
|
||||
"""Per final-model-schemas.md - Model definitions + pricing"""
|
||||
model_name = CharField(max_length=100, unique=True) # gpt-4o-mini, dall-e-3, runware:97@1
|
||||
model_type = CharField(max_length=20) # text, image
|
||||
provider = CharField(max_length=50) # Links to IntegrationProvider
|
||||
display_name = CharField(max_length=200)
|
||||
|
||||
is_default = BooleanField(default=False) # One default per type
|
||||
is_active = BooleanField(default=True)
|
||||
|
||||
# Text Model Pricing (per 1K tokens)
|
||||
cost_per_1k_input = DecimalField(max_digits=10, decimal_places=6, null=True)
|
||||
cost_per_1k_output = DecimalField(max_digits=10, decimal_places=6, null=True)
|
||||
tokens_per_credit = IntegerField(null=True) # e.g., 1000, 10000
|
||||
|
||||
# Image Model Pricing
|
||||
credits_per_image = IntegerField(null=True) # e.g., 1, 5, 15
|
||||
quality_tier = CharField(max_length=20, null=True) # basic, quality, premium
|
||||
|
||||
# Model Limits
|
||||
max_tokens = IntegerField(null=True)
|
||||
context_window = IntegerField(null=True)
|
||||
capabilities = JSONField(default=dict) # vision, function_calling, etc.
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Credit Configuration Examples:**
|
||||
|
||||
| Model | Type | tokens_per_credit | credits_per_image | quality_tier |
|
||||
|-------|------|-------------------|-------------------|--------------|
|
||||
| gpt-4o | text | 1000 | - | - |
|
||||
| gpt-4o-mini | text | 10000 | - | - |
|
||||
| runware:97@1 | image | - | 1 | basic |
|
||||
| dall-e-3 | image | - | 5 | quality |
|
||||
| google:4@2 | image | - | 15 | premium |
|
||||
|
||||
**Image Model Reference (v1.5.0 Planned):**
|
||||
|
||||
| Model | AIR ID | Tier | Supported Dimensions |
|
||||
|-------|--------|------|---------------------|
|
||||
| Hi Dream Full | `runware:97@1` | Basic | 1024×1024, 1280×768 |
|
||||
| Bria 3.2 | `bria:10@1` | Quality | 1024×1024, 1344×768 |
|
||||
| Nano Banana | `google:4@2` | Premium | 1024×1024, 1376×768 |
|
||||
|
||||
**Helper Methods:**
|
||||
- `AIModelConfig.get_default_text_model()` - Get default text model
|
||||
- `AIModelConfig.get_default_image_model()` - Get default image model
|
||||
- `AIModelConfig.get_image_models_by_tier()` - List image models by quality tier
|
||||
|
||||
---
|
||||
|
||||
### SystemAISettings (NEW v1.4.0)
|
||||
|
||||
System-wide AI defaults. Singleton (pk=1).
|
||||
|
||||
```python
|
||||
class SystemAISettings(models.Model):
|
||||
"""Per final-model-schemas.md - Renamed from GlobalIntegrationSettings"""
|
||||
# AI Parameters
|
||||
temperature = FloatField(default=0.7) # 0.0-2.0
|
||||
max_tokens = IntegerField(default=8192)
|
||||
|
||||
# Image Generation Settings
|
||||
image_style = CharField(max_length=30, default='photorealistic')
|
||||
image_quality = CharField(max_length=20, default='standard') # standard, hd
|
||||
max_images_per_article = IntegerField(default=4) # 1-8
|
||||
image_size = CharField(max_length=20, default='1024x1024')
|
||||
|
||||
# Audit
|
||||
updated_by = ForeignKey(User, null=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Image Style Choices:**
|
||||
- `photorealistic` - Ultra realistic photography
|
||||
- `illustration` - Digital illustration
|
||||
- `3d_render` - Computer generated 3D
|
||||
- `minimal_flat` - Minimal / Flat Design
|
||||
- `artistic` - Artistic / Painterly
|
||||
- `cartoon` - Cartoon / Stylized
|
||||
|
||||
**Image Size Choices:**
|
||||
- `1024x1024` - Square
|
||||
- `1792x1024` - Landscape
|
||||
- `1024x1792` - Portrait
|
||||
|
||||
**Helper Methods:**
|
||||
- `SystemAISettings.get_instance()` - Get singleton instance
|
||||
- `SystemAISettings.get_effective_temperature(account)` - Get with account override
|
||||
- `SystemAISettings.get_effective_image_style(account)` - Get with account override
|
||||
|
||||
---
|
||||
|
||||
### AccountSettings (Per-Account Overrides)
|
||||
|
||||
Generic key-value store for account-specific settings.
|
||||
|
||||
```python
|
||||
class AccountSettings(AccountBaseModel):
|
||||
"""Per final-model-schemas.md - Account overrides"""
|
||||
key = CharField(max_length=100) # Setting key
|
||||
value = JSONField(default=dict) # Setting value
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**AI-Related Keys** (override SystemAISettings defaults):
|
||||
|
||||
| Key | Type | Example | Notes |
|
||||
|-----|------|---------|-------|
|
||||
| `ai.temperature` | float | 0.8 | Override system default |
|
||||
| `ai.max_tokens` | int | 8192 | Override system default |
|
||||
| `ai.image_style` | string | "illustration" | Override system default |
|
||||
| `ai.image_quality` | string | "hd" | Override system default |
|
||||
| `ai.max_images` | int | 6 | Override system default |
|
||||
| `ai.image_quality_tier` | string | "premium" | User's preferred tier |
|
||||
|
||||
---
|
||||
|
||||
### CreditCostConfig (Operation-Level Pricing)
|
||||
|
||||
Fixed credit costs per operation type.
|
||||
|
||||
```python
|
||||
class CreditCostConfig(models.Model):
|
||||
"""Per final-model-schemas.md - Operation pricing"""
|
||||
operation_type = CharField(max_length=50, primary_key=True) # Unique operation ID
|
||||
display_name = CharField(max_length=100)
|
||||
base_credits = IntegerField(default=1) # Fixed credits per operation
|
||||
is_active = BooleanField(default=True)
|
||||
description = TextField(blank=True)
|
||||
```
|
||||
|
||||
**Note:** `tokens_per_credit` moved to AIModelConfig as of v1.4.0.
|
||||
|
||||
---
|
||||
|
||||
## DEPRECATED Models
|
||||
|
||||
### GlobalIntegrationSettings (DEPRECATED in v1.4.0)
|
||||
|
||||
**Replaced by:** `IntegrationProvider` (API keys) + `AIModelConfig` (model configs) + `SystemAISettings` (defaults)
|
||||
|
||||
```python
|
||||
# DEPRECATED - Do not use
|
||||
class GlobalIntegrationSettings(models.Model):
|
||||
# API keys now in IntegrationProvider
|
||||
openai_api_key = CharField(max_length=500) # → IntegrationProvider.get_api_key('openai')
|
||||
runware_api_key = CharField(max_length=500) # → IntegrationProvider.get_api_key('runware')
|
||||
|
||||
# Models now in AIModelConfig
|
||||
openai_model = CharField(default='gpt-4o-mini') # → AIModelConfig.is_default
|
||||
|
||||
# Settings now in SystemAISettings
|
||||
openai_temperature = FloatField(default=0.7) # → SystemAISettings.temperature
|
||||
openai_max_tokens = IntegerField(default=8192) # → SystemAISettings.max_tokens
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GlobalAIPrompt
|
||||
|
||||
```python
|
||||
class GlobalAIPrompt(models.Model):
|
||||
prompt_type = CharField(max_length=100) # clustering, ideas, content_generation
|
||||
prompt_value = TextField()
|
||||
variables = JSONField(default=list)
|
||||
is_active = BooleanField(default=True)
|
||||
```
|
||||
|
||||
### GlobalAuthorProfile
|
||||
|
||||
```python
|
||||
class GlobalAuthorProfile(models.Model):
|
||||
name = CharField(max_length=255)
|
||||
tone = CharField(max_length=50) # professional, casual, technical
|
||||
language = CharField(max_length=10, default='en')
|
||||
is_active = BooleanField(default=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Auth Models (`igny8_core/auth/models/`)
|
||||
|
||||
### User
|
||||
|
||||
```python
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
id = UUIDField(primary_key=True)
|
||||
email = EmailField(unique=True)
|
||||
first_name = CharField(max_length=150)
|
||||
last_name = CharField(max_length=150)
|
||||
|
||||
account = ForeignKey(Account, related_name='users')
|
||||
role = ForeignKey(Group, null=True)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
is_staff = BooleanField(default=False)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Relations:** Account (many-to-one)
|
||||
|
||||
---
|
||||
|
||||
### Account
|
||||
|
||||
```python
|
||||
class Account(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
|
||||
plan = ForeignKey(Plan, null=True)
|
||||
owner = ForeignKey(User, related_name='owned_accounts')
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Plan (many-to-one), Users (one-to-many), Sites (one-to-many)
|
||||
|
||||
---
|
||||
|
||||
### Site
|
||||
|
||||
```python
|
||||
class Site(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
domain = CharField(max_length=255, blank=True)
|
||||
|
||||
account = ForeignKey(Account, related_name='sites')
|
||||
industry = ForeignKey(Industry, null=True)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Account (many-to-one), Sectors (one-to-many), Industries (many-to-one)
|
||||
|
||||
---
|
||||
|
||||
### Sector
|
||||
|
||||
```python
|
||||
class Sector(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='sectors')
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Site (many-to-one)
|
||||
|
||||
---
|
||||
|
||||
### Industry
|
||||
|
||||
```python
|
||||
class Industry(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
```
|
||||
|
||||
**Used for:** Default seed keywords, industry-specific prompts
|
||||
|
||||
---
|
||||
|
||||
## Planner Models (`igny8_core/modules/planner/models.py`)
|
||||
|
||||
### Keyword
|
||||
|
||||
```python
|
||||
class Keyword(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
keyword = CharField(max_length=255)
|
||||
|
||||
site = ForeignKey(Site, related_name='keywords')
|
||||
sector = ForeignKey(Sector, null=True, related_name='keywords')
|
||||
cluster = ForeignKey(Cluster, null=True, related_name='keywords')
|
||||
|
||||
search_volume = IntegerField(null=True)
|
||||
difficulty = IntegerField(null=True)
|
||||
cpc = DecimalField(null=True)
|
||||
|
||||
status = CharField(choices=KEYWORD_STATUS) # new, mapped
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Status Values:**
|
||||
- `new` - Ready for clustering
|
||||
- `mapped` - Assigned to a cluster
|
||||
|
||||
---
|
||||
|
||||
### Cluster
|
||||
|
||||
```python
|
||||
class Cluster(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='clusters')
|
||||
sector = ForeignKey(Sector, null=True, related_name='clusters')
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
**Relations:** Site, Sector, Keywords (one-to-many), ContentIdeas (one-to-many)
|
||||
|
||||
---
|
||||
|
||||
### ContentIdea
|
||||
|
||||
```python
|
||||
class ContentIdea(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
title = CharField(max_length=255)
|
||||
description = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='ideas')
|
||||
sector = ForeignKey(Sector, null=True)
|
||||
cluster = ForeignKey(Cluster, related_name='ideas')
|
||||
|
||||
primary_keyword = ForeignKey(Keyword, related_name='primary_ideas')
|
||||
secondary_keywords = ManyToManyField(Keyword, related_name='secondary_ideas')
|
||||
|
||||
status = CharField(choices=IDEA_STATUS) # pending, approved, used, archived
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Writer Models (`igny8_core/modules/writer/models.py`)
|
||||
|
||||
### Task
|
||||
|
||||
```python
|
||||
class Task(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
title = CharField(max_length=255)
|
||||
brief = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='tasks')
|
||||
sector = ForeignKey(Sector, null=True)
|
||||
idea = ForeignKey(ContentIdea, null=True, related_name='tasks')
|
||||
|
||||
primary_keyword = CharField(max_length=255)
|
||||
secondary_keywords = JSONField(default=list)
|
||||
|
||||
status = CharField(choices=TASK_STATUS) # queued, completed
|
||||
|
||||
assigned_to = ForeignKey(User, null=True)
|
||||
due_date = DateField(null=True)
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Content
|
||||
|
||||
```python
|
||||
class Content(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
title = CharField(max_length=255)
|
||||
body = TextField() # HTML content
|
||||
excerpt = TextField(blank=True)
|
||||
|
||||
site = ForeignKey(Site, related_name='content')
|
||||
sector = ForeignKey(Sector, null=True)
|
||||
task = ForeignKey(Task, related_name='content')
|
||||
|
||||
meta_title = CharField(max_length=255, blank=True)
|
||||
meta_description = TextField(blank=True)
|
||||
|
||||
# Workflow status
|
||||
status = CharField(choices=CONTENT_STATUS) # draft, review, approved, published
|
||||
|
||||
# Publishing status (v1.3.2)
|
||||
site_status = CharField(choices=SITE_STATUS) # not_published, scheduled, publishing, published, failed
|
||||
scheduled_publish_at = DateTimeField(null=True)
|
||||
site_status_updated_at = DateTimeField(null=True)
|
||||
|
||||
# External site reference
|
||||
external_id = CharField(max_length=255, blank=True) # WordPress post ID
|
||||
external_url = URLField(blank=True) # Published URL
|
||||
|
||||
word_count = IntegerField(default=0)
|
||||
|
||||
created_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Workflow Status Values (status):**
|
||||
- `draft` - Initial state after generation
|
||||
- `review` - Pending human review
|
||||
- `approved` - Ready for publishing (v1.3.2: NEW status)
|
||||
- `published` - Published to WordPress
|
||||
|
||||
**Publishing Status Values (site_status) - v1.3.2:**
|
||||
- `not_published` - Not yet scheduled for external site
|
||||
- `scheduled` - Scheduled for future publishing
|
||||
- `publishing` - Currently being published
|
||||
- `published` - Successfully published to external site
|
||||
- `failed` - Publishing failed
|
||||
|
||||
---
|
||||
|
||||
### ContentImage
|
||||
|
||||
```python
|
||||
class ContentImage(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
content = ForeignKey(Content, related_name='images')
|
||||
|
||||
url = URLField()
|
||||
thumbnail_url = URLField(blank=True)
|
||||
alt_text = CharField(max_length=255)
|
||||
caption = TextField(blank=True)
|
||||
|
||||
is_featured = BooleanField(default=False)
|
||||
position = IntegerField(default=0)
|
||||
|
||||
# AI generation metadata
|
||||
prompt = TextField(blank=True)
|
||||
provider = CharField(max_length=50) # dalle, runware
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Models (`igny8_core/business/integration/models.py`)
|
||||
|
||||
### SiteIntegration
|
||||
|
||||
**⚠️ Note:** For WordPress, `Site.wp_api_key` is the **SINGLE source of truth** for API authentication. SiteIntegration is used for sync tracking and future multi-platform support.
|
||||
|
||||
```python
|
||||
class SiteIntegration(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=255)
|
||||
|
||||
site = ForeignKey(Site, related_name='integrations')
|
||||
platform = CharField(max_length=50) # wordpress, shopify (future)
|
||||
|
||||
# Configuration
|
||||
external_site_url = URLField()
|
||||
config_json = JSONField(default=dict) # Platform-specific settings
|
||||
credentials_json = JSONField(default=dict) # Reserved for future platforms (NOT for WordPress)
|
||||
|
||||
# Sync Tracking
|
||||
sync_enabled = BooleanField(default=True)
|
||||
sync_status = CharField(max_length=50) # pending/syncing/completed/error
|
||||
last_sync_at = DateTimeField(null=True)
|
||||
sync_error = TextField(null=True)
|
||||
|
||||
# Connection
|
||||
connection_status = CharField(max_length=50) # connected/error
|
||||
is_active = BooleanField(default=True)
|
||||
|
||||
# Cached WordPress structure (from initial sync)
|
||||
categories = JSONField(default=list)
|
||||
tags = JSONField(default=list)
|
||||
authors = JSONField(default=list)
|
||||
post_types = JSONField(default=list)
|
||||
structure_updated_at = DateTimeField(null=True)
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PublishingSettings (NEW v1.3.2)
|
||||
|
||||
```python
|
||||
class PublishingSettings(AccountBaseModel):
|
||||
"""Site-level publishing configuration. Controls auto-approval, publishing limits, and scheduling."""
|
||||
|
||||
site = OneToOneField(Site, related_name='publishing_settings')
|
||||
|
||||
# Auto-approval settings
|
||||
auto_approval_enabled = BooleanField(default=True)
|
||||
|
||||
# Auto-publish settings
|
||||
auto_publish_enabled = BooleanField(default=True)
|
||||
|
||||
# Publishing limits
|
||||
daily_publish_limit = PositiveIntegerField(default=3)
|
||||
weekly_publish_limit = PositiveIntegerField(default=15)
|
||||
monthly_publish_limit = PositiveIntegerField(default=50)
|
||||
|
||||
# Publishing schedule
|
||||
publish_days = JSONField(default=['mon', 'tue', 'wed', 'thu', 'fri'])
|
||||
publish_time_slots = JSONField(default=['09:00', '14:00', '18:00'])
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
**Default Values:**
|
||||
- `auto_approval_enabled`: True (auto-approve after review)
|
||||
- `auto_publish_enabled`: True (auto-publish approved content)
|
||||
- `daily_publish_limit`: 3 articles per day
|
||||
- `weekly_publish_limit`: 15 articles per week
|
||||
- `monthly_publish_limit`: 50 articles per month
|
||||
- `publish_days`: Monday through Friday
|
||||
- `publish_time_slots`: 9:00 AM, 2:00 PM, 6:00 PM
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
# Get or create with defaults
|
||||
settings, created = PublishingSettings.get_or_create_for_site(site)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Billing Models (`igny8_core/business/billing/models.py`)
|
||||
|
||||
### Plan
|
||||
|
||||
```python
|
||||
class Plan(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
name = CharField(max_length=100)
|
||||
slug = SlugField(unique=True)
|
||||
|
||||
idea_credits = IntegerField(default=0)
|
||||
content_credits = IntegerField(default=0)
|
||||
image_credits = IntegerField(default=0)
|
||||
optimization_credits = IntegerField(default=0)
|
||||
|
||||
max_sites = IntegerField(default=1)
|
||||
max_users = IntegerField(default=1)
|
||||
|
||||
price_monthly = DecimalField(max_digits=10, decimal_places=2)
|
||||
price_yearly = DecimalField(max_digits=10, decimal_places=2)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
is_internal = BooleanField(default=False)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreditBalance
|
||||
|
||||
```python
|
||||
class CreditBalance(models.Model):
|
||||
account = ForeignKey(Account, related_name='credit_balances')
|
||||
site = ForeignKey(Site, null=True, related_name='credit_balances')
|
||||
|
||||
idea_credits = IntegerField(default=0)
|
||||
content_credits = IntegerField(default=0)
|
||||
image_credits = IntegerField(default=0)
|
||||
optimization_credits = IntegerField(default=0)
|
||||
|
||||
period_start = DateField()
|
||||
period_end = DateField()
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CreditUsage
|
||||
|
||||
```python
|
||||
class CreditUsage(models.Model):
|
||||
account = ForeignKey(Account, related_name='credit_usage')
|
||||
site = ForeignKey(Site, null=True)
|
||||
user = ForeignKey(User)
|
||||
|
||||
credit_type = CharField(max_length=50) # idea, content, image, optimization
|
||||
amount = IntegerField()
|
||||
operation = CharField(max_length=100) # generate_content, etc.
|
||||
|
||||
related_content_type = ForeignKey(ContentType, null=True)
|
||||
related_object_id = UUIDField(null=True)
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Models (`igny8_core/modules/system/`)
|
||||
|
||||
### ModuleEnableSettings
|
||||
|
||||
```python
|
||||
class ModuleEnableSettings(models.Model):
|
||||
account = OneToOneField(Account, primary_key=True)
|
||||
|
||||
planner_enabled = BooleanField(default=True)
|
||||
writer_enabled = BooleanField(default=True)
|
||||
linker_enabled = BooleanField(default=False)
|
||||
optimizer_enabled = BooleanField(default=False)
|
||||
automation_enabled = BooleanField(default=True)
|
||||
integration_enabled = BooleanField(default=True)
|
||||
publisher_enabled = BooleanField(default=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AIIntegrationSettings
|
||||
|
||||
```python
|
||||
class AIIntegrationSettings(models.Model):
|
||||
account = ForeignKey(Account, related_name='ai_settings')
|
||||
|
||||
# OpenAI
|
||||
openai_api_key = CharField(max_length=255, blank=True)
|
||||
openai_model = CharField(max_length=50, default='gpt-4')
|
||||
|
||||
# Image generation
|
||||
image_provider = CharField(max_length=50, default='dalle') # dalle, runware
|
||||
dalle_api_key = CharField(max_length=255, blank=True)
|
||||
runware_api_key = CharField(max_length=255, blank=True)
|
||||
|
||||
is_validated = BooleanField(default=False)
|
||||
validated_at = DateTimeField(null=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### PromptTemplate
|
||||
|
||||
```python
|
||||
class PromptTemplate(models.Model):
|
||||
account = ForeignKey(Account, null=True) # null = system default
|
||||
|
||||
prompt_type = CharField(max_length=100) # auto_cluster, generate_ideas, etc.
|
||||
template = TextField()
|
||||
variables = JSONField(default=list)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
updated_at = DateTimeField(auto_now=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Publisher Models (`igny8_core/modules/publisher/models.py`)
|
||||
|
||||
### PublishingRecord
|
||||
|
||||
```python
|
||||
class PublishingRecord(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
content = ForeignKey(Content, related_name='publishing_records')
|
||||
integration = ForeignKey(SiteIntegration, related_name='publishing_records')
|
||||
|
||||
external_id = CharField(max_length=255) # WordPress post ID
|
||||
external_url = URLField(blank=True)
|
||||
|
||||
status = CharField(max_length=50) # pending, published, failed
|
||||
|
||||
published_at = DateTimeField(null=True)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation Models (`igny8_core/modules/automation/models.py`)
|
||||
|
||||
### AutomationConfig
|
||||
|
||||
```python
|
||||
class AutomationConfig(models.Model):
|
||||
site = ForeignKey(Site, related_name='automation_configs')
|
||||
|
||||
# Stage limits
|
||||
clustering_limit = IntegerField(default=10)
|
||||
ideas_limit = IntegerField(default=10)
|
||||
content_limit = IntegerField(default=5)
|
||||
image_limit = IntegerField(default=10)
|
||||
publish_limit = IntegerField(default=5)
|
||||
|
||||
# Timing
|
||||
delay_between_operations = IntegerField(default=5) # seconds
|
||||
max_runtime = IntegerField(default=3600) # 1 hour
|
||||
|
||||
# Behavior
|
||||
auto_approve = BooleanField(default=False)
|
||||
auto_publish = BooleanField(default=False)
|
||||
stop_on_error = BooleanField(default=True)
|
||||
|
||||
is_active = BooleanField(default=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### AutomationRun
|
||||
|
||||
```python
|
||||
class AutomationRun(models.Model):
|
||||
id = UUIDField(primary_key=True)
|
||||
site = ForeignKey(Site, related_name='automation_runs')
|
||||
config = ForeignKey(AutomationConfig)
|
||||
|
||||
status = CharField(max_length=50) # pending, running, paused, completed, failed, cancelled
|
||||
|
||||
# Progress tracking
|
||||
current_stage = CharField(max_length=50, blank=True)
|
||||
items_processed = IntegerField(default=0)
|
||||
items_total = IntegerField(default=0)
|
||||
|
||||
# Timing
|
||||
started_at = DateTimeField(null=True)
|
||||
completed_at = DateTimeField(null=True)
|
||||
|
||||
# Results
|
||||
error_message = TextField(blank=True)
|
||||
|
||||
started_by = ForeignKey(User)
|
||||
created_at = DateTimeField(auto_now_add=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entity Relationship Overview
|
||||
|
||||
```
|
||||
Account
|
||||
├── Users (many)
|
||||
├── Sites (many)
|
||||
│ ├── Sectors (many)
|
||||
│ ├── Keywords (many)
|
||||
│ ├── Clusters (many)
|
||||
│ ├── ContentIdeas (many)
|
||||
│ ├── Tasks (many)
|
||||
│ ├── Content (many)
|
||||
│ │ └── ContentImages (many)
|
||||
│ ├── SiteIntegrations (many)
|
||||
│ │ └── PublishingRecords (many)
|
||||
│ └── AutomationConfigs (many)
|
||||
│ └── AutomationRuns (many)
|
||||
├── Plan (one)
|
||||
├── CreditBalances (many)
|
||||
├── CreditUsage (many)
|
||||
├── ModuleEnableSettings (one)
|
||||
├── AIIntegrationSettings (many)
|
||||
└── PromptTemplates (many)
|
||||
```
|
||||
@@ -0,0 +1,679 @@
|
||||
# Payment System Documentation
|
||||
|
||||
> **Version:** 2.0.0
|
||||
> **Last Updated:** January 20, 2026
|
||||
> **Status:** Production Ready
|
||||
|
||||
> **Complete Billing Reference:** For comprehensive billing documentation including the two-pool credit system and renewal workflows, see [BILLING-PAYMENTS-COMPLETE.md](../10-MODULES/BILLING-PAYMENTS-COMPLETE.md)
|
||||
|
||||
This document provides payment gateway implementation details for IGNY8.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [System Overview](#system-overview)
|
||||
2. [Payment Entry Points](#payment-entry-points)
|
||||
3. [Backend Architecture](#backend-architecture)
|
||||
4. [Frontend Architecture](#frontend-architecture)
|
||||
5. [Payment Flows](#payment-flows)
|
||||
6. [Country-Based Payment Rules](#country-based-payment-rules)
|
||||
7. [Webhook Processing](#webhook-processing)
|
||||
8. [Models Reference](#models-reference)
|
||||
9. [Security Features](#security-features)
|
||||
|
||||
---
|
||||
|
||||
## System Overview
|
||||
|
||||
### Supported Payment Methods
|
||||
|
||||
| Method | Type | Regions | Use Cases |
|
||||
|--------|------|---------|-----------|
|
||||
| **Stripe** | Credit/Debit Card | Global | Subscriptions, Credit packages |
|
||||
| **PayPal** | PayPal account | Global (except PK) | Subscriptions, Credit packages |
|
||||
| **Bank Transfer** | Manual | Pakistan (PK) | Subscriptions, Credit packages |
|
||||
|
||||
### Payment Method Selection Logic
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ Country-Based Payment Rules │
|
||||
├────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Global Users (non-PK): │
|
||||
│ ✅ Stripe (Credit/Debit Card) │
|
||||
│ ✅ PayPal │
|
||||
│ ❌ Bank Transfer (not available) │
|
||||
│ │
|
||||
│ Pakistan Users (PK): │
|
||||
│ ✅ Stripe (Credit/Debit Card) │
|
||||
│ ❌ PayPal (not available in PK) │
|
||||
│ ✅ Bank Transfer (manual) │
|
||||
│ │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PAYMENT SYSTEM FLOW │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Signup │───────▶ │ /account/ │ │
|
||||
│ │ (no pay) │ │ plans │ │
|
||||
│ └──────────────┘ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────┴─────────────┐ │
|
||||
│ │ │ │
|
||||
│ New User? Existing User? │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ PendingPay- │ │ Plans/Billing│ │
|
||||
│ │ mentView │ │ Dashboard │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ ┌─────────┼─────────┐ ┌────────┼────────┐ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ Stripe PayPal Bank Upgrade Credits Manage │
|
||||
│ Transfer │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Payment Entry Points
|
||||
|
||||
### 1. Signup Flow
|
||||
|
||||
**File:** `frontend/src/components/auth/SignUpFormUnified.tsx`
|
||||
|
||||
**Simplified Signup (No Payment on Signup):**
|
||||
- User selects plan and provides details
|
||||
- Account created with `status='pending_payment'` for paid plans
|
||||
- User redirected to `/account/plans` to complete payment
|
||||
- No payment gateway redirect from signup page
|
||||
|
||||
```typescript
|
||||
// Signup flow creates account only, no checkout
|
||||
const handleSignup = async (data) => {
|
||||
const result = await register({
|
||||
email, password, plan_slug, billing_country
|
||||
});
|
||||
// Redirect to plans page for payment
|
||||
navigate('/account/plans');
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Plans & Billing Page
|
||||
|
||||
**File:** `frontend/src/pages/account/PlansAndBillingPage.tsx`
|
||||
|
||||
Central hub for all payment-related actions:
|
||||
|
||||
**For New Users (pending_payment):**
|
||||
- Shows `PendingPaymentView` component
|
||||
- Full-page payment interface
|
||||
- Invoice details and payment method selection
|
||||
|
||||
**For Existing Users:**
|
||||
- Current plan and subscription status
|
||||
- Credit balance and purchase
|
||||
- Invoice history and downloads
|
||||
- Subscription management
|
||||
|
||||
### 3. PendingPaymentView Component
|
||||
|
||||
**File:** `frontend/src/components/billing/PendingPaymentView.tsx`
|
||||
|
||||
Full-page payment interface for new users:
|
||||
- Displays invoice details and plan info
|
||||
- Payment method selection based on country
|
||||
- Stripe/PayPal redirect or Bank Transfer form
|
||||
- Status checking for bank transfer submissions
|
||||
|
||||
### 4. Bank Transfer Form
|
||||
|
||||
**File:** `frontend/src/components/billing/BankTransferForm.tsx`
|
||||
|
||||
Manual payment submission for Pakistan users:
|
||||
- Bank account details display
|
||||
- Transaction reference input
|
||||
- File upload for payment proof
|
||||
- Submission and status tracking
|
||||
|
||||
---
|
||||
|
||||
## Backend Architecture
|
||||
|
||||
### Service Layer
|
||||
|
||||
#### StripeService
|
||||
|
||||
**File:** `backend/igny8_core/business/billing/services/stripe_service.py`
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `create_checkout_session()` | Create subscription checkout |
|
||||
| `create_credit_checkout_session()` | Create credit package checkout |
|
||||
| `create_billing_portal_session()` | Customer billing portal |
|
||||
| `get_or_create_customer()` | Stripe customer management |
|
||||
| `construct_webhook_event()` | Verify webhook signatures |
|
||||
|
||||
#### PayPalService
|
||||
|
||||
**File:** `backend/igny8_core/business/billing/services/paypal_service.py`
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `create_order()` | Create one-time payment order |
|
||||
| `create_subscription_order()` | Create subscription order |
|
||||
| `capture_order()` | Capture approved payment |
|
||||
| `verify_webhook_signature()` | Webhook verification |
|
||||
|
||||
#### InvoiceService
|
||||
|
||||
**File:** `backend/igny8_core/business/billing/services/invoice_service.py`
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `create_subscription_invoice()` | Invoice for plan subscription |
|
||||
| `create_credit_package_invoice()` | Invoice for credit purchase |
|
||||
| `mark_paid()` | Mark invoice as paid |
|
||||
| `generate_pdf()` | Generate PDF invoice |
|
||||
|
||||
#### PaymentService
|
||||
|
||||
**File:** `backend/igny8_core/business/billing/services/payment_service.py`
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `create_stripe_payment()` | Record Stripe payment |
|
||||
| `create_paypal_payment()` | Record PayPal payment |
|
||||
| `create_manual_payment()` | Record bank transfer |
|
||||
| `approve_manual_payment()` | Admin approval |
|
||||
|
||||
### API Endpoints
|
||||
|
||||
#### Stripe Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/v1/billing/stripe/config/` | GET | Publishable key |
|
||||
| `/v1/billing/stripe/checkout/` | POST | Create checkout session |
|
||||
| `/v1/billing/stripe/credit-checkout/` | POST | Credit package checkout |
|
||||
| `/v1/billing/stripe/billing-portal/` | POST | Billing portal |
|
||||
| `/v1/billing/webhooks/stripe/` | POST | Webhook handler |
|
||||
|
||||
#### PayPal Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/v1/billing/paypal/config/` | GET | Client ID |
|
||||
| `/v1/billing/paypal/create-order/` | POST | Credit package order |
|
||||
| `/v1/billing/paypal/create-subscription-order/` | POST | Subscription order |
|
||||
| `/v1/billing/paypal/capture-order/` | POST | Capture payment |
|
||||
| `/v1/billing/webhooks/paypal/` | POST | Webhook handler |
|
||||
|
||||
#### Invoice Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/v1/billing/invoices/` | GET | List invoices |
|
||||
| `/v1/billing/invoices/{id}/` | GET | Invoice detail |
|
||||
| `/v1/billing/invoices/{id}/download_pdf/` | GET | Download PDF |
|
||||
|
||||
#### Payment Endpoints
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/v1/billing/payments/` | GET | List payments |
|
||||
| `/v1/billing/payments/manual/` | POST | Submit bank transfer |
|
||||
| `/v1/billing/admin/payments/confirm/` | POST | Admin approve/reject |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Architecture
|
||||
|
||||
### Services
|
||||
|
||||
**File:** `frontend/src/services/billing.api.ts`
|
||||
|
||||
Key functions:
|
||||
|
||||
```typescript
|
||||
// Gateway availability (country-based)
|
||||
getAvailablePaymentGateways(userCountry?: string)
|
||||
|
||||
// Subscription helpers
|
||||
subscribeToPlan(planId, gateway, options)
|
||||
purchaseCredits(packageId, gateway, options)
|
||||
|
||||
// Stripe functions
|
||||
createStripeCheckout(planId, options)
|
||||
createStripeCreditCheckout(packageId, options)
|
||||
openStripeBillingPortal(returnUrl)
|
||||
|
||||
// PayPal functions
|
||||
createPayPalSubscriptionOrder(planId, options)
|
||||
createPayPalCreditOrder(packageId, options)
|
||||
capturePayPalOrder(orderId, metadata)
|
||||
|
||||
// Manual payment
|
||||
submitManualPayment(invoiceId, data)
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
| Component | File | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `PendingPaymentView` | `/components/billing/PendingPaymentView.tsx` | New user payment |
|
||||
| `BankTransferForm` | `/components/billing/BankTransferForm.tsx` | Bank transfer submission |
|
||||
| `PendingPaymentBanner` | `/components/billing/PendingPaymentBanner.tsx` | Alert for pending payments |
|
||||
| `PaymentGatewaySelector` | `/components/billing/PaymentGatewaySelector.tsx` | Gateway selection UI |
|
||||
| `PayInvoiceModal` | `/components/billing/PayInvoiceModal.tsx` | Pay invoice modal |
|
||||
|
||||
---
|
||||
|
||||
## Payment Flows
|
||||
|
||||
### Flow 1: New User Signup with Stripe
|
||||
|
||||
```
|
||||
1. User submits signup form with plan
|
||||
2. Backend creates:
|
||||
- User account
|
||||
- Account (status='pending_payment')
|
||||
- Subscription (status='pending_payment')
|
||||
- Invoice (status='pending')
|
||||
3. User redirected to /account/plans
|
||||
4. PendingPaymentView displays
|
||||
5. User selects Stripe, clicks Pay
|
||||
6. Redirect to Stripe Checkout
|
||||
7. User completes payment
|
||||
8. Stripe webhook received:
|
||||
- Payment recorded
|
||||
- Invoice marked paid
|
||||
- Account activated
|
||||
- Credits added
|
||||
9. User redirected back to /account/plans
|
||||
10. Success message, dashboard displays
|
||||
```
|
||||
|
||||
### Flow 2: New User with PayPal (Non-PK)
|
||||
|
||||
```
|
||||
1. Same as Stripe steps 1-4
|
||||
5. User selects PayPal, clicks Pay
|
||||
6. PayPal order created
|
||||
7. Redirect to PayPal approval
|
||||
8. User approves on PayPal
|
||||
9. Redirect back with order_id
|
||||
10. Frontend calls capture-order
|
||||
11. Backend processes:
|
||||
- Payment captured
|
||||
- Payment recorded
|
||||
- Invoice marked paid
|
||||
- Account activated
|
||||
- Credits added
|
||||
12. Success displayed
|
||||
```
|
||||
|
||||
### Flow 3: Pakistan User with Bank Transfer
|
||||
|
||||
```
|
||||
1. Same as signup steps 1-4
|
||||
5. User sees Stripe + Bank Transfer options
|
||||
6. User selects Bank Transfer
|
||||
7. BankTransferForm displays:
|
||||
- Bank details (SCB Pakistan)
|
||||
- Reference input
|
||||
- Proof upload option
|
||||
8. User makes transfer, submits form
|
||||
9. Backend creates:
|
||||
- Payment (status='pending_approval')
|
||||
10. User sees "Awaiting Approval" status
|
||||
11. Admin reviews in Django Admin
|
||||
12. Admin approves:
|
||||
- Payment marked succeeded
|
||||
- Invoice marked paid
|
||||
- Account activated
|
||||
- Credits added
|
||||
13. User receives email confirmation
|
||||
```
|
||||
|
||||
### Flow 4: Existing User Buys Credits
|
||||
|
||||
```
|
||||
1. User on /account/plans clicks "Buy Credits"
|
||||
2. Credit package selection modal
|
||||
3. User selects package and gateway
|
||||
4. For Stripe/PayPal: redirect flow
|
||||
5. For Bank Transfer: form submission
|
||||
6. On success: credits added to account
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Country-Based Payment Rules
|
||||
|
||||
### Implementation in Frontend
|
||||
|
||||
```typescript
|
||||
// billing.api.ts
|
||||
export async function getAvailablePaymentGateways(userCountry?: string) {
|
||||
const [stripeAvailable, paypalAvailable] = await Promise.all([
|
||||
isStripeConfigured(),
|
||||
isPayPalConfigured(),
|
||||
]);
|
||||
|
||||
const isPakistan = userCountry?.toUpperCase() === 'PK';
|
||||
|
||||
return {
|
||||
stripe: stripeAvailable,
|
||||
// PayPal: NOT available for Pakistan
|
||||
paypal: !isPakistan && paypalAvailable,
|
||||
// Bank Transfer: ONLY for Pakistan
|
||||
manual: isPakistan,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Usage in Components
|
||||
|
||||
```typescript
|
||||
// PendingPaymentView.tsx
|
||||
const isPakistan = userCountry === 'PK';
|
||||
|
||||
// Load gateways with country filter
|
||||
const gateways = await getAvailablePaymentGateways(userCountry);
|
||||
|
||||
// Show appropriate options
|
||||
const paymentOptions = [
|
||||
{ type: 'stripe', ... }, // Always shown if configured
|
||||
isPakistan ? { type: 'manual', ... } : { type: 'paypal', ... },
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Webhook Processing
|
||||
|
||||
### Stripe Webhooks
|
||||
|
||||
**Endpoint:** `POST /v1/billing/webhooks/stripe/`
|
||||
|
||||
| Event | Handler Action |
|
||||
|-------|----------------|
|
||||
| `checkout.session.completed` | Activate subscription, add credits |
|
||||
| `invoice.paid` | Add renewal credits |
|
||||
| `invoice.payment_failed` | Send notification |
|
||||
| `customer.subscription.updated` | Sync changes |
|
||||
| `customer.subscription.deleted` | Cancel subscription |
|
||||
|
||||
**Idempotency:** Checks `WebhookEvent` model before processing:
|
||||
```python
|
||||
# Check if already processed
|
||||
if WebhookEvent.objects.filter(
|
||||
provider='stripe',
|
||||
event_id=event_id,
|
||||
status='processed'
|
||||
).exists():
|
||||
return # Already handled
|
||||
```
|
||||
|
||||
### PayPal Webhooks
|
||||
|
||||
**Endpoint:** `POST /v1/billing/webhooks/paypal/`
|
||||
|
||||
| Event | Handler Action |
|
||||
|-------|----------------|
|
||||
| `CHECKOUT.ORDER.APPROVED` | Auto-capture if configured |
|
||||
| `PAYMENT.CAPTURE.COMPLETED` | Mark succeeded, add credits |
|
||||
| `PAYMENT.CAPTURE.DENIED` | Mark failed |
|
||||
| `BILLING.SUBSCRIPTION.ACTIVATED` | Activate subscription |
|
||||
|
||||
**Signature Verification:** Enabled and enforced:
|
||||
```python
|
||||
is_valid = service.verify_webhook_signature(...)
|
||||
if not is_valid:
|
||||
return Response({'error': 'Invalid signature'}, status=400)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Models Reference
|
||||
|
||||
### Invoice Model
|
||||
|
||||
```python
|
||||
class Invoice(AccountBaseModel):
|
||||
STATUS_CHOICES = [
|
||||
('draft', 'Draft'),
|
||||
('pending', 'Pending'),
|
||||
('paid', 'Paid'),
|
||||
('void', 'Void'),
|
||||
('uncollectible', 'Uncollectible'),
|
||||
]
|
||||
|
||||
invoice_number = models.CharField(max_length=50, unique=True)
|
||||
subscription = models.ForeignKey('auth.Subscription', ...)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
|
||||
subtotal = models.DecimalField(...)
|
||||
tax = models.DecimalField(...)
|
||||
total = models.DecimalField(...)
|
||||
currency = models.CharField(max_length=3, default='USD')
|
||||
due_date = models.DateField()
|
||||
line_items = models.JSONField(default=list)
|
||||
```
|
||||
|
||||
### Payment Model
|
||||
|
||||
```python
|
||||
class Payment(AccountBaseModel):
|
||||
STATUS_CHOICES = [
|
||||
('pending', 'Pending'),
|
||||
('pending_approval', 'Pending Approval'),
|
||||
('succeeded', 'Succeeded'),
|
||||
('failed', 'Failed'),
|
||||
('refunded', 'Refunded'),
|
||||
]
|
||||
|
||||
PAYMENT_METHOD_CHOICES = [
|
||||
('stripe', 'Stripe'),
|
||||
('paypal', 'PayPal'),
|
||||
('bank_transfer', 'Bank Transfer'),
|
||||
('manual', 'Manual'),
|
||||
]
|
||||
|
||||
invoice = models.ForeignKey('Invoice', ...)
|
||||
amount = models.DecimalField(...)
|
||||
currency = models.CharField(max_length=3)
|
||||
payment_method = models.CharField(choices=PAYMENT_METHOD_CHOICES)
|
||||
status = models.CharField(choices=STATUS_CHOICES)
|
||||
stripe_payment_intent_id = models.CharField(...)
|
||||
paypal_order_id = models.CharField(...)
|
||||
manual_reference = models.CharField(..., unique=True)
|
||||
```
|
||||
|
||||
### WebhookEvent Model
|
||||
|
||||
```python
|
||||
class WebhookEvent(models.Model):
|
||||
"""Audit trail for webhook processing"""
|
||||
PROVIDER_CHOICES = [
|
||||
('stripe', 'Stripe'),
|
||||
('paypal', 'PayPal'),
|
||||
]
|
||||
|
||||
provider = models.CharField(choices=PROVIDER_CHOICES)
|
||||
event_id = models.CharField(max_length=255)
|
||||
event_type = models.CharField(max_length=100)
|
||||
payload = models.JSONField()
|
||||
status = models.CharField() # 'pending', 'processed', 'failed'
|
||||
processed_at = models.DateTimeField(null=True)
|
||||
error_message = models.TextField(blank=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Features
|
||||
|
||||
### Implemented Security Measures
|
||||
|
||||
1. **Webhook Signature Verification**
|
||||
- Stripe: `stripe.Webhook.construct_event()` with signing secret
|
||||
- PayPal: `verify_webhook_signature()` API call
|
||||
|
||||
2. **Idempotency**
|
||||
- `WebhookEvent` model tracks processed events
|
||||
- Duplicate detection before processing
|
||||
|
||||
3. **Amount Validation**
|
||||
- PayPal capture validates amount matches expected
|
||||
- Prevents manipulation attacks
|
||||
|
||||
4. **Manual Reference Uniqueness**
|
||||
- Database constraint prevents duplicate bank transfer references
|
||||
- Prevents double submission
|
||||
|
||||
5. **CSRF Protection**
|
||||
- Webhook endpoints exempt (external callers)
|
||||
- All other endpoints protected
|
||||
|
||||
6. **Authentication**
|
||||
- Payment endpoints require `IsAuthenticatedAndActive`
|
||||
- Config endpoints allow `AllowAny` (public keys only)
|
||||
|
||||
---
|
||||
|
||||
## Admin Operations
|
||||
|
||||
### Django Admin Features
|
||||
|
||||
**Location:** Django Admin > Billing
|
||||
|
||||
- **Invoices:** View, filter, download PDF
|
||||
- **Payments:** View, approve/reject manual payments
|
||||
- **Credit Transactions:** Audit trail
|
||||
- **Credit Packages:** Manage packages
|
||||
|
||||
### Manual Payment Approval
|
||||
|
||||
```
|
||||
1. Admin navigates to Payments in Django Admin
|
||||
2. Filter by status='pending_approval'
|
||||
3. Review payment details and proof
|
||||
4. Click "Approve" or "Reject" action
|
||||
5. System automatically:
|
||||
- Updates payment status
|
||||
- Marks invoice paid (if approved)
|
||||
- Activates account (if approved)
|
||||
- Adds credits (if approved)
|
||||
- Sends email notification
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# Stripe
|
||||
STRIPE_SECRET_KEY=sk_...
|
||||
STRIPE_PUBLISHABLE_KEY=pk_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
|
||||
# PayPal
|
||||
PAYPAL_CLIENT_ID=...
|
||||
PAYPAL_CLIENT_SECRET=...
|
||||
PAYPAL_WEBHOOK_ID=...
|
||||
PAYPAL_MODE=sandbox|live
|
||||
|
||||
# General
|
||||
DEFAULT_CURRENCY=USD
|
||||
```
|
||||
|
||||
### IntegrationProvider Setup
|
||||
|
||||
Payment gateways configured via `IntegrationProvider` model in Django Admin:
|
||||
|
||||
1. **Stripe Provider:**
|
||||
- Name: "Stripe"
|
||||
- Provider Type: "stripe"
|
||||
- Credentials: `{"secret_key": "...", "publishable_key": "...", "webhook_secret": "..."}`
|
||||
|
||||
2. **PayPal Provider:**
|
||||
- Name: "PayPal"
|
||||
- Provider Type: "paypal"
|
||||
- Credentials: `{"client_id": "...", "client_secret": "...", "webhook_id": "..."}`
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| "Stripe not configured" | Missing IntegrationProvider | Add Stripe provider in admin |
|
||||
| "PayPal not configured" | Missing IntegrationProvider | Add PayPal provider in admin |
|
||||
| PayPal shown for PK users | Country not passed correctly | Ensure `billing_country` saved on account |
|
||||
| Duplicate payments | Webhook retry without idempotency | Check `WebhookEvent` for duplicates |
|
||||
| PDF download fails | Missing `reportlab` | Run `pip install reportlab` |
|
||||
|
||||
### Debug Logging
|
||||
|
||||
Enable billing debug logs:
|
||||
```python
|
||||
# settings.py
|
||||
LOGGING = {
|
||||
'loggers': {
|
||||
'igny8_core.business.billing': {
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Reference
|
||||
|
||||
### Backend Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `billing/views/stripe_views.py` | Stripe API endpoints |
|
||||
| `billing/views/paypal_views.py` | PayPal API endpoints |
|
||||
| `billing/views/refund_views.py` | Refund processing |
|
||||
| `billing/services/stripe_service.py` | Stripe service layer |
|
||||
| `billing/services/paypal_service.py` | PayPal service layer |
|
||||
| `billing/services/invoice_service.py` | Invoice operations |
|
||||
| `billing/services/payment_service.py` | Payment operations |
|
||||
| `billing/services/pdf_service.py` | PDF generation |
|
||||
| `billing/services/email_service.py` | Email notifications |
|
||||
| `billing/models.py` | Billing models |
|
||||
| `billing/urls.py` | URL routing |
|
||||
|
||||
### Frontend Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `services/billing.api.ts` | API client functions |
|
||||
| `pages/account/PlansAndBillingPage.tsx` | Main billing page |
|
||||
| `components/billing/PendingPaymentView.tsx` | New user payment |
|
||||
| `components/billing/BankTransferForm.tsx` | Bank transfer form |
|
||||
| `components/billing/PayInvoiceModal.tsx` | Invoice payment modal |
|
||||
| `components/billing/PaymentGatewaySelector.tsx` | Gateway selection |
|
||||
| `components/billing/PendingPaymentBanner.tsx` | Payment alert banner |
|
||||
|
||||
---
|
||||
|
||||
*Document generated from production codebase - January 8, 2026*
|
||||
@@ -0,0 +1,198 @@
|
||||
# Global Keywords Database (SeedKeyword) - Import Guide
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Version:** 1.8.4
|
||||
|
||||
## Overview
|
||||
|
||||
The Global Keywords Database stores canonical keyword suggestions that can be imported into account-specific keywords. These are organized by Industry and Sector.
|
||||
|
||||
**Admin URL:** `https://api.igny8.com/admin/igny8_core_auth/seedkeyword/`
|
||||
|
||||
---
|
||||
|
||||
## Import Functionality
|
||||
|
||||
### CSV Format
|
||||
|
||||
The import expects a CSV file with the following columns:
|
||||
|
||||
| Column | Type | Required | Description | Example |
|
||||
|--------|------|----------|-------------|---------|
|
||||
| `keyword` | String | **Yes** | The keyword phrase | "best massage chairs" |
|
||||
| `industry` | String | **Yes** | Industry name (must exist) | "Health & Wellness" |
|
||||
| `sector` | String | **Yes** | Sector name (must exist) | "Massage Products" |
|
||||
| `volume` | Integer | No | Monthly search volume | 5400 |
|
||||
| `difficulty` | Integer | No | Keyword difficulty (0-100) | 45 |
|
||||
| `country` | String | No | Country code (US, CA, GB, etc.) | "US" |
|
||||
| `is_active` | Boolean | No | Active status | True |
|
||||
|
||||
### Sample CSV
|
||||
|
||||
```csv
|
||||
keyword,industry,sector,volume,difficulty,country,is_active
|
||||
best massage chairs,Health & Wellness,Massage Products,5400,45,US,True
|
||||
deep tissue massage chair,Health & Wellness,Massage Products,720,52,US,True
|
||||
shiatsu massage chair,Health & Wellness,Massage Products,1200,48,US,True
|
||||
```
|
||||
|
||||
**Template file available:** `/data/app/igny8/backend/seed_keywords_import_template.csv`
|
||||
|
||||
---
|
||||
|
||||
## How to Import
|
||||
|
||||
### Step 1: Prepare Your CSV File
|
||||
|
||||
1. Download the template: `seed_keywords_import_template.csv`
|
||||
2. Add your keywords (one per row)
|
||||
3. Ensure Industry and Sector names **exactly match** existing records
|
||||
4. Save as CSV (UTF-8 encoding)
|
||||
|
||||
### Step 2: Import via Django Admin
|
||||
|
||||
1. Go to: `https://api.igny8.com/admin/igny8_core_auth/seedkeyword/`
|
||||
2. Click **"Import"** button (top right)
|
||||
3. Click **"Choose File"** and select your CSV
|
||||
4. Click **"Submit"**
|
||||
5. Review the preview:
|
||||
- ✅ Green = New records to be created
|
||||
- 🔵 Blue = Existing records to be updated
|
||||
- ❌ Red = Errors (fix and re-import)
|
||||
6. If preview looks good, click **"Confirm import"**
|
||||
|
||||
### Step 3: Verify Import
|
||||
|
||||
- Check the list to see your imported keywords
|
||||
- Use filters to find specific industries/sectors
|
||||
- Edit any records if needed
|
||||
|
||||
---
|
||||
|
||||
## Data Validation
|
||||
|
||||
The import process automatically:
|
||||
|
||||
✅ **Validates volume:** Ensures it's a positive integer (defaults to 0 if invalid)
|
||||
✅ **Validates difficulty:** Clamps to 0-100 range
|
||||
✅ **Validates country:** Must be one of: US, CA, GB, AE, AU, IN, PK (defaults to US)
|
||||
✅ **Handles duplicates:** Uses `(keyword, industry, sector)` as unique key
|
||||
✅ **Skip unchanged:** If keyword already exists with same data, it's skipped
|
||||
|
||||
---
|
||||
|
||||
## Bulk Delete
|
||||
|
||||
### How to Delete Keywords
|
||||
|
||||
1. Select keywords using checkboxes (or "Select all")
|
||||
2. Choose **"Delete selected keywords"** from the action dropdown
|
||||
3. Click **"Go"**
|
||||
4. Review the confirmation page showing all related objects
|
||||
5. Click **"Yes, I'm sure"** to confirm deletion
|
||||
|
||||
**Note:** Only superusers and developers can delete seed keywords.
|
||||
|
||||
---
|
||||
|
||||
## Export Functionality
|
||||
|
||||
### Export to CSV/Excel
|
||||
|
||||
1. Go to: `https://api.igny8.com/admin/igny8_core_auth/seedkeyword/`
|
||||
2. (Optional) Use filters to narrow down results
|
||||
3. Click **"Export"** button (top right)
|
||||
4. Choose format: CSV, Excel, JSON, etc.
|
||||
5. File downloads with all selected fields
|
||||
|
||||
**Export includes:**
|
||||
- All keyword data
|
||||
- Related industry/sector names
|
||||
- SEO metrics (volume, difficulty)
|
||||
- Metadata (created date, active status)
|
||||
|
||||
---
|
||||
|
||||
## Common Issues & Solutions
|
||||
|
||||
### Issue: "Industry not found" error during import
|
||||
|
||||
**Solution:**
|
||||
- Ensure the industry name in your CSV **exactly matches** an existing Industry record
|
||||
- Check spelling, capitalization, and spacing
|
||||
- View existing industries: `/admin/igny8_core_auth/industry/`
|
||||
|
||||
### Issue: "Sector not found" error during import
|
||||
|
||||
**Solution:**
|
||||
- Ensure the sector name in your CSV **exactly matches** an existing IndustrySector record
|
||||
- The sector must belong to the specified industry
|
||||
- View existing sectors: `/admin/igny8_core_auth/industrysector/`
|
||||
|
||||
### Issue: Import shows errors for all rows
|
||||
|
||||
**Solution:**
|
||||
- Check CSV encoding (must be UTF-8)
|
||||
- Ensure column headers match exactly: `keyword,industry,sector,volume,difficulty,country,is_active`
|
||||
- Remove any extra columns or spaces in headers
|
||||
- Verify there are no special characters causing parsing issues
|
||||
|
||||
### Issue: Duplicate keyword error
|
||||
|
||||
**Solution:**
|
||||
- Keywords are unique per `(keyword, industry, sector)` combination
|
||||
- If importing a keyword that already exists, it will be updated (not duplicated)
|
||||
- Use `skip_unchanged = True` to avoid unnecessary updates
|
||||
|
||||
### Issue: Delete confirmation page has no "Delete" button
|
||||
|
||||
**Solution:** ✅ **FIXED** - Custom bulk delete action now includes proper delete button on confirmation page
|
||||
|
||||
---
|
||||
|
||||
## Permissions
|
||||
|
||||
| Action | Permission Required |
|
||||
|--------|-------------------|
|
||||
| View | Staff users |
|
||||
| Add | Superuser |
|
||||
| Edit | Superuser |
|
||||
| Delete | Superuser or Developer |
|
||||
| Import | Superuser |
|
||||
| Export | Staff users |
|
||||
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Model Location
|
||||
- **Model:** `backend/igny8_core/auth/models.py` - `SeedKeyword`
|
||||
- **Admin:** `backend/igny8_core/auth/admin.py` - `SeedKeywordAdmin`
|
||||
- **Resource:** `backend/igny8_core/auth/admin.py` - `SeedKeywordResource`
|
||||
|
||||
### Database Table
|
||||
- **Table name:** `igny8_seed_keywords`
|
||||
- **Unique constraint:** `(keyword, industry, sector)`
|
||||
- **Indexes:**
|
||||
- `keyword`
|
||||
- `industry, sector`
|
||||
- `industry, sector, is_active`
|
||||
- `country`
|
||||
|
||||
### API Access (Read-Only)
|
||||
- **Endpoint:** `/api/v1/auth/seed-keywords/`
|
||||
- **ViewSet:** `SeedKeywordViewSet` (ReadOnlyModelViewSet)
|
||||
- **Filters:** industry, sector, country, is_active
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Django Admin Guide](../../docs/90-REFERENCE/DJANGO-ADMIN-ACCESS-GUIDE.md)
|
||||
- [Models Reference](../../docs/90-REFERENCE/MODELS.md)
|
||||
- [Planner Module](../../docs/10-MODULES/PLANNER.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** January 11, 2026
|
||||
**Maintainer:** IGNY8 Team
|
||||
@@ -0,0 +1,314 @@
|
||||
# Final Model Schemas - Clean State
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the simplified, clean architecture for AI configuration and billing models.
|
||||
|
||||
**Total Models**: 5 (down from 7)
|
||||
- IntegrationProvider (API keys)
|
||||
- AIModelConfig (model definitions + pricing)
|
||||
- AISettings (system defaults + account overrides) - *renamed from GlobalIntegrationSettings*
|
||||
- AccountSettings (generic key-value store)
|
||||
- CreditCostConfig (operation-level pricing)
|
||||
|
||||
**Models Removed**:
|
||||
- IntegrationSettings (merged into AISettings/AccountSettings)
|
||||
- CreditCostConfig.tokens_per_credit (moved to AIModelConfig)
|
||||
|
||||
---
|
||||
|
||||
## 1. IntegrationProvider (API Keys Only)
|
||||
|
||||
Centralized storage for ALL external service API keys.
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `provider_id` | CharField(50) PK | Yes | openai, runware, stripe, paypal, resend |
|
||||
| `display_name` | CharField(100) | Yes | Human-readable name |
|
||||
| `provider_type` | CharField(20) | Yes | ai / payment / email / storage |
|
||||
| `api_key` | CharField(500) | No | Primary API key |
|
||||
| `api_secret` | CharField(500) | No | Secondary secret (Stripe, PayPal) |
|
||||
| `webhook_secret` | CharField(500) | No | Webhook signing secret |
|
||||
| `api_endpoint` | URLField | No | Custom endpoint (optional) |
|
||||
| `config` | JSONField | No | Provider-specific config |
|
||||
| `is_active` | BooleanField | Yes | Enable/disable provider |
|
||||
| `is_sandbox` | BooleanField | Yes | Test mode flag |
|
||||
| `updated_by` | FK(User) | No | Audit trail |
|
||||
| `created_at` | DateTime | Auto | |
|
||||
| `updated_at` | DateTime | Auto | |
|
||||
|
||||
**Seeded Providers**:
|
||||
- `openai` - AI (text + DALL-E)
|
||||
- `runware` - AI (images)
|
||||
- `anthropic` - AI (future)
|
||||
- `stripe` - Payment
|
||||
- `paypal` - Payment
|
||||
- `resend` - Email
|
||||
|
||||
---
|
||||
|
||||
## 2. AIModelConfig (Single Source of Truth for Models)
|
||||
|
||||
All AI models (text + image) with pricing and credit configuration.
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `id` | AutoField PK | Auto | |
|
||||
| `model_name` | CharField(100) | Yes | gpt-5.1, dall-e-3, runware:97@1 |
|
||||
| `model_type` | CharField(20) | Yes | text / image |
|
||||
| `provider` | CharField(50) | Yes | Links to IntegrationProvider |
|
||||
| `display_name` | CharField(200) | Yes | Human-readable |
|
||||
| `is_default` | BooleanField | Yes | One default per type |
|
||||
| `is_active` | BooleanField | Yes | Enable/disable |
|
||||
| `cost_per_1k_input` | DecimalField | No | Provider cost (USD) - text models |
|
||||
| `cost_per_1k_output` | DecimalField | No | Provider cost (USD) - text models |
|
||||
| `tokens_per_credit` | IntegerField | No | Text: tokens per 1 credit (e.g., 1000) |
|
||||
| `credits_per_image` | IntegerField | No | Image: credits per image (e.g., 1, 5, 15) |
|
||||
| `quality_tier` | CharField(20) | No | basic / quality / premium |
|
||||
| `max_tokens` | IntegerField | No | Model token limit |
|
||||
| `context_window` | IntegerField | No | Model context size |
|
||||
| `capabilities` | JSONField | No | vision, function_calling, etc. |
|
||||
| `created_at` | DateTime | Auto | |
|
||||
| `updated_at` | DateTime | Auto | |
|
||||
|
||||
**Credit Configuration Examples**:
|
||||
|
||||
| Model | Type | tokens_per_credit | credits_per_image | quality_tier |
|
||||
|-------|------|-------------------|-------------------|--------------|
|
||||
| gpt-5.1 | text | 1000 | - | - |
|
||||
| gpt-4o-mini | text | 10000 | - | - |
|
||||
| runware:97@1 | image | - | 1 | basic |
|
||||
| dall-e-3 | image | - | 5 | quality |
|
||||
| google:4@2 | image | - | 15 | premium |
|
||||
|
||||
---
|
||||
|
||||
## 3. AISettings (Renamed from GlobalIntegrationSettings)
|
||||
|
||||
System-wide AI defaults. Singleton (pk=1).
|
||||
|
||||
| Field | Type | Required | Default | Notes |
|
||||
|-------|------|----------|---------|-------|
|
||||
| `id` | AutoField PK | Auto | 1 | Singleton |
|
||||
| `temperature` | FloatField | Yes | 0.7 | AI temperature (0.0-2.0) |
|
||||
| `max_tokens` | IntegerField | Yes | 8192 | Max response tokens |
|
||||
| `image_style` | CharField(30) | Yes | photorealistic | Default image style |
|
||||
| `image_quality` | CharField(20) | Yes | standard | standard / hd |
|
||||
| `max_images_per_article` | IntegerField | Yes | 4 | Max in-article images |
|
||||
| `image_size` | CharField(20) | Yes | 1024x1024 | Default image dimensions |
|
||||
| `updated_by` | FK(User) | No | | Audit trail |
|
||||
| `updated_at` | DateTime | Auto | | |
|
||||
|
||||
**Removed Fields** (now elsewhere):
|
||||
- All `*_api_key` fields → IntegrationProvider
|
||||
- All `*_model` fields → AIModelConfig.is_default
|
||||
- `default_text_provider` → AIModelConfig.is_default where model_type='text'
|
||||
- `default_image_service` → AIModelConfig.is_default where model_type='image'
|
||||
|
||||
**Image Style Choices**:
|
||||
- `photorealistic` - Ultra realistic photography
|
||||
- `illustration` - Digital illustration
|
||||
- `3d_render` - Computer generated 3D
|
||||
- `minimal_flat` - Minimal / Flat Design
|
||||
- `artistic` - Artistic / Painterly
|
||||
- `cartoon` - Cartoon / Stylized
|
||||
|
||||
---
|
||||
|
||||
## 4. AccountSettings (Per-Account Overrides)
|
||||
|
||||
Generic key-value store for account-specific settings.
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `id` | AutoField PK | Auto | |
|
||||
| `account` | FK(Account) | Yes | |
|
||||
| `key` | CharField(100) | Yes | Setting key |
|
||||
| `value` | JSONField | Yes | Setting value |
|
||||
| `created_at` | DateTime | Auto | |
|
||||
| `updated_at` | DateTime | Auto | |
|
||||
|
||||
**Unique Constraint**: `(account, key)`
|
||||
|
||||
**AI-Related Keys** (override AISettings defaults):
|
||||
|
||||
| Key | Type | Example | Notes |
|
||||
|-----|------|---------|-------|
|
||||
| `ai.temperature` | float | 0.7 | Override system default |
|
||||
| `ai.max_tokens` | int | 8192 | Override system default |
|
||||
| `ai.image_style` | string | "photorealistic" | Override system default |
|
||||
| `ai.image_quality` | string | "hd" | Override system default |
|
||||
| `ai.max_images` | int | 6 | Override system default |
|
||||
| `ai.image_quality_tier` | string | "quality" | User's preferred tier |
|
||||
|
||||
---
|
||||
|
||||
## 5. CreditCostConfig (Operation-Level Pricing)
|
||||
|
||||
Fixed credit costs per operation type.
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `operation_type` | CharField(50) PK | Yes | Unique operation ID |
|
||||
| `display_name` | CharField(100) | Yes | Human-readable |
|
||||
| `base_credits` | IntegerField | Yes | Fixed credits per operation |
|
||||
| `is_active` | BooleanField | Yes | Enable/disable |
|
||||
| `description` | TextField | No | Admin notes |
|
||||
|
||||
**Removed**: `tokens_per_credit` (now in AIModelConfig)
|
||||
|
||||
---
|
||||
|
||||
## Frontend Settings Structure
|
||||
|
||||
### Content Generation Settings Tab
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Content Generation Settings │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ AI Parameters │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Temperature [=====○====] 0.7 │ │
|
||||
│ │ More focused ←→ More creative │ │
|
||||
│ │ │ │
|
||||
│ │ Max Tokens [8192 ▼] │ │
|
||||
│ │ Response length limit │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Image Generation │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Image Quality │ │
|
||||
│ │ ○ Basic (1 credit/image) - Fast, simple │ │
|
||||
│ │ ● Quality (5 credits/image) - Balanced │ │
|
||||
│ │ ○ Premium (15 credits/image) - Best quality │ │
|
||||
│ │ │ │
|
||||
│ │ Image Style [Photorealistic ▼] │ │
|
||||
│ │ │ │
|
||||
│ │ Images per Article [4 ▼] (max 8) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Save Settings] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Frontend API Response
|
||||
|
||||
```
|
||||
GET /api/v1/accounts/settings/ai/
|
||||
|
||||
Response:
|
||||
{
|
||||
"content_generation": {
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 8192
|
||||
},
|
||||
"image_generation": {
|
||||
"quality_tiers": [
|
||||
{"tier": "basic", "credits": 1, "label": "Basic", "description": "Fast, simple images"},
|
||||
{"tier": "quality", "credits": 5, "label": "Quality", "description": "Balanced quality"},
|
||||
{"tier": "premium", "credits": 15, "label": "Premium", "description": "Best quality"}
|
||||
],
|
||||
"selected_tier": "quality",
|
||||
"styles": [
|
||||
{"value": "photorealistic", "label": "Photorealistic"},
|
||||
{"value": "illustration", "label": "Illustration"},
|
||||
{"value": "3d_render", "label": "3D Render"},
|
||||
{"value": "minimal_flat", "label": "Minimal / Flat"},
|
||||
{"value": "artistic", "label": "Artistic"},
|
||||
{"value": "cartoon", "label": "Cartoon"}
|
||||
],
|
||||
"selected_style": "photorealistic",
|
||||
"max_images": 4,
|
||||
"max_allowed": 8
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Save Settings Request
|
||||
|
||||
```
|
||||
PUT /api/v1/accounts/settings/ai/
|
||||
|
||||
Request:
|
||||
{
|
||||
"temperature": 0.8,
|
||||
"max_tokens": 4096,
|
||||
"image_quality_tier": "premium",
|
||||
"image_style": "illustration",
|
||||
"max_images": 6
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
│ IntegrationProv. │ │ AIModelConfig │ │ AISettings │
|
||||
│ │ │ │ │ (Singleton) │
|
||||
│ - API keys │◄────│ - Model list │ │ │
|
||||
│ - Provider info │ │ - Pricing │ │ - Defaults │
|
||||
└──────────────────┘ │ - Credits config │ │ - temperature │
|
||||
│ - quality_tier │ │ - max_tokens │
|
||||
└────────┬─────────┘ │ - image_style │
|
||||
│ └────────┬─────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ AccountSettings │
|
||||
│ │
|
||||
│ Account-specific overrides: │
|
||||
│ - ai.temperature = 0.8 │
|
||||
│ - ai.image_quality_tier = "premium" │
|
||||
│ - ai.image_style = "illustration" │
|
||||
└──────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Frontend Settings UI │
|
||||
│ │
|
||||
│ GET /api/v1/accounts/settings/ai/ │
|
||||
│ - Merges AISettings + AccountSettings │
|
||||
│ - Returns effective values for account │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Summary
|
||||
|
||||
| From | To | Action |
|
||||
|------|----|--------|
|
||||
| GlobalIntegrationSettings.*_api_key | IntegrationProvider | Already done |
|
||||
| GlobalIntegrationSettings.*_model | AIModelConfig.is_default | Already done |
|
||||
| GlobalIntegrationSettings (remaining) | AISettings (rename) | Phase 3 |
|
||||
| IntegrationSettings | AccountSettings | Phase 3 - delete model |
|
||||
| CreditCostConfig.tokens_per_credit | AIModelConfig.tokens_per_credit | Already done |
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 Implementation Steps
|
||||
|
||||
1. **Rename Model**: GlobalIntegrationSettings → AISettings
|
||||
2. **Remove Fields**: All `*_api_key`, `*_model` fields from AISettings
|
||||
3. **Create API Endpoint**: `/api/v1/accounts/settings/ai/`
|
||||
4. **Update Frontend**: Load from new endpoint, use quality_tier picker
|
||||
5. **Delete Model**: IntegrationSettings (after migrating any data)
|
||||
6. **Cleanup**: Remove deprecated choices/constants
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify (Phase 3)
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `modules/system/global_settings_models.py` | Rename class, remove deprecated fields |
|
||||
| `modules/system/admin.py` | Update admin for AISettings |
|
||||
| `modules/system/views.py` | New AI settings API endpoint |
|
||||
| `modules/system/serializers.py` | AISettingsSerializer |
|
||||
| `settings.py` | Update admin sidebar |
|
||||
| `ai/ai_core.py` | Use AISettings instead of GlobalIntegrationSettings |
|
||||
| Frontend | New settings component with quality tier picker |
|
||||
277
v2/Live Docs on Server/igny8-app-docs/INDEX.md
Normal file
277
v2/Live Docs on Server/igny8-app-docs/INDEX.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# IGNY8 Technical Documentation
|
||||
|
||||
**Version:** 1.8.4
|
||||
**Last Updated:** January 20, 2026
|
||||
**Purpose:** Complete technical reference for the IGNY8 AI content platform
|
||||
|
||||
---
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
| I want to... | Go to |
|
||||
|--------------|-------|
|
||||
| Understand system architecture | [00-SYSTEM/ARCHITECTURE.md](00-SYSTEM/ARCHITECTURE.md) |
|
||||
| See repository structure | [00-SYSTEM/REPO-STRUCTURE.md](00-SYSTEM/REPO-STRUCTURE.md) |
|
||||
| Read executive summary | [00-SYSTEM/IGNY8-APP.md](00-SYSTEM/IGNY8-APP.md) |
|
||||
| Work with a specific module | [10-MODULES/](#modules) |
|
||||
| Find an API endpoint | [20-API/ENDPOINTS.md](20-API/ENDPOINTS.md) |
|
||||
| **Use UI components** | [30-FRONTEND/COMPONENT-SYSTEM.md](30-FRONTEND/COMPONENT-SYSTEM.md) |
|
||||
| **Check design tokens** | [30-FRONTEND/DESIGN-TOKENS.md](30-FRONTEND/DESIGN-TOKENS.md) |
|
||||
| **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) |
|
||||
| **Automation & Publishing Scheduling** | [40-WORKFLOWS/AUTOMATION-AND-PUBLISHING-SCHEDULING.md](40-WORKFLOWS/AUTOMATION-AND-PUBLISHING-SCHEDULING.md) |
|
||||
| **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) |
|
||||
| **Billing & Credits (Complete)** | [10-MODULES/BILLING-PAYMENTS-COMPLETE.md](10-MODULES/BILLING-PAYMENTS-COMPLETE.md) |
|
||||
| **Payment gateways (Stripe/PayPal/Bank)** | [90-REFERENCE/PAYMENT-SYSTEM.md](90-REFERENCE/PAYMENT-SYSTEM.md) |
|
||||
| See prelaunch checklist | [plans/LAUNCH-VERIFICATION-CHECKLIST.md](plans/LAUNCH-VERIFICATION-CHECKLIST.md) |
|
||||
| **Understand publishing flow** | [50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md](50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md) |
|
||||
| **AI model architecture (v1.4.0)** | [plans/4th-jan-refactor/final-model-schemas.md](plans/4th-jan-refactor/final-model-schemas.md) |
|
||||
|
||||
---
|
||||
|
||||
## 00-SYSTEM - Architecture & Core
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [ARCHITECTURE.md](00-SYSTEM/ARCHITECTURE.md) | Tech stack, deployment, system design |
|
||||
| [REPO-STRUCTURE.md](00-SYSTEM/REPO-STRUCTURE.md) | Repository layout and directory trees |
|
||||
| [AUTH-FLOWS.md](00-SYSTEM/AUTH-FLOWS.md) | Authentication, JWT, sessions, roles |
|
||||
| [TENANCY.md](00-SYSTEM/TENANCY.md) | Multi-tenant architecture, Account/Site/Sector |
|
||||
| [IGNY8-APP.md](00-SYSTEM/IGNY8-APP.md) | Executive summary (non-technical) |
|
||||
|
||||
---
|
||||
|
||||
## 10-MODULES - Feature Modules {#modules}
|
||||
|
||||
| Module | Status | Description | Document |
|
||||
|--------|--------|-------------|----------|
|
||||
| **Planner** | ✅ Active | Keywords → Clusters → Ideas | [PLANNER.md](10-MODULES/PLANNER.md) |
|
||||
| **Writer** | ✅ Active | Tasks → Content → Images | [WRITER.md](10-MODULES/WRITER.md) |
|
||||
| **Automation** | ✅ Active | 7-stage automated pipeline | [AUTOMATION.md](10-MODULES/AUTOMATION.md) |
|
||||
| **Billing** | ✅ Active | Two-pool credits, plans, payments | [BILLING-PAYMENTS-COMPLETE.md](10-MODULES/BILLING-PAYMENTS-COMPLETE.md) |
|
||||
| **Integrations** | ✅ Active | WordPress sync, webhooks | [INTEGRATIONS.md](10-MODULES/INTEGRATIONS.md) |
|
||||
| **Notifications** | ✅ Active | Real-time notifications for AI tasks | [NOTIFICATIONS.md](10-MODULES/NOTIFICATIONS.md) |
|
||||
| **System** | ✅ Active | Settings, prompts, AI config | [SYSTEM-SETTINGS.md](10-MODULES/SYSTEM-SETTINGS.md) |
|
||||
| **Publisher** | ✅ Active | Content calendar, scheduled publishing | [PUBLISHER.md](10-MODULES/PUBLISHER.md) |
|
||||
| **Linker** | ⏸️ Inactive | Internal linking (disabled by default) | [LINKER.md](10-MODULES/LINKER.md) |
|
||||
| **Optimizer** | ⏸️ Inactive | Content optimization (disabled by default) | [OPTIMIZER.md](10-MODULES/OPTIMIZER.md) |
|
||||
|
||||
---
|
||||
|
||||
## 20-API - REST API Reference
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [ENDPOINTS.md](20-API/ENDPOINTS.md) | Complete endpoint list with methods and handlers |
|
||||
| [SCHEMAS.md](20-API/SCHEMAS.md) | Request/response examples |
|
||||
|
||||
---
|
||||
|
||||
## 30-FRONTEND - React Application
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [COMPONENT-SYSTEM.md](30-FRONTEND/COMPONENT-SYSTEM.md) | **UI components reference** (Button, InputField, etc.) |
|
||||
| [DESIGN-GUIDE.md](30-FRONTEND/DESIGN-GUIDE.md) | **Design system guide** (colors, rules) |
|
||||
| [DESIGN-TOKENS.md](30-FRONTEND/DESIGN-TOKENS.md) | **Design tokens** (CSS variables, color scales) |
|
||||
| [PAGE-AUDIT.md](30-FRONTEND/PAGE-AUDIT.md) | Page-by-page function audit (in progress) |
|
||||
| [PAGES.md](30-FRONTEND/PAGES.md) | All pages and routes |
|
||||
| [PAGE-REQUIREMENTS.md](30-FRONTEND/PAGE-REQUIREMENTS.md) | Site/sector selector requirements |
|
||||
| [STORES.md](30-FRONTEND/STORES.md) | Zustand state management |
|
||||
|
||||
### Current Page Structure (v1.8.3)
|
||||
|
||||
```
|
||||
/ → Dashboard (Home.tsx)
|
||||
├── AUTH
|
||||
│ /signin → Sign In (SignIn.tsx)
|
||||
│ /signup → Sign Up (SignUp.tsx)
|
||||
│ /signup/pk → Legacy redirect to Sign Up
|
||||
│ /forgot-password → Forgot Password (ForgotPassword.tsx)
|
||||
│ /reset-password → Reset Password (ResetPassword.tsx)
|
||||
│ /verify-email → Verify Email (VerifyEmail.tsx)
|
||||
│ /unsubscribe → Unsubscribe (Unsubscribe.tsx)
|
||||
│ /payment → Payment (Payment.tsx)
|
||||
├── LEGAL
|
||||
│ /terms → Terms (Terms.tsx)
|
||||
│ /privacy → Privacy (Privacy.tsx)
|
||||
├── SETUP
|
||||
│ /setup/wizard → Onboarding Wizard (SetupWizard.tsx)
|
||||
│ /keywords-library → Keywords Library (IndustriesSectorsKeywords.tsx)
|
||||
├── SITES
|
||||
│ /sites → Sites List (List.tsx)
|
||||
│ /sites/:id → Site Dashboard (Dashboard.tsx)
|
||||
│ /sites/:id/pages → Page Manager (PageManager.tsx)
|
||||
│ /sites/:id/content → Site Content (Content.tsx)
|
||||
│ /sites/:id/content/structure → Content Structure (ContentStructure.tsx)
|
||||
│ /sites/:id/settings → Site Settings (Settings.tsx)
|
||||
│ /sites/:id/sync → Sync Dashboard (SyncDashboard.tsx)
|
||||
│ /sites/:id/deploy → Deployment Panel (DeploymentPanel.tsx)
|
||||
│ /sites/:id/posts/:postId → Post Editor (PostEditor.tsx)
|
||||
├── WORKFLOW
|
||||
│ /planner/keywords → Planner Keywords (Keywords.tsx)
|
||||
│ /planner/clusters → Planner Clusters (Clusters.tsx)
|
||||
│ /planner/clusters/:id → Cluster Detail (ClusterDetail.tsx)
|
||||
│ /planner/ideas → Planner Ideas (Ideas.tsx)
|
||||
│ /writer/tasks → Writer Tasks (Tasks.tsx)
|
||||
│ /writer/content → Writer Content (Content.tsx)
|
||||
│ /writer/content/:id → Content View (ContentView.tsx)
|
||||
│ /writer/images → Writer Images (Images.tsx)
|
||||
│ /writer/review → Review Queue (Review.tsx)
|
||||
│ /writer/approved → Approved (Approved.tsx)
|
||||
│ /automation/overview → Automation Runs (AutomationOverview.tsx)
|
||||
│ /automation/runs/:runId → Run Detail (AutomationRunDetail.tsx)
|
||||
│ /automation/run → Automation Run (AutomationPage.tsx)
|
||||
├── PUBLISHER
|
||||
│ /publisher/content-calendar → Content Calendar (ContentCalendar.tsx)
|
||||
├── OPTIONAL MODULES
|
||||
│ /linker/content → Linker Content (ContentList.tsx)
|
||||
│ /optimizer/content → Optimizer Content (ContentSelector.tsx)
|
||||
│ /optimizer/analyze/:id → Optimization Preview (AnalysisPreview.tsx)
|
||||
├── THINKER (ADMIN)
|
||||
│ /thinker/prompts → Prompts (Prompts.tsx)
|
||||
│ /thinker/author-profiles → Author Profiles (AuthorProfiles.tsx)
|
||||
│ /thinker/profile → Thinker Profile (Profile.tsx)
|
||||
│ /thinker/strategies → Strategies (Strategies.tsx)
|
||||
│ /thinker/image-testing → Image Testing (ImageTesting.tsx)
|
||||
├── ACCOUNT
|
||||
│ /account/notifications → Notifications (NotificationsPage.tsx)
|
||||
│ /account/settings → Account Settings (AccountSettingsPage.tsx)
|
||||
│ /account/settings/profile → Profile Tab
|
||||
│ /account/settings/team → Team Tab
|
||||
│ /account/plans → Plans & Billing (PlansAndBillingPage.tsx)
|
||||
│ /account/plans/upgrade → Upgrade Tab
|
||||
│ /account/plans/history → History Tab
|
||||
│ /account/usage → Usage Dashboard (UsageDashboardPage.tsx)
|
||||
│ /account/content-settings → Content Settings (ContentSettingsPage.tsx)
|
||||
├── SETTINGS
|
||||
│ /settings → General Settings (General.tsx)
|
||||
│ /settings/users → Users (Users.tsx)
|
||||
│ /settings/subscriptions → Subscriptions (Subscriptions.tsx)
|
||||
│ /settings/system → System Settings (System.tsx)
|
||||
│ /settings/account → Account Settings (Account.tsx)
|
||||
│ /settings/plans → Plans (Plans.tsx)
|
||||
│ /settings/industries → Industries (Industries.tsx)
|
||||
│ /settings/integration → Integration (Integration.tsx) [Admin]
|
||||
│ /settings/publishing → Publishing (Publishing.tsx)
|
||||
│ /settings/sites → Sites Settings (Sites.tsx)
|
||||
├── REFERENCE
|
||||
│ /reference/seed-keywords → Seed Keywords (SeedKeywords.tsx)
|
||||
│ /reference/industries → Industries (Industries.tsx)
|
||||
├── HELP
|
||||
│ /help → Help Center (Help.tsx)
|
||||
└── INTERNAL
|
||||
/ui-elements → UI Elements (UIElements.tsx)
|
||||
/components → Components (Components.tsx)
|
||||
```
|
||||
|
||||
**Removed in v1.8.0:**
|
||||
- `/automation/settings` - Merged into Site Settings → Automation tab
|
||||
- `/publisher/settings` - Merged into Site Settings → Automation tab
|
||||
- `/sites/:id/settings` AI Settings tab - Merged into Automation tab
|
||||
|
||||
**Removed in v1.4.0:**
|
||||
- `/settings/ai` - AI Settings page merged into Site Settings AI tab
|
||||
|
||||
---
|
||||
|
||||
## 40-WORKFLOWS - Cross-Module Flows {#workflows}
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [CONTENT-PIPELINE.md](40-WORKFLOWS/CONTENT-PIPELINE.md) | Keyword → Published article flow |
|
||||
| [CREDIT-SYSTEM.md](40-WORKFLOWS/CREDIT-SYSTEM.md) | When and how credits are used |
|
||||
|
||||
---
|
||||
|
||||
## 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 |
|
||||
|----------|---------|
|
||||
| [MODELS.md](90-REFERENCE/MODELS.md) | All database models and fields |
|
||||
| [AI-FUNCTIONS.md](90-REFERENCE/AI-FUNCTIONS.md) | AI engine capabilities and costs |
|
||||
| [PAYMENT-SYSTEM.md](90-REFERENCE/PAYMENT-SYSTEM.md) | Payment gateways (Stripe, PayPal, Bank Transfer) |
|
||||
| [TROUBLESHOOTING.md](90-REFERENCE/TROUBLESHOOTING.md) | Common issues and fixes |
|
||||
|
||||
---
|
||||
|
||||
## Root Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| [/CHANGELOG.md](/CHANGELOG.md) | Version history and changes |
|
||||
| [/IGNY8-APP.md](/IGNY8-APP.md) | Platform overview (non-technical) |
|
||||
| [/PRE-LAUNCH-AUDIT.md](/PRE-LAUNCH-AUDIT.md) | Known issues and improvement roadmap |
|
||||
|
||||
---
|
||||
|
||||
## Module → File Quick Reference
|
||||
|
||||
| Module | Backend Path | Frontend Path |
|
||||
|--------|--------------|---------------|
|
||||
| Dashboard | N/A | `frontend/src/pages/Dashboard/` |
|
||||
| Setup | N/A | `frontend/src/pages/Setup/` |
|
||||
| Planner | `backend/igny8_core/modules/planner/` | `frontend/src/pages/Planner/` |
|
||||
| Writer | `backend/igny8_core/modules/writer/` | `frontend/src/pages/Writer/` |
|
||||
| Billing | `backend/igny8_core/modules/billing/` + `business/billing/` | `frontend/src/pages/account/` |
|
||||
| Automation | `backend/igny8_core/business/automation/` | `frontend/src/pages/Automation/` |
|
||||
| Integrations | `backend/igny8_core/modules/integration/` + `business/integration/` | `frontend/src/pages/Settings/` |
|
||||
| System | `backend/igny8_core/modules/system/` | `frontend/src/pages/Settings/` |
|
||||
| Linker | `backend/igny8_core/modules/linker/` | `frontend/src/pages/Linker/` |
|
||||
| Optimizer | `backend/igny8_core/modules/optimizer/` + `business/optimization/` | `frontend/src/pages/Optimizer/` |
|
||||
| Publisher | `backend/igny8_core/modules/publisher/` + `business/publishing/` | N/A (backend only) |
|
||||
| Auth | `backend/igny8_core/auth/` | `frontend/src/pages/AuthPages/` |
|
||||
| AI Engine | `backend/igny8_core/ai/` | N/A |
|
||||
| Thinker | `backend/igny8_core/modules/thinker/` | `frontend/src/pages/Thinker/` |
|
||||
| Sites | `backend/igny8_core/modules/sites/` | `frontend/src/pages/Sites/` |
|
||||
| Help | N/A | `frontend/src/pages/Help/` |
|
||||
| Account | `backend/igny8_core/modules/accounts/` | `frontend/src/pages/account/` |
|
||||
|
||||
---
|
||||
|
||||
## API Base Paths
|
||||
|
||||
| Module | Base URL |
|
||||
|--------|----------|
|
||||
| Auth | `/api/v1/auth/` |
|
||||
| Planner | `/api/v1/planner/` |
|
||||
| Writer | `/api/v1/writer/` |
|
||||
| Billing | `/api/v1/billing/` |
|
||||
| Integration | `/api/v1/integration/` |
|
||||
| System | `/api/v1/system/` |
|
||||
| Linker | `/api/v1/linker/` |
|
||||
| Optimizer | `/api/v1/optimizer/` |
|
||||
| Publisher | `/api/v1/publisher/` |
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack Summary
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|------------|
|
||||
| Backend | Django 5.x, Django REST Framework, PostgreSQL 15+ |
|
||||
| Frontend | React 19, TypeScript, Vite, TailwindCSS |
|
||||
| State | Zustand |
|
||||
| Async | Celery + Redis |
|
||||
| AI | OpenAI (GPT-4, GPT-4 Turbo), DALL-E 3, Runware |
|
||||
| Auth | JWT + Redis Sessions |
|
||||
| Deployment | Docker, Docker Compose, Caddy |
|
||||
@@ -0,0 +1,484 @@
|
||||
# IGNY8 Billing System Comprehensive Audit
|
||||
|
||||
**Date:** January 20, 2026
|
||||
**Status:** CRITICAL - Multiple issues identified
|
||||
**Reviewer:** System Audit
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The billing system has **fundamental architectural flaws** that cause incorrect behavior across multiple payment methods, credit management, and account activation flows. This audit identifies all issues and provides a detailed analysis of the current implementation.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Critical Issues](#1-critical-issues)
|
||||
2. [Account Status & Activation Flow](#2-account-status--activation-flow)
|
||||
3. [Credit System Analysis](#3-credit-system-analysis)
|
||||
4. [Invoice Management](#4-invoice-management)
|
||||
5. [Payment Method Flows](#5-payment-method-flows)
|
||||
6. [Subscription Renewal Flow](#6-subscription-renewal-flow)
|
||||
7. [Credit Package Purchases](#7-credit-package-purchases)
|
||||
8. [Data Model Issues](#8-data-model-issues)
|
||||
9. [Missing Features](#9-missing-features)
|
||||
10. [Recommended Fixes](#10-recommended-fixes)
|
||||
|
||||
---
|
||||
|
||||
## 1. Critical Issues
|
||||
|
||||
### Issue #1: Credit Package Approval Adds WRONG Credits (CRITICAL)
|
||||
**Location:** `AdminBillingViewSet.approve_payment()` and `BillingViewSet.approve_payment()`
|
||||
|
||||
**Current Behavior:**
|
||||
- When admin approves a credit package payment, the code adds `subscription.plan.included_credits` (e.g., 200 credits from the plan)
|
||||
- It should add the `credit_package.credits` from the purchased package (e.g., 500 credits)
|
||||
|
||||
**Evidence:**
|
||||
```
|
||||
User bought 500-credit package → Admin approved → System added 200 credits (plan credits)
|
||||
```
|
||||
|
||||
**Root Cause:** Both approval methods look for subscription/plan credits instead of checking if the invoice is for a credit package purchase via `invoice.metadata.credit_package_id`.
|
||||
|
||||
---
|
||||
|
||||
### Issue #2: Invoice Types Not Differentiated (CRITICAL)
|
||||
**Problem:** No clear distinction between:
|
||||
- Subscription invoices (for plan activation/renewal)
|
||||
- Credit package invoices (for additional credit purchases)
|
||||
|
||||
**Impact:**
|
||||
- Frontend checks `hasPendingInvoice` which includes BOTH types
|
||||
- Creating a credit package invoice should NOT affect account status
|
||||
- Creating a credit package invoice should NOT block other purchases
|
||||
|
||||
---
|
||||
|
||||
### Issue #3: Credits Have No Validity/Expiration (CRITICAL)
|
||||
**Current State:** Credits are a simple integer counter on Account model:
|
||||
```python
|
||||
credits = models.IntegerField(default=0)
|
||||
```
|
||||
|
||||
**Missing:**
|
||||
- No expiration date for credits
|
||||
- No validity period tracking (1 month as business rule)
|
||||
- No automatic reset mechanism
|
||||
- Purchased credits vs subscription credits not tracked separately
|
||||
|
||||
**Impact:** Users can accumulate credits indefinitely, no monthly reset logic.
|
||||
|
||||
---
|
||||
|
||||
### Issue #4: Subscription Renewal Does NOT Reset Credits Correctly (CRITICAL)
|
||||
**Current Behavior:**
|
||||
- Stripe webhook `_handle_invoice_paid()` calls `CreditService.reset_credits_for_renewal()` ✓
|
||||
- Manual payment approval uses `CreditService.add_credits()` instead of reset ✗
|
||||
|
||||
**Problem with add_credits:**
|
||||
- If user had 50 credits remaining
|
||||
- Plan gives 200 credits monthly
|
||||
- `add_credits(200)` → User now has 250 credits
|
||||
- Should be: Reset to 200 credits
|
||||
|
||||
---
|
||||
|
||||
### Issue #5: No Invoice Expiration/Cancellation Mechanism
|
||||
**Current State:**
|
||||
- Pending invoices remain pending indefinitely
|
||||
- No automatic void/cancellation after timeout (e.g., 24-48 hours)
|
||||
- User cannot cancel/void their own pending credit package invoices
|
||||
- No cleanup task for stale invoices
|
||||
|
||||
---
|
||||
|
||||
## 2. Account Status & Activation Flow
|
||||
|
||||
### Status Values
|
||||
|
||||
| Status | Description | When Set |
|
||||
|--------|-------------|----------|
|
||||
| `active` | Account fully operational | After successful payment or trial activation |
|
||||
| `trial` | Free trial period | On signup with free plan |
|
||||
| `pending_payment` | Awaiting first payment | After signup with paid plan, before payment |
|
||||
| `suspended` | Manually suspended | Admin action |
|
||||
| `cancelled` | Subscription cancelled | After cancellation |
|
||||
|
||||
### Current Flow Problems
|
||||
|
||||
#### New User with Paid Plan (Bank Transfer):
|
||||
1. User signs up selecting paid plan with bank transfer → `status = pending_payment` ✓
|
||||
2. Invoice created → Invoice `status = pending` ✓
|
||||
3. User submits bank transfer confirmation → Payment `status = pending_approval` ✓
|
||||
4. Admin approves payment → Account `status = active` ✓
|
||||
5. **BUG:** Credits added = plan credits (not credit package credits if that's what was purchased)
|
||||
|
||||
#### Existing Active User Buying Credits (Bank Transfer):
|
||||
1. User has `status = active`
|
||||
2. User creates credit package invoice → Invoice created with `status = pending`
|
||||
3. **BUG:** Frontend incorrectly interprets this as "account has pending invoice"
|
||||
4. **BUG:** Some UI elements get disabled unnecessarily
|
||||
5. Admin approves → **BUG:** Wrong credits added (plan credits instead of package credits)
|
||||
|
||||
---
|
||||
|
||||
## 3. Credit System Analysis
|
||||
|
||||
### Current Data Model
|
||||
|
||||
**Account.credits** (integer)
|
||||
- Single counter for all credits
|
||||
- No distinction between:
|
||||
- Plan/subscription credits
|
||||
- Purchased credit packages
|
||||
- Bonus/promotional credits
|
||||
- No expiration tracking
|
||||
|
||||
**CreditTransaction** (history log)
|
||||
- Records additions and deductions
|
||||
- Transaction types: `purchase`, `subscription`, `refund`, `deduction`, `adjustment`
|
||||
- Has `balance_after` field for audit trail
|
||||
|
||||
**CreditUsageLog** (detailed usage)
|
||||
- Per-operation usage tracking
|
||||
- Links to site, operation type, model used
|
||||
- Used for analytics and billing reports
|
||||
|
||||
### Credit Flow Issues
|
||||
|
||||
| Scenario | Expected Behavior | Current Behavior | Status |
|
||||
|----------|-------------------|------------------|--------|
|
||||
| New subscription | Reset credits to plan amount | Add credits to existing | ❌ BUG |
|
||||
| Subscription renewal (Stripe) | Reset credits to plan amount | Reset credits | ✓ OK |
|
||||
| Subscription renewal (Manual) | Reset credits to plan amount | Add credits | ❌ BUG |
|
||||
| Credit package purchase | Add package credits | Add plan credits | ❌ BUG |
|
||||
| Monthly reset | Reset subscription credits, keep purchased | No reset logic | ❌ MISSING |
|
||||
|
||||
### Credit Validity Rules (Business Requirement - NOT IMPLEMENTED)
|
||||
|
||||
Per business rules, credits should have a **1-month validity period**:
|
||||
- Subscription credits reset monthly on billing cycle
|
||||
- Purchased credits have 1-month validity from purchase date
|
||||
- Expired credits should be voided
|
||||
|
||||
**Current Implementation:** None - credits never expire or reset.
|
||||
|
||||
---
|
||||
|
||||
## 4. Invoice Management
|
||||
|
||||
### Invoice Types (Implicit, Not Enforced)
|
||||
|
||||
| Type | Identifier | Purpose |
|
||||
|------|-----------|---------|
|
||||
| Subscription | `invoice.subscription IS NOT NULL` | Plan activation/renewal |
|
||||
| Credit Package | `invoice.metadata.credit_package_id EXISTS` | Additional credit purchase |
|
||||
| Custom | Neither | Admin-created invoices |
|
||||
|
||||
### Invoice Status Flow
|
||||
|
||||
```
|
||||
draft → pending → paid/void/uncollectible
|
||||
```
|
||||
|
||||
### Issues
|
||||
|
||||
1. **No Expiration:** Invoices stay pending forever
|
||||
2. **No User Cancellation:** Users cannot cancel their own credit package invoices
|
||||
3. **No Automatic Cleanup:** No task to void stale invoices
|
||||
4. **No Type Enforcement:** Invoice type determined by checking multiple fields
|
||||
|
||||
### Recommended Invoice Statuses
|
||||
|
||||
| Status | Description | Auto-Transition |
|
||||
|--------|-------------|-----------------|
|
||||
| `draft` | Created, not sent | → pending when finalized |
|
||||
| `pending` | Awaiting payment | → void after 48h (credit packages) |
|
||||
| `paid` | Payment received | Terminal |
|
||||
| `void` | Cancelled/expired | Terminal |
|
||||
| `uncollectible` | Cannot be collected | Terminal |
|
||||
|
||||
---
|
||||
|
||||
## 5. Payment Method Flows
|
||||
|
||||
### Supported Payment Methods
|
||||
|
||||
| Method | Countries | Auto-Approval | Account Activation |
|
||||
|--------|-----------|---------------|-------------------|
|
||||
| Stripe (Card) | All except PK | Yes | Immediate |
|
||||
| PayPal | All except PK | Yes | Immediate |
|
||||
| Bank Transfer | PK only | No (Manual) | After admin approval |
|
||||
|
||||
### Stripe Flow (Working ✓)
|
||||
1. User initiates checkout → Stripe session created
|
||||
2. User completes payment on Stripe
|
||||
3. Webhook `checkout.session.completed` → Account activated, credits added
|
||||
4. Webhook `invoice.paid` (renewal) → Credits reset
|
||||
|
||||
### PayPal Flow (Working ✓)
|
||||
1. User initiates checkout → PayPal order created
|
||||
2. User approves on PayPal
|
||||
3. Payment captured → Account activated, credits added
|
||||
|
||||
### Bank Transfer Flow (BUGGY ❌)
|
||||
1. User initiates → Invoice created, account status may change incorrectly
|
||||
2. User submits confirmation → Payment created with `pending_approval`
|
||||
3. Admin approves → **WRONG CREDITS ADDED**
|
||||
4. User rejection flow exists but no automatic expiration
|
||||
|
||||
---
|
||||
|
||||
## 6. Subscription Renewal Flow
|
||||
|
||||
### Automatic (Stripe/PayPal)
|
||||
- Stripe handles automatic billing
|
||||
- Webhook `invoice.paid` triggers renewal
|
||||
- `reset_credits_for_renewal()` correctly resets credits ✓
|
||||
|
||||
### Manual (Bank Transfer)
|
||||
1. Task `process_subscription_renewals` creates renewal invoice
|
||||
2. Subscription status → `pending_renewal`
|
||||
3. Grace period (7 days) starts
|
||||
4. User pays manually
|
||||
5. **BUG:** Admin approval uses `add_credits()` instead of `reset_credits_for_renewal()`
|
||||
6. Task `check_expired_renewals` expires after grace period ✓
|
||||
|
||||
---
|
||||
|
||||
## 7. Credit Package Purchases
|
||||
|
||||
### Current Flow
|
||||
|
||||
1. User selects package
|
||||
2. For Stripe/PayPal: Redirect to checkout
|
||||
3. For Bank Transfer: Create invoice → Show payment form
|
||||
|
||||
### Issues
|
||||
|
||||
| Step | Issue | Severity |
|
||||
|------|-------|----------|
|
||||
| Invoice Creation | Uses `InvoiceService.create_credit_package_invoice()` | OK |
|
||||
| Invoice stores `credit_package_id` in metadata | OK | OK |
|
||||
| Approval reads `subscription.plan.included_credits` | **WRONG** | CRITICAL |
|
||||
| Should read `invoice.metadata.credit_package_id` → get package → add `package.credits` | Missing | CRITICAL |
|
||||
|
||||
### Stripe Credit Purchase (Partially Working)
|
||||
- Webhook handler `_handle_credit_purchase()` correctly reads metadata
|
||||
- Uses `CreditService.add_credits()` with correct amount ✓
|
||||
|
||||
### Manual Credit Purchase (BROKEN)
|
||||
- `approve_payment()` does NOT check if invoice is credit package
|
||||
- Adds plan credits instead of package credits
|
||||
- Completely wrong behavior
|
||||
|
||||
---
|
||||
|
||||
## 8. Data Model Issues
|
||||
|
||||
### Missing Fields
|
||||
|
||||
**Invoice Model:**
|
||||
- `invoice_type` enum: `subscription`, `credit_package`, `custom`
|
||||
- `expires_at` datetime for auto-void
|
||||
- `credit_package_id` FK (instead of in metadata)
|
||||
|
||||
**Account Model:**
|
||||
- `subscription_credits` (monthly reset)
|
||||
- `purchased_credits` (expire after 1 month)
|
||||
- `credits_valid_until` datetime
|
||||
|
||||
**CreditTransaction:**
|
||||
- `expires_at` datetime
|
||||
- `source_type`: `subscription`, `purchase`, `bonus`
|
||||
|
||||
### Proposed Schema Changes
|
||||
|
||||
```
|
||||
Account:
|
||||
+ subscription_credits: int (resets monthly)
|
||||
+ purchased_credits: int (separate pool)
|
||||
+ credits_last_reset: datetime
|
||||
|
||||
Invoice:
|
||||
+ invoice_type: enum('subscription', 'credit_package', 'custom')
|
||||
+ expires_at: datetime (nullable)
|
||||
|
||||
CreditPurchase: (NEW MODEL)
|
||||
- account: FK
|
||||
- credit_package: FK
|
||||
- credits: int
|
||||
- purchase_date: datetime
|
||||
- expires_at: datetime
|
||||
- credits_remaining: int
|
||||
- status: enum('active', 'expired', 'used')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Missing Features
|
||||
|
||||
### Invoice Management
|
||||
- [ ] User can cancel own pending credit package invoices
|
||||
- [ ] Auto-void invoices after 24-48 hours
|
||||
- [ ] Invoice expiration emails
|
||||
- [ ] Invoice reminder emails (before expiration)
|
||||
|
||||
### Credit Management
|
||||
- [ ] Credit expiration tracking
|
||||
- [ ] Monthly credit reset task
|
||||
- [ ] Separate subscription vs purchased credits
|
||||
- [ ] Credit validity period configuration
|
||||
- [ ] Low credit warnings before operations
|
||||
|
||||
### Payment Management
|
||||
- [ ] Payment retry for failed payments
|
||||
- [ ] Automatic payment reminder emails
|
||||
- [ ] Payment receipt PDFs
|
||||
- [ ] Refund processing
|
||||
|
||||
### Admin Features
|
||||
- [ ] Bulk invoice void
|
||||
- [ ] Credit adjustment with reason
|
||||
- [ ] Payment history export
|
||||
- [ ] Revenue reports
|
||||
|
||||
---
|
||||
|
||||
## 10. Recommended Fixes
|
||||
|
||||
### Priority 1: Critical (Immediate Fix Required)
|
||||
|
||||
#### Fix 1.1: Credit Package Approval Logic
|
||||
**File:** `billing_views.py` - `approve_payment()` method
|
||||
|
||||
**Change:** Before adding credits, check if invoice has `credit_package_id` in metadata:
|
||||
```
|
||||
IF invoice.metadata.credit_package_id EXISTS:
|
||||
package = CreditPackage.get(invoice.metadata.credit_package_id)
|
||||
credits_to_add = package.credits
|
||||
ELSE:
|
||||
credits_to_add = subscription.plan.included_credits
|
||||
```
|
||||
|
||||
#### Fix 1.2: Manual Renewal Should Reset Credits
|
||||
**File:** `billing_views.py` - `approve_payment()` method
|
||||
|
||||
**Change:** For subscription renewals, use `reset_credits_for_renewal()` instead of `add_credits()`.
|
||||
|
||||
#### Fix 1.3: Separate Invoice Type Checking in Frontend
|
||||
**File:** `PlansAndBillingPage.tsx`
|
||||
|
||||
**Already Fixed:** `hasPendingSubscriptionInvoice` and `hasPendingCreditInvoice` now separate.
|
||||
|
||||
### Priority 2: High (Fix Within 1 Week)
|
||||
|
||||
#### Fix 2.1: Add Invoice Expiration
|
||||
- Add `expires_at` field to Invoice model
|
||||
- Set expiration on creation (48 hours for credit packages)
|
||||
- Create Celery task to void expired invoices
|
||||
- Send reminder email 24 hours before expiration
|
||||
|
||||
#### Fix 2.2: User Invoice Cancellation
|
||||
- Add API endpoint for user to cancel own pending credit package invoices
|
||||
- Add "Cancel" button in frontend invoice list
|
||||
- Set invoice status to `void` on cancellation
|
||||
|
||||
### Priority 3: Medium (Fix Within 1 Month)
|
||||
|
||||
#### Fix 3.1: Credit Expiration System
|
||||
- Add credit validity tracking
|
||||
- Create monthly reset task for subscription credits
|
||||
- Track purchased credits separately with expiration
|
||||
|
||||
#### Fix 3.2: Invoice Type Field
|
||||
- Add `invoice_type` enum field
|
||||
- Migrate existing invoices based on metadata
|
||||
- Update all queries to use type field
|
||||
|
||||
### Priority 4: Low (Future Enhancement)
|
||||
|
||||
- Payment retry automation
|
||||
- Revenue reporting
|
||||
- Credit pool separation
|
||||
- Advanced analytics
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Current Code Locations
|
||||
|
||||
| Component | File Path |
|
||||
|-----------|-----------|
|
||||
| Account Model | `auth/models.py` |
|
||||
| Plan Model | `auth/models.py` |
|
||||
| Subscription Model | `auth/models.py` |
|
||||
| Invoice Model | `business/billing/models.py` |
|
||||
| Payment Model | `business/billing/models.py` |
|
||||
| CreditPackage Model | `business/billing/models.py` |
|
||||
| CreditTransaction Model | `business/billing/models.py` |
|
||||
| CreditService | `business/billing/services/credit_service.py` |
|
||||
| InvoiceService | `business/billing/services/invoice_service.py` |
|
||||
| PaymentService | `business/billing/services/payment_service.py` |
|
||||
| Admin Approval (v1) | `business/billing/billing_views.py` |
|
||||
| Admin Approval (v2) | `modules/billing/views.py` |
|
||||
| Stripe Webhooks | `business/billing/views/stripe_views.py` |
|
||||
| PayPal Webhooks | `business/billing/views/paypal_views.py` |
|
||||
| Renewal Tasks | `business/billing/tasks/subscription_renewal.py` |
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: Test Scenarios Required
|
||||
|
||||
| Scenario | Current Result | Expected Result |
|
||||
|----------|---------------|-----------------|
|
||||
| PK user buys 500 credit package via bank transfer | Gets 200 credits (plan) | Gets 500 credits |
|
||||
| Active user creates credit invoice | Account blocked | Account stays active |
|
||||
| Subscription renewal (manual) | Credits added | Credits reset |
|
||||
| Invoice unpaid after 48 hours | Stays pending | Auto-void |
|
||||
| User wants to cancel own invoice | No option | Can cancel |
|
||||
| Credits unused for 1 month | Stay forever | Should expire |
|
||||
|
||||
---
|
||||
|
||||
## Appendix C: Affected User Flows
|
||||
|
||||
### Flow 1: New PK User Subscription
|
||||
```
|
||||
Signup → Select Plan → Bank Transfer → Submit Confirmation → Wait for Approval → Account Active
|
||||
```
|
||||
**Issues:** None if subscription only
|
||||
|
||||
### Flow 2: Active PK User Buys Credits
|
||||
```
|
||||
Go to Credits → Select Package → Bank Transfer → Invoice Created → Submit Confirmation → Wait → WRONG CREDITS ADDED
|
||||
```
|
||||
**Issues:**
|
||||
- Invoice creation may show incorrect UI states
|
||||
- Wrong credits added on approval
|
||||
|
||||
### Flow 3: Active Non-PK User Buys Credits
|
||||
```
|
||||
Go to Credits → Select Package → Stripe Checkout → Payment → Credits Added
|
||||
```
|
||||
**Issues:** None - Stripe handler works correctly
|
||||
|
||||
### Flow 4: Subscription Renewal (Manual)
|
||||
```
|
||||
Renewal Date → Invoice Created → User Pays → Admin Approves → CREDITS ADDED (not reset)
|
||||
```
|
||||
**Issues:** Should reset credits, not add
|
||||
|
||||
---
|
||||
|
||||
**End of Audit Report**
|
||||
|
||||
**Next Steps:**
|
||||
1. Review this audit with team
|
||||
2. Prioritize fixes based on business impact
|
||||
3. Implement Priority 1 fixes immediately
|
||||
4. Create automated tests for all scenarios
|
||||
5. Schedule remaining fixes
|
||||
|
||||
@@ -0,0 +1,907 @@
|
||||
# IGNY8 Frontend Component Audit & TailAdmin Migration Plan
|
||||
|
||||
> **Generated:** 2026-03-15
|
||||
> **Scope:** `frontend/src/` — 483 TypeScript/TSX files
|
||||
> **Base Template:** TailAdmin Free React v1.8.4 (Tailwind CSS 4, React 19)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
### Total Component Count
|
||||
|
||||
| Category | Count |
|
||||
|----------|-------|
|
||||
| **Total .ts/.tsx files** | 483 |
|
||||
| **UI primitive components** (`components/ui/`) | 52 files → ~40 unique components |
|
||||
| **Common/shared components** (`components/common/`) | 51 files |
|
||||
| **Page files** (`pages/`) | 68 files |
|
||||
| **Feature components** (dashboard, billing, automation, etc.) | 80 files |
|
||||
| **Form components** (`components/form/`) | 25 files |
|
||||
| **Header components** | 6 files |
|
||||
| **Stores** (Zustand) | 11 files |
|
||||
| **Services/API** | 8 files (5,626 LOC) |
|
||||
| **Templates** | 5 files |
|
||||
| **Config files** | 24 files |
|
||||
| **Hooks** | 9 files |
|
||||
| **Utils** | 14 files |
|
||||
| **Context providers** | 5 files |
|
||||
| **Layout/shell** | 5 files |
|
||||
| **Marketing site** | 22 files |
|
||||
|
||||
### Origin Breakdown
|
||||
|
||||
| Origin | Count | Percentage |
|
||||
|--------|-------|------------|
|
||||
| **Custom** (IGNY8-specific) | ~340 | ~70% |
|
||||
| **TailAdmin-original** (unchanged) | ~25 | ~5% |
|
||||
| **TailAdmin-modified** (customized) | ~20 | ~4% |
|
||||
| **TailAdmin-derived** (structural inheritance) | ~15 | ~3% |
|
||||
| **TailAdmin demo** (static examples) | ~10 | ~2% |
|
||||
| **Third-party** | ~5 | ~1% |
|
||||
| **Infrastructure** (config, types, barrel exports) | ~68 | ~14% |
|
||||
|
||||
### Key Findings
|
||||
|
||||
1. **Package name confirms TailAdmin base**: `package.json` → `"name": "tailadmin-react"`, version `1.8.4`
|
||||
2. **Core shell is TailAdmin**: Layout, sidebar, header, dark mode, backdrop all follow TailAdmin Free patterns
|
||||
3. **Design system is heavily customized**: Tailwind v4 CSS-first config with `color-mix()` computed palettes, 6 base hex tokens only
|
||||
4. **Config-driven table system**: 10 pages use `TablePageTemplate` with declarative column/filter/action configs — the most mature pattern
|
||||
5. **Massive API monolith**: `api.ts` is 3,056 lines containing 90+ functions
|
||||
6. **Three-way route divergence**: Routes defined independently in `App.tsx`, `AppSidebar.tsx`, and `routes.config.ts`
|
||||
7. **Significant duplication**: Settings forms (6 pages), billing pages (3 directories), filter boilerplate (8 pages), manual tables (4+ pages)
|
||||
8. **MUI contamination**: One component (`BulkWordPressPublish` in `BulkWordPressPublish/`) uses `@mui/material` — inconsistent with rest of codebase
|
||||
9. **No `cn()` utility**: TailAdmin's `cn()` pattern not used; `clsx` + `twMerge` called separately
|
||||
10. **Style-locked components**: 7 files have `🔒 STYLE LOCKED` headers (Button, IconButton, Badge, Table, WordPressPublish components)
|
||||
|
||||
---
|
||||
|
||||
## 2. App Shell & Layout
|
||||
|
||||
### Layout Structure
|
||||
|
||||
```
|
||||
<ErrorBoundary>
|
||||
<ThemeProvider> ← dark mode (class strategy)
|
||||
<HeaderMetricsProvider> ← credit/page metric badges
|
||||
<ToastProvider> ← toast notifications
|
||||
<BrowserRouter>
|
||||
<PageProvider> ← page title/badge context
|
||||
<HelmetProvider>
|
||||
<Routes>
|
||||
<ProtectedRoute> ← JWT auth guard
|
||||
<SidebarProvider> ← sidebar expand/collapse state
|
||||
<PageLoadingProvider>
|
||||
<AppLayout> ← flex shell (sidebar + main)
|
||||
<AppSidebar> ← left nav (290px / 90px)
|
||||
<Backdrop> ← mobile overlay
|
||||
<AppHeader> ← sticky top bar
|
||||
<Outlet> ← page content
|
||||
```
|
||||
|
||||
**Origin:** `AppLayout`, `AppSidebar`, `AppHeader`, `Backdrop` — **TailAdmin-modified**. Core structure from TailAdmin Free but heavily extended with Zustand stores, credit metrics, module system, and page context.
|
||||
|
||||
### Sidebar Configuration
|
||||
|
||||
| Aspect | Implementation |
|
||||
|--------|---------------|
|
||||
| **Nav structure** | **Hardcoded** in `AppSidebar.tsx` via `useMemo` arrays |
|
||||
| **Sections** | Dashboard (standalone), SETUP (Setup Wizard, Sites, Keywords Library, Thinker), WORKFLOW (Planner, Writer, Publisher, Automation) |
|
||||
| **Collapsible groups** | Yes — TailAdmin pattern with `ChevronDownIcon` rotation, animated height |
|
||||
| **Module gating** | `useModuleStore().isModuleEnabled()` (API-driven) |
|
||||
| **Admin filtering** | User role check for admin-only items |
|
||||
| **Config-driven?** | No — `routes.config.ts` exists but is **not used** by sidebar |
|
||||
| **TailAdmin patterns** | `menu-item` / `menu-item-active` / `menu-item-inactive` CSS utility classes, 90px→290px hover expansion, `no-scrollbar` |
|
||||
|
||||
### Header Structure
|
||||
|
||||
- Sticky header with `z-99999`
|
||||
- Mobile: hamburger toggle + logo
|
||||
- Desktop: page title with colored badge (from `PageContext`), sidebar toggle
|
||||
- Right side: `HeaderMetrics` (credit badges), route-conditional site/sector selectors (3 variants based on URL), search (⌘K), `ThemeToggleButton`, `NotificationDropdown`, `UserDropdown`
|
||||
- **Origin:** TailAdmin-modified — dropdown patterns and responsive layout from TailAdmin, but metric badges, page context, and site selectors are custom
|
||||
|
||||
### Routing
|
||||
|
||||
**Three independent route definitions exist (maintenance risk):**
|
||||
|
||||
| Source | Purpose | Route Count | Synchronized? |
|
||||
|--------|---------|-------------|---------------|
|
||||
| `App.tsx` | Actual React Router routes | ~100+ | Source of truth |
|
||||
| `AppSidebar.tsx` | Sidebar navigation items | ~25 | Subset of App.tsx |
|
||||
| `routes.config.ts` | Breadcrumb metadata | ~15 | Partial, stale |
|
||||
|
||||
- All page components (except auth) are **lazy-loaded** via `React.lazy()`
|
||||
- `ProtectedRoute` wraps all authenticated routes
|
||||
- `AdminRoute` wraps Thinker/Integration routes
|
||||
- `<Navigate>` handles legacy route redirects
|
||||
|
||||
---
|
||||
|
||||
## 3. Design System Status
|
||||
|
||||
### Color System
|
||||
|
||||
**Architecture:** 6 base hex tokens → `color-mix()` generates full palettes → Tailwind `@theme` consumes them
|
||||
|
||||
| Token | Value | Palette Generated |
|
||||
|-------|-------|-------------------|
|
||||
| `--color-primary` | `#1c86d1` | `brand-25` → `brand-950` |
|
||||
| `--color-success` | `#3fcd9f` | `success-25` → `success-950` |
|
||||
| `--color-warning` | `#f87f4c` | `warning-25` → `warning-950` |
|
||||
| `--color-danger` | `#ff656f` | `error-25` → `error-950` |
|
||||
| `--color-info` | `#18b2c4` | `info-25` → `info-950` |
|
||||
| `--color-gray-base` | `#243249` | `gray-25` → `gray-950` + `gray-dark` |
|
||||
|
||||
**All default Tailwind color palettes are disabled** (`--color-red-*: initial`, etc.). Only the above semantic palettes exist.
|
||||
|
||||
Additional derived tokens: `--navy`, `--surface`, `--panel`, `--text`, `--text-dim`, `--stroke`, gradients.
|
||||
|
||||
**Module color mapping** in `colors.config.ts`: Every module stage (keywords, clusters, ideas, tasks, content, images, review, approved, published) maps to `bg`, `bgLight`, `text`, `border`, `gradient`, and `cssVar` tokens.
|
||||
|
||||
### Typography
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Font family** | `Outfit` (Google Fonts), `ui-monospace` for code |
|
||||
| **Title sizes** | `title-2xl` (72px) → `title-sm` (30px) |
|
||||
| **Body sizes** | `theme-xl` (20px), `theme-sm` (14px), `theme-xs` (12px) |
|
||||
| **CMS fonts** | Separate system — `Inter` for published content |
|
||||
|
||||
### Spacing
|
||||
|
||||
Standard Tailwind spacing scale with custom additions:
|
||||
- Border radius: `xs` (2px) → `4xl` (20px)
|
||||
- Shadows: `theme-xs` → `theme-xl`, plus `datepicker`, `focus-ring`, `tooltip`
|
||||
- Z-index: 6 tiers from `z-index-1` to `z-index-999999`
|
||||
|
||||
### Dark Mode
|
||||
|
||||
| Aspect | Implementation |
|
||||
|--------|---------------|
|
||||
| **Strategy** | Tailwind `class` strategy — `.dark` on `<html>` |
|
||||
| **Storage** | `localStorage` key `"theme"` |
|
||||
| **Toggle** | `ThemeContext` → `toggleTheme()` |
|
||||
| **Default** | Light |
|
||||
| **CSS** | `@custom-variant dark (&:is(.dark *))` in design-system.css |
|
||||
| **Override vars** | Surface, panel, text, stroke, shadow overrides in `.dark` block |
|
||||
|
||||
### Responsive Approach
|
||||
|
||||
| Breakpoint | Value | Usage |
|
||||
|------------|-------|-------|
|
||||
| `2xsm` | 375px | Mobile-small |
|
||||
| `xsm` | 425px | Mobile |
|
||||
| `sm` | 640px | Small tablet |
|
||||
| **`md`** | **768px** | **Mobile detection** (SidebarContext) |
|
||||
| **`lg`** | **1024px** | **Primary:** sidebar show/hide, header layout |
|
||||
| **`xl`** | **1280px** | Flex layout trigger |
|
||||
| `2xl` | 1536px | Wide desktop |
|
||||
| `3xl` | 2000px | Ultra-wide |
|
||||
|
||||
---
|
||||
|
||||
## 4. Component Inventory
|
||||
|
||||
### 4.1 UI Primitives (`components/ui/`)
|
||||
|
||||
| Path | Component | Purpose | Origin | Key UI Info |
|
||||
|------|-----------|---------|--------|-------------|
|
||||
| `ui/button/Button.tsx` | `Button` | Multi-variant button (primary/secondary/outline/ghost/gradient) × 5 tones × 6 sizes | custom | 🔒 STYLE LOCKED. `twMerge` + `clsx` |
|
||||
| `ui/button/IconButton.tsx` | `IconButton` | Icon-only button with tone/variant/shape system | custom | 🔒 STYLE LOCKED |
|
||||
| `ui/button/ButtonWithTooltip.tsx` | `ButtonWithTooltip` | Wraps Button + Tooltip for disabled state hint | custom | |
|
||||
| `ui/card/Card.tsx` | `Card` + 7 sub-components | Card container system (surface/panel/frosted/borderless/gradient variants) | custom | Uses `--color-panel` CSS var |
|
||||
| `ui/modal/index.tsx` | `Modal` | Portal-based modal with backdrop blur, ESC, scroll lock | tailadmin-derived | `z-99999` convention |
|
||||
| `ui/table/index.tsx` | `Table` + 4 sub-components | Unstyled table primitives | custom | 🔒 STYLE LOCKED |
|
||||
| `ui/tabs/Tabs.tsx` | `Tabs`, `TabList`, `Tab`, `TabPanel` | Segmented control/tabs with render-prop state | tailadmin-derived | `shadow-theme-xs` token |
|
||||
| `ui/badge/Badge.tsx` | `Badge` | 4 variants × 12 tones × 3 sizes with backward-compat | custom | 🔒 STYLE LOCKED |
|
||||
| `ui/alert/Alert.tsx` | `Alert` | Inline notification banner with variant icons | tailadmin-derived | SVG icons match TailAdmin |
|
||||
| `ui/alert/AlertModal.tsx` | `AlertModal` | Centered modal alert with decorative icons | custom | |
|
||||
| `ui/accordion/Accordion.tsx` | `Accordion`, `AccordionItem` | Collapsible with animated height | custom | |
|
||||
| `ui/breadcrumb/Breadcrumb.tsx` | `Breadcrumb` | Navigation breadcrumb from items array | tailadmin-derived | Chevron SVG matches TailAdmin |
|
||||
| `ui/dropdown/Dropdown.tsx` | `Dropdown` | Portal-based positioned dropdown with edge detection | custom | Major rewrite from TailAdmin |
|
||||
| `ui/dropdown/DropdownItem.tsx` | `DropdownItem` | Menu item (button or link) | tailadmin-derived | |
|
||||
| `ui/spinner/Spinner.tsx` | `Spinner` | CSS border-based loading spinner | custom | |
|
||||
| `ui/pagination/Pagination.tsx` | `Pagination` | Full pagination with ellipsis | tailadmin-derived | Arrow SVGs match TailAdmin |
|
||||
| `ui/pagination/CompactPagination.tsx` | `CompactPagination` | Compact pagination + page size selector | custom | |
|
||||
| `ui/progress/ProgressBar.tsx` | `ProgressBar` | Horizontal progress bar | custom | |
|
||||
| `ui/tooltip/Tooltip.tsx` | `Tooltip` | Portal-based tooltip with directional arrow | custom | |
|
||||
| `ui/tooltip/EnhancedTooltip.tsx` | `EnhancedTooltip` | Rich-content tooltip with delay | custom | |
|
||||
| `ui/tooltip/CalendarItemTooltip.tsx` | `CalendarItemTooltip` | Calendar event tooltip | custom | Business-specific |
|
||||
| `ui/toast/Toast.tsx` | `ToastItem` | Individual toast notification (5s auto-dismiss) | custom | `animate-slide-in-right` |
|
||||
| `ui/toast/ToastContainer.tsx` | `ToastProvider`, `useToast` | Context-based toast system | custom | Portal, fixed top-right |
|
||||
| `ui/ribbon/Ribbon.tsx` | `Ribbon` | Decorative ribbon overlay | tailadmin-derived | Corner variant matches TailAdmin |
|
||||
| `ui/list/List.tsx` | `List` + 5 sub-components | Multi-variant list with checkbox/radio items | tailadmin-derived | Extended with checkbox/radio |
|
||||
| `ui/button-group/ButtonGroup.tsx` | `ButtonGroup`, `ButtonGroupItem` | Grouped button strip | tailadmin-derived | `shadow-theme-xs` |
|
||||
| `ui/dataview/DataView.tsx` | `DataView` + 3 sub-components | Data display container with header/toolbar/empty | custom | SaaS-specific |
|
||||
| `ui/pricing-table/PricingTable.tsx` | `PricingTable` (3 variants) | Full pricing table with toggle, discount calc | tailadmin-derived | Extended with dynamic pricing |
|
||||
| `ui/pricing-table/index.tsx` | `PricingTable` (named export) | Alternate SaaS-specific pricing table | custom | **Duplicate** of above |
|
||||
| `ui/avatar/Avatar.tsx` | `Avatar` | User avatar with status dot | tailadmin-derived | |
|
||||
| `ui/videos/*.tsx` | Video aspect ratio components | Hardcoded YouTube embeds | tailadmin-demo | **Bug**: SixteenIsToNine uses 4:3 |
|
||||
| `ui/images/*.tsx` | Image grid components | Hardcoded demo image grids | tailadmin-demo | Static — cleanup candidates |
|
||||
|
||||
### 4.2 Common Components (`components/common/`)
|
||||
|
||||
| Path | Component | Purpose | Origin |
|
||||
|------|-----------|---------|--------|
|
||||
| `common/PageBreadCrumb.tsx` | `PageBreadcrumb` | Simple Home > Page breadcrumb | tailadmin-original |
|
||||
| `common/ComponentCard.tsx` | `ComponentCard` | Generic card wrapper with title/description | tailadmin-modified |
|
||||
| `common/GridShape.tsx` | `GridShape` | Decorative SVG background shapes | tailadmin-original |
|
||||
| `common/ThemeTogglerTwo.tsx` | `ThemeTogglerTwo` | Floating round theme toggle button | tailadmin-original |
|
||||
| `common/PageMeta.tsx` | `PageMeta` | `<title>` and `<meta>` via react-helmet | tailadmin-original |
|
||||
| `common/ScrollToTop.tsx` | `ScrollToTop` | Auto-scroll to top on route change | tailadmin-original |
|
||||
| `common/ChartTab.tsx` | `ChartTab` | Chart time period segmented control | tailadmin-original |
|
||||
| `common/ThemeToggleButton.tsx` | `ThemeToggleButton` | Theme toggle using IconButton | tailadmin-modified |
|
||||
| `common/SiteSelector.tsx` | `SiteAndSectorSelector` | Combined site + sector dropdown | custom |
|
||||
| `common/SingleSiteSelector.tsx` | `SingleSiteSelector` | Site-only dropdown | custom |
|
||||
| `common/SiteWithAllSitesSelector.tsx` | `SiteWithAllSitesSelector` | Site dropdown with "All Sites" | custom |
|
||||
| `common/SiteAndSectorSelector.tsx` | `SiteAndSectorSelector` | Combined site + sector (dual-mode) | custom |
|
||||
| `common/SectorSelector.tsx` | `SectorSelector` | Standalone sector dropdown | custom |
|
||||
| `common/SiteInfoBar.tsx` | `SiteInfoBar` | Horizontal site info bar with badges | custom |
|
||||
| `common/SiteCard.tsx` | `SiteCard` | Site card with status toggle and actions | custom |
|
||||
| `common/FormModal.tsx` | `FormModal` | Generic modal with auto-generated form | custom |
|
||||
| `common/ProgressModal.tsx` | `ProgressModal` | Multi-step AI progress modal | custom |
|
||||
| `common/ConfirmDialog.tsx` | `ConfirmDialog` | Generic confirmation modal | custom |
|
||||
| `common/ContentViewerModal.tsx` | `ContentViewerModal` | Article preview modal | custom |
|
||||
| `common/ErrorDetailsModal.tsx` | `ErrorDetailsModal` | Publishing error detail modal | custom |
|
||||
| `common/BulkStatusUpdateModal.tsx` | `BulkStatusUpdateModal` | Bulk status change modal | custom |
|
||||
| `common/BulkScheduleModal.tsx` | `BulkScheduleModal` | Bulk scheduling modal | custom |
|
||||
| `common/BulkSchedulePreviewModal.tsx` | `BulkSchedulePreviewModal` | Schedule preview before confirm | custom |
|
||||
| `common/BulkExportModal.tsx` | `BulkExportModal` | Bulk export modal | custom |
|
||||
| `common/BulkPublishingModal.tsx` | `BulkPublishingModal` | Bulk publishing queue modal | custom |
|
||||
| `common/PublishingProgressModal.tsx` | `PublishingProgressModal` | Single-content publishing progress | custom |
|
||||
| `common/PublishLimitModal.tsx` | `PublishLimitModal` | Publish limit warning | custom |
|
||||
| `common/ImageQueueModal.tsx` | `ImageQueueModal` | Image generation queue | custom |
|
||||
| `common/ScheduleContentModal.tsx` | `ScheduleContentModal` | Schedule single content | custom |
|
||||
| `common/SearchModal.tsx` | `SearchModal` | Global search (⌘K) | custom |
|
||||
| `common/SingleRecordStatusUpdateModal.tsx` | `SingleRecordStatusUpdateModal` | Single record status change | custom |
|
||||
| `common/ColumnSelector.tsx` | `ColumnSelector` | Table column visibility toggle | custom |
|
||||
| `common/ViewToggle.tsx` | `ViewToggle` | List/grid view toggle | custom |
|
||||
| `common/ToggleTableRow.tsx` | `ToggleTableRow` | Expandable table row | custom |
|
||||
| `common/HTMLContentRenderer.tsx` | `HTMLContentRenderer` | Safe HTML/JSON content renderer | custom |
|
||||
| `common/ContentImageCell.tsx` | `ContentImageCell` | Table cell with image status | custom |
|
||||
| `common/ImageResultCard.tsx` | `ImageResultCard` | AI image result display | custom |
|
||||
| `common/ImageGenerationCard.tsx` | `ImageGenerationCard` | Image generation test form | custom |
|
||||
| `common/ImageServiceCard.tsx` | `ImageServiceCard` | Image service provider card | custom |
|
||||
| `common/IntegrationCard.tsx` | `IntegrationCard` | API integration toggle card | custom |
|
||||
| `common/ValidationCard.tsx` | `ValidationCard` | Validation status card | custom |
|
||||
| `common/WorkflowInsights.tsx` | `WorkflowInsights` | Collapsible insights notifications | custom |
|
||||
| `common/PageHeader.tsx` | `PageHeader` | Page header (sets PageContext) | custom |
|
||||
| `common/PageTransition.tsx` | `PageTransition` | Route transition animation | custom |
|
||||
| `common/PageLoader.tsx` | `PageLoader` | Global loading indicator | custom |
|
||||
| `common/SuspenseLoader.tsx` | `SuspenseLoader` | Suspense fallback spinner | custom |
|
||||
| `common/ErrorBoundary.tsx` | `ErrorBoundary` | Top-level error boundary | custom |
|
||||
| `common/PageErrorBoundary.tsx` | `PageErrorBoundary` | Per-page error boundary | custom |
|
||||
| `common/GlobalErrorDisplay.tsx` | `GlobalErrorDisplay` | Global error overlay | custom |
|
||||
| `common/LoadingStateMonitor.tsx` | `LoadingStateMonitor` | Stuck-loading detector (10s timeout) | custom |
|
||||
| `common/ModuleGuard.tsx` | `ModuleGuard` | **DEPRECATED** — pass-through | custom |
|
||||
| `common/DebugSiteSelector.tsx` | `DebugSiteSelector` | Debug site selector | custom |
|
||||
|
||||
### 4.3 Form Components (`components/form/`)
|
||||
|
||||
| Path | Component | Purpose | Origin |
|
||||
|------|-----------|---------|--------|
|
||||
| `form/input/InputField.tsx` | `InputField` | Text input with states | tailadmin-modified |
|
||||
| `form/input/TextArea.tsx` | `TextArea` | Textarea with states | tailadmin-modified |
|
||||
| `form/input/Checkbox.tsx` | `Checkbox` | Custom checkbox with SVG | tailadmin-modified |
|
||||
| `form/input/Radio.tsx` | `Radio` | Custom radio button | tailadmin-original |
|
||||
| `form/input/RadioSm.tsx` | `RadioSm` | Small radio variant | tailadmin-original |
|
||||
| `form/input/FileInput.tsx` | `FileInput` | File input with pseudo-styling | tailadmin-original |
|
||||
| `form/switch/Switch.tsx` | `Switch` | Toggle switch | tailadmin-modified |
|
||||
| `form/group-input/PhoneInput.tsx` | `PhoneInput` | Phone with country code | tailadmin-original |
|
||||
| `form/Form.tsx` | `Form` | Simple form wrapper | tailadmin-original |
|
||||
| `form/Label.tsx` | `Label` | Label primitive | tailadmin-original |
|
||||
| `form/Select.tsx` | `Select` | Native `<select>` styled | tailadmin-original |
|
||||
| `form/SelectDropdown.tsx` | `SelectDropdown` | Custom dropdown select | custom |
|
||||
| `form/MultiSelect.tsx` | `MultiSelect` | Multi-select with chips | tailadmin-modified |
|
||||
| `form/FormFieldRenderer.tsx` | `FormFieldRenderer` | Dynamic field renderer from config | custom |
|
||||
| `form/date-picker.tsx` | `DatePicker` | flatpickr-based date picker | third-party |
|
||||
| `form/form-elements/*.tsx` | 9 demo components | Form element showcase pages | tailadmin-original |
|
||||
|
||||
### 4.4 Header Components (`components/header/`)
|
||||
|
||||
| Path | Component | Purpose | Origin |
|
||||
|------|-----------|---------|--------|
|
||||
| `header/Header.tsx` | `Header` | Main header with search, theme, dropdowns | tailadmin-modified |
|
||||
| `header/SiteSwitcher.tsx` | `SiteSwitcher` | Site switch dropdown | custom |
|
||||
| `header/HeaderMetrics.tsx` | `HeaderMetrics` | Credit/page metric badges | custom |
|
||||
| `header/NotificationDropdown.tsx` | `NotificationDropdown` | Notification dropdown with API sync | custom |
|
||||
| `header/NotificationDropdownNew.tsx` | `NotificationDropdownNew` | Alternate notification dropdown (older?) | custom |
|
||||
| `header/UserDropdown.tsx` | `UserDropdown` | User profile menu | tailadmin-modified |
|
||||
|
||||
### 4.5 Feature Components
|
||||
|
||||
#### Dashboard (`components/dashboard/`) — 22 components, ALL custom
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| `EnhancedMetricCard` | KPI card with accent border, trend, tooltip |
|
||||
| `WorkflowPipeline` / `WorkflowPipelineWidget` | Visual 7-stage pipeline |
|
||||
| `NeedsAttentionBar` | Collapsible alert bar |
|
||||
| `StandardizedModuleWidget` | Module pipeline stats |
|
||||
| `WorkflowCompletionWidget` | Time-filtered workflow stats |
|
||||
| `AIOperationsWidget` | AI operation statistics |
|
||||
| `RecentActivityWidget` | Last 5 activities |
|
||||
| `CreditsUsageWidget` / `CreditBalanceWidget` / `CreditAvailabilityWidget` | Credit displays |
|
||||
| `AutomationStatusWidget` | Automation run status |
|
||||
| `SitesOverviewWidget` | Site list with status |
|
||||
| `UsageChartWidget` | Usage chart with date range |
|
||||
| `KeywordLibraryStatsWidget` | Keyword library stats |
|
||||
| `AccountInfoWidget` | Account billing info |
|
||||
| `SiteConfigWidget` | Site setup checklist |
|
||||
| `QuickActionsWidget` | 8-step workflow guide |
|
||||
| `ContentVelocityWidget` | Content production rates |
|
||||
| `OperationsCostsWidget` | AI operation costs |
|
||||
| `ThreeWidgetFooter` / `StandardThreeWidgetFooter` | 3-column footer layouts |
|
||||
|
||||
#### Billing (`components/billing/`) — 17 components, ALL custom
|
||||
|
||||
Key components: `BillingBalancePanel`, `BillingUsagePanel`, `CreditInsightsCharts`, `PayInvoiceModal`, `InsufficientCreditsModal`, `PendingPaymentBanner`, `PendingPaymentView`, `PaymentHistory`, `UsageLimitsPanel`, `CreditCostBreakdownPanel`, `CreditCostsPanel`
|
||||
|
||||
#### Automation (`components/Automation/`) — 20 components, ALL custom
|
||||
|
||||
Top-level: `ConfigModal`, `CurrentProcessingCard`/V2, `GlobalProgressBar`, `RunHistory`, `StageCard`, `ActivityLog`
|
||||
|
||||
DetailView: `RunSummaryCard`, `StageAccordion`, `InsightsPanel`, `EfficiencyMetrics`, `CreditBreakdownChart`, `PredictiveCostAnalysis`, `ProductionSummary`, `EnhancedRunHistory`, `MeaningfulRunHistory`, `PipelineOverviewCard`, `AttentionItemsAlert`, `RunStatisticsSummary`
|
||||
|
||||
#### Sites (`components/sites/`) — 11 components, ALL custom
|
||||
|
||||
Key: `SiteSetupChecklist`, `SiteProgressWidget`, `WordPressIntegrationCard`/`Form`/`Modal`, `TemplateLibrary`, `TemplateCustomizer`, `StyleEditor`, `LayoutSelector`/`Preview`, `SiteTypeBadge`
|
||||
|
||||
#### WordPress Publish (`components/WordPressPublish/`) — 4 components, ALL custom STYLE LOCKED
|
||||
|
||||
`WordPressPublish`, `BulkWordPressPublish`, `ContentActionsMenu`
|
||||
|
||||
#### Other Feature Components — ALL custom
|
||||
|
||||
| Directory | Components |
|
||||
|-----------|-----------|
|
||||
| `onboarding/` | `OnboardingWizard`, `WorkflowGuide`, Steps 1-5 (7 files) |
|
||||
| `keywords-library/` | `SectorCardsGrid`, `SectorMetricCard`, `SmartSuggestions`, `BulkAddConfirmation` |
|
||||
| `tasks/` | `KanbanBoard` (tailadmin-modified), `TaskList` (tailadmin-modified), `RelationshipMap` |
|
||||
| `content/` | `ContentFilter`, `SourceBadge`, `SyncStatusBadge` |
|
||||
| `linker/` | `LinkResults` |
|
||||
| `optimizer/` | `OptimizationScores`, `ScoreComparison` |
|
||||
| `integration/` | `IntegrationStatus`, `PlatformSelector`, `SiteIntegrationsSection` |
|
||||
| `publishing/` | `PublishingRules` |
|
||||
| `auth/` | `AdminRoute`, `ProtectedRoute`, `SignInForm`, `SignUpFormUnified` |
|
||||
| `navigation/` | `ModuleNavigationTabs` |
|
||||
| `UserProfile/` | `UserAddressCard`, `UserInfoCard`, `UserMetaCard` (tailadmin-original) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Shared Components — Usage Patterns
|
||||
|
||||
### Most-Used Shared Components
|
||||
|
||||
| Component | Estimated Usage Count | Notes |
|
||||
|-----------|----------------------|-------|
|
||||
| `Button` | 50+ pages | Universal — primary UI action element |
|
||||
| `Modal` | 30+ components | All modals in codebase |
|
||||
| `Badge` | 25+ pages | Status/label badges |
|
||||
| `Card` | 20+ widgets | Dashboard widgets, cards |
|
||||
| `InputField` | 15+ forms | All text inputs |
|
||||
| `SelectDropdown` | 12+ forms | Custom dropdown selects |
|
||||
| `Dropdown`/`DropdownItem` | 10+ menus | Header, site selectors, actions |
|
||||
| `Tooltip` / `EnhancedTooltip` | 10+ | Pipeline, metrics, help text |
|
||||
| `CompactPagination` | 10 pages | All TablePageTemplate pages |
|
||||
| `Alert` | 8+ pages | Inline notifications |
|
||||
| `Switch` | 6+ | Settings toggles |
|
||||
| `Spinner` | 5+ | Loading states |
|
||||
| `Accordion` | 4+ | Automation details, settings |
|
||||
|
||||
### Duplicate Patterns Found
|
||||
|
||||
| Pattern | Instances | Details |
|
||||
|---------|-----------|---------|
|
||||
| **Site selector variants** | 4 | `SiteSelector`, `SingleSiteSelector`, `SiteWithAllSitesSelector`, `SiteAndSectorSelector` — could be 1 component with props |
|
||||
| **Notification dropdown** | 2 | `NotificationDropdown` + `NotificationDropdownNew` — one is likely stale |
|
||||
| **Pricing table** | 2 | `PricingTable.tsx` (default) + `index.tsx` (named) — overlapping implementations |
|
||||
| **Theme toggle** | 2 | `ThemeTogglerTwo` (floating) + `ThemeToggleButton` (header icon) |
|
||||
| **BulkWordPressPublish** | 2 | `WordPressPublish/BulkWordPressPublish.tsx` (custom UI) + `BulkWordPressPublish/BulkWordPressPublish.tsx` (**MUI-based** — different library!) |
|
||||
| **Error boundary** | 2 | `ErrorBoundary` (top-level) + `PageErrorBoundary` (per-page) — similar patterns |
|
||||
| **Three-widget footer** | 2 | `ThreeWidgetFooter` + `StandardThreeWidgetFooter` |
|
||||
| **Date formatting** | 2 | `dateUtils.ts` + `date.ts` — overlapping functions (`formatDateTime` exists in both) |
|
||||
|
||||
### Inconsistencies
|
||||
|
||||
| Issue | Details |
|
||||
|-------|---------|
|
||||
| **Icon library mixing** | `PaymentGatewaySelector.tsx` uses `lucide-react` instead of `../../icons`; `BulkWordPressPublish/` uses `@mui/icons-material` |
|
||||
| **Import path inconsistency** | `PaymentGatewaySelector.tsx` uses `@/` alias; rest of codebase uses relative paths |
|
||||
| **Inline buttons** | `PageErrorBoundary`, `ErrorBoundary` use raw `<button>` instead of shared `Button` component |
|
||||
| **Inline table styling** | Sites Dashboard, Automation pages have inline table implementations instead of using `Table` primitives |
|
||||
| **Settings form patterns** | No shared settings form template — each settings page builds its own form layout |
|
||||
|
||||
---
|
||||
|
||||
## 6. Page-by-Page Breakdown
|
||||
|
||||
### Template Distribution
|
||||
|
||||
| Template | Pages Using It |
|
||||
|----------|---------------|
|
||||
| **`TablePageTemplate`** (config-driven) | Keywords, Clusters, Ideas, Tasks, Content, Review, Approved, Images, Sites/List, Setup/IndustriesSectorsKeywords |
|
||||
| **`ContentViewTemplate`** | ContentView (single article detail) |
|
||||
| **`DashboardTemplate`** (structure, not widely used) | Available but most dashboard pages are custom |
|
||||
| **`FormPageTemplate`** (settings layout) | Available but not widely adopted |
|
||||
| **Custom layouts** | ~50 pages |
|
||||
| **TailAdmin demo/reference** | UIElements, Components |
|
||||
| **Placeholder/stub** | 5 pages |
|
||||
|
||||
### Planner Module (4 pages)
|
||||
|
||||
| Page | Template | Notes |
|
||||
|------|----------|-------|
|
||||
| Keywords | `TablePageTemplate` | Config-driven via `keywords.config.tsx`. Auto-cluster, import/export. |
|
||||
| Clusters | `TablePageTemplate` | Config-driven via `clusters.config.tsx`. Auto-generate ideas. |
|
||||
| ClusterDetail | Custom | Tabbed detail view (keywords, ideas, stats). Manual table. |
|
||||
| Ideas | `TablePageTemplate` | Config-driven via `ideas.config.tsx`. Bulk queue to writer. |
|
||||
|
||||
### Writer Module (6 pages)
|
||||
|
||||
| Page | Template | Notes |
|
||||
|------|----------|-------|
|
||||
| Tasks | `TablePageTemplate` | Config-driven. Auto-generate content/images. Has Kanban/List toggles. |
|
||||
| Content | `TablePageTemplate` | Config-driven. Inline editing disabled. |
|
||||
| Review | `TablePageTemplate` | Config-driven. Content review workflow. |
|
||||
| Approved | `TablePageTemplate` | Config-driven. Scheduling, publishing. |
|
||||
| Images | `TablePageTemplate` | Config-driven. Dynamic in-article image columns. |
|
||||
| ContentView | `ContentViewTemplate` | Single article detail with scheduling, image management. |
|
||||
|
||||
### Publisher Module (2 pages)
|
||||
|
||||
| Page | Template | Notes |
|
||||
|------|----------|-------|
|
||||
| ContentCalendar | Custom | FullCalendar integration with month/week/list views. Custom scheduling modals. |
|
||||
| PublishSettings | Custom | Multi-tab settings (general, frequency, scheduling). Inline form. |
|
||||
|
||||
### Sites Module (11 pages)
|
||||
|
||||
| Page | Template | Notes |
|
||||
|------|----------|-------|
|
||||
| List | `TablePageTemplate` | Sites management list. |
|
||||
| Dashboard | Custom | Multi-section dashboard with widget grid. Manual tables. |
|
||||
| Settings | Custom | Tab-based site settings. Inline forms. |
|
||||
| Content | Custom | Site content management with source filtering. |
|
||||
| ContentStructure | Custom | Content structure config. |
|
||||
| PostEditor | Custom | Post editing interface. |
|
||||
| PageManager | Custom | Site page management. |
|
||||
| SyncDashboard | Custom | WordPress sync status. |
|
||||
| PublishingQueue | Custom | Publishing queue with status tracking. |
|
||||
| AIAutomationSettings | Custom | Per-site automation config. |
|
||||
| DeploymentPanel | Custom | **Deprecated** placeholder. |
|
||||
|
||||
### Automation Module (4 pages)
|
||||
|
||||
| Page | Template | Notes |
|
||||
|------|----------|-------|
|
||||
| AutomationPage | Custom | Main automation view with config, processing, history. |
|
||||
| AutomationOverview | Custom | Multi-site overview with eligibility status. |
|
||||
| AutomationRunDetail | Custom | Detailed run breakdown with stage accordion. |
|
||||
| PipelineSettings | Custom | Pipeline stage configuration form. |
|
||||
|
||||
### Thinker Module (5 pages)
|
||||
|
||||
| Page | Template | Notes |
|
||||
|------|----------|-------|
|
||||
| Prompts | Custom | Prompt library with CRUD and categories. |
|
||||
| AuthorProfiles | Custom | Author voice profiles with CRUD. |
|
||||
| Strategies | Custom | Content strategy management. |
|
||||
| Profile | Custom | User profile view (TailAdmin-original layout). |
|
||||
| ImageTesting | Custom | Image generation testing interface. |
|
||||
|
||||
### Other Modules
|
||||
|
||||
| Module | Page | Template | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| Optimizer | ContentSelector | Custom | Content selection for optimization. |
|
||||
| Optimizer | AnalysisPreview | Custom | Optimization results preview. |
|
||||
| Linker | ContentList | Custom | Internal linking content list. |
|
||||
| Setup | SetupWizard | Custom | Multi-step onboarding wizard. |
|
||||
| Help | Help | Custom | Help center page. |
|
||||
| Reference | Industries, SeedKeywords | Custom | Read-only reference data. |
|
||||
|
||||
### Settings (12 pages) — High duplication risk
|
||||
|
||||
| Page | Notes |
|
||||
|------|-------|
|
||||
| General | Inline form layout |
|
||||
| Publishing | Tab-based publishing config |
|
||||
| Integration | Integration CRUD |
|
||||
| Plans | Plan display (duplicated in account/) |
|
||||
| Subscriptions | Subscription management (duplicated) |
|
||||
| CreditsAndBilling | Billing info (duplicated) |
|
||||
| Industries | Industry selection |
|
||||
| Account | Account settings (duplicated in account/) |
|
||||
| Sites | Sites management |
|
||||
| Users | User management (admin) |
|
||||
| System | System admin dashboard |
|
||||
| WordPressIntegrationDebug | Debug tool |
|
||||
|
||||
### Account (7 pages) — Supersedes Settings billing
|
||||
|
||||
| Page | Notes |
|
||||
|------|-------|
|
||||
| AccountSettingsPage | Account settings + profile |
|
||||
| ContentSettingsPage | Content generation settings |
|
||||
| NotificationsPage | Notification preferences |
|
||||
| PlansAndBillingPage | Plan + billing unified view |
|
||||
| PurchaseCreditsPage | Credit purchase flow |
|
||||
| UsageDashboardPage | Usage overview |
|
||||
| UsageAnalyticsPage | Detailed usage analytics |
|
||||
|
||||
### Auth Pages (7 pages)
|
||||
|
||||
| Page | Origin | Notes |
|
||||
|------|--------|-------|
|
||||
| AuthPageLayout | tailadmin-modified | Split-screen auth layout |
|
||||
| SignIn | tailadmin-modified | Custom logic, TailAdmin layout |
|
||||
| SignUp | tailadmin-modified | Custom with inline pricing |
|
||||
| ForgotPassword | tailadmin-modified | TailAdmin layout |
|
||||
| ResetPassword | tailadmin-modified | TailAdmin layout |
|
||||
| VerifyEmail | custom | Email verification flow |
|
||||
| Unsubscribe | custom | Email unsubscribe |
|
||||
|
||||
### Demo/Reference Pages
|
||||
|
||||
| Page | Origin | Notes |
|
||||
|------|--------|-------|
|
||||
| UIElements | tailadmin-original | 737 lines — TailAdmin component showcase |
|
||||
| Components | tailadmin-original | 680 lines — TailAdmin demo page |
|
||||
| NotFound | tailadmin-original | 404 page with grid shapes |
|
||||
|
||||
---
|
||||
|
||||
## 7. Stores & API Layer
|
||||
|
||||
### Zustand Stores (11 stores, 1,802 LOC)
|
||||
|
||||
| Store | State Managed | LOC | Persistence | API Calls |
|
||||
|-------|--------------|-----|-------------|-----------|
|
||||
| `authStore` | User, tokens, auth state | 540 | localStorage (`auth-storage`) | 5 endpoints (raw `fetch`) |
|
||||
| `siteStore` | Active site | 140 | localStorage (`site-storage`) | `GET /v1/auth/sites/` |
|
||||
| `sectorStore` | Active sector, sector list | 168 | localStorage (`sector-storage`) | `GET /v1/auth/sites/{id}/sectors/` |
|
||||
| `plannerStore` | Keywords, clusters | 110 | None | 2 endpoints (stubs for CUD) |
|
||||
| `billingStore` | Balance, usage, limits | 82 | None | 3 billing endpoints |
|
||||
| `notificationStore` | Notifications, unread count | 336 | None (in-memory) | 5 notification endpoints |
|
||||
| `onboardingStore` | Guide state | 102 | localStorage (`onboarding-storage`) | 3 user-setting endpoints |
|
||||
| `settingsStore` | Account/module settings | 195 | localStorage (`settings-storage` v4) | 6 settings endpoints |
|
||||
| `moduleStore` | Module enablement | 56 | None | 1 endpoint |
|
||||
| `columnVisibilityStore` | Column visibility per page | 53 | Direct localStorage | None |
|
||||
| `pageSizeStore` | Global page size | 20 | localStorage (`igny8-page-size`) | None |
|
||||
|
||||
### API Service Layer (8 files, 5,626 LOC)
|
||||
|
||||
| File | LOC | Organization | Endpoints |
|
||||
|------|-----|-------------|-----------|
|
||||
| **`api.ts`** | **3,056** | **Monolithic** — 90+ functions | 75+ endpoints across planner, writer, sites, sectors, settings, billing, keywords-library |
|
||||
| `billing.api.ts` | 1,507 | Well-organized (sectioned) | 50+ billing/payment/subscription endpoints |
|
||||
| `automationService.ts` | 438 | Well-organized | 19 automation endpoints |
|
||||
| `unifiedSettings.api.ts` | 207 | Well-organized | 2 unified settings endpoints |
|
||||
| `integration.api.ts` | 158 | Well-organized | 7 integration endpoints |
|
||||
| `notifications.api.ts` | 142 | Well-organized | 5 notification endpoints |
|
||||
| `optimizer.api.ts` | 86 | Well-organized | 3 optimizer endpoints |
|
||||
| `linker.api.ts` | 32 | Well-organized | 2 linker endpoints |
|
||||
|
||||
**Auth pattern:** All APIs use JWT Bearer tokens via `fetchAPI()`. Token sourced from Zustand → localStorage fallback. Auto-refresh on 401. Force-logout on refresh failure.
|
||||
|
||||
**`api.ts` should be split** — it contains planner, writer, sites, sectors, settings, billing, and keywords-library endpoints in a single file. The other service files demonstrate the correct pattern.
|
||||
|
||||
### API Directory (`api/`)
|
||||
|
||||
| File | Exports | Purpose |
|
||||
|------|---------|---------|
|
||||
| `optimizer.api.ts` | `optimizerApi` | Optimize, batch optimize, analyze |
|
||||
| `linker.api.ts` | `linkerApi` | Process, batch process internal links |
|
||||
|
||||
---
|
||||
|
||||
## 8. Styling Audit
|
||||
|
||||
### Tailwind Configuration
|
||||
|
||||
| Aspect | Details |
|
||||
|--------|---------|
|
||||
| **Version** | Tailwind CSS 4 (CSS-first config via `@theme` blocks) |
|
||||
| **Config file** | No `tailwind.config.js` — all in `design-system.css` `@theme` |
|
||||
| **PostCSS** | `@tailwindcss/postcss` plugin only |
|
||||
| **Dark mode** | `@custom-variant dark (&:is(.dark *))` |
|
||||
| **Default palettes** | **All disabled** (`--color-red-*: initial`, etc.) |
|
||||
| **Custom palettes** | 6 semantic scales via `color-mix()` |
|
||||
| **Font** | `Outfit` (Google Fonts) |
|
||||
| **Breakpoints** | Standard + `2xsm`(375), `xsm`(425), `3xl`(2000) |
|
||||
|
||||
### CSS Files
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|------|-------|---------|
|
||||
| `styles/design-system.css` | 1,006 | Master design system — tokens, `@theme`, base, utilities, components |
|
||||
| `marketing/styles/marketing.css` | 210 | Marketing site — re-imports design-system + own `@theme` |
|
||||
| `components/shared/blocks/blocks.css` | 125 | CMS block component styles |
|
||||
| `components/shared/layouts/layouts.css` | 80 | CMS layout scaffolding |
|
||||
|
||||
### Custom CSS Utilities
|
||||
|
||||
| Category | Classes | Purpose |
|
||||
|----------|---------|---------|
|
||||
| Sidebar nav | `menu-item`, `menu-item-active`, `menu-item-inactive`, `menu-item-icon-*`, `menu-dropdown-*` | TailAdmin sidebar styling via `@utility` |
|
||||
| Tables | `igny8-table-*`, `igny8-data-row`, `igny8-skeleton-row` | Compact table styling, loading transitions |
|
||||
| Header | `igny8-header-metric*` | Header metric badges |
|
||||
| Filters | `igny8-filter-bar` | Filter bar layout |
|
||||
| Selects | `igny8-select-styled` | Native select custom chevron |
|
||||
| Keywords | `keywords-library-sector-*` | Keyword sector cards |
|
||||
| Misc | `no-scrollbar`, `chat-height`, `inbox-height` | Utility classes |
|
||||
| Animations | `animate-slide-in-right` | Toast slide-in |
|
||||
|
||||
### CMS Theme System (`styles/cms/`)
|
||||
|
||||
Separate TypeScript-based theme system for CMS-published content (not admin UI):
|
||||
- `colors.ts` — Color scheme definitions (blue, purple, green)
|
||||
- `typography.ts` — Typography presets (modern, classic, editorial, minimal, tech)
|
||||
- `components.ts` — Component style defaults (buttons, cards, inputs)
|
||||
- `presets.ts` — Complete theme presets (modern, classic, minimal, bold, elegant, tech)
|
||||
|
||||
### Style Issues Found
|
||||
|
||||
| Issue | Severity | Details |
|
||||
|-------|----------|---------|
|
||||
| Marketing CSS hex conflict | Medium | `marketing.css` has hardcoded hex color scales that conflict with `design-system.css` `color-mix()` values |
|
||||
| Hardcoded hex in blocks.css/layouts.css | Low | CMS CSS uses raw hex instead of CSS variables |
|
||||
| Dual `@custom-variant dark` | Low | Declared in both design-system.css and marketing.css (redundant) |
|
||||
| 30 `!important` usages | Low | For ApexCharts/jVectorMap overrides — acceptable |
|
||||
| 4 commented-out palettes | Low | Development artifacts in design-system.css `:root` |
|
||||
|
||||
---
|
||||
|
||||
## 9. Duplication & Inconsistency Report
|
||||
|
||||
### Critical Duplications
|
||||
|
||||
| # | Pattern | Locations | Impact |
|
||||
|----|---------|-----------|--------|
|
||||
| 1 | **Settings form layout** | 6+ Settings pages + 7 Account pages (13 total, partially overlapping) | No shared `FormPageTemplate` adoption. Each page builds its own form layout. |
|
||||
| 2 | **Billing/Credits pages** | `Settings/CreditsAndBilling` + `Billing/` (3 pages) + `account/PlansAndBillingPage` + `account/PurchaseCreditsPage` + `account/UsageDashboardPage` | 7+ pages covering similar billing/credit ground |
|
||||
| 3 | **API monolith** | `api.ts` (3,056 LOC) | 90+ functions in one file. Other service files demonstrate the pattern to follow. |
|
||||
| 4 | **Site selector components** | 4 components (`SiteSelector`, `SingleSiteSelector`, `SiteWithAllSitesSelector`, `SiteAndSectorSelector`) | Could be unified into 1 component with mode prop |
|
||||
| 5 | **Manual tables** | Sites/Dashboard, Automation pages, Billing pages, Thinker pages | Should use `Table` primitives or `TablePageTemplate` |
|
||||
| 6 | **Filter boilerplate** | 8 table pages each have ~50-100 lines of cascading filter state | Now handled by `TablePageTemplate` — older pages not migrated |
|
||||
| 7 | **Three-way route config** | `App.tsx` + `AppSidebar.tsx` + `routes.config.ts` | Navigation/routing defined in 3 places independently |
|
||||
| 8 | **Date formatting** | `dateUtils.ts` + `date.ts` | Overlapping `formatDateTime` functions |
|
||||
| 9 | **BulkWordPressPublish** | `WordPressPublish/` (custom UI) + `BulkWordPressPublish/` (MUI) | Two implementations using different UI libraries |
|
||||
| 10 | **Notification dropdown** | `NotificationDropdown` + `NotificationDropdownNew` | One appears stale |
|
||||
|
||||
### Inconsistent UI Patterns
|
||||
|
||||
| Pattern | Expected | Actual |
|
||||
|---------|----------|--------|
|
||||
| Icon library | `../../icons` (custom SVG) | `lucide-react` in `PaymentGatewaySelector`, `@mui/icons-material` in `BulkWordPressPublish/` |
|
||||
| Component library | Custom `ui/` | MUI (`@mui/material`) in `BulkWordPressPublish/` |
|
||||
| Import paths | Relative (`../../`) | `@/` alias in `PaymentGatewaySelector` |
|
||||
| Button usage | `<Button>` component | Raw `<button>` in error boundaries |
|
||||
| Table implementation | `Table` primitives or `TablePageTemplate` | Inline `<table>` in 4+ pages |
|
||||
| Form layout | Should use `FormPageTemplate` | Each settings page custom |
|
||||
| Tab component | `<Tabs>` from `ui/tabs/` | Inline tab implementations in 3+ pages |
|
||||
|
||||
### Components That Should Be Shared But Aren't
|
||||
|
||||
| Pattern | Pages Implementing It | Proposed Shared Component |
|
||||
|---------|----------------------|--------------------------|
|
||||
| Settings form page with sections + save bar | 13 settings/account pages | Adopt `FormPageTemplate` universally |
|
||||
| Status badge (content status) | 10+ pages | Already exists as `Badge` but inline styling still found |
|
||||
| Metric summary cards (4-up grid) | Dashboard, Billing, Automation | `MetricCardGrid` component |
|
||||
| Confirm + Execute pattern (modal → API → toast) | 20+ actions across pages | `useConfirmAction` hook |
|
||||
| Pagination + page size | Manual in 4+ pages | Already handled by `TablePageTemplate` — migrate remaining |
|
||||
|
||||
---
|
||||
|
||||
## 10. TailAdmin Pro Migration Plan
|
||||
|
||||
### TailAdmin Pro Components That Would Replace Existing
|
||||
|
||||
| TailAdmin Pro Component | Replaces Current | Priority |
|
||||
|------------------------|------------------|----------|
|
||||
| **Layout shell** (Sidebar, Header, Footer) | `layout/AppSidebar.tsx`, `layout/AppHeader.tsx`, `layout/Backdrop.tsx`, `layout/AppLayout.tsx` | Phase 1 |
|
||||
| **Button** (all variants) | `ui/button/Button.tsx` (🔒 STYLE LOCKED — needs careful migration) | Phase 1 |
|
||||
| **Modal/Dialog** | `ui/modal/index.tsx` | Phase 1 |
|
||||
| **Form inputs** (Input, TextArea, Select, Checkbox, Radio, Switch, FileInput) | All `form/input/` + `form/switch/` + `form/Select.tsx` | Phase 2 |
|
||||
| **Table** | `ui/table/index.tsx` (🔒 STYLE LOCKED) | Phase 2 |
|
||||
| **Badge** | `ui/badge/Badge.tsx` (🔒 STYLE LOCKED) | Phase 2 |
|
||||
| **Tabs** | `ui/tabs/Tabs.tsx` | Phase 2 |
|
||||
| **Alert** | `ui/alert/Alert.tsx` | Phase 2 |
|
||||
| **Breadcrumb** | `ui/breadcrumb/Breadcrumb.tsx` | Phase 3 |
|
||||
| **Dropdown** | `ui/dropdown/Dropdown.tsx` (custom portal — evaluate if Pro is better) | Phase 3 |
|
||||
| **Pagination** | `ui/pagination/Pagination.tsx` | Phase 3 |
|
||||
| **Avatar** | `ui/avatar/Avatar.tsx` | Phase 3 |
|
||||
| **Tooltip** | `ui/tooltip/Tooltip.tsx` (custom portal — may keep) | Phase 3 |
|
||||
| **Toast/Notification** | `ui/toast/ToastContainer.tsx` (custom system — evaluate) | Phase 3 |
|
||||
| **Progress Bar** | `ui/progress/ProgressBar.tsx` | Phase 3 |
|
||||
| **Accordion** | `ui/accordion/Accordion.tsx` | Phase 3 |
|
||||
| **Ribbon** | `ui/ribbon/Ribbon.tsx` | Phase 3 |
|
||||
| **List** | `ui/list/List.tsx` | Phase 3 |
|
||||
| **ButtonGroup** | `ui/button-group/ButtonGroup.tsx` | Phase 3 |
|
||||
| **Pricing Table** | `ui/pricing-table/PricingTable.tsx` + `index.tsx` | Phase 3 |
|
||||
| **Card** | `ui/card/Card.tsx` (custom system — evaluate) | Phase 3 |
|
||||
| **Spinner** | `ui/spinner/Spinner.tsx` | Phase 3 |
|
||||
| **Date Picker** | `form/date-picker.tsx` (flatpickr — Pro may include) | Phase 3 |
|
||||
| **Auth pages** | `AuthPages/SignIn.tsx`, `SignUp.tsx`, etc. | Phase 4 |
|
||||
| **Profile pages** | `UserProfile/*.tsx` | Phase 4 |
|
||||
| **DataView** | `ui/dataview/DataView.tsx` (custom SaaS — may not exist in Pro) | Evaluate |
|
||||
| **Video/Image demos** | `ui/videos/`, `ui/images/` | Delete — use Pro examples |
|
||||
|
||||
### Custom Components That MUST Be Kept
|
||||
|
||||
These contain IGNY8-specific business logic and have no TailAdmin Pro equivalent:
|
||||
|
||||
| Category | Components | Reason |
|
||||
|----------|-----------|--------|
|
||||
| **All 5 templates** | `TablePageTemplate`, `ContentViewTemplate`, `DashboardTemplate`, `FormPageTemplate`, `SystemPageTemplate` | Core IGNY8 page architecture — config-driven |
|
||||
| **All page configs** | 12 config files in `config/pages/` | Business-specific table/filter/action definitions |
|
||||
| **All config snippets** | `columns.snippets.ts`, `filters.snippets.ts`, `actions.snippets.ts` | Reusable table config pieces |
|
||||
| **All stores** | 11 Zustand stores | Application state management |
|
||||
| **All services/API** | 8 service files | Backend API integration |
|
||||
| **All hooks** | 9 custom hooks (except `useModal`) | Business logic hooks |
|
||||
| **All utils** | 14 utility files | Business helpers |
|
||||
| **Dashboard widgets** | 22 components | IGNY8 dashboard-specific |
|
||||
| **Billing components** | 17 components | Billing/payment business logic |
|
||||
| **Automation components** | 20 components | Automation pipeline UI |
|
||||
| **WordPress integration** | 15+ components across sites/, WordPressPublish/ | CMS integration |
|
||||
| **Onboarding wizard** | 7 components | IGNY8 setup flow |
|
||||
| **Content management** | ContentFilter, SourceBadge, SyncStatusBadge | Content workflow-specific |
|
||||
| **Keywords library** | 4 components | Keyword management UI |
|
||||
| **Module navigation** | ModuleNavigationTabs | IGNY8 module system |
|
||||
| **Header metrics** | HeaderMetrics, HeaderMetricsContext | Credit display system |
|
||||
| **Page context system** | PageContext, PageHeader, PageLoader | IGNY8 page metadata |
|
||||
| **Search modal** | SearchModal (⌘K) | App-wide search |
|
||||
| **Site selectors** | 4 site/sector selector variants | Multi-site architecture |
|
||||
| **All bulk action modals** | 8+ modal components | Workflow-specific bulk operations |
|
||||
| **Colors config** | `colors.config.ts` | Module color mapping system |
|
||||
| **CMS theme system** | `styles/cms/*.ts` | Published content theming |
|
||||
| **Icons** | `icons/index.ts`, `icons/lazy.ts` | Custom icon registry (may need to merge with Pro icons) |
|
||||
|
||||
### Migration Order (Least Risk First)
|
||||
|
||||
#### Phase 1: Layout Shell (Highest Impact, Moderate Risk)
|
||||
|
||||
1. **Install TailAdmin Pro** alongside existing code
|
||||
2. **Map design tokens**: Extract IGNY8's 6 base colors + `color-mix()` system → TailAdmin Pro Tailwind config
|
||||
3. **Migrate layout components**: Replace `AppLayout`, `AppSidebar`, `AppHeader`, `Backdrop` with Pro equivalents. Preserve:
|
||||
- Module-gated sidebar items
|
||||
- Credit metric badges in header
|
||||
- Site/sector selectors
|
||||
- Page context system
|
||||
4. **Migrate auth pages**: SignIn, SignUp, ForgotPassword, ResetPassword — closest to TailAdmin originals
|
||||
5. **Delete demo files**: `UIElements.tsx`, `Components.tsx`, `ui/videos/`, `ui/images/`
|
||||
|
||||
#### Phase 2: Core UI Primitives (High Impact, Higher Risk)
|
||||
|
||||
1. **Button** → Pro Button. Must preserve: tone/variant/size matrix, gradient variant, `🔒 STYLE LOCKED` contract. Create adapter if Pro API differs.
|
||||
2. **Table** → Pro Table. Must work with `TablePageTemplate` config system.
|
||||
3. **Form inputs** → Pro inputs. Must preserve: error/success/disabled states, `twMerge`+`clsx` composition.
|
||||
4. **Badge** → Pro Badge. Must preserve: 12 tones, 4 variants, backward-compat `color` prop.
|
||||
5. **Modal** → Pro Modal. Must preserve: portal rendering, ESC/backdrop close, scroll lock.
|
||||
6. **Tabs** → Pro Tabs.
|
||||
|
||||
#### Phase 3: Secondary UI Components (Lower Impact, Low Risk)
|
||||
|
||||
Breadcrumb, Dropdown, Pagination, Avatar, Tooltip, Toast, ProgressBar, Accordion, Ribbon, List, ButtonGroup, PricingTable, Card, Spinner, DatePicker.
|
||||
|
||||
Each is a drop-in replacement with prop mapping. Can be done component-by-component.
|
||||
|
||||
#### Phase 4: Page Templates & Cleanup
|
||||
|
||||
1. **Unify route definitions**: Consolidate `App.tsx`, `AppSidebar.tsx`, `routes.config.ts` into a single config
|
||||
2. **Migrate manual tables**: Convert 4+ pages with inline tables to `TablePageTemplate`
|
||||
3. **Consolidate settings pages**: Adopt `FormPageTemplate` across all settings/account pages
|
||||
4. **Clean up billing duplication**: Merge Settings billing + Billing pages + Account pages
|
||||
5. **Split `api.ts`**: Break into `planner.api.ts`, `writer.api.ts`, `sites.api.ts`, `settings.api.ts`, `keywords-library.api.ts`
|
||||
6. **Consolidate site selectors**: Unify 4 variants into 1 configurable component
|
||||
7. **Remove stale components**: `ModuleGuard` (deprecated), `NotificationDropdownNew` (likely stale), `BulkWordPressPublish/` (MUI version)
|
||||
8. **Merge date utils**: Consolidate `dateUtils.ts` + `date.ts`
|
||||
9. **Fix MUI contamination**: Replace `@mui/material` usage in `BulkWordPressPublish/` with custom UI components
|
||||
|
||||
### Color Scheme Extraction for TailAdmin Pro Config
|
||||
|
||||
```
|
||||
Primary: #1c86d1 → brand-* scale (via color-mix)
|
||||
Success: #3fcd9f → success-* scale
|
||||
Warning: #f87f4c → warning-* scale
|
||||
Danger: #ff656f → error-* scale
|
||||
Info: #18b2c4 → info-* scale
|
||||
Gray Base: #243249 → gray-* scale
|
||||
|
||||
Font: Outfit (Google Fonts)
|
||||
Dark mode: class strategy on <html>
|
||||
```
|
||||
|
||||
These 6 hex values + the `color-mix()` derivation system are the **only** color configuration needed in TailAdmin Pro's theme config. All other colors are computed.
|
||||
|
||||
### Estimated Scope Per Section
|
||||
|
||||
| Phase | Components Affected | Files to Modify | Risk Level |
|
||||
|-------|-------------------|-----------------|------------|
|
||||
| Phase 1: Layout Shell | 10 | ~20 | ⚠️ Moderate — touches every page via layout |
|
||||
| Phase 2: Core Primitives | 15 | ~80 (imports update) | ⚠️ High — Button/Table/Badge used everywhere |
|
||||
| Phase 3: Secondary UI | 15 | ~30 | ✅ Low — isolated replacements |
|
||||
| Phase 4: Cleanup | 30+ | ~50 | ⚠️ Moderate — structural changes |
|
||||
| **Total** | **~70 UI components** | **~180 files** | |
|
||||
|
||||
### Pre-Migration Checklist
|
||||
|
||||
- [ ] Obtain TailAdmin Pro React bundle
|
||||
- [ ] Verify TailAdmin Pro supports Tailwind CSS 4 (CSS-first config)
|
||||
- [ ] Map Pro component API to current component prop interfaces
|
||||
- [ ] Create component adapter layer for breaking changes
|
||||
- [ ] Set up visual regression testing (screenshots before/after)
|
||||
- [ ] Document all `🔒 STYLE LOCKED` components — these need extra care
|
||||
- [ ] Back up current `design-system.css` and all 6 hex tokens
|
||||
- [ ] Plan for icon merging (current custom SVGs + Pro icons)
|
||||
- [ ] Audit `@theme` block compatibility between current CSS and Pro CSS
|
||||
- [ ] Remove `@mui/material` dependency before migration starts
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Third-Party Dependencies (UI-Related)
|
||||
|
||||
| Package | Version | Used By | Notes |
|
||||
|---------|---------|---------|-------|
|
||||
| `react` | ^19.0.0 | All | Core framework |
|
||||
| `react-dom` | ^19.0.0 | All | DOM rendering |
|
||||
| `react-router-dom` | ^7.9.5 | Routing, Link | v7 — latest |
|
||||
| `zustand` | ^5.0.8 | 11 stores | State management |
|
||||
| `tailwind-merge` | ^3.0.1 | Button, IconButton | Class dedup |
|
||||
| `clsx` | ^2.1.1 | 10+ components | Conditional classes |
|
||||
| `apexcharts` / `react-apexcharts` | ^4.1.0 / ^1.7.0 | Charts | Dashboard widgets |
|
||||
| `@fullcalendar/*` | ^6.1.15 | ContentCalendar | Calendar view |
|
||||
| `flatpickr` | ^4.6.13 | DatePicker | Date picker |
|
||||
| `react-dropzone` | ^14.3.5 | DropZone | File upload |
|
||||
| `react-dnd` / `react-dnd-html5-backend` | ^16.0.1 | KanbanBoard | Drag-and-drop |
|
||||
| `swiper` | ^11.2.3 | Marketing site | Carousel |
|
||||
| `@react-jvectormap/*` | ^1.0.4 / ^1.1.2 | Map widgets | Vector maps |
|
||||
| `react-helmet-async` | ^2.0.5 | PageMeta/SEO | Head management |
|
||||
| `lucide-react` | ^0.554.0 | PaymentGatewaySelector only | **Should not be used** — use `icons/` |
|
||||
| `@heroicons/react` | ^2.2.0 | Icons (via icons/) | SVG icon source |
|
||||
|
||||
## Appendix B: File Count by Directory
|
||||
|
||||
| Directory | .tsx/.ts Files |
|
||||
|-----------|---------------|
|
||||
| `components/ui/` | 52 |
|
||||
| `components/common/` | 51 |
|
||||
| `components/dashboard/` | 22 |
|
||||
| `components/Automation/` | 20 |
|
||||
| `components/billing/` | 17 |
|
||||
| `components/form/` | 25 |
|
||||
| `components/sites/` | 11 |
|
||||
| `components/onboarding/` | 7 |
|
||||
| `components/header/` | 6 |
|
||||
| `components/WordPressPublish/` | 4 |
|
||||
| `components/auth/` | 4 |
|
||||
| `components/tasks/` | 4 |
|
||||
| `components/keywords-library/` | 5 |
|
||||
| `components/content/` | 4 |
|
||||
| `components/integration/` | 3 |
|
||||
| `components/optimizer/` | 2 |
|
||||
| `components/shared/` | 22 |
|
||||
| `components/UserProfile/` | 3 |
|
||||
| `components/navigation/` | 1 |
|
||||
| `components/publishing/` | 1 |
|
||||
| `components/linker/` | 1 |
|
||||
| `components/BulkWordPressPublish/` | 1 |
|
||||
| `pages/` | 68 |
|
||||
| `store/` | 11 |
|
||||
| `services/` | 6 |
|
||||
| `api/` | 2 |
|
||||
| `config/` | 24 |
|
||||
| `templates/` | 5 |
|
||||
| `hooks/` | 9 |
|
||||
| `utils/` | 14 |
|
||||
| `context/` | 5 |
|
||||
| `layout/` | 5 |
|
||||
| `icons/` | 2 |
|
||||
| `styles/` | 5 |
|
||||
| `marketing/` | 22 |
|
||||
| `constants/` | 1 |
|
||||
| `types/` | 1 |
|
||||
| Other (main.tsx, App.tsx, etc.) | 4 |
|
||||
| **Total** | **483** |
|
||||
@@ -0,0 +1,596 @@
|
||||
# IGNY8 ↔ WordPress Integration Complete Security Audit
|
||||
|
||||
**Audit Date:** 2026-01-13
|
||||
**Auditor:** System Audit (Documentation Calibrated)
|
||||
**Scope:** Backend (Django) + WordPress Plugin + Documentation Review
|
||||
**Status:** COMPLETE - CALIBRATED WITH DOCUMENTATION
|
||||
|
||||
---
|
||||
|
||||
## EXECUTIVE SUMMARY
|
||||
|
||||
This comprehensive audit examined the IGNY8 backend and WordPress plugin integration system, cross-referencing against all existing documentation. The audit identified **17 security vulnerabilities** (3 critical, 5 high, 6 medium, 3 low), **6 unused/redundant database fields**, **2 major data duplication issues**, and several **documentation vs. implementation gaps**.
|
||||
|
||||
**Key Findings:**
|
||||
- API keys stored in plain text (no encryption) despite documentation claims
|
||||
- Public endpoints expose sensitive configuration data
|
||||
- Timing attack vulnerability in Bearer token validation
|
||||
- Documentation states "credentials encrypted at rest" but encryption NOT implemented
|
||||
- Several fields documented but never used
|
||||
- Missing scheduled publishing task from Celery Beat (documented but not configured)
|
||||
|
||||
---
|
||||
|
||||
## PART 1: DOCUMENTATION VS IMPLEMENTATION GAPS
|
||||
|
||||
### 1.1 Critical Discrepancies Found
|
||||
|
||||
| Documentation Claim | Reality | Impact |
|
||||
|---------------------|---------|--------|
|
||||
| "API keys encrypted at rest" (INTEGRATIONS.md) | Plain text CharField in database | **CRITICAL** - False security assumption |
|
||||
| "Webhook signature verification" (INTEGRATIONS.md) | No signature verification implemented | **HIGH** - Anyone with key can spoof webhooks |
|
||||
| "Rate limiting on webhook endpoint" (INTEGRATIONS.md) | Webhooks explicitly disable throttling (`NoThrottle`) | **HIGH** - DoS possible |
|
||||
| "auto_publish_enabled triggers WordPress push" | Automation Stage 7 sets status but does NOT trigger WP push | **MEDIUM** - Manual publish required |
|
||||
| "Scheduled auto-publish task active" | Task exists but NOT in Celery Beat schedule | **MEDIUM** - Content sits unpublished |
|
||||
| SiteIntegration.api_key field (INTEGRATIONS.md line 97) | API key stored in Site.wp_api_key, NOT SiteIntegration | Documentation outdated |
|
||||
| "credentials_json stores WordPress credentials" | credentials_json is empty for WordPress; Site.wp_api_key is source of truth | Documentation outdated |
|
||||
|
||||
### 1.2 Documentation Accuracy Summary
|
||||
|
||||
| Document | Accuracy | Issues Found |
|
||||
|----------|----------|--------------|
|
||||
| WORDPRESS-INTEGRATION-FLOW.md | 90% | Accurate on auth, slight gaps on scheduling |
|
||||
| WORDPRESS-INTEGRATION.md | 85% | Missing encryption truth, good on plugin distribution |
|
||||
| PUBLISHER.md | 95% | Accurate on models and flows |
|
||||
| INTEGRATIONS.md | 70% | Contains outdated field references, false encryption claim |
|
||||
| SCHEDULED-CONTENT-PUBLISHING.md | 95% | Accurate on Celery tasks, correctly notes site_status vs status |
|
||||
| CONTENT-PIPELINE.md | 90% | Accurate on stages, slight gap on Stage 8 auto-publish |
|
||||
|
||||
---
|
||||
|
||||
## PART 2: SYSTEM ARCHITECTURE (VERIFIED)
|
||||
|
||||
### 2.1 Actual Data Flow (Verified Against Code)
|
||||
|
||||
```
|
||||
IGNY8 Backend WordPress Plugin
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ Site Model │ │ wp_options │
|
||||
│ ├─ wp_api_key ◄──────┼────────────────┼─► igny8_api_key │
|
||||
│ │ (SINGLE SOURCE) │ │ igny8_site_id │
|
||||
│ ├─ domain │ │ igny8_integration_id│
|
||||
│ └─ wp_url (LEGACY) │ │ │
|
||||
│ │ │ Post Meta │
|
||||
│ SiteIntegration │ │ ├─ _igny8_content_id │
|
||||
│ ├─ config_json │ │ ├─ _igny8_task_id │
|
||||
│ │ (site_url only) │ │ └─ _igny8_last_synced│
|
||||
│ ├─ credentials_json │ │ │
|
||||
│ │ (EMPTY for WP!) │ │ │
|
||||
│ └─ sync_status │ │ │
|
||||
│ │ │ │
|
||||
│ Content Model │ │ │
|
||||
│ ├─ external_id ◄─────┼────────────────┼─► post_id │
|
||||
│ ├─ external_url │ │ │
|
||||
│ ├─ status (editorial)│ │ │
|
||||
│ └─ site_status │ │ │
|
||||
│ (publishing) │ │ │
|
||||
└──────────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 Authentication Flow (Verified)
|
||||
|
||||
**IGNY8 → WordPress:**
|
||||
```python
|
||||
# publisher_service.py (line 136, 144)
|
||||
destination_config = {
|
||||
'api_key': site.wp_api_key, # FROM SITE MODEL
|
||||
'site_url': site.domain or site.wp_url # Fallback to legacy
|
||||
}
|
||||
|
||||
# wordpress_adapter.py
|
||||
headers = {
|
||||
'X-IGNY8-API-KEY': api_key, # Primary method
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
```
|
||||
|
||||
**WordPress → IGNY8:**
|
||||
```php
|
||||
// Plugin: check_permission() method
|
||||
$stored_api_key = get_option('igny8_api_key');
|
||||
$header_api_key = $request->get_header('x-igny8-api-key');
|
||||
|
||||
// CORRECT: hash_equals for X-IGNY8-API-KEY
|
||||
if (hash_equals($stored_api_key, $header_api_key)) return true;
|
||||
|
||||
// VULNERABLE: strpos for Bearer token
|
||||
if (strpos($auth_header, 'Bearer ' . $stored_api_key) !== false) return true;
|
||||
```
|
||||
|
||||
### 2.3 API Key Generation (Verified)
|
||||
|
||||
```
|
||||
POST /api/v1/integration/integrations/generate-api-key/
|
||||
Body: { "site_id": 123 }
|
||||
|
||||
Key Format: igny8_site_{site_id}_{timestamp_ms}_{random_10_chars}
|
||||
Example: igny8_site_123_1736780400000_a7b9c3d2e1
|
||||
|
||||
Storage: Site.wp_api_key (CharField, plain text, max 255)
|
||||
Recovery: NOT POSSIBLE - shown once on generation
|
||||
Revocation: Sets Site.wp_api_key = None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PART 3: CONTENT STATUS SYSTEM (DOCUMENTED CORRECTLY)
|
||||
|
||||
### 3.1 Two-Status Architecture
|
||||
|
||||
The documentation correctly describes the dual-status system:
|
||||
|
||||
| Field | Purpose | Values |
|
||||
|-------|---------|--------|
|
||||
| `Content.status` | Editorial workflow | draft → review → approved → (published - legacy) |
|
||||
| `Content.site_status` | WordPress publishing | not_published → scheduled → publishing → published / failed |
|
||||
|
||||
### 3.2 Publishing Flow Paths
|
||||
|
||||
**Path 1: Manual Publish (Working)**
|
||||
```
|
||||
User clicks "Publish" → ContentViewSet.publish_to_wordpress
|
||||
→ Celery task: publish_content_to_wordpress
|
||||
→ WordPress API call
|
||||
→ Update external_id, external_url, site_status='published'
|
||||
```
|
||||
|
||||
**Path 2: Scheduled Publish (Partially Working)**
|
||||
```
|
||||
schedule_approved_content (hourly)
|
||||
→ Find approved content with site_status='not_published'
|
||||
→ Assign scheduled_publish_at
|
||||
→ Set site_status='scheduled'
|
||||
|
||||
process_scheduled_publications (every 5 min)
|
||||
→ Find content where scheduled_publish_at <= now
|
||||
→ Queue publish_content_to_wordpress task
|
||||
→ WordPress API call
|
||||
```
|
||||
|
||||
**Path 3: Automation Stage 7 (NOT TRIGGERING WP PUSH)**
|
||||
```
|
||||
Automation Stage 7:
|
||||
→ Content.status = 'published' (legacy status change)
|
||||
→ NO site_status change
|
||||
→ NO WordPress API call queued
|
||||
→ Content sits with site_status='not_published'
|
||||
```
|
||||
|
||||
**GAP IDENTIFIED:** Automation Stage 7 does NOT call publishing scheduler or queue WordPress task.
|
||||
|
||||
---
|
||||
|
||||
## PART 4: SECURITY VULNERABILITIES (VERIFIED)
|
||||
|
||||
### 4.1 CRITICAL ISSUES
|
||||
|
||||
#### CRITICAL-1: API Keys Stored in Plain Text
|
||||
|
||||
**Location:** Backend - `Site.wp_api_key` field (auth/models.py line 491)
|
||||
**Documentation Claim:** "Credentials encrypted at rest" (INTEGRATIONS.md line 345)
|
||||
**Reality:** CharField stores plain text - NO encryption
|
||||
|
||||
**Evidence:**
|
||||
```python
|
||||
# auth/models.py
|
||||
wp_api_key = models.CharField(max_length=255, blank=True, null=True,
|
||||
help_text="API key for WordPress integration via IGNY8 WP Bridge plugin")
|
||||
```
|
||||
|
||||
**Impact:** Database compromise exposes ALL WordPress API keys
|
||||
**Risk Score:** 9/10
|
||||
|
||||
---
|
||||
|
||||
#### CRITICAL-2: Timing Attack in Bearer Token Validation
|
||||
|
||||
**Location:** Plugin - `class-igny8-rest-api.php:140`
|
||||
**Impact:** API key can be guessed character-by-character
|
||||
|
||||
**Vulnerable Code:**
|
||||
```php
|
||||
// Uses strpos (VULNERABLE)
|
||||
if (strpos($auth_header, 'Bearer ' . $stored_api_key) !== false)
|
||||
|
||||
// Should use hash_equals (SAFE)
|
||||
if (hash_equals($stored_api_key, substr($auth_header, 7)))
|
||||
```
|
||||
|
||||
**Risk Score:** 8/10
|
||||
|
||||
---
|
||||
|
||||
#### CRITICAL-3: Diagnostic Logging Exposes Sensitive Data
|
||||
|
||||
**Location:** Plugin - `class-igny8-rest-api.php:533-565`
|
||||
**Impact:** Full request bodies logged including all content
|
||||
|
||||
**Evidence:**
|
||||
```php
|
||||
error_log('========== RAW REQUEST BODY ==========');
|
||||
error_log($raw_body);
|
||||
error_log('========== PARSED JSON DATA ==========');
|
||||
error_log(print_r($content_data, true));
|
||||
```
|
||||
|
||||
**Risk Score:** 8/10
|
||||
|
||||
---
|
||||
|
||||
### 4.2 HIGH SEVERITY ISSUES
|
||||
|
||||
#### HIGH-1: Public Endpoints Expose Configuration
|
||||
|
||||
**Verified Endpoints:**
|
||||
| Endpoint | Permission | Data Exposed |
|
||||
|----------|------------|--------------|
|
||||
| `/wp-json/igny8/v1/status` | `__return_true` (PUBLIC) | has_api_key, connected, versions |
|
||||
| `/wp-json/igny8/v1/site-metadata/` | `__return_true` (PUBLIC) | post_types, taxonomies, counts |
|
||||
|
||||
**Note:** Documentation (WORDPRESS-INTEGRATION-FLOW.md line 86-91) does NOT flag these as security issues.
|
||||
|
||||
---
|
||||
|
||||
#### HIGH-2: Permission Architecture Inconsistency
|
||||
|
||||
**Location:** `test_connection_collection` endpoint
|
||||
**Permission:** `AllowAny` (then checks auth manually inside view)
|
||||
|
||||
**Documentation:** Does not mention this inconsistency.
|
||||
|
||||
---
|
||||
|
||||
#### HIGH-3: No Webhook Signature Verification
|
||||
|
||||
**Documentation Claim:** "Optional signature verification" (INTEGRATIONS.md line 349)
|
||||
**Reality:** Only API key validation, NO HMAC signature verification
|
||||
|
||||
**Code Evidence (webhooks.py):**
|
||||
```python
|
||||
api_key = request.headers.get('X-IGNY8-API-KEY')
|
||||
if not stored_api_key or stored_api_key != api_key:
|
||||
return error_response('Invalid API key', ...)
|
||||
# NO signature validation
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### HIGH-4: Webhooks Disable Rate Limiting
|
||||
|
||||
**Documentation Claim:** "Rate limiting on webhook endpoint" (INTEGRATIONS.md line 351)
|
||||
**Reality:** Webhooks explicitly use `NoThrottle` class
|
||||
|
||||
---
|
||||
|
||||
#### HIGH-5: Encryption Falls Back Silently
|
||||
|
||||
**Location:** Plugin - `functions.php:31-48`
|
||||
**Impact:** Admin unaware if encryption fails
|
||||
|
||||
---
|
||||
|
||||
### 4.3 MEDIUM SEVERITY ISSUES
|
||||
|
||||
1. **SSRF Risk in Connection Testing** - No IP validation
|
||||
2. **credentials_json Exposed in Serializer** - All fields returned
|
||||
3. **Flexible ID Lookup Enables Enumeration** - `/post-status/{id}` accepts both IDs
|
||||
4. **Excessive API Request Logging** - Keys in logs
|
||||
5. **WordPress URL Not Validated** - No HTTPS enforcement
|
||||
6. **API Key Partially Visible** - `/verify-key` shows 15-char prefix
|
||||
|
||||
### 4.4 LOW SEVERITY ISSUES
|
||||
|
||||
1. Version disclosure in multiple endpoints
|
||||
2. No CORS headers defined on plugin endpoints
|
||||
3. Webhook logs stored long-term (500 entries)
|
||||
|
||||
---
|
||||
|
||||
## PART 5: REST API ENDPOINTS INVENTORY (VERIFIED)
|
||||
|
||||
### 5.1 Backend Endpoints (Verified Against urls.py)
|
||||
|
||||
| Endpoint | Method | Permission | Documentation |
|
||||
|----------|--------|------------|---------------|
|
||||
| `/api/v1/integration/integrations/` | CRUD | IsAuthenticatedAndActive + IsEditorOrAbove | Correct |
|
||||
| `/api/v1/integration/integrations/test-connection/` (collection) | POST | **AllowAny** | NOT documented as AllowAny |
|
||||
| `/api/v1/integration/integrations/{id}/test-connection/` | POST | Authenticated | Correct |
|
||||
| `/api/v1/integration/integrations/generate-api-key/` | POST | Authenticated | Correct |
|
||||
| `/api/v1/integration/integrations/revoke-api-key/` | POST | Authenticated | Correct |
|
||||
| `/api/v1/integration/webhooks/wordpress/status/` | POST | AllowAny (header auth) | Correct |
|
||||
| `/api/v1/integration/webhooks/wordpress/metadata/` | POST | AllowAny (header auth) | Correct |
|
||||
| `/api/v1/publisher/publish/` | POST | Authenticated | Correct |
|
||||
| `/api/v1/writer/content/{id}/publish_to_wordpress/` | POST | Authenticated | Correct |
|
||||
| `/api/v1/writer/content/{id}/schedule/` | POST | Authenticated | Correct (v1.3.2) |
|
||||
| `/api/v1/writer/content/{id}/unschedule/` | POST | Authenticated | Correct (v1.3.2) |
|
||||
|
||||
### 5.2 Plugin Endpoints (Verified Against class-igny8-rest-api.php)
|
||||
|
||||
| Endpoint | Method | Permission | Documentation Match |
|
||||
|----------|--------|------------|---------------------|
|
||||
| `/wp-json/igny8/v1/status` | GET | **PUBLIC** | Not flagged as security issue |
|
||||
| `/wp-json/igny8/v1/site-metadata/` | GET | **PUBLIC** (internal check) | Not flagged |
|
||||
| `/wp-json/igny8/v1/verify-key` | GET | check_permission | Correct |
|
||||
| `/wp-json/igny8/v1/publish` | POST | check_permission | Correct |
|
||||
| `/wp-json/igny8/v1/post-by-content-id/{id}` | GET | check_permission | Correct |
|
||||
| `/wp-json/igny8/v1/post-by-task-id/{id}` | GET | check_permission | Correct |
|
||||
| `/wp-json/igny8/v1/post-status/{id}` | GET | check_permission | Correct |
|
||||
| `/wp-json/igny8/v1/event` | POST | verify_webhook_secret | Correct |
|
||||
|
||||
---
|
||||
|
||||
## PART 6: DATA STORAGE ANALYSIS (VERIFIED)
|
||||
|
||||
### 6.1 Site Model Fields (auth/models.py)
|
||||
|
||||
| Field | Documentation Status | Actual Usage | Action |
|
||||
|-------|---------------------|--------------|--------|
|
||||
| `wp_api_key` | Documented as primary | ACTIVE - single source of truth | KEEP + ENCRYPT |
|
||||
| `domain` | Documented | ACTIVE - WordPress URL | KEEP |
|
||||
| `wp_url` | Documented as "deprecated" | LEGACY - fallback in publisher_service | EVALUATE for removal |
|
||||
| `wp_username` | Documented as "deprecated" | **ZERO USAGE** in codebase | **REMOVE** |
|
||||
| `wp_app_password` | Documented as "deprecated" | **ZERO USAGE** in codebase | **REMOVE** |
|
||||
|
||||
### 6.2 SiteIntegration Model Fields
|
||||
|
||||
| Field | Documentation Status | Actual Usage | Action |
|
||||
|-------|---------------------|--------------|--------|
|
||||
| `site` | Documented | ACTIVE | KEEP |
|
||||
| `platform` | Documented | ACTIVE ('wordpress') | KEEP |
|
||||
| `config_json` | Documented as storing URL | ACTIVE (site_url only) | KEEP |
|
||||
| `credentials_json` | Documented as storing creds | **EMPTY** for WordPress | Documentation outdated |
|
||||
| `sync_status` | Documented | ACTIVE | KEEP |
|
||||
|
||||
**Documentation Gap:** INTEGRATIONS.md line 97-98 shows `api_key` and `username` as SiteIntegration fields, but WordPress actually uses Site.wp_api_key.
|
||||
|
||||
### 6.3 Content Model Fields
|
||||
|
||||
| Field | Documentation Status | Actual Usage | Action |
|
||||
|-------|---------------------|--------------|--------|
|
||||
| `status` | Correctly documented as editorial | ACTIVE | KEEP |
|
||||
| `site_status` | Correctly documented as publishing | ACTIVE | KEEP |
|
||||
| `external_id` | Documented | ACTIVE - WordPress post ID | KEEP |
|
||||
| `external_url` | Documented | ACTIVE - WordPress URL | KEEP |
|
||||
| `external_type` | Not documented | **NEVER USED** | **REMOVE** |
|
||||
| `external_metadata` | Not documented | Only set to {} | **REMOVE** |
|
||||
| `sync_status` (on Content) | Not documented | **NEVER USED** | **REMOVE** |
|
||||
|
||||
### 6.4 ContentTaxonomy Model Fields
|
||||
|
||||
| Field | Documentation Status | Actual Usage | Action |
|
||||
|-------|---------------------|--------------|--------|
|
||||
| `external_id` | Documented | ACTIVE | KEEP |
|
||||
| `external_taxonomy` | Documented | ACTIVE | KEEP |
|
||||
| `sync_status` | Not documented | **NEVER USED** | **REMOVE** |
|
||||
|
||||
---
|
||||
|
||||
## PART 7: CELERY TASKS STATUS (VERIFIED)
|
||||
|
||||
### 7.1 Scheduled Tasks (celery.py Beat Schedule)
|
||||
|
||||
| Task | Schedule | Documentation | Status |
|
||||
|------|----------|---------------|--------|
|
||||
| `schedule_approved_content` | Every hour | Documented (SCHEDULED-CONTENT-PUBLISHING.md) | **ACTIVE** |
|
||||
| `process_scheduled_publications` | Every 5 min | Documented | **ACTIVE** |
|
||||
| `publish_content_to_wordpress` | On-demand | Documented | **ACTIVE** |
|
||||
|
||||
### 7.2 Missing Tasks (Documented but NOT Scheduled)
|
||||
|
||||
**`process_pending_wordpress_publications`** - WORDPRESS-INTEGRATION-FLOW.md mentions this task exists but notes:
|
||||
> "CURRENT STATUS: This task is NOT in Celery Beat schedule!"
|
||||
|
||||
This matches the documentation correctly.
|
||||
|
||||
---
|
||||
|
||||
## PART 8: PLUGIN DISTRIBUTION SYSTEM (VERIFIED)
|
||||
|
||||
### 8.1 Plugin Models (Correctly Documented)
|
||||
|
||||
| Model | Documentation | Implementation | Match |
|
||||
|-------|---------------|----------------|-------|
|
||||
| `Plugin` | WORDPRESS-INTEGRATION.md | plugins/models.py | ✅ |
|
||||
| `PluginVersion` | Documented with all fields | Matches implementation | ✅ |
|
||||
| `PluginInstallation` | Documented | Matches implementation | ✅ |
|
||||
| `PluginDownload` | Documented | Matches implementation | ✅ |
|
||||
|
||||
### 8.2 Plugin API Endpoints (Correctly Documented)
|
||||
|
||||
| Endpoint | Documentation | Implementation |
|
||||
|----------|---------------|----------------|
|
||||
| `/api/plugins/{slug}/download/` | INDEX.md line 24 | ✅ Working |
|
||||
| `/api/plugins/{slug}/check-update/` | INDEX.md line 25 | ✅ Working |
|
||||
| `/api/plugins/{slug}/info/` | INDEX.md line 26 | ✅ Working |
|
||||
| `/api/plugins/{slug}/register/` | INDEX.md line 27 | ✅ Working |
|
||||
| `/api/plugins/{slug}/health-check/` | INDEX.md line 28 | ✅ Working |
|
||||
|
||||
---
|
||||
|
||||
## PART 9: UNUSED/DEAD CODE INVENTORY
|
||||
|
||||
### 9.1 Fields to Remove (Verified Zero Usage)
|
||||
|
||||
| Model | Field | Evidence |
|
||||
|-------|-------|----------|
|
||||
| Site | `wp_username` | `grep -r "wp_username" --include="*.py"` = 0 results |
|
||||
| Site | `wp_app_password` | `grep -r "wp_app_password" --include="*.py"` = 0 results |
|
||||
| Content | `external_type` | Never read in codebase |
|
||||
| Content | `external_metadata` | Only set to {} in publisher_service.py |
|
||||
| Content | `sync_status` | SiteIntegration.sync_status used instead |
|
||||
| ContentTaxonomy | `sync_status` | Never read or written |
|
||||
|
||||
### 9.2 Redundant Code Paths
|
||||
|
||||
1. **Duplicate Connection Testing** - Two endpoints with same logic
|
||||
2. **Duplicate API Key Validation** - Same validation in 4+ files
|
||||
3. **Dead Admin Bulk Actions** - `bulk_trigger_sync()`, `bulk_test_connection()` have TODO comments
|
||||
|
||||
### 9.3 Plugin Unused Options
|
||||
|
||||
| Option | Status |
|
||||
|--------|--------|
|
||||
| `igny8_access_token` | Redundant with igny8_api_key |
|
||||
| `igny8_access_token_issued` | Referenced but not used in auth |
|
||||
|
||||
---
|
||||
|
||||
## PART 10: DATA DUPLICATION ISSUES
|
||||
|
||||
### 10.1 API Key Duplication
|
||||
|
||||
| Location | Field | Role |
|
||||
|----------|-------|------|
|
||||
| Django Site model | `wp_api_key` | **PRIMARY** (single source of truth) |
|
||||
| Django SiteIntegration | `credentials_json` | EMPTY for WordPress |
|
||||
| WordPress | `igny8_api_key` | COPY (must match primary) |
|
||||
| WordPress | `igny8_access_token` | REDUNDANT (should remove) |
|
||||
|
||||
**Risk:** Documentation mentions credentials_json but WordPress doesn't use it.
|
||||
|
||||
### 10.2 URL Duplication
|
||||
|
||||
| Location | Field | Role |
|
||||
|----------|-------|------|
|
||||
| Site.domain | Primary | ACTIVE |
|
||||
| Site.wp_url | Legacy | Fallback only |
|
||||
| SiteIntegration.config_json['site_url'] | Configuration | ACTIVE |
|
||||
|
||||
---
|
||||
|
||||
## PART 11: RECOMMENDATIONS
|
||||
|
||||
### Immediate Actions (Critical)
|
||||
|
||||
| Priority | Issue | Action | Documentation Update |
|
||||
|----------|-------|--------|---------------------|
|
||||
| 1 | Plain text API keys | Implement field encryption | Update INTEGRATIONS.md |
|
||||
| 2 | Timing attack (strpos) | Use hash_equals everywhere | Update plugin docs |
|
||||
| 3 | Diagnostic logging | Remove or conditional | Update plugin docs |
|
||||
| 4 | Public endpoints | Secure /status and /site-metadata | Update WORDPRESS-INTEGRATION-FLOW.md |
|
||||
|
||||
### Short-term Actions (High)
|
||||
|
||||
| Priority | Issue | Action | Documentation Update |
|
||||
|----------|-------|--------|---------------------|
|
||||
| 5 | Permission inconsistency | Fix test_connection_collection | Update ENDPOINTS.md |
|
||||
| 6 | No webhook signatures | Implement HMAC verification | Update INTEGRATIONS.md |
|
||||
| 7 | No rate limiting | Enable throttling on webhooks | Update INTEGRATIONS.md |
|
||||
| 8 | API key in response | Remove from verify-key | N/A |
|
||||
|
||||
### Documentation Updates Required
|
||||
|
||||
| Document | Updates Needed |
|
||||
|----------|----------------|
|
||||
| INTEGRATIONS.md | Remove "encrypted at rest" claim, fix field references |
|
||||
| WORDPRESS-INTEGRATION-FLOW.md | Flag public endpoints as security concern |
|
||||
| ENDPOINTS.md | Note AllowAny on test-connection collection |
|
||||
|
||||
### Cleanup Actions
|
||||
|
||||
| Action | Fields to Remove | Migration Required |
|
||||
|--------|------------------|-------------------|
|
||||
| Remove from Site | wp_username, wp_app_password | Yes |
|
||||
| Remove from Content | external_type, external_metadata, sync_status | Yes |
|
||||
| Remove from ContentTaxonomy | sync_status | Yes |
|
||||
| Remove from Plugin | igny8_access_token option | Plugin update |
|
||||
|
||||
---
|
||||
|
||||
## PART 12: PUBLISHING WORKFLOW SUMMARY
|
||||
|
||||
### What's Working
|
||||
|
||||
| Flow | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Manual publish button | ✅ Working | ContentViewSet.publish_to_wordpress |
|
||||
| Scheduled publishing | ✅ Working | Celery Beat tasks active |
|
||||
| Plugin distribution | ✅ Working | Auto-update mechanism functional |
|
||||
| Webhook status sync | ✅ Working | WordPress → IGNY8 updates |
|
||||
|
||||
### What's NOT Working
|
||||
|
||||
| Flow | Status | Issue |
|
||||
|------|--------|-------|
|
||||
| Automation Stage 7 → WP | ❌ Broken | Sets status but no WP push |
|
||||
| Content update sync | ❌ Missing | No republish capability |
|
||||
| WordPress → IGNY8 import | ❌ Missing | No pull sync feature |
|
||||
|
||||
### Documented But Not Implemented
|
||||
|
||||
| Feature | Documentation Reference | Status |
|
||||
|---------|------------------------|--------|
|
||||
| Webhook signature verification | INTEGRATIONS.md line 349 | NOT implemented |
|
||||
| Webhook rate limiting | INTEGRATIONS.md line 351 | NOT implemented |
|
||||
| Credential encryption | INTEGRATIONS.md line 345 | NOT implemented |
|
||||
|
||||
---
|
||||
|
||||
## APPENDIX A: FILES AUDITED
|
||||
|
||||
### Backend Files
|
||||
- `backend/igny8_core/auth/models.py` - Site model, wp_api_key
|
||||
- `backend/igny8_core/business/integration/models.py` - SiteIntegration, SyncEvent
|
||||
- `backend/igny8_core/business/publishing/models.py` - PublishingRecord
|
||||
- `backend/igny8_core/business/content/models.py` - Content, external fields
|
||||
- `backend/igny8_core/modules/integration/views.py` - API endpoints
|
||||
- `backend/igny8_core/modules/integration/webhooks.py` - Webhook handlers
|
||||
- `backend/igny8_core/business/publishing/services/publisher_service.py`
|
||||
- `backend/igny8_core/business/publishing/services/adapters/wordpress_adapter.py`
|
||||
- `backend/igny8_core/tasks/publishing_scheduler.py` - Celery tasks
|
||||
- `backend/igny8_core/tasks/wordpress_publishing.py` - Publishing task
|
||||
- `backend/igny8_core/celery.py` - Beat schedule
|
||||
|
||||
### Plugin Files
|
||||
- `plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-rest-api.php`
|
||||
- `plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-api.php`
|
||||
- `plugins/wordpress/source/igny8-wp-bridge/includes/class-igny8-webhooks.php`
|
||||
- `plugins/wordpress/source/igny8-wp-bridge/includes/functions.php`
|
||||
|
||||
### Documentation Files Reviewed
|
||||
- `docs/60-PLUGINS/WORDPRESS-INTEGRATION.md`
|
||||
- `docs/60-PLUGINS/INDEX.md`
|
||||
- `docs/60-PLUGINS/PLUGIN-UPDATE-WORKFLOW.md`
|
||||
- `docs/50-DEPLOYMENT/WORDPRESS-INTEGRATION-FLOW.md`
|
||||
- `docs/10-MODULES/PUBLISHER.md`
|
||||
- `docs/10-MODULES/INTEGRATIONS.md`
|
||||
- `docs/40-WORKFLOWS/CONTENT-PIPELINE.md`
|
||||
- `docs/40-WORKFLOWS/SCHEDULED-CONTENT-PUBLISHING.md`
|
||||
- `docs/00-SYSTEM/ARCHITECTURE.md`
|
||||
- `docs/20-API/ENDPOINTS.md`
|
||||
|
||||
---
|
||||
|
||||
## APPENDIX B: VULNERABILITY SEVERITY MATRIX
|
||||
|
||||
| ID | Title | CVSS | Documentation Claim | Reality |
|
||||
|----|-------|------|---------------------|---------|
|
||||
| CRITICAL-1 | Plain text API keys | 9.0 | "Encrypted at rest" | Plain text CharField |
|
||||
| CRITICAL-2 | Timing attack | 8.0 | Not mentioned | strpos vulnerability |
|
||||
| CRITICAL-3 | Diagnostic logging | 8.0 | Not mentioned | Full request bodies logged |
|
||||
| HIGH-1 | Public endpoints | 7.0 | Not flagged | Information disclosure |
|
||||
| HIGH-2 | Permission inconsistency | 6.5 | Not documented | AllowAny misuse |
|
||||
| HIGH-3 | No webhook signatures | 6.0 | "Optional" | Not implemented at all |
|
||||
| HIGH-4 | No rate limiting | 5.5 | "Rate limiting enabled" | NoThrottle class used |
|
||||
| HIGH-5 | Silent encryption fail | 6.0 | Not mentioned | Falls back to plain text |
|
||||
|
||||
---
|
||||
|
||||
**End of Calibrated Audit Report**
|
||||
|
||||
**Next Steps:**
|
||||
1. Review findings with development team
|
||||
2. Prioritize critical security fixes
|
||||
3. Update documentation to match reality
|
||||
4. Create migration plan for field removal
|
||||
5. Implement encryption before production
|
||||
1139
v2/Live Docs on Server/igny8-app-docs/audits/pages-audit.md
Normal file
1139
v2/Live Docs on Server/igny8-app-docs/audits/pages-audit.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,156 @@
|
||||
# Content Types Writing Plan (Detailed)
|
||||
|
||||
**Goal:** Make IGNY8 capable of **writing content for multiple post types** (pages, products, services, company pages, taxonomy terms) using the **same Planner → Ideas → Tasks → Writer → Images** workflow as posts.
|
||||
|
||||
**Scope:** WordPress-only for current release. Shopify/custom support is future.
|
||||
|
||||
---
|
||||
|
||||
## 1) Core Principle
|
||||
All content types follow the **same pipeline**:
|
||||
|
||||
```
|
||||
Clusters → Ideas → Tasks → Content → Images → Review → Publish
|
||||
```
|
||||
|
||||
Differences are handled by **content_type** and **content_structure** presets, plus targeted prompts and schema.
|
||||
|
||||
---
|
||||
|
||||
## 2) Content Types & Structures
|
||||
|
||||
### 2.1 Pages (Static)
|
||||
- **content_type:** `page`
|
||||
- **content_structure:** `landing_page`, `business_page`, `general`
|
||||
- **Use cases:** About, Contact, FAQs, Pillar landing pages
|
||||
|
||||
### 2.2 Products (Custom Post Type)
|
||||
- **content_type:** `product`
|
||||
- **content_structure:** `product_page`
|
||||
- **Use cases:** Individual products or product collections
|
||||
|
||||
### 2.3 Services (Custom Post Type)
|
||||
- **content_type:** `service`
|
||||
- **content_structure:** `service_page`
|
||||
- **Use cases:** Service offering pages, location variants
|
||||
|
||||
### 2.4 Company Pages
|
||||
- **content_type:** `page`
|
||||
- **content_structure:** `business_page` or `general`
|
||||
- **Use cases:** Team, Careers, Press, Mission
|
||||
|
||||
### 2.5 Taxonomy Terms (Landing Pages)
|
||||
- **content_type:** `taxonomy`
|
||||
- **content_structure:** `category_archive`, `tag_archive`, `attribute_archive`, `cluster_hub`
|
||||
- **Use cases:** Category/tag/attribute landing pages with SEO content
|
||||
|
||||
---
|
||||
|
||||
## 3) Pipeline Mapping (Same as Posts)
|
||||
|
||||
### 3.1 Clusters → Ideas
|
||||
- Each cluster generates ideas **per content type** based on intent.
|
||||
- Example: one cluster may yield a page, a product page, and a service page idea.
|
||||
|
||||
### 3.2 Ideas → Tasks
|
||||
- `Tasks` created with `content_type` and `content_structure`.
|
||||
- Taxonomy terms can create tasks tied to `taxonomy_term`.
|
||||
|
||||
### 3.3 Tasks → Content
|
||||
- Writer uses existing prompt system with type-specific prompts.
|
||||
- Outputs HTML + meta title + meta description + structure.
|
||||
|
||||
### 3.4 Content → Images
|
||||
- Uses existing image pipeline (featured + in‑article).
|
||||
- Products/services can add attribute-based prompts.
|
||||
|
||||
---
|
||||
|
||||
## 4) WordPress Integration (Current Scope)
|
||||
|
||||
### 4.1 Post Types
|
||||
- `post`, `page`, custom post types (`product`, `service`)
|
||||
|
||||
### 4.2 Taxonomies
|
||||
- `category`, `post_tag`, `product_cat`, `product_tag`
|
||||
- WooCommerce attributes (e.g., `pa_color`, `pa_size`)
|
||||
|
||||
---
|
||||
|
||||
## 5) Writing Rules per Content Type
|
||||
|
||||
### 5.1 Pages
|
||||
- Clear CTA and conversion focus.
|
||||
- Structured sections for trust + proof.
|
||||
|
||||
### 5.2 Products
|
||||
- Feature/benefit sections, specs table, FAQs.
|
||||
- Product schema + review snippets.
|
||||
|
||||
### 5.3 Services
|
||||
- Service overview, process steps, outcomes, FAQs.
|
||||
- Local SEO sections if location-based.
|
||||
|
||||
### 5.4 Company Pages
|
||||
- Brand mission, values, history, team bios.
|
||||
- FAQ and media/press block where relevant.
|
||||
|
||||
### 5.5 Taxonomy Landing Pages
|
||||
- Intro + topical definition
|
||||
- Key subtopics + internal links
|
||||
- Related products/posts/services
|
||||
- FAQ section
|
||||
|
||||
---
|
||||
|
||||
## 6) Cluster Alignment
|
||||
- Each page is mapped to a **primary cluster**.
|
||||
- Secondary clusters used for semantic coverage.
|
||||
- Cluster keywords drive headings and metadata.
|
||||
|
||||
---
|
||||
|
||||
## 7) Required Enhancements
|
||||
|
||||
### 7.1 Ideas Generation
|
||||
- Add type-specific idea templates:
|
||||
- Product ideas (features/specs/benefits)
|
||||
- Service ideas (process/outcomes)
|
||||
- Company page ideas (mission/team/careers)
|
||||
- Term landing ideas (taxonomy descriptions)
|
||||
|
||||
### 7.2 Writer Prompts
|
||||
- Add prompt variants for each `content_structure`.
|
||||
|
||||
### 7.3 Publishing
|
||||
- Map content types to WordPress post types correctly.
|
||||
- Taxonomy landing pages publish to term descriptions or custom fields.
|
||||
|
||||
---
|
||||
|
||||
## 8) Rollout Phases
|
||||
|
||||
**Phase 1**
|
||||
- Pages + services + company pages in Writer
|
||||
- Task creation with new structures
|
||||
|
||||
**Phase 2**
|
||||
- Products pages writing (WooCommerce)
|
||||
- Attribute-based prompts
|
||||
|
||||
**Phase 3**
|
||||
- Taxonomy landing pages
|
||||
- Cluster hub pages
|
||||
|
||||
---
|
||||
|
||||
## 9) Success Criteria
|
||||
- All content types generated via same pipeline.
|
||||
- Cluster alignment across all page types.
|
||||
- Improved SEO coverage beyond posts.
|
||||
|
||||
---
|
||||
|
||||
## 10) Non‑Goals (v1)
|
||||
- Shopify/custom CMS adapters
|
||||
- Auto‑publish without review
|
||||
@@ -0,0 +1,210 @@
|
||||
## IGNY8 Socializer Module - Development Flow
|
||||
|
||||
---
|
||||
|
||||
### 1. Meta (Facebook) Setup
|
||||
|
||||
**One-time Config:**
|
||||
- Create Business App at developers.facebook.com
|
||||
- Add Facebook Login + Pages API products
|
||||
- Set redirect URI: `https://igny8.com/auth/facebook/callback`
|
||||
- Request permissions: `pages_manage_posts`, `pages_read_engagement`, `publish_to_groups`
|
||||
- Submit for App Review (required for production)
|
||||
|
||||
**What to Build:**
|
||||
|
||||
| Component | Details |
|
||||
|-----------|---------|
|
||||
| Connect Button | Triggers OAuth, stores long-lived user token + page tokens |
|
||||
| Account Picker | After auth, user selects which Pages/Groups to enable |
|
||||
| Token Refresh Job | Cron to refresh tokens before 60-day expiry |
|
||||
| Post Service | Accepts content + destination, calls `/{page-id}/feed` |
|
||||
| Webhook Listener | Optional: receive post status updates |
|
||||
|
||||
**Posting Capabilities:**
|
||||
- ✅ Pages (as Page)
|
||||
- ⚠️ Groups (limited, user must be admin, needs approval)
|
||||
- ❌ Personal profiles (blocked by Meta)
|
||||
|
||||
---
|
||||
|
||||
### 2. LinkedIn Setup
|
||||
|
||||
**One-time Config:**
|
||||
- Create App at linkedin.com/developers
|
||||
- Verify/associate with a Company Page
|
||||
- Request products: "Share on LinkedIn", "Marketing Developer Platform" (for org posting)
|
||||
- Set redirect URI: `https://igny8.com/auth/linkedin/callback`
|
||||
- Permissions: `w_member_social`, `w_organization_social`, `r_liteprofile`, `r_organization_social`
|
||||
|
||||
**What to Build:**
|
||||
|
||||
| Component | Details |
|
||||
|-----------|---------|
|
||||
| Connect Button | OAuth flow, store access token + refresh token |
|
||||
| Account Picker | User selects personal profile and/or Company Pages they admin |
|
||||
| Token Refresh Job | Use refresh token before 60-day expiry |
|
||||
| Post Service | Calls `/rest/posts` with author URN (person or organization) |
|
||||
| URN Storage | Store `urn:li:person:{id}` and `urn:li:organization:{id}` per account |
|
||||
|
||||
**Posting Capabilities:**
|
||||
- ✅ Personal profile
|
||||
- ✅ Company Pages (user must be admin)
|
||||
- ❌ Groups (no API)
|
||||
|
||||
---
|
||||
|
||||
### 3. Database Schema (additions)
|
||||
|
||||
```
|
||||
SocialAccount
|
||||
├── user (FK to IGNY8 user)
|
||||
├── platform (facebook | linkedin)
|
||||
├── platform_user_id
|
||||
├── access_token (encrypted)
|
||||
├── refresh_token (encrypted, LinkedIn only)
|
||||
├── token_expires_at
|
||||
├── scopes_granted
|
||||
├── created_at / updated_at
|
||||
|
||||
SocialDestination
|
||||
├── social_account (FK)
|
||||
├── destination_type (page | group | profile | company)
|
||||
├── destination_id (platform's ID)
|
||||
├── destination_name
|
||||
├── is_active
|
||||
├── permissions_valid
|
||||
|
||||
SocialPost
|
||||
├── site (FK to user's IGNY8 site)
|
||||
├── content_source (FK to generated content)
|
||||
├── destination (FK to SocialDestination)
|
||||
├── platform_post_id
|
||||
├── status (pending | posted | failed)
|
||||
├── posted_at
|
||||
├── error_message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Backend Services
|
||||
|
||||
| Service | Purpose |
|
||||
|---------|---------|
|
||||
| `SocialAuthService` | Handle OAuth callbacks, token exchange, storage |
|
||||
| `TokenRefreshService` | Scheduled job to refresh expiring tokens |
|
||||
| `DestinationSyncService` | After auth, fetch and store available pages/groups/companies |
|
||||
| `SocialPublisherService` | Generic interface → platform-specific posting logic |
|
||||
| `PostQueueWorker` | Process scheduled social posts from queue |
|
||||
|
||||
---
|
||||
|
||||
### 5. Frontend Components
|
||||
|
||||
| Component | Purpose |
|
||||
|-----------|---------|
|
||||
| Social Connections Page | List connected accounts, connect/disconnect buttons |
|
||||
| Destination Selector | Per-site config: which destinations receive auto-posts |
|
||||
| Post Preview Modal | Before posting: preview how content appears |
|
||||
| Post History Table | Show status of all social posts per site |
|
||||
| Settings per Destination | Customize format, hashtags, include link, image behavior |
|
||||
|
||||
---
|
||||
|
||||
### 6. Content-to-Social Flow
|
||||
|
||||
```
|
||||
Content Published to WP
|
||||
↓
|
||||
Check site's social destinations (active ones)
|
||||
↓
|
||||
For each destination:
|
||||
→ Format content (title, excerpt, link, image)
|
||||
→ Apply destination-specific template
|
||||
→ Queue SocialPost record
|
||||
↓
|
||||
PostQueueWorker picks up
|
||||
↓
|
||||
Call platform API → update status → log result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Post Formatting Options (per destination)
|
||||
|
||||
- Post template: `{title}\n\n{excerpt}\n\nRead more: {url}`
|
||||
- Include featured image: yes/no
|
||||
- Auto-hashtags from keywords
|
||||
- Character limit handling (LinkedIn: 3000, Facebook: 63,206)
|
||||
- Link preview behavior
|
||||
|
||||
---
|
||||
|
||||
### 8. Error Handling
|
||||
|
||||
- Token expired → mark account as needs reconnection, notify user
|
||||
- Rate limited → exponential backoff retry
|
||||
- Permission revoked → disable destination, notify user
|
||||
- API error → log, retry up to 3x, then mark failed
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
## Rate Limits & User Limits
|
||||
|
||||
---
|
||||
|
||||
### Facebook/Meta
|
||||
|
||||
**No limit on number of users/pages/groups** connecting to your single app.
|
||||
|
||||
**But there are API call limits:**
|
||||
|
||||
| Limit Type | Details |
|
||||
|------------|---------|
|
||||
| App-level | 200 calls/user/hour baseline, scales with active users |
|
||||
| Page posting | ~25 posts/page/day (soft limit, not documented hard cap) |
|
||||
| Groups | Much stricter, ~5-10 posts/day/group to avoid spam flags |
|
||||
| Burst protection | Too many calls in short time = temporary throttle |
|
||||
|
||||
**Scaling factor:** Your app's call limit increases automatically as more users connect (more users = higher pool).
|
||||
|
||||
---
|
||||
|
||||
### LinkedIn
|
||||
|
||||
**No limit on users/companies** connecting.
|
||||
|
||||
**API limits:**
|
||||
|
||||
| Limit Type | Details |
|
||||
|------------|---------|
|
||||
| Daily app limit | 100,000 calls/day (standard tier) |
|
||||
| Per-user posting | ~100-150 posts/day/member (undocumented soft limit) |
|
||||
| Company pages | ~100 posts/day/org |
|
||||
| Rate limit | 100 requests/minute per user token |
|
||||
|
||||
**Marketing Developer Platform** (if approved): Higher limits for company page management.
|
||||
|
||||
---
|
||||
|
||||
### Practical Concerns
|
||||
|
||||
| Concern | Reality |
|
||||
|---------|---------|
|
||||
| 100 IGNY8 users, each with 2 pages | No problem at all |
|
||||
| 1000 users, each with 5 pages | Still fine, monitor call volume |
|
||||
| Spam behavior | Biggest risk - platforms will flag/ban aggressive posting |
|
||||
|
||||
---
|
||||
|
||||
### Recommendations for IGNY8
|
||||
|
||||
1. **Implement per-destination cooldowns** - minimum 4-6 hours between posts to same page/group
|
||||
2. **Track API usage** - log calls, alert if approaching limits
|
||||
3. **Queue with spreading** - don't burst 50 posts at once, spread across hours
|
||||
4. **User-level limits** - cap posts/day per destination in your own system
|
||||
|
||||
---
|
||||
|
||||
No licensing fees or per-seat costs from Meta/LinkedIn for API access itself. Just respect the rate limits and avoid spammy patterns.
|
||||
@@ -0,0 +1,264 @@
|
||||
# Linker Module Plan (Detailed)
|
||||
|
||||
**Scope:** Current implementation is **WordPress-only** for publishing/sync and content discovery. The design supports future Shopify and custom sites but is **not enabled** for them in v1.
|
||||
|
||||
**Status Today:** Linker module exists but is inactive by default and not fully protected by feature flags. See [docs/10-MODULES/LINKER.md](../../10-MODULES/LINKER.md).
|
||||
|
||||
---
|
||||
|
||||
## 1) Goal
|
||||
Enable AI-driven internal and external linking for all content types that IGNY8 manages or syncs, while staying consistent with the existing pipeline and settings architecture.
|
||||
|
||||
**Primary objectives:**
|
||||
- Increase topical authority via strategic internal linking (posts, pages, taxonomy pages, cluster pages, products, services).
|
||||
- Improve UX and SEO through better anchor placement and link coverage.
|
||||
- Maintain link hygiene with ongoing link health monitoring.
|
||||
- Support outbound linking to authoritative sources with relevance scoring.
|
||||
- Provide a pathway for **backlink discovery** (informational only in v1, no outreach automation).
|
||||
|
||||
---
|
||||
|
||||
## 2) Feature Set (Detailed)
|
||||
|
||||
### 2.1 Internal Linking (Core)
|
||||
**What it does**
|
||||
- Suggests internal links for content based on keyword clusters, topic similarity, and taxonomy alignment.
|
||||
|
||||
**Targets supported**
|
||||
- Posts
|
||||
- Pages
|
||||
- Taxonomy archives (category/tag)
|
||||
- Cluster pages (IGNY8-defined topical clusters)
|
||||
- Product pages *(WordPress product CPT if present; Shopify later)*
|
||||
- Service pages *(custom post types if configured)*
|
||||
|
||||
**Anchor strategies**
|
||||
- Exact match
|
||||
- Partial match
|
||||
- Semantic match
|
||||
- Brand-safe anchor constraints (from site-level settings)
|
||||
|
||||
**Controls**
|
||||
- Max links per content item
|
||||
- Per-section link density caps
|
||||
- Avoid repeated anchors
|
||||
- Exclude URLs or content statuses (draft, private)
|
||||
|
||||
---
|
||||
|
||||
### 2.2 External Linking (Authority Sources)
|
||||
**What it does**
|
||||
- Recommends outbound links to reputable domains relevant to content topic.
|
||||
|
||||
**Constraints**
|
||||
- Domain whitelist/blacklist
|
||||
- Authority threshold (DR/DA proxy)
|
||||
- No competitor domains (configurable)
|
||||
- Link type: dofollow/nofollow
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Backlink Discovery (Read-only in v1)
|
||||
**What it does**
|
||||
- Lists relevant external sites that are topically related for future outreach.
|
||||
|
||||
**Notes**
|
||||
- No automated outreach in v1.
|
||||
- Future: integrate with email/outreach tools.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 Link Health & Monitoring
|
||||
**What it does**
|
||||
- Detects broken internal/external links.
|
||||
- Tracks link status and changes.
|
||||
|
||||
**Actions**
|
||||
- Flag broken links
|
||||
- Suggest replacements
|
||||
- Quick-fix internal links
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Automation & Pipeline Integration
|
||||
**What it does**
|
||||
- Can run after Writer stage to enrich IGNY8-generated content.
|
||||
- Can run on synced WordPress content.
|
||||
|
||||
**Modes**
|
||||
- Manual: per content item
|
||||
- Batch: selected items
|
||||
- Automation stage: optional in automation pipeline
|
||||
|
||||
---
|
||||
|
||||
## 3) App-Consistent Architecture
|
||||
|
||||
### 3.1 Backend Modules (Aligned with Current Structure)
|
||||
- Existing: `backend/igny8_core/modules/linker/`
|
||||
- Existing: `backend/igny8_core/business/content/services/content_pipeline_service.py`
|
||||
|
||||
**Add / Extend**
|
||||
- `business/linker/` (services, scoring, health checks)
|
||||
- `modules/linker/models.py` (link suggestions, health checks)
|
||||
- `modules/linker/serializers.py`
|
||||
- `modules/linker/views.py` (extend API)
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Data Models (Proposed)
|
||||
|
||||
**LinkSuggestion**
|
||||
- `account`, `site`, `content`
|
||||
- `anchor_text`, `anchor_strategy`
|
||||
- `target_url`, `target_content_id` (nullable for external)
|
||||
- `target_type` (internal/external/taxonomy/cluster/product/service)
|
||||
- `confidence_score`
|
||||
- `status` (suggested/applied/rejected)
|
||||
- `metadata` (keyword match, similarity score)
|
||||
|
||||
**LinkHealthStatus**
|
||||
- `site`, `url`, `status_code`, `last_checked_at`, `is_broken`
|
||||
|
||||
**ExternalSourceCandidate**
|
||||
- `topic`, `domain`, `url`, `authority_score`, `relevance_score`
|
||||
|
||||
---
|
||||
|
||||
### 3.3 API Endpoints (Aligned with Existing Style)
|
||||
|
||||
**Existing**
|
||||
- `POST /api/v1/linker/process/`
|
||||
- `POST /api/v1/linker/batch_process/`
|
||||
|
||||
**Add**
|
||||
- `GET /api/v1/linker/suggestions/?content_id=`
|
||||
- `POST /api/v1/linker/apply/`
|
||||
- `POST /api/v1/linker/reject/`
|
||||
- `POST /api/v1/linker/health/scan/`
|
||||
- `GET /api/v1/linker/health/status/?site_id=`
|
||||
- `GET /api/v1/linker/external-sources/?content_id=`
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Frontend Pages (Consistent with Current UI)
|
||||
|
||||
**Pages**
|
||||
- `/linker/content` (list content + status + batch actions)
|
||||
- `/linker/content/:id` (link suggestions + apply)
|
||||
|
||||
**Components**
|
||||
- Suggestions table
|
||||
- Inline preview with anchor highlights
|
||||
- Link health panel
|
||||
- External sources panel
|
||||
|
||||
---
|
||||
|
||||
## 4) WordPress-Only Scope (Current Release)
|
||||
|
||||
### 4.1 WordPress Content Types
|
||||
- Posts
|
||||
- Pages
|
||||
- Categories / Tags (taxonomy archives)
|
||||
- Custom post types (products/services) if configured in integration
|
||||
|
||||
### 4.2 WordPress Sync Integration
|
||||
- Leverage existing sync flows to ingest WordPress content and taxonomies.
|
||||
- Maintain `wordpress_id` mapping for internal link targeting.
|
||||
|
||||
---
|
||||
|
||||
## 5) Linking Logic (High-Level)
|
||||
|
||||
### 5.1 Internal Link Discovery
|
||||
- Cluster overlap
|
||||
- Keyword match to title + headers
|
||||
- Taxonomy match (category/tag)
|
||||
- Content type compatibility
|
||||
|
||||
### 5.2 External Link Discovery
|
||||
- Use curated sources or internal dataset
|
||||
- Score by relevance + authority
|
||||
|
||||
### 5.3 Anchor Placement
|
||||
- Avoid headings and existing links
|
||||
- Prefer first 60% of content
|
||||
- Cap per section
|
||||
|
||||
---
|
||||
|
||||
## 6) Settings & Controls (Unified Settings)
|
||||
|
||||
**Site Settings → Automation tab**
|
||||
- Enable Linker module
|
||||
- Max internal links per content
|
||||
- Max external links per content
|
||||
- Target link density
|
||||
- Excluded URLs / domains
|
||||
- Anchor text rules
|
||||
|
||||
---
|
||||
|
||||
## 7) Notifications
|
||||
- “Link suggestions ready”
|
||||
- “Links applied”
|
||||
- “Broken links detected”
|
||||
|
||||
---
|
||||
|
||||
## 8) Rollout Phases
|
||||
|
||||
**Phase 1 (MVP)**
|
||||
- Internal links + anchor suggestions
|
||||
- Manual apply
|
||||
- Content-level preview
|
||||
|
||||
**Phase 2**
|
||||
- External link suggestions
|
||||
- Batch processing
|
||||
|
||||
**Phase 3**
|
||||
- Link health scanning
|
||||
- Automation stage support
|
||||
|
||||
**Phase 4**
|
||||
- Backlink discovery (read-only)
|
||||
|
||||
---
|
||||
|
||||
## 9) Non-Goals (v1)
|
||||
- Automated outreach
|
||||
- Paid backlink acquisition
|
||||
- Shopify support (future)
|
||||
- Custom CMS adapters (future)
|
||||
|
||||
---
|
||||
|
||||
## 10) Success Criteria
|
||||
- Increased internal link coverage per content piece
|
||||
- Reduced orphaned content
|
||||
- Improved ranking on linked clusters
|
||||
- Minimal false-positive link placements
|
||||
|
||||
---
|
||||
|
||||
## 11) Dependencies
|
||||
- Content metadata quality (keywords, clusters, taxonomies)
|
||||
- WordPress integration sync health
|
||||
- Automation settings availability
|
||||
|
||||
---
|
||||
|
||||
## 12) Risks & Mitigations
|
||||
- **Risk:** Over-linking → **Mitigation:** enforce density caps and editor review.
|
||||
- **Risk:** Bad anchors → **Mitigation:** anchor validation + manual approval.
|
||||
- **Risk:** Broken external links → **Mitigation:** link health checks + replacements.
|
||||
|
||||
---
|
||||
|
||||
## 13) Future Extensions (Post‑WordPress)
|
||||
- Shopify product catalogs
|
||||
- Custom CMS content ingestion
|
||||
- Cross-domain linking for multi-brand portfolios
|
||||
- Outreach workflows for backlink acquisition
|
||||
@@ -0,0 +1,235 @@
|
||||
# Optimizer Module Plan (Detailed)
|
||||
|
||||
**Scope:** Optimizer focuses on **rewriting/optimizing existing content** across posts, pages, products, services, company pages, and taxonomy archives. Current release scope is **WordPress-only** (publish/sync + content discovery). Shopify/custom support is future.
|
||||
|
||||
**Status Today:** Optimizer module exists but is inactive by default and partially implemented. See [docs/10-MODULES/OPTIMIZER.md](../../10-MODULES/OPTIMIZER.md).
|
||||
|
||||
---
|
||||
|
||||
## 1) Goal
|
||||
Upgrade existing content to SEO‑strong, intent‑aligned pages by:
|
||||
- Mapping content to **semantic/topic clusters**.
|
||||
- Aligning page intent with cluster keyword targets.
|
||||
- Rewriting structure + content to match current search intent.
|
||||
- Adding schema, metadata, and on‑page SEO improvements.
|
||||
|
||||
---
|
||||
|
||||
## 2) Feature Set (Detailed)
|
||||
|
||||
### 2.1 Content Types Supported (WordPress v1)
|
||||
- Posts (blog/article)
|
||||
- Pages (static)
|
||||
- Products (custom post type, if configured)
|
||||
- Services (custom post type, if configured)
|
||||
- Company pages (about/team/careers/FAQ/press)
|
||||
- Taxonomy archives (category/tag)
|
||||
- Cluster pages (IGNY8 cluster views)
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Cluster‑Aligned Optimization
|
||||
**What it does**
|
||||
- Associates each content item with a **primary cluster** + secondary clusters.
|
||||
- Uses cluster keyword list as optimization targets.
|
||||
|
||||
**Outputs**
|
||||
- Updated content aligned to the best matching cluster intent.
|
||||
- Keyword coverage improvements with semantic variants.
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Intent‑Aligned Rewrite Engine
|
||||
**What it does**
|
||||
- Rewrites content to match user intent (informational, commercial, transactional).
|
||||
- Adjusts structure and sections to meet SERP expectations.
|
||||
|
||||
**Capabilities**
|
||||
- Expand thin pages into full SEO pages.
|
||||
- Compress overly long pages to reduce fluff.
|
||||
- Add missing sections and re‑order content flow.
|
||||
|
||||
---
|
||||
|
||||
### 2.4 SEO‑Rich Output (Structure + Meta + Schema)
|
||||
**What it does**
|
||||
- Generates SEO‑optimized headings, internal structure, and metadata.
|
||||
- Adds schema markup when applicable.
|
||||
|
||||
**Includes**
|
||||
- Meta title + meta description refresh
|
||||
- H1/H2/H3 structure alignment
|
||||
- Internal/external link placeholders (Linker integration)
|
||||
- Image alt text improvements
|
||||
- Schema JSON‑LD (Article, Product, FAQ, Organization, Breadcrumb, Service)
|
||||
|
||||
---
|
||||
|
||||
### 2.5 Page‑Level Optimization Scores
|
||||
**What it does**
|
||||
- Scores before/after content quality using current Optimizer scoring model.
|
||||
- Tracks improvements by cluster alignment + keyword coverage.
|
||||
|
||||
---
|
||||
|
||||
## 3) App‑Consistent Architecture
|
||||
|
||||
### 3.1 Backend Modules (Aligned with Current Structure)
|
||||
- Existing: `backend/igny8_core/modules/optimizer/`
|
||||
- Existing: `backend/igny8_core/business/optimization/`
|
||||
|
||||
**Add / Extend**
|
||||
- `business/optimization/cluster_mapping.py` (cluster assignment & keyword targets)
|
||||
- `modules/optimizer/models.py` (extend OptimizationTask)
|
||||
- `modules/optimizer/serializers.py` (add cluster mapping + schema)
|
||||
- `modules/optimizer/views.py` (extend API)
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Data Models (Proposed Extensions)
|
||||
|
||||
**OptimizationTask** (extend)
|
||||
- `primary_cluster_id`
|
||||
- `secondary_cluster_ids` (JSON)
|
||||
- `keyword_targets` (JSON)
|
||||
- `schema_type` (article/product/service/faq/org)
|
||||
- `schema_json` (JSON)
|
||||
- `metadata_before` (JSON)
|
||||
- `metadata_after` (JSON)
|
||||
- `structure_changes` (JSON)
|
||||
|
||||
---
|
||||
|
||||
### 3.3 API Endpoints (Aligned with Existing Style)
|
||||
|
||||
**Existing**
|
||||
- `POST /api/v1/optimizer/analyze/`
|
||||
- `POST /api/v1/optimizer/optimize/`
|
||||
- `POST /api/v1/optimizer/batch_optimize/`
|
||||
|
||||
**Add**
|
||||
- `POST /api/v1/optimizer/assign_cluster/`
|
||||
- `GET /api/v1/optimizer/cluster_suggestions/?content_id=`
|
||||
- `POST /api/v1/optimizer/preview/` (structure + metadata + schema preview)
|
||||
- `POST /api/v1/optimizer/apply/` (persist optimized version)
|
||||
|
||||
---
|
||||
|
||||
### 3.4 Frontend Pages (Consistent with Current UI)
|
||||
|
||||
**Pages**
|
||||
- `/optimizer/content` (content list + status)
|
||||
- `/optimizer/preview/:id` (analysis + diff + schema preview)
|
||||
|
||||
**Components**
|
||||
- Cluster mapping panel
|
||||
- Keyword target editor
|
||||
- Structure diff viewer
|
||||
- Metadata + schema preview
|
||||
|
||||
---
|
||||
|
||||
## 4) WordPress‑Only Scope (Current Release)
|
||||
|
||||
### 4.1 WordPress Content Coverage
|
||||
- Posts and Pages
|
||||
- Taxonomy archives (category/tag)
|
||||
- Custom post types (products/services if configured)
|
||||
|
||||
### 4.2 WordPress Sync Integration
|
||||
- Use existing sync pipelines to pull WordPress content.
|
||||
- Maintain `wordpress_id` mapping for optimized updates.
|
||||
|
||||
---
|
||||
|
||||
## 5) Optimization Logic (High‑Level)
|
||||
|
||||
### 5.1 Cluster Matching
|
||||
- Analyze content title, headings, and keyword density.
|
||||
- Match to existing cluster keyword sets.
|
||||
- Provide suggestions if confidence < threshold.
|
||||
|
||||
### 5.2 Content Rewrite
|
||||
- Rebuild structure to match intent + cluster focus.
|
||||
- Inject missing sections (FAQs, comparisons, benefits, use cases).
|
||||
- Normalize tone with existing Writer prompt settings.
|
||||
|
||||
### 5.3 SEO Enhancements
|
||||
- Meta title/description refresh
|
||||
- Heading structure refinement
|
||||
- Internal link opportunities (handoff to Linker)
|
||||
- Schema generation based on content type
|
||||
|
||||
---
|
||||
|
||||
## 6) Settings & Controls (Unified Settings)
|
||||
|
||||
**Site Settings → Automation tab**
|
||||
- Enable Optimizer module
|
||||
- Default schema type per content type
|
||||
- Keyword density targets
|
||||
- Content length guidelines
|
||||
- Allow auto‑apply vs manual review
|
||||
|
||||
---
|
||||
|
||||
## 7) Notifications
|
||||
- “Optimization analysis ready”
|
||||
- “Optimization applied”
|
||||
- “Schema generated”
|
||||
|
||||
---
|
||||
|
||||
## 8) Rollout Phases
|
||||
|
||||
**Phase 1 (MVP)**
|
||||
- Analyze + optimize existing posts/pages
|
||||
- Cluster mapping suggestions
|
||||
- Manual apply
|
||||
|
||||
**Phase 2**
|
||||
- Products/services/company pages support
|
||||
- Schema + metadata output
|
||||
|
||||
**Phase 3**
|
||||
- Taxonomy archive optimization
|
||||
- Batch optimization
|
||||
|
||||
**Phase 4**
|
||||
- Full automation stage integration
|
||||
|
||||
---
|
||||
|
||||
## 9) Non‑Goals (v1)
|
||||
- Shopify/custom CMS support
|
||||
- Automated schema validation tools
|
||||
- Automatic publishing without review
|
||||
|
||||
---
|
||||
|
||||
## 10) Success Criteria
|
||||
- Increased keyword coverage vs cluster targets
|
||||
- Higher content quality scores (before/after)
|
||||
- Improved SERP performance for optimized pages
|
||||
|
||||
---
|
||||
|
||||
## 11) Dependencies
|
||||
- Cluster data quality in Planner
|
||||
- WordPress sync reliability
|
||||
- Unified settings availability
|
||||
|
||||
---
|
||||
|
||||
## 12) Risks & Mitigations
|
||||
- **Risk:** Over‑optimization → **Mitigation:** density caps + manual review.
|
||||
- **Risk:** Wrong cluster mapping → **Mitigation:** suggestion + override flow.
|
||||
- **Risk:** Schema mismatch → **Mitigation:** type validation + preview.
|
||||
|
||||
---
|
||||
|
||||
## 13) Future Extensions (Post‑WordPress)
|
||||
- Shopify product catalogs
|
||||
- Custom CMS adapters
|
||||
- Automated schema validation pipeline
|
||||
- SERP‑based optimization suggestions
|
||||
@@ -0,0 +1,187 @@
|
||||
# Socializer + Video Content Creator Plan (Detailed)
|
||||
|
||||
**Scope:** Q3–Q4 2026 modules from https://igny8.com/upcoming. Integrations target top social platforms. Content sources are IGNY8‑generated and optimized (new or rewritten), linked back to the original page/post/product/service.
|
||||
|
||||
**Goal:** Automate multi‑platform social publishing and video creation for every IGNY8 content item, using consistent app structure (Ideas → Tasks → Content → Images → Publish + Automation + Calendar).
|
||||
|
||||
---
|
||||
|
||||
## 1) Socializer Module (Multi‑Platform Social Publishing)
|
||||
|
||||
### 1.1 Core Capabilities
|
||||
- Auto‑generate platform‑specific social posts from any IGNY8 content item.
|
||||
- Schedule and auto‑publish to connected social accounts.
|
||||
- Maintain back‑links to the original page/post/product/service.
|
||||
- Provide a unified social calendar integrated with content calendar.
|
||||
|
||||
### 1.2 Content Sources (What can be socialized)
|
||||
- Posts (blog/article)
|
||||
- Pages (static)
|
||||
- Products (WooCommerce)
|
||||
- Services (custom post type)
|
||||
- Company pages
|
||||
- Taxonomy landing pages
|
||||
|
||||
**Rule:** Any IGNY8 content item (new or optimized) can generate social content.
|
||||
|
||||
### 1.3 Platform Targets (Phase 1)
|
||||
- LinkedIn
|
||||
- Twitter/X
|
||||
- Facebook
|
||||
- Instagram (posts + reels)
|
||||
- TikTok (short video)
|
||||
|
||||
### 1.4 Platform‑Specific Output
|
||||
- **Text posts:** title + summary + CTA + link
|
||||
- **Captions:** tone/length variations per platform
|
||||
- **Images:** resized and cropped per platform specs
|
||||
- **Short videos / reels:** generated from content (via Video module)
|
||||
|
||||
### 1.5 Social Post Types
|
||||
- **Announcement:** “New article/product/service” with link
|
||||
- **Highlights:** key takeaways + CTA
|
||||
- **Quote cards:** single insight with branded template
|
||||
- **FAQ snippets:** for services or product categories
|
||||
- **Carousel (future):** multi‑panel posts
|
||||
|
||||
### 1.6 Scheduling & Automation
|
||||
- Auto‑publish on content approval or scheduled publish.
|
||||
- Schedule rules: time windows, frequency caps, platform‑specific limits.
|
||||
- Sync with Publisher scheduling (same calendar).
|
||||
|
||||
---
|
||||
|
||||
## 2) Video Content Creator (AI Video Generation & Publishing)
|
||||
|
||||
### 2.1 Core Capabilities
|
||||
- Convert articles/pages into video scripts.
|
||||
- Generate short‑form videos (Reels/Shorts/TikTok).
|
||||
- Generate long‑form YouTube videos with chapters.
|
||||
- Auto‑publish to video platforms.
|
||||
|
||||
### 2.2 Video Types
|
||||
- **Short:** 15–60s highlights
|
||||
- **Medium:** 60–180s summaries
|
||||
- **Long:** 5–12 min explainer (YouTube)
|
||||
|
||||
### 2.3 Video Outputs
|
||||
- Script + captions
|
||||
- AI voiceover or user‑uploaded voice
|
||||
- Visuals: stock + AI images + brand templates
|
||||
- Titles, descriptions, tags (SEO optimized)
|
||||
|
||||
### 2.4 Publishing Targets (Phase 1)
|
||||
- YouTube
|
||||
- TikTok
|
||||
- Instagram Reels
|
||||
- YouTube Shorts
|
||||
|
||||
---
|
||||
|
||||
## 3) Integration with IGNY8 Content Workflow
|
||||
|
||||
### 3.1 Trigger Points
|
||||
- On **content approval**
|
||||
- On **scheduled publish**
|
||||
- On **optimizer rewrite completion**
|
||||
|
||||
### 3.2 Link‑Back Rule
|
||||
Every social/video post must link to the original page/post/product on the site.
|
||||
|
||||
### 3.3 Metadata Alignment
|
||||
- Use IGNY8 meta title + description as base.
|
||||
- Add platform‑specific variations.
|
||||
|
||||
---
|
||||
|
||||
## 4) App‑Consistent Architecture
|
||||
|
||||
### 4.1 Backend Modules (Proposed)
|
||||
- `backend/igny8_core/modules/socializer/`
|
||||
- `backend/igny8_core/business/social/`
|
||||
- `backend/igny8_core/modules/video/`
|
||||
- `backend/igny8_core/business/video/`
|
||||
|
||||
### 4.2 Data Models (Proposed)
|
||||
|
||||
**SocialAccount**
|
||||
- `account`, `site`, `platform`, `auth_tokens`, `status`
|
||||
|
||||
**SocialPost**
|
||||
- `content_id`, `platform`, `post_type`, `caption`, `media_url`, `status`, `scheduled_at`, `published_at`, `external_id`
|
||||
|
||||
**VideoProject**
|
||||
- `content_id`, `type`, `script`, `voice`, `status`, `video_url`, `published_at`
|
||||
|
||||
**EngagementMetric**
|
||||
- `platform`, `post_id`, `views`, `likes`, `comments`, `shares`
|
||||
|
||||
---
|
||||
|
||||
## 5) API Endpoints (Proposed)
|
||||
|
||||
### Socializer
|
||||
- `POST /api/v1/social/accounts/connect/`
|
||||
- `GET /api/v1/social/accounts/`
|
||||
- `POST /api/v1/social/posts/generate/`
|
||||
- `POST /api/v1/social/posts/schedule/`
|
||||
- `POST /api/v1/social/posts/publish/`
|
||||
- `GET /api/v1/social/posts/status/`
|
||||
|
||||
### Video Creator
|
||||
- `POST /api/v1/video/projects/generate/`
|
||||
- `POST /api/v1/video/projects/publish/`
|
||||
- `GET /api/v1/video/projects/status/`
|
||||
|
||||
---
|
||||
|
||||
## 6) Frontend Pages
|
||||
|
||||
### Socializer
|
||||
- `/social/accounts` – connect platforms
|
||||
- `/social/calendar` – unified social calendar
|
||||
- `/social/posts` – list + status + actions
|
||||
|
||||
### Video Creator
|
||||
- `/video/projects` – list + status
|
||||
- `/video/preview/:id` – script + preview + publish
|
||||
|
||||
---
|
||||
|
||||
## 7) Scheduling & Automation Rules
|
||||
- Respect per‑platform rate limits.
|
||||
- Enforce daily/weekly caps per site.
|
||||
- Time‑slot scheduling aligned with Publisher rules.
|
||||
|
||||
---
|
||||
|
||||
## 8) Rollout Phases
|
||||
|
||||
**Phase 1**
|
||||
- Social accounts + post generation
|
||||
- Manual scheduling + publish
|
||||
|
||||
**Phase 2**
|
||||
- Auto‑publish on content approval
|
||||
- Calendar integration
|
||||
|
||||
**Phase 3**
|
||||
- Video creator (short‑form)
|
||||
- Auto‑publish to video platforms
|
||||
|
||||
**Phase 4**
|
||||
- Long‑form video creation + analytics
|
||||
|
||||
---
|
||||
|
||||
## 9) Success Criteria
|
||||
- Every IGNY8 content item can generate social + video assets.
|
||||
- Social posts consistently link back to the original page.
|
||||
- Improved traffic attribution from social channels.
|
||||
|
||||
---
|
||||
|
||||
## 10) Non‑Goals (v1)
|
||||
- Social ad automation
|
||||
- Outreach automation
|
||||
- Shopify/custom CMS publishing
|
||||
@@ -0,0 +1,199 @@
|
||||
# Taxonomy & Term Content Plan (Detailed)
|
||||
|
||||
**Scope:** WordPress-only for current release. Shopify/custom sites remain future.
|
||||
|
||||
**Goal:** Make taxonomy terms and archives first‑class content pages (SEO landing pages) that are generated, optimized, and mapped to clusters using the same Planner → Writer → Optimizer pipeline as posts.
|
||||
|
||||
---
|
||||
|
||||
## 1) Current App Findings (Relevant Parts)
|
||||
|
||||
### 1.1 Content Models Already Support Taxonomy
|
||||
- `Tasks` supports `content_type='taxonomy'` and `content_structure` values including `category_archive`, `tag_archive`, `attribute_archive`, `cluster_hub`. See [backend/igny8_core/business/content/models.py](../../backend/igny8_core/business/content/models.py).
|
||||
- `Content` supports `content_type='taxonomy'` and taxonomy‑specific structures. See [backend/igny8_core/business/content/models.py](../../backend/igny8_core/business/content/models.py).
|
||||
- `ContentTaxonomy` exists with `external_taxonomy`, `external_id`, `description`, and `metadata` for sync. See [backend/igny8_core/business/content/models.py](../../backend/igny8_core/business/content/models.py).
|
||||
|
||||
### 1.2 WordPress Client Already Knows Taxonomies
|
||||
- WordPress REST: `get_categories`, `get_tags`, `create_category`, `create_tag`. See [backend/igny8_core/utils/wordpress.py](../../backend/igny8_core/utils/wordpress.py).
|
||||
- WooCommerce: product categories + attributes and terms are fetchable. See [backend/igny8_core/utils/wordpress.py](../../backend/igny8_core/utils/wordpress.py).
|
||||
|
||||
### 1.3 Sync Service Partially Implemented (Then Removed)
|
||||
- Taxonomy sync functions exist but are currently **skipped** with a “legacy removed” note. See [backend/igny8_core/business/integration/services/content_sync_service.py](../../backend/igny8_core/business/integration/services/content_sync_service.py).
|
||||
- Posts sync assigns placeholder categories/tags by ID; product sync stores categories/tags/attributes in metadata only.
|
||||
|
||||
**Conclusion:** The database and data model already allow taxonomy content, but sync + content generation for terms is incomplete.
|
||||
|
||||
---
|
||||
|
||||
## 2) Target Outcome
|
||||
|
||||
### 2.1 First‑Class Term Landing Pages
|
||||
Every taxonomy term becomes a **rich SEO landing page** rather than a simple list of posts.
|
||||
|
||||
**Examples**
|
||||
- Category: “Electric Toothbrushes” → full landing page with intro, buyer guidance, top items, FAQs.
|
||||
- Tag: “Remote Work” → topical overview + curated posts.
|
||||
- Product Attribute: “Material: Stainless Steel” → attribute landing page.
|
||||
|
||||
### 2.2 Cluster‑Aligned Structure
|
||||
- Each term is mapped to **primary cluster** + optional secondary clusters.
|
||||
- Each term page follows a **cluster‑aligned structure** (H2/H3 outline, keyword coverage, semantic variants).
|
||||
|
||||
### 2.3 Same Pipeline as Posts
|
||||
Planner → Writer → Optimizer should work for taxonomy pages the same way it works for posts.
|
||||
|
||||
---
|
||||
|
||||
## 3) Functional Scope
|
||||
|
||||
### 3.1 Content Writer for All Terms (Default + Custom Taxonomies)
|
||||
- WordPress default: category, post_tag
|
||||
- WooCommerce: product_cat, product_tag
|
||||
- Product attributes (e.g., `pa_color`, `pa_size`)
|
||||
- Custom taxonomies (configurable list from site integration)
|
||||
|
||||
### 3.2 Products Pages Writing
|
||||
- Use existing product sync data + clusters to generate product landing pages and individual product enhancements.
|
||||
|
||||
### 3.3 Services / Company Pages Writing
|
||||
- Use existing Writer structures (`service_page`, `business_page`, `general`) and cluster mapping.
|
||||
|
||||
### 3.4 Cluster Hub Pages
|
||||
- Create **cluster landing pages** that aggregate all related terms + posts + products.
|
||||
|
||||
---
|
||||
|
||||
## 4) Architecture Plan (Aligned with App)
|
||||
|
||||
### 4.1 Taxonomy Sync (WordPress)
|
||||
**Add/Restore taxonomy sync** in `ContentSyncService`:
|
||||
- Fetch categories & tags via `WordPressClient`.
|
||||
- Fetch WooCommerce product categories + attributes + terms.
|
||||
- Sync to `ContentTaxonomy` with correct `external_taxonomy` values.
|
||||
|
||||
**Why:** term metadata is required for mapping, writing, and linking.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Term‑to‑Cluster Mapping
|
||||
**New service:** `business/optimization/cluster_mapping.py` (shared with Optimizer).
|
||||
- Map term name/description to clusters.
|
||||
- Persist mapping in `Content.cluster` and/or a `ContentClusterMap`.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Term Content Generation (Writer)
|
||||
**Flow (mirrors posts):**
|
||||
1. Create `ContentIdea` from term + cluster data.
|
||||
2. Create `Tasks` with `content_type='taxonomy'` and appropriate `content_structure`.
|
||||
3. Use Writer to generate HTML content, meta, and structure.
|
||||
4. Save as `Content` linked to `ContentTaxonomy`.
|
||||
|
||||
**Suggested structures:**
|
||||
- Category → `category_archive`
|
||||
- Tag → `tag_archive`
|
||||
- Attribute → `attribute_archive`
|
||||
- Cluster landing → `cluster_hub`
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Publishing Back to WordPress
|
||||
**WordPress update targets:**
|
||||
- Write generated content to **term description** for taxonomy pages.
|
||||
- Store structured content in term meta (future: ACF or custom IGNY8 plugin fields).
|
||||
|
||||
**Notes:**
|
||||
- WordPress term descriptions accept HTML and can be displayed by themes.
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Optimizer Alignment
|
||||
Use Optimizer to:
|
||||
- Rewrite existing term descriptions.
|
||||
- Align term pages with cluster keyword targets.
|
||||
- Inject schema and metadata updates.
|
||||
|
||||
---
|
||||
|
||||
## 5) API & UI Plan
|
||||
|
||||
### 5.1 API Endpoints (Proposed)
|
||||
- `GET /api/v1/taxonomy/terms/?site_id=`
|
||||
- `POST /api/v1/taxonomy/terms/sync/`
|
||||
- `POST /api/v1/taxonomy/terms/create_tasks/`
|
||||
- `POST /api/v1/taxonomy/terms/optimize/`
|
||||
- `POST /api/v1/taxonomy/terms/publish/`
|
||||
|
||||
### 5.2 UI Pages
|
||||
- **Taxonomy Overview**: list terms, cluster mapping status, content status.
|
||||
- **Term Content Editor**: preview + rewrite + publish.
|
||||
- **Cluster Hub Manager**: cluster landing pages and cross‑links.
|
||||
|
||||
---
|
||||
|
||||
## 6) Keyword + Cluster Strategy
|
||||
|
||||
### 6.1 Term‑Cluster Assignment
|
||||
- Auto‑assign cluster based on keyword overlap + semantic similarity.
|
||||
- Manual override in UI.
|
||||
|
||||
### 6.2 Term Keyword Targets
|
||||
- Use cluster keywords as primary/secondary targets.
|
||||
- Add term‑specific modifiers (e.g., “best”, “vs”, “near me”).
|
||||
|
||||
---
|
||||
|
||||
## 7) Term Landing Page Structure (SEO‑Rich)
|
||||
|
||||
**Default sections (taxonomy pages):**
|
||||
1. Intro + term definition
|
||||
2. Key subtopics (H2/H3)
|
||||
3. Top related posts/products/services
|
||||
4. FAQs
|
||||
5. Internal links to related clusters
|
||||
|
||||
**Schema:**
|
||||
- `CollectionPage` or `WebPage`
|
||||
- `FAQPage` if FAQs present
|
||||
|
||||
---
|
||||
|
||||
## 8) Rollout Phases
|
||||
|
||||
**Phase 1**
|
||||
- Taxonomy sync (categories + tags)
|
||||
- Term mapping to clusters
|
||||
- Writer tasks for term pages
|
||||
|
||||
**Phase 2**
|
||||
- WooCommerce product categories + attributes
|
||||
- Term optimization with Optimizer
|
||||
|
||||
**Phase 3**
|
||||
- Cluster hub pages
|
||||
- Full taxonomy publish flow
|
||||
|
||||
**Phase 4**
|
||||
- Custom taxonomies configuration
|
||||
- Cross‑site cluster navigation
|
||||
|
||||
---
|
||||
|
||||
## 9) Success Criteria
|
||||
- All taxonomy terms have generated content and cluster mapping.
|
||||
- Term landing pages outperform plain archive pages.
|
||||
- Consistent internal linking between clusters, terms, and posts.
|
||||
|
||||
---
|
||||
|
||||
## 10) Risks & Mitigations
|
||||
- **Risk:** Themes not showing term descriptions → **Mitigation:** IGNY8 plugin blocks or template guidance.
|
||||
- **Risk:** Incorrect cluster mapping → **Mitigation:** manual override + suggestions.
|
||||
- **Risk:** Over‑optimization → **Mitigation:** density caps + manual review.
|
||||
|
||||
---
|
||||
|
||||
## 11) Non‑Goals (v1)
|
||||
- Shopify taxonomy sync
|
||||
- Custom CMS adapters
|
||||
- Automated publishing without review
|
||||
145
v2/Live Docs on Server/igny8-app-docs/plans/GO-LIVE-CHECKLIST.md
Normal file
145
v2/Live Docs on Server/igny8-app-docs/plans/GO-LIVE-CHECKLIST.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# 🚀 IGNY8 Go-Live Checklist
|
||||
|
||||
**Date:** January 20, 2026
|
||||
**Purpose:** Quick reference for launching IGNY8 to production
|
||||
|
||||
---
|
||||
|
||||
## ✅ Pre-Launch Checklist
|
||||
|
||||
### Infrastructure Ready
|
||||
- [ ] PostgreSQL running and accessible
|
||||
- [ ] Redis running and accessible
|
||||
- [ ] Caddy configured with SSL for all domains
|
||||
- [ ] DNS records pointing to server
|
||||
- [ ] Firewall configured (ports 80, 443 open)
|
||||
|
||||
### Application Ready
|
||||
- [ ] Production `.env` configured with real secrets
|
||||
- [ ] All API keys set (OpenAI, Stripe, etc.)
|
||||
- [ ] Django `SECRET_KEY` is unique and secure
|
||||
- [ ] `DEBUG=False` in production
|
||||
- [ ] CORS and ALLOWED_HOSTS configured
|
||||
|
||||
### Operational Scripts Ready
|
||||
- [x] `/data/app/igny8/scripts/ops/backup-db.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/backup-full.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/restore-db.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/deploy-production.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/deploy-staging.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/rollback.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/health-check.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/sync-prod-to-staging.sh`
|
||||
- [x] `/data/app/igny8/scripts/ops/log-rotate.sh`
|
||||
|
||||
### Staging Environment (Optional but Recommended)
|
||||
- [x] `docker-compose.staging.yml` created
|
||||
- [x] `.env.staging.example` created
|
||||
- [ ] Copy to `.env.staging` and configure
|
||||
- [ ] Create staging database
|
||||
- [ ] Configure staging DNS records
|
||||
|
||||
---
|
||||
|
||||
## 🏁 Go-Live Steps
|
||||
|
||||
### Step 1: Create Initial Backup
|
||||
```bash
|
||||
/data/app/igny8/scripts/ops/backup-db.sh pre-deploy
|
||||
```
|
||||
|
||||
### Step 2: Verify Health
|
||||
```bash
|
||||
/data/app/igny8/scripts/ops/health-check.sh
|
||||
```
|
||||
|
||||
### Step 3: Set Up Automated Backups
|
||||
```bash
|
||||
# Install cron job
|
||||
sudo cp /data/app/igny8/scripts/ops/igny8-cron /etc/cron.d/igny8
|
||||
sudo chmod 644 /etc/cron.d/igny8
|
||||
|
||||
# Verify cron
|
||||
sudo crontab -l -u root
|
||||
```
|
||||
|
||||
### Step 4: Test Backup & Restore (Optional)
|
||||
```bash
|
||||
# Create test backup
|
||||
/data/app/igny8/scripts/ops/backup-db.sh daily
|
||||
|
||||
# Verify backup exists
|
||||
ls -la /data/backups/daily/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Daily Operations
|
||||
|
||||
### Check System Health
|
||||
```bash
|
||||
/data/app/igny8/scripts/ops/health-check.sh
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# Backend logs
|
||||
docker logs -f igny8_backend
|
||||
|
||||
# All app logs
|
||||
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app logs -f
|
||||
```
|
||||
|
||||
### Container Status
|
||||
```bash
|
||||
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Emergency Procedures
|
||||
|
||||
### Immediate Rollback
|
||||
```bash
|
||||
/data/app/igny8/scripts/ops/rollback.sh
|
||||
```
|
||||
|
||||
### Restore Database
|
||||
```bash
|
||||
# List available backups
|
||||
ls -la /data/backups/
|
||||
|
||||
# Restore from latest
|
||||
/data/app/igny8/scripts/ops/restore-db.sh /data/backups/latest_db.sql.gz
|
||||
```
|
||||
|
||||
### Restart All Services
|
||||
```bash
|
||||
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app restart
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Key File Locations
|
||||
|
||||
| Item | Location |
|
||||
|------|----------|
|
||||
| Production Compose | `/data/app/igny8/docker-compose.app.yml` |
|
||||
| Staging Compose | `/data/app/igny8/docker-compose.staging.yml` |
|
||||
| Production Env | `/data/app/igny8/.env` |
|
||||
| Staging Env | `/data/app/igny8/.env.staging` |
|
||||
| Ops Scripts | `/data/app/igny8/scripts/ops/` |
|
||||
| Backups | `/data/backups/` |
|
||||
| Logs | `/data/logs/` |
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support Contacts
|
||||
|
||||
- **Documentation:** `/data/app/igny8/docs/`
|
||||
- **Deployment Guide:** `/data/app/igny8/docs/50-DEPLOYMENT/`
|
||||
- **Operations Guide:** `/data/app/igny8/docs/50-DEPLOYMENT/DEVOPS-OPERATIONS-GUIDE.md`
|
||||
|
||||
---
|
||||
|
||||
**You're ready to go live! 🎉**
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,820 @@
|
||||
# Doc C — WordPress Ecosystem: Development Guide for Claude Code
|
||||
|
||||
**Version:** 1.0
|
||||
**Date:** March 2026
|
||||
**For:** Claude Code (Opus 4.6) in VSCode
|
||||
**Purpose:** Step-by-step implementation guide for the IGNY8 WordPress Plugin (free + connected), Companion Theme, and Toolkit Plugin — where every file goes, what it does, build sequence, and how it connects to the IGNY8 SaaS platform
|
||||
**Scope:** Plugin (14 modules, 10 build phases), Theme (7 CPTs, 9 taxonomies, 15 landing sections, 50+ block patterns, 5 starters, WooCommerce), Toolkit Plugin (5 modules)
|
||||
**Rule:** Plugin works with ANY theme. Theme works WITHOUT the plugin. Both work WITHOUT the IGNY8 SaaS platform. Connected mode enhances both but is never required.
|
||||
**Dependency:** Doc A (SAG Architecture) defines the blueprint/cluster data the plugin receives in connected mode. Doc B (Platform Modules) defines the IGNY8 SaaS features that push content/schemas/links to the plugin.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architecture Overview — Three Deliverables](#1-architecture-overview)
|
||||
2. [Build Sequence — What Comes First](#2-build-sequence)
|
||||
3. [IGNY8 Plugin — Foundation & Infrastructure](#3-plugin-foundation)
|
||||
4. [IGNY8 Plugin — Module 1: SEO Core](#4-seo-core)
|
||||
5. [IGNY8 Plugin — Module 2: Schema](#5-schema)
|
||||
6. [IGNY8 Plugin — Module 3: Sitemap](#6-sitemap)
|
||||
7. [IGNY8 Plugin — Module 4: Redirects](#7-redirects)
|
||||
8. [IGNY8 Plugin — Module 5: Site Intelligence](#8-site-intelligence)
|
||||
9. [IGNY8 Plugin — Module 6: Internal Linking](#9-internal-linking)
|
||||
10. [IGNY8 Plugin — Module 7: GSC](#10-gsc)
|
||||
11. [IGNY8 Plugin — Module 8: Socializer](#11-socializer)
|
||||
12. [IGNY8 Plugin — Module 9: Analytics + SMTP](#12-analytics-smtp)
|
||||
13. [IGNY8 Plugin — Module 10: Content Sync (Connected)](#13-content-sync)
|
||||
14. [IGNY8 Plugin — Module 11: SAG Structure (Connected)](#14-sag-structure)
|
||||
15. [IGNY8 Plugin — Setup Wizard & Import](#15-setup-wizard)
|
||||
16. [IGNY8 Plugin — Data Architecture](#16-data-architecture)
|
||||
17. [IGNY8 Plugin — REST API Endpoints](#17-rest-api)
|
||||
18. [IGNY8 Plugin — Performance Rules](#18-performance)
|
||||
19. [Theme — CPTs, Taxonomies, Meta Fields](#19-theme-structure)
|
||||
20. [Theme — Term Landing Pages & SAG Templates](#20-term-templates)
|
||||
21. [Theme — Landing Page Builder](#21-landing-pages)
|
||||
22. [Theme — Block Patterns & Starter Templates](#22-patterns-starters)
|
||||
23. [Theme — Interlinking Display Components](#23-interlinking-display)
|
||||
24. [Theme — WooCommerce Integration](#24-woocommerce)
|
||||
25. [Theme — Design System & Build Plan](#25-design-build)
|
||||
26. [Toolkit Plugin — All 5 Modules](#26-toolkit)
|
||||
27. [Free vs Premium Split](#27-free-premium)
|
||||
28. [Theme ↔ Plugin Contract](#28-theme-plugin-contract)
|
||||
29. [Plugin ↔ IGNY8 SaaS Communication](#29-saas-communication)
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Overview — Three Deliverables {#1-architecture-overview}
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────┐
|
||||
│ IGNY8 PLUGIN │
|
||||
│ SEO Core, Schema, Sitemap, Redirects, Analytics, GSC, │
|
||||
│ Site Intelligence, Internal Linking, Socializer, SMTP │
|
||||
│ + Connected: Content Sync, SAG Structure │
|
||||
│ │
|
||||
│ Distribution: Free on WordPress.org │
|
||||
│ Connected premium features via IGNY8 SaaS subscription │
|
||||
│ Works with ANY theme │
|
||||
└──────────────────────┬─────────────────────────────────────┘
|
||||
│ reads plugin data via igny8() API
|
||||
┌──────────────────────┴─────────────────────────────────────┐
|
||||
│ COMPANION THEME │
|
||||
│ 7 CPTs, 9 custom taxonomies, meta fields, SAG templates, │
|
||||
│ term landing pages, landing page builder, 50+ patterns, │
|
||||
│ 5 site-type starters, WooCommerce templates, design system │
|
||||
│ │
|
||||
│ Distribution: Free on WordPress.org (limited), Premium │
|
||||
│ Works WITHOUT the plugin (graceful degradation) │
|
||||
└──────────────────────┬─────────────────────────────────────┘
|
||||
│ extends theme with infrastructure
|
||||
┌──────────────────────┴─────────────────────────────────────┐
|
||||
│ TOOLKIT PLUGIN │
|
||||
│ Performance/Caching, Forms, Security, SMTP, WooCommerce │
|
||||
│ │
|
||||
│ Distribution: Free on WordPress.org (limited), Premium │
|
||||
│ Works with ANY theme │
|
||||
└────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key principle:** Each piece works independently. Together they create the full SAG-optimized WordPress experience.
|
||||
|
||||
---
|
||||
|
||||
## 2. Build Sequence — What Comes First {#2-build-sequence}
|
||||
|
||||
```
|
||||
PHASE 1 (Days 1-3) ─── Plugin Foundation
|
||||
│ Skeleton, module manager, admin menu, REST API base,
|
||||
│ setup wizard, database installer, compatibility layer
|
||||
|
||||
PHASE 2 (Days 4-7) ─── SEO Core Module
|
||||
│ Meta box, title tags, meta description, content analysis,
|
||||
│ OG tags, breadcrumbs, robots.txt, verification codes
|
||||
|
||||
PHASE 3 (Days 8-10) ─── Schema + Sitemap + Redirects
|
||||
│ JSON-LD schemas, XML sitemap, redirect manager, 404 monitor
|
||||
|
||||
PHASE 4 (Days 11-13) ── Site Intelligence
|
||||
│ Site audit, orphan detection, thin content, cannibalization,
|
||||
│ cluster detection, intelligence dashboard
|
||||
|
||||
PHASE 5 (Days 14-15) ── Internal Linking
|
||||
│ Link crawl, link audit, suggestions, link graph
|
||||
|
||||
PHASE 6 (Days 16-18) ── Socializer + Analytics + SMTP
|
||||
│ Share buttons, social profiles, GA/GTM connector,
|
||||
│ pixel manager, SMTP override, GSC OAuth + dashboard
|
||||
|
||||
PHASE 7 (Days 19-20) ── SEO Import
|
||||
│ Yoast, RankMath, AIOSEO importers
|
||||
|
||||
PHASE 8 (Days 21-23) ── Connected Mode — Content Sync
|
||||
│ API client, content puller, mapper, sync queue, scheduler
|
||||
|
||||
PHASE 9 (Days 24-27) ── Connected Mode — SAG Structure
|
||||
│ Blueprint sync, taxonomy builder, term builder,
|
||||
│ cluster manager, structure visualizer
|
||||
|
||||
PHASE 10 (Days 28-30) ─ Polish + Package
|
||||
Cross-module testing, performance audit, WP.org compliance
|
||||
|
||||
--- THEME BUILD (30 days, can overlap with plugin Phases 6+) ---
|
||||
|
||||
THEME PHASE 1 (Days 1-3) Foundation (templates, design system, CSS)
|
||||
THEME PHASE 2 (Days 4-6) CPTs + Taxonomies + Meta Boxes
|
||||
THEME PHASE 3 (Days 7-9) Term Landing Page Templates
|
||||
THEME PHASE 4 (Days 10-11) Interlinking Display Components
|
||||
THEME PHASE 5 (Days 12-14) Landing Page Builder
|
||||
THEME PHASE 6 (Days 15-16) Single Templates (service, portfolio, docs)
|
||||
THEME PHASE 7 (Days 17-19) Custom Blocks + 50+ Patterns
|
||||
THEME PHASE 8 (Days 20-22) Site-Type Starters + WooCommerce
|
||||
THEME PHASE 9 (Days 23-27) Toolkit Plugin (all 5 modules)
|
||||
THEME PHASE 10 (Days 28-30) Setup Wizard + Polish
|
||||
|
||||
--- THEME CAN START after Plugin Phase 5 (interlinking data contract defined) ---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. IGNY8 Plugin — Foundation & Infrastructure {#3-plugin-foundation}
|
||||
|
||||
### 3.1 Complete File Structure
|
||||
|
||||
```
|
||||
igny8/
|
||||
├── igny8.php # Plugin header, bootstrap, constants
|
||||
├── readme.txt # WordPress.org readme
|
||||
├── uninstall.php # Full cleanup on uninstall
|
||||
│
|
||||
├── includes/
|
||||
│ ├── class-igny8.php # Main singleton class
|
||||
│ ├── class-module-manager.php # Register, activate, deactivate modules
|
||||
│ ├── class-api-client.php # IGNY8 SaaS API client (connected mode)
|
||||
│ ├── class-connection.php # API key validation, connection status
|
||||
│ ├── class-utils.php # Shared utilities
|
||||
│ ├── class-ajax.php # Shared AJAX handler
|
||||
│ ├── class-rest-api.php # Plugin REST endpoints (for theme + external)
|
||||
│ ├── class-compatibility.php # Detect conflicting plugins, show notices
|
||||
│ │
|
||||
│ ├── modules/ # 14 modules (see sections 4-14)
|
||||
│ │ ├── seo/ # SEO Core (Phase 2)
|
||||
│ │ ├── schema/ # Schema (Phase 3)
|
||||
│ │ ├── sitemap/ # Sitemap (Phase 3)
|
||||
│ │ ├── redirects/ # Redirects (Phase 3)
|
||||
│ │ ├── analytics/ # Analytics (Phase 6)
|
||||
│ │ ├── site-intelligence/ # Site Intelligence (Phase 4)
|
||||
│ │ ├── linking/ # Internal Linking (Phase 5)
|
||||
│ │ ├── gsc/ # Google Search Console (Phase 6)
|
||||
│ │ ├── socializer/ # Social sharing + posting (Phase 6)
|
||||
│ │ ├── smtp/ # Email delivery (Phase 6)
|
||||
│ │ ├── content-sync/ # [Connected] Content from IGNY8 (Phase 8)
|
||||
│ │ └── sag/ # [Connected] SAG structure (Phase 9)
|
||||
│ │
|
||||
│ ├── admin/ # Admin UI infrastructure
|
||||
│ │ ├── class-admin-menu.php # Menu registration (top-level "IGNY8" menu)
|
||||
│ │ ├── class-dashboard.php # Main plugin dashboard page
|
||||
│ │ ├── class-setup-wizard.php # First-run wizard (6 steps)
|
||||
│ │ ├── class-compatibility-notice.php # Admin notices for conflicts
|
||||
│ │ ├── views/ # PHP templates for admin pages
|
||||
│ │ │ ├── dashboard.php
|
||||
│ │ │ ├── wizard/step-1 through step-6.php
|
||||
│ │ │ └── connect-prompt.php
|
||||
│ │ └── assets/
|
||||
│ │ ├── admin.css, admin.js
|
||||
│ │ └── meta-box.css, meta-box.js
|
||||
│ │
|
||||
│ ├── frontend/
|
||||
│ │ ├── class-head-output.php # ALL <head> output (meta, schema, OG) in single hook
|
||||
│ │ ├── class-share-output.php # Share button HTML/CSS/JS (conditional)
|
||||
│ │ └── assets/
|
||||
│ │ ├── share-buttons.css # <3KB, loaded only if share enabled
|
||||
│ │ └── share-buttons.js # <2KB, loaded only if share enabled
|
||||
│ │
|
||||
│ ├── data/
|
||||
│ │ ├── class-installer.php # Create all custom DB tables on activation
|
||||
│ │ ├── class-migrator.php # Version-based migration handler
|
||||
│ │ └── class-importer.php # Import from Yoast/RankMath/AIOSEO
|
||||
│ │
|
||||
│ └── integrations/
|
||||
│ ├── class-woocommerce.php # WooCommerce-specific SEO/schema
|
||||
│ └── class-theme-bridge.php # Data passing to companion theme
|
||||
│
|
||||
└── languages/
|
||||
└── igny8.pot # Translation template
|
||||
```
|
||||
|
||||
### 3.2 Module Manager Pattern
|
||||
|
||||
Every module follows the same lifecycle:
|
||||
|
||||
```php
|
||||
// class-module-manager.php pattern
|
||||
|
||||
class ModuleManager {
|
||||
private $modules = [];
|
||||
|
||||
public function register($slug, $class, $options = []) {
|
||||
// Register module with slug, class path, free/connected flag
|
||||
}
|
||||
|
||||
public function activate($slug) {
|
||||
// Enable module, call module's activate() method
|
||||
}
|
||||
|
||||
public function deactivate($slug) {
|
||||
// Disable module, call module's deactivate() method
|
||||
}
|
||||
|
||||
public function is_active($slug) {
|
||||
// Check igny8_active_modules option
|
||||
}
|
||||
|
||||
public function boot_active_modules() {
|
||||
// Called on plugins_loaded — instantiate all active modules
|
||||
}
|
||||
}
|
||||
|
||||
// Each module extends:
|
||||
abstract class IGNY8_Module {
|
||||
abstract public function get_slug();
|
||||
abstract public function get_name();
|
||||
abstract public function init(); // Hook registration
|
||||
public function activate() {} // On module enable
|
||||
public function deactivate() {} // On module disable
|
||||
public function is_connected_only() { return false; }
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Admin Menu Structure
|
||||
|
||||
```
|
||||
IGNY8 (top-level menu, dashicons-chart-area)
|
||||
├── Dashboard # Plugin overview + connect prompt
|
||||
├── SEO # SEO settings page
|
||||
├── Schema # Schema settings
|
||||
├── Sitemaps # Sitemap configuration
|
||||
├── Redirects # Redirect manager + 404 log
|
||||
├── Site Intelligence # Audit dashboard
|
||||
├── Internal Links # Link audit + suggestions
|
||||
├── Search Console # GSC connection + metrics
|
||||
├── Socializer # Share buttons + accounts
|
||||
├── Analytics # GA/GTM/pixel settings
|
||||
├── SMTP # Mail settings
|
||||
├── Settings # Global plugin settings
|
||||
│
|
||||
└── (Connected only, appear after connection):
|
||||
├── Content Sync # Sync dashboard
|
||||
└── SAG Structure # Blueprint + structure visualizer
|
||||
```
|
||||
|
||||
### 3.4 Public API Class
|
||||
|
||||
The plugin exposes a global function for themes and other plugins:
|
||||
|
||||
```php
|
||||
function igny8() {
|
||||
return IGNY8::instance();
|
||||
}
|
||||
|
||||
// Usage by theme:
|
||||
if (function_exists('igny8')) {
|
||||
$title = igny8()->seo->get_title($post_id);
|
||||
$schema = igny8()->schema->get_json_ld($post_id);
|
||||
$breadcrumbs = igny8()->seo->get_breadcrumbs();
|
||||
$related = igny8()->linking->get_related_links($post_id);
|
||||
$cluster_nav = igny8()->linking->get_cluster_navigation($post_id);
|
||||
$term_content = igny8()->seo->get_term_content($term_id);
|
||||
$share = igny8()->socializer->render_share_buttons($post_id);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4-12. Plugin Modules — Build Reference {#4-seo-core}
|
||||
|
||||
Each module section below specifies: files to create, what each file does, which hooks it uses, what data it reads/writes, and how it connects to the IGNY8 SaaS platform in connected mode.
|
||||
|
||||
### Module 1: SEO Core (Phase 2, Days 4-7)
|
||||
|
||||
**Files:** `modules/seo/` — 9 PHP files + 2 view templates (see file structure above)
|
||||
|
||||
**What it does:**
|
||||
- `class-meta-box.php` — Adds SEO meta box below editor on all post types. Fields: focus keyword, SEO title (with SERP preview), meta description (with character counter), canonical URL, robots index/follow toggles. Saves to `_igny8_*` post meta keys. Connected enhancement: multi-keyword fields, cluster-aware scoring.
|
||||
- `class-title-tag.php` — Manages `<title>` tag output. Template system: `{site_title} {separator} {page_title}`. Customizable per post type. Filters: `igny8_title_parts`, `igny8_title_separator`.
|
||||
- `class-meta-tags.php` — Outputs `<meta name="description">`, `<meta name="robots">`, `<link rel="canonical">` in `<head>`. Single `wp_head` hook at priority 1.
|
||||
- `class-content-analysis.php` — Real-time JavaScript analysis in editor: keyword density, heading check (H1 count, H2 usage), readability score (Flesch), internal link count, image alt text check, meta length check. Outputs score 0-100 stored in `_igny8_seo_score`.
|
||||
- `class-breadcrumbs.php` — Generates breadcrumb HTML. Shortcode `[igny8_breadcrumbs]`. Also outputs BreadcrumbList schema. SAG-aware: when connected, breadcrumbs follow dimensional path (Home → Attribute → Term → Post).
|
||||
- `class-opengraph.php` — OG title, description, image, type, URL. Twitter Card meta. Per-post overrides via meta box.
|
||||
- `class-robots-txt.php` — Virtual robots.txt manager. Adds sitemap URL, custom rules.
|
||||
- `class-verification.php` — Google, Bing, Pinterest, Yandex verification meta tags.
|
||||
|
||||
**WP Hooks used:** `wp_head` (priority 1), `add_meta_boxes`, `save_post`, `wp_title`, `document_title_parts`, `the_content` (for breadcrumb auto-insertion option).
|
||||
|
||||
### Module 2: Schema (Phase 3, Days 8-9)
|
||||
|
||||
**Files:** `modules/schema/` — 12 PHP files
|
||||
|
||||
**What it does:** Generates JSON-LD for all schema types and outputs as a single `<script type="application/ld+json">` block in `wp_head`. Schema types: Article/BlogPosting, Organization/Person, WebPage/CollectionPage, BreadcrumbList, FAQPage, HowTo, LocalBusiness, Service, Product (WooCommerce), WebSite+SearchAction, custom field for advanced users.
|
||||
|
||||
**Schema selection logic:** Content type → schema type mapping. Blog posts get Article. Service CPT gets Service. Products get Product. FAQ CPT gets FAQPage. Hub pages get WebPage. Users can override via `_igny8_schema_type` meta.
|
||||
|
||||
**Connected enhancement:** IGNY8 SaaS pushes pre-generated schemas (FAQ pairs, HowTo steps, Pros/Cons) via bulk endpoint. Plugin stores in post meta and renders alongside auto-generated schemas.
|
||||
|
||||
### Module 3: Sitemap (Phase 3, Day 9)
|
||||
|
||||
**Files:** `modules/sitemap/` — 5 PHP files
|
||||
|
||||
**What it does:** XML sitemap index at `/sitemap_index.xml`. Sub-sitemaps per post type, per taxonomy, image sitemap. XSL stylesheet for browser viewing. Respects noindex settings. Includes `<lastmod>`, `<image:image>`. WooCommerce: includes price, availability in product sitemap entries.
|
||||
|
||||
### Module 4: Redirects (Phase 3, Day 10)
|
||||
|
||||
**Files:** `modules/redirects/` — 4 PHP files + 1 view
|
||||
|
||||
**What it does:** CRUD for 301/302/307 redirects in custom DB table. 404 monitoring with hit count. Auto-redirect when post slug changes. CSV import/export. Admin page with sortable/filterable table.
|
||||
|
||||
### Module 5: Site Intelligence (Phase 4, Days 11-13)
|
||||
|
||||
**Files:** `modules/site-intelligence/` — 10 PHP files + 2 views
|
||||
|
||||
**What it does (Free):** Site audit runner (WP Cron scheduled, weekly). Orphan page detection (pages with 0 internal links pointing to them). Thin content scanner (below word count threshold, configurable). Empty taxonomy terms. Duplicate title/meta detection. Keyword cannibalization (multiple pages targeting same keyword). Basic cluster detection (auto-group content by topic similarity).
|
||||
|
||||
**What it does (Connected):** Full SAG gap analysis: missing clusters, missing term pages, missing attribute values. Cluster health scores: content coverage, link density per cluster. Architecture blueprint showing recommended new pages. Priority queue: which gaps to fill first. Topical authority scoring per cluster.
|
||||
|
||||
**Dashboard:** Admin page showing audit results. Severity color coding (critical/warning/info). Per-issue action buttons. Re-run audit button. Connected: SAG gap analysis section with "Connect to IGNY8 to fix these gaps" CTA.
|
||||
|
||||
### Module 6: Internal Linking (Phase 5, Days 14-15)
|
||||
|
||||
**Files:** `modules/linking/` — 7 PHP files + 2 views
|
||||
|
||||
**What it does (Free):** Link crawl: scans all content, builds `{prefix}igny8_link_map` table. Link audit page: per-post inbound/outbound link counts, broken links, orphan pages. Link suggestions: "This post could link to these 5 related posts" based on shared categories/tags/keywords. Link equity distribution overview.
|
||||
|
||||
**What it does (Connected):** SAG-aware automatic link suggestions based on cluster relationships (data from Doc A Linker module). Bulk link insertion tool. Link template rules (keyword → URL mapping). Contextual link placement (AI-determined anchor text and position). Link priority scoring based on cluster importance.
|
||||
|
||||
### Module 7: GSC (Phase 6, Day 18)
|
||||
|
||||
**Files:** `modules/gsc/` — 6 PHP files + 2 views
|
||||
|
||||
**What it does (Free):** Google OAuth connection. Search performance dashboard: clicks, impressions, CTR, position. Keyword position tracking. Date range filtering.
|
||||
|
||||
**What it does (Connected):** URL Inspection API: inspect any URL's index status. Auto-indexing: content published via IGNY8 gets auto-inspected and index-requested (driven by IGNY8 SaaS, plugin receives status updates). Status display: index status column in content list, metabox on edit screen.
|
||||
|
||||
### Module 8: Socializer (Phase 6, Days 16-17)
|
||||
|
||||
**Files:** `modules/socializer/` — 8 PHP files + 2 views
|
||||
|
||||
**What it does (Free):** Pure CSS/JS share buttons (zero external dependencies, <5KB total). Position options: floating, above/below content, inline. Networks: Facebook, X, LinkedIn, Pinterest, WhatsApp, Telegram, Email, Copy Link. Social profile links management (site-wide, used in schema).
|
||||
|
||||
**What it does (Connected):** Auto-post to social platforms on WordPress publish (content adapted by IGNY8 SaaS Socializer module). Per-platform message templates. Scheduling. Platform API connections: X, Facebook Pages, LinkedIn, Instagram. Dynamic OG image generation. Social post performance tracking.
|
||||
|
||||
### Module 9: Analytics + SMTP (Phase 6, Days 17-18)
|
||||
|
||||
**Analytics files:** `modules/analytics/` — 4 PHP files
|
||||
**SMTP files:** `modules/smtp/` — 3 PHP files
|
||||
|
||||
**Analytics:** GA4 connector (tracking code injection), GTM connector, Facebook/TikTok/Pinterest pixel manager, custom header/footer scripts manager.
|
||||
|
||||
**SMTP:** Override `wp_mail()` with SMTP credentials. Email delivery log in custom DB table. Test email utility.
|
||||
|
||||
---
|
||||
|
||||
## 13. IGNY8 Plugin — Content Sync (Connected Mode) {#13-content-sync}
|
||||
|
||||
### Files
|
||||
|
||||
```
|
||||
modules/content-sync/
|
||||
├── class-sync-module.php # Module entry, WP Cron scheduling
|
||||
├── class-content-puller.php # Fetch content from IGNY8 SaaS API
|
||||
├── class-content-mapper.php # Map IGNY8 content_type → WP post type
|
||||
├── class-image-downloader.php # Download AI images → attach to posts
|
||||
├── class-sync-queue.php # {prefix}igny8_sync_queue table management
|
||||
├── class-publish-scheduler.php # Review queue, scheduled publishing
|
||||
└── views/
|
||||
├── sync-dashboard.php # Status, controls, history
|
||||
└── content-review.php # Review queue before publish
|
||||
```
|
||||
|
||||
### Content Type Mapping
|
||||
|
||||
| IGNY8 content_type | WP post_type | WP template | Notes |
|
||||
|-------------------|-------------|-------------|-------|
|
||||
| post | post | Standard | Default |
|
||||
| page | page | Default page | — |
|
||||
| product | product | WooCommerce product | Requires WooCommerce |
|
||||
| service | service | single-service template | Requires theme CPT |
|
||||
| company_page | page | page-company template | — |
|
||||
| taxonomy_landing | — | — | Written to term description, not a post |
|
||||
| cluster_hub | page | page-hub template | Landing page preset applied |
|
||||
| comparison | post | Standard | — |
|
||||
| brand_page | post or page | Standard | — |
|
||||
|
||||
### Sync Flow
|
||||
|
||||
```
|
||||
IGNY8 SaaS publishes content
|
||||
→ Webhook to plugin: POST /wp-json/igny8/v1/sync/push
|
||||
→ Plugin creates sync queue entry (status: pending)
|
||||
→ WP Cron processes queue:
|
||||
1. Download images → attach to media library
|
||||
2. Map content_type → WP post_type
|
||||
3. Create/update WP post with content, meta, featured image
|
||||
4. Assign taxonomies from IGNY8 cluster/attribute data
|
||||
5. If taxonomy_landing: write to term description instead
|
||||
6. Status: 'review' (manual review required) or 'synced' (auto-publish)
|
||||
→ Plugin POSTs confirmation back to IGNY8 SaaS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. IGNY8 Plugin — SAG Structure (Connected Mode) {#14-sag-structure}
|
||||
|
||||
### Files
|
||||
|
||||
```
|
||||
modules/sag/
|
||||
├── class-sag-module.php # Module entry
|
||||
├── class-blueprint-sync.php # Receive SAG blueprint JSON from IGNY8
|
||||
├── class-taxonomy-builder.php # register_taxonomy() from SAG attributes
|
||||
├── class-term-builder.php # wp_insert_term() from attribute values
|
||||
├── class-cluster-manager.php # Map clusters → hub pages/terms
|
||||
├── class-structure-visualizer.php # Visual map of site structure
|
||||
└── views/
|
||||
├── sag-dashboard.php # SAG structure overview
|
||||
├── blueprint-review.php # Review proposed taxonomies before applying
|
||||
└── structure-map.php # Visual cluster/taxonomy/content map
|
||||
```
|
||||
|
||||
### Blueprint Sync Flow
|
||||
|
||||
```
|
||||
IGNY8 SaaS confirms blueprint
|
||||
→ POST to plugin: /wp-json/igny8/v1/sag/sync-blueprint
|
||||
→ Plugin stores blueprint in igny8_sag_blueprint option
|
||||
→ Blueprint review UI shows proposed changes:
|
||||
- New taxonomies to create
|
||||
- New terms to create
|
||||
- Existing taxonomies to map
|
||||
→ User approves → Taxonomy Builder executes:
|
||||
1. register_taxonomy() for each SAG attribute
|
||||
(stored in option for persistence across requests)
|
||||
2. wp_insert_term() for each attribute value
|
||||
3. Set term meta: _igny8_term_sag_attribute, _igny8_term_sag_level
|
||||
4. Map clusters to existing hub pages or create placeholders
|
||||
→ Plugin POSTs success + term IDs back to IGNY8 SaaS
|
||||
→ IGNY8 SaaS updates SAGAttribute.wp_taxonomy_slug and wp_sync_status
|
||||
```
|
||||
|
||||
### Structure Visualizer
|
||||
|
||||
Admin page showing:
|
||||
- Tree view: Taxonomies → Terms → assigned posts/products
|
||||
- Cluster map: clusters as cards with hub page status, supporting content count
|
||||
- Gap indicators: terms without content, clusters without hubs
|
||||
- Connected to IGNY8: "Generate content for this term" links to IGNY8 app
|
||||
|
||||
---
|
||||
|
||||
## 15. Setup Wizard & SEO Import {#15-setup-wizard}
|
||||
|
||||
### Wizard Steps
|
||||
|
||||
| Step | Title | What It Does |
|
||||
|------|-------|-------------|
|
||||
| 1 | Site Type | Blog, Business, eCommerce, Local Business, SaaS, Portfolio — sets schema defaults |
|
||||
| 2 | SEO Import | Detects Yoast/RankMath/AIOSEO → one-click import via WP Cron |
|
||||
| 3 | Site Basics | Site name, Organization/Person, logo, title separator |
|
||||
| 4 | Social Profiles | All social URLs + default sharing image |
|
||||
| 5 | Connect to IGNY8 | API key input, connection test, site pairing. Clear skip option. |
|
||||
| 6 | Done | Summary, "Run first site audit" CTA, key settings links |
|
||||
|
||||
### SEO Import Data Mapping
|
||||
|
||||
| Source | Source Meta Key | IGNY8 Meta Key |
|
||||
|--------|----------------|---------------|
|
||||
| Yoast | `_yoast_wpseo_focuskw` | `_igny8_focus_keyword` |
|
||||
| Yoast | `_yoast_wpseo_title` | `_igny8_seo_title` |
|
||||
| Yoast | `_yoast_wpseo_metadesc` | `_igny8_meta_description` |
|
||||
| Yoast | `_yoast_wpseo_canonical` | `_igny8_canonical_url` |
|
||||
| Yoast | `_yoast_wpseo_meta-robots-noindex` | `_igny8_robots_index` |
|
||||
| RankMath | `rank_math_focus_keyword` | `_igny8_focus_keyword` |
|
||||
| RankMath | `rank_math_title` | `_igny8_seo_title` |
|
||||
| RankMath | `rank_math_description` | `_igny8_meta_description` |
|
||||
| RankMath | `rank_math_canonical_url` | `_igny8_canonical_url` |
|
||||
| RankMath | `rank_math_robots` | `_igny8_robots_index` + `_igny8_robots_follow` |
|
||||
| AIOSEO | `_aioseo_title` | `_igny8_seo_title` |
|
||||
| AIOSEO | `_aioseo_description` | `_igny8_meta_description` |
|
||||
|
||||
Import runs as background WP Cron task with progress tracking. Completion report shows counts per data type.
|
||||
|
||||
---
|
||||
|
||||
## 16. Data Architecture {#16-data-architecture}
|
||||
|
||||
### Custom Database Tables
|
||||
|
||||
| Table | Purpose | Created In |
|
||||
|-------|---------|-----------|
|
||||
| `{prefix}igny8_redirects` | Redirect rules (source, target, type, hits) | Phase 3 |
|
||||
| `{prefix}igny8_404_log` | 404 hit log (URL, referrer, hits, resolved) | Phase 3 |
|
||||
| `{prefix}igny8_link_map` | Internal link graph (source→target, anchor, position) | Phase 5 |
|
||||
| `{prefix}igny8_audit_results` | Site audit findings (type, severity, message, data) | Phase 4 |
|
||||
| `{prefix}igny8_email_log` | SMTP delivery log (to, subject, status, error) | Phase 6 |
|
||||
| `{prefix}igny8_sync_queue` | Content sync queue (IGNY8 ID, status, WP post/term ID) | Phase 8 |
|
||||
|
||||
### Post Meta Keys (all prefixed `_igny8_`)
|
||||
|
||||
SEO: `focus_keyword`, `secondary_keywords`, `seo_title`, `meta_description`, `canonical_url`, `robots_index`, `robots_follow`, `og_title`, `og_description`, `og_image`, `twitter_title`, `twitter_description`, `schema_type`, `schema_custom`, `seo_score`, `content_score`, `readability_score`
|
||||
|
||||
Connected: `cluster_id`, `related_links`, `link_suggestions`, `last_analysis`, `igny8_content_id`, `sync_status`, `schema_faq`, `schema_howto`, `schema_article`, `schema_review`
|
||||
|
||||
### Term Meta Keys (all prefixed `_igny8_term_`)
|
||||
|
||||
`seo_title`, `meta_description`, `robots_index`, `og_image`, `content`, `cluster_id`, `sag_attribute`, `sag_level`, `faq`, `related_terms`, `igny8_id`
|
||||
|
||||
### Options Keys (all prefixed `igny8_`)
|
||||
|
||||
Global: `version`, `active_modules`, `seo_settings`, `schema_settings`, `sitemap_settings`, `social_profiles`, `analytics_settings`, `redirect_settings`, `smtp_settings`, `share_settings`, `linking_settings`, `intelligence_settings`
|
||||
|
||||
Connected: `api_key`, `api_connected`, `site_id`, `last_sync`, `sag_blueprint`, `gsc_token`, `gsc_property`
|
||||
|
||||
---
|
||||
|
||||
## 17. REST API Endpoints {#17-rest-api}
|
||||
|
||||
### Plugin-Provided Endpoints (for theme + external consumers)
|
||||
|
||||
```
|
||||
GET /wp-json/igny8/v1/related/{post_id} # Related content links
|
||||
GET /wp-json/igny8/v1/cluster/{cluster_id} # All content in cluster
|
||||
GET /wp-json/igny8/v1/term/{term_id}/content # Term landing page data
|
||||
GET /wp-json/igny8/v1/structure/overview # Site structure summary
|
||||
GET /wp-json/igny8/v1/audit/summary # Site audit summary
|
||||
```
|
||||
|
||||
### Plugin-Received Endpoints (from IGNY8 SaaS platform)
|
||||
|
||||
```
|
||||
POST /wp-json/igny8/v1/sync/push # Push content from IGNY8
|
||||
POST /wp-json/igny8/v1/sag/sync-blueprint # Push SAG blueprint
|
||||
POST /wp-json/igny8/v1/sag/create-taxonomies # Create taxonomies from blueprint
|
||||
POST /wp-json/igny8/v1/terms/{term_id}/content # Push term content
|
||||
POST /wp-json/igny8/v1/schema/bulk-update # Push schemas for multiple posts
|
||||
POST /wp-json/igny8/v1/gsc/status-sync # Push GSC index statuses
|
||||
POST /wp-json/igny8/v1/event # Webhooks (task ready, etc.)
|
||||
```
|
||||
|
||||
All endpoints validate `X-IGNY8-API-KEY` header against `Site.wp_api_key`.
|
||||
|
||||
---
|
||||
|
||||
## 18. Performance Rules {#18-performance}
|
||||
|
||||
| Rule | How |
|
||||
|------|-----|
|
||||
| Zero frontend CSS unless share buttons enabled | `wp_enqueue_scripts` with conditional check |
|
||||
| Zero frontend JS unless share buttons or dynamic features active | Same conditional |
|
||||
| ALL `<head>` output in single `wp_head` hook at priority 1 | `class-head-output.php` consolidates everything |
|
||||
| Schema as single JSON-LD block, not multiple `<script>` tags | `class-schema-generator.php` merges all schemas |
|
||||
| Admin assets only on IGNY8 admin pages | `admin_enqueue_scripts` with screen check |
|
||||
| Meta box assets only on editor screens | Screen check before enqueue |
|
||||
| Site audit runs via WP Cron, never on page load | `wp_schedule_event` weekly |
|
||||
| Link crawl is background process | WP Cron, batch processing |
|
||||
| Connected mode API calls are async | WP Cron queue, never blocks |
|
||||
| Option reads use object cache | `wp_cache_get/set` wrapping |
|
||||
|
||||
---
|
||||
|
||||
## 19-25. Theme Build Reference {#19-theme-structure}
|
||||
|
||||
### 19. CPTs, Taxonomies, Meta Fields
|
||||
|
||||
**7 CPTs:** service, landing_page, portfolio, team_member, testimonial, faq, documentation
|
||||
|
||||
**9 Custom Taxonomies:** service_category, service_area, service_attribute, cluster, portfolio_category, portfolio_tag, faq_category, doc_category, topic_tag
|
||||
|
||||
**WooCommerce:** Theme does NOT register Woo taxonomies. Provides rich templates for product_cat, product_tag, pa_* archives. Registers `cluster` taxonomy for `product` post type.
|
||||
|
||||
**Meta fields per CPT:** Service (10 fields: price, duration, process steps, outcomes, FAQs, CTA, areas, gallery, related). Portfolio (8 fields: client, date, URL, results, tech, testimonial, gallery, before/after). Team (6 fields: position, email, phone, social, order, department). Testimonial (8 fields: author name/title, company, rating, image, featured, linked service/product). FAQ (2 fields: order, schema enabled). Documentation (4 fields: sidebar, TOC, last reviewed, related docs).
|
||||
|
||||
All theme meta keys use `_theme_` prefix. Theme reads `_igny8_` prefix for plugin data.
|
||||
|
||||
### 20. Term Landing Pages & SAG Templates
|
||||
|
||||
Every taxonomy term archive renders as a rich landing page with 7 sections:
|
||||
|
||||
1. **Term Hero** — H1 from term name, description, breadcrumbs, post count
|
||||
2. **Rich Content** — From IGNY8 `_igny8_term_content` or WP term description
|
||||
3. **Child Terms** — Grid of child term cards (if hierarchical)
|
||||
4. **Posts Grid** — All posts in this term, paginated
|
||||
5. **Related Clusters** — Other clusters sharing attribute values
|
||||
6. **FAQ** — From `_igny8_term_faq` or `_theme_*` meta
|
||||
7. **CTA** — Configurable call to action
|
||||
|
||||
Template files: `taxonomy-service_category.html`, `taxonomy-service_area.html`, `taxonomy-service_attribute.html`, `taxonomy-cluster.html`, `taxonomy-faq_category.html`, `taxonomy-product_cat.html`, `taxonomy-product_tag.html`
|
||||
|
||||
### 21. Landing Page Builder
|
||||
|
||||
**CPT:** `landing_page` with section repeater meta box (`_theme_landing_sections`)
|
||||
|
||||
**15 section types:** hero, features-grid, how-it-works, social-proof, pricing, faq, cta-band, content-block, stats-counter, team, portfolio-grid, contact-form, video-embed, logo-bar, comparison-table
|
||||
|
||||
**8 presets:** SaaS Product, Service Business, Product Launch, Lead Generation, Portfolio/Agency, Event/Webinar, Cluster Hub, Term Landing
|
||||
|
||||
Admin JS: drag-to-reorder sections, add/remove/duplicate, per-section field configuration. Frontend renderer: loops through sections, renders each with its template.
|
||||
|
||||
### 22. Block Patterns & Starter Templates
|
||||
|
||||
**50+ block patterns** organized by category: Heroes (4), Content (4), Social Proof (4), Conversion (4), Services (3), Portfolio (3), Footers (3), plus additional patterns.
|
||||
|
||||
**5 site-type starters** (importable demo content): Blog, SaaS, Corporate, eCommerce, Portfolio. Each includes: front-page, core pages, sample CPT entries, sample taxonomy terms.
|
||||
|
||||
### 23. Interlinking Display Components
|
||||
|
||||
6 template parts that read IGNY8 plugin data with graceful fallbacks:
|
||||
|
||||
| Component | Template Part | Data Source | Fallback |
|
||||
|-----------|-------------|-------------|----------|
|
||||
| Related Content | `parts/related-content.html` | `_igny8_related_links` post meta | WordPress related by category |
|
||||
| Cluster Navigation | `parts/cluster-navigation.html` | `igny8()->linking->get_cluster_navigation()` | Posts in same cluster term |
|
||||
| Attribute Browse | `parts/attribute-browse.html` | Custom taxonomy terms for context | — |
|
||||
| Breadcrumb Trail | `parts/breadcrumbs.html` | `igny8()->seo->get_breadcrumbs()` | Theme simple breadcrumbs |
|
||||
| Term Quick Links | `parts/term-quick-links.html` | All terms assigned to current post | — |
|
||||
| Child Term Grid | `parts/child-term-grid.html` | `get_term_children()` + term meta | — |
|
||||
|
||||
### 24. WooCommerce Integration
|
||||
|
||||
When WooCommerce active, theme provides:
|
||||
- `woocommerce/archive-product.php` — SAG-structured shop page
|
||||
- `woocommerce/single-product.php` — Enhanced product page
|
||||
- `woocommerce/taxonomy-product_cat.php` — Rich category landing page
|
||||
- `woocommerce/content-product.php` — Product card in archives
|
||||
- Cart, checkout, account template overrides
|
||||
|
||||
### 25. Design System & Build Execution
|
||||
|
||||
**theme.json:** Complete design token system — colors (primary palette + extended), typography (heading + body from curated list), spacing scale, shadows, border radius, transitions. Custom tokens for term pages and interlinking components.
|
||||
|
||||
**CSS:** BEM naming `.tn-block__element--modifier`. Total CSS budget <35KB gzipped.
|
||||
**JS:** ES6+, vanilla JS, no jQuery on frontend. Each feature in own file, conditionally loaded. Total JS budget <15KB gzipped.
|
||||
|
||||
**Performance targets:** FCP <1.0s, LCP <1.5s, TBT <50ms, CLS <0.05, PageSpeed 95+ mobile.
|
||||
|
||||
---
|
||||
|
||||
## 26. Toolkit Plugin — All 5 Modules {#26-toolkit}
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
theme-name-toolkit/
|
||||
├── theme-name-toolkit.php # Plugin header, module loader
|
||||
├── readme.txt
|
||||
├── includes/
|
||||
│ ├── class-toolkit.php # Main class
|
||||
│ ├── class-module-manager.php # Module toggle system
|
||||
│ ├── modules/
|
||||
│ │ ├── performance/ # Page cache, asset optimizer, image optimizer,
|
||||
│ │ │ # critical CSS, HTML minifier, browser cache, preload
|
||||
│ │ ├── forms/ # Form builder, renderer, processor, notifications,
|
||||
│ │ │ # entries admin, anti-spam
|
||||
│ │ ├── security/ # Login protection, firewall, hardening, headers, audit log
|
||||
│ │ ├── smtp/ # SMTP mailer, email log, test email
|
||||
│ │ └── woocommerce/ # Quick view, wishlist, AJAX cart, product filters, gallery
|
||||
│ └── admin/
|
||||
│ ├── class-dashboard.php # Module toggle dashboard
|
||||
│ └── views/dashboard.php
|
||||
└── languages/
|
||||
```
|
||||
|
||||
Each module: independent enable/disable toggle from toolkit dashboard. Zero frontend impact when disabled.
|
||||
|
||||
---
|
||||
|
||||
## 27. Free vs Premium Split {#27-free-premium}
|
||||
|
||||
### Theme
|
||||
|
||||
| Feature | Free (WordPress.org) | Premium |
|
||||
|---------|---------------------|---------|
|
||||
| All 7 CPTs + taxonomies | ✅ | ✅ |
|
||||
| All term landing page templates | ✅ | ✅ |
|
||||
| Basic interlinking components | ✅ | ✅ |
|
||||
| Landing page sections | 3 (hero, content-block, cta-band) | All 15 |
|
||||
| Landing page presets | 2 (generic, service) | All 8 |
|
||||
| Block patterns | 10 (2 per category) | All 50+ |
|
||||
| Starter templates | 2 (blog, corporate) | All 5 |
|
||||
| WooCommerce templates | ❌ | ✅ |
|
||||
| Setup wizard + demo import | ❌ | ✅ |
|
||||
| IGNY8 plugin bridge | ✅ Full | ✅ Full |
|
||||
|
||||
### Toolkit Plugin
|
||||
|
||||
| Feature | Free | Premium |
|
||||
|---------|------|---------|
|
||||
| Performance: page cache + HTML minification | ✅ | ✅ |
|
||||
| Performance: full suite (critical CSS, image opt, WebP, assets) | ❌ | ✅ |
|
||||
| Security: basic hardening + login protection | ✅ | ✅ |
|
||||
| Security: full suite (firewall, audit log, 2FA) | ❌ | ✅ |
|
||||
| Forms: basic (5 field types) | ✅ | ✅ |
|
||||
| Forms: full suite (conditional logic, multi-step, file upload) | ❌ | ✅ |
|
||||
| SMTP: basic mail override | ✅ | ✅ |
|
||||
| SMTP: full suite (email log, templates) | ❌ | ✅ |
|
||||
| WooCommerce enhancements | ❌ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 28. Theme ↔ Plugin Contract {#28-theme-plugin-contract}
|
||||
|
||||
### What Theme Reads from Plugin
|
||||
|
||||
| Data | Source | Used For |
|
||||
|------|--------|---------|
|
||||
| SEO title | `igny8()->seo->get_title()` | `<title>` tag (theme defers to plugin) |
|
||||
| Breadcrumbs | `igny8()->seo->get_breadcrumbs()` | Breadcrumb display |
|
||||
| Related links | `_igny8_related_links` post meta | Related Articles section |
|
||||
| Cluster navigation | `igny8()->linking->get_cluster_navigation()` | "More in this topic" section |
|
||||
| Term landing content | `_igny8_term_content` term meta | Term archive hero section |
|
||||
| Term FAQs | `_igny8_term_faq` term meta | FAQ accordion on term pages |
|
||||
| Related terms | `_igny8_term_related_terms` term meta | "Related topics" block |
|
||||
| SAG attribute label | `_igny8_term_sag_attribute` term meta | Attribute navigation sections |
|
||||
| Share buttons | `igny8()->socializer->render_share_buttons()` | Social sharing section |
|
||||
| Schema | `igny8()->schema->get_json_ld()` | Theme does NOT render schema (plugin owns `<head>`) |
|
||||
|
||||
### Graceful Degradation
|
||||
|
||||
```php
|
||||
// Theme pattern for ALL plugin data reads:
|
||||
if (function_exists('igny8')) {
|
||||
$data = igny8()->module->get_data($id);
|
||||
} else {
|
||||
$data = theme_fallback_function($id);
|
||||
}
|
||||
```
|
||||
|
||||
When plugin is NOT installed: breadcrumbs use theme's simple implementation, related content uses category-based matching, term pages show standard WP description, share buttons don't render, schema doesn't output.
|
||||
|
||||
---
|
||||
|
||||
## 29. Plugin ↔ IGNY8 SaaS Communication {#29-saas-communication}
|
||||
|
||||
### Authentication
|
||||
|
||||
- Plugin stores `igny8_api_key` (from setup wizard Step 5)
|
||||
- All requests to IGNY8 SaaS include `Authorization: Bearer {api_key}`
|
||||
- All requests FROM IGNY8 SaaS to plugin include `X-IGNY8-API-KEY` header
|
||||
- Plugin validates against `igny8_api_key` option
|
||||
|
||||
### Outbound (Plugin → SaaS)
|
||||
|
||||
| Event | Endpoint on SaaS | Payload |
|
||||
|-------|------------------|---------|
|
||||
| Site structure sync (daily) | `POST /v1/integration/integrations/{id}/update-structure/` | Post types, taxonomies, counts |
|
||||
| Incremental data sync | `POST /v1/integration/integrations/{id}/sync-data/` | Modified posts since last sync |
|
||||
| Full site scan (weekly) | `POST /v1/integration/integrations/{id}/full-scan/` | All posts, products, taxonomies |
|
||||
| Content publish confirmation | `POST /v1/integration/integrations/{id}/publish-confirm/` | WP post ID, URL, status |
|
||||
| Taxonomy creation report | `POST /v1/integration/integrations/{id}/taxonomy-report/` | Created taxonomy slugs + term IDs |
|
||||
|
||||
### Inbound (SaaS → Plugin)
|
||||
|
||||
| Event | Plugin Endpoint | Payload |
|
||||
|-------|----------------|---------|
|
||||
| Push content | `POST /wp-json/igny8/v1/sync/push` | Content HTML, meta, images, content_type |
|
||||
| Push blueprint | `POST /wp-json/igny8/v1/sag/sync-blueprint` | Full SAG blueprint JSON |
|
||||
| Create taxonomies | `POST /wp-json/igny8/v1/sag/create-taxonomies` | Taxonomy definitions + terms |
|
||||
| Push term content | `POST /wp-json/igny8/v1/terms/{id}/content` | HTML content + meta for term |
|
||||
| Push schemas | `POST /wp-json/igny8/v1/schema/bulk-update` | Schema JSON-LD per post |
|
||||
| Push GSC statuses | `POST /wp-json/igny8/v1/gsc/status-sync` | URL index statuses |
|
||||
| Push events | `POST /wp-json/igny8/v1/event` | task_ready, link_suggestion, etc. |
|
||||
|
||||
### Sync Architecture
|
||||
|
||||
```
|
||||
IGNY8 SaaS (api.igny8.com)
|
||||
│
|
||||
│ Outbound webhooks (push content, blueprints, schemas, statuses)
|
||||
▼
|
||||
WordPress Plugin (site.com/wp-json/igny8/v1/*)
|
||||
│
|
||||
│ Periodic sync (structure, data, confirmations)
|
||||
▼
|
||||
IGNY8 SaaS (api.igny8.com/v1/integration/*)
|
||||
```
|
||||
|
||||
All communication is HTTPS. Plugin-side: async via WP Cron (never blocks page load). SaaS-side: async via Celery tasks (never blocks API response).
|
||||
|
||||
---
|
||||
|
||||
## Reference Documents
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| **IGNY8-Plugin-Build-Plan.md** | Complete plugin plan — modules, features, phases, file structure |
|
||||
| **Theme-Build-Plan.md** | Complete theme plan — CPTs, taxonomies, templates, patterns, toolkit |
|
||||
| **IGNY8-Current-State.md** | Current IGNY8 SaaS state — integration layer, plugin distribution, sync architecture |
|
||||
| **Doc A — SAG Architecture Dev Guide** | SAG models, blueprints — what the plugin receives in connected mode |
|
||||
| **Doc B — Platform Modules Dev Guide** | SaaS modules — what generates content/schemas/links pushed to plugin |
|
||||
|
||||
---
|
||||
|
||||
*End of Doc C — WordPress Ecosystem Development Guide*
|
||||
@@ -0,0 +1,813 @@
|
||||
# Doc D — Business & Services: Development Guide for Claude Code
|
||||
|
||||
**Version:** 1.0
|
||||
**Date:** March 2026
|
||||
**For:** Claude Code (Opus 4.6) in VSCode on IGNY8 repo
|
||||
**Purpose:** Implementation guide for the business/revenue layer — Managed Services Add-On, Backlink Service Tiers, client onboarding workflows, white-label reporting, and billing integration for services
|
||||
**Scope:** Managed Add-On tiers (Lite $100, Pro $399/site/month), Backlink service packaging (self-service + managed), Alorig client portfolio servicing, billing models and margin tracking
|
||||
**Rule:** Core IGNY8 SaaS subscription plans (Free/Starter/Growth/Scale) are unchanged. Managed services are add-ons purchased per site, layered on top of existing plans. All service delivery uses existing IGNY8 platform features — no separate tool stack.
|
||||
**Dependency:** Doc A (SAG Architecture) provides blueprints and campaign generation. Doc B (Platform Modules) provides content pipeline, GSC, Socializer. Doc C (WordPress Ecosystem) provides plugin infrastructure for delivery.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Business Context — Current Revenue Model](#1-business-context)
|
||||
2. [Managed Services Add-On — Product Definition](#2-managed-addon)
|
||||
3. [Backlink Services — Product Definition](#3-backlink-services)
|
||||
4. [Implementation: Backend Models & Billing](#4-backend-models)
|
||||
5. [Implementation: Frontend — Service Management UI](#5-frontend-ui)
|
||||
6. [Implementation: Service Delivery Workflow](#6-delivery-workflow)
|
||||
7. [Implementation: Client Onboarding Automation](#7-onboarding)
|
||||
8. [Implementation: Reporting & White-Label](#8-reporting)
|
||||
9. [Implementation: Alorig Client Portfolio Servicing](#9-alorig-portfolio)
|
||||
10. [API Endpoints](#10-api-endpoints)
|
||||
11. [Celery Tasks](#11-celery-tasks)
|
||||
12. [Feature Flags](#12-feature-flags)
|
||||
13. [Margin Tracking & Business Intelligence](#13-margin-tracking)
|
||||
|
||||
---
|
||||
|
||||
## 1. Business Context — Current Revenue Model {#1-business-context}
|
||||
|
||||
### 1.1 Existing Subscription Plans
|
||||
|
||||
| Plan | Price | Sites | Users | Credits/Month |
|
||||
|------|-------|-------|-------|---------------|
|
||||
| Free | $0 | 1 | 1 | 100 |
|
||||
| Starter | $49 | 3 | 3 | 1,000 |
|
||||
| Growth | $149 | 10 | 10 | 5,000 |
|
||||
| Scale | $349 | Unlimited | Unlimited | 25,000 |
|
||||
|
||||
**Payment:** Stripe and PayPal integrated (pending production credentials). Manual payment methods available.
|
||||
|
||||
**Billing models:** `modules/billing/` (API layer), `business/billing/` (services). `CreditService` manages all credit operations. `Plan` model in `auth/models.py`.
|
||||
|
||||
### 1.2 Revenue Expansion Opportunities
|
||||
|
||||
The current model is pure SaaS subscription + credits. Two service layers add high-margin revenue without changing the core platform:
|
||||
|
||||
1. **Managed Services Add-On** — Alorig team operates the IGNY8 platform on behalf of clients. Client pays subscription + managed fee. Alorig does the work.
|
||||
2. **Backlink Services** — Link building packages using FatGrid/PR distribution. Self-service (client buys intelligence, executes themselves) or managed (Alorig team executes).
|
||||
|
||||
Both use the existing platform. No separate tools needed. The "service" is human expertise + platform operation — not new software features.
|
||||
|
||||
### 1.3 Existing Infrastructure to Leverage
|
||||
|
||||
| What Exists | How Services Use It |
|
||||
|-------------|-------------------|
|
||||
| SAG Site Builder (Doc A Phase 4) | Managed: Alorig runs wizard for client. Self-service: client runs it themselves. |
|
||||
| Content Pipeline (Doc B Modules 1-2) | Managed: Alorig configures and runs automation. Client gets published content. |
|
||||
| Backlink Campaign Generator (Doc A Phase 10) | Both: Platform generates campaign plan. Managed: Alorig executes. Self-service: client executes. |
|
||||
| GSC Integration (Doc B Module 4) | Managed: Alorig monitors indexing, submits URLs. Reports to client. |
|
||||
| Socializer (Doc B Module 6) | Managed Pro: Alorig configures social posting for client. |
|
||||
| Rich Schema (Doc B Module 5) | Managed: Alorig runs retroactive enhancement on client sites. |
|
||||
| WordPress Plugin (Doc C) | Both: Plugin installed on client site. Managed: Alorig configures connected mode. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Managed Services Add-On — Product Definition {#2-managed-addon}
|
||||
|
||||
### 2.1 Tiers
|
||||
|
||||
| Tier | Price | What Client Gets | What Alorig Does |
|
||||
|------|-------|------------------|-----------------|
|
||||
| **Managed Lite** | $100/site/month | 10 articles/month, basic SEO setup, monthly report | Run SAG wizard, configure automation for 10 articles, publish to WordPress, basic optimization, email monthly report |
|
||||
| **Managed Pro** | $399/site/month | 30 articles/month, full SAG architecture, backlink campaign management, social posting, weekly report, dedicated account manager | Full SAG build, configure all automation stages, manage backlink campaign execution, configure socializer, run retroactive schema enhancement, weekly report with KPIs |
|
||||
|
||||
### 2.2 What's Included in Each Tier
|
||||
|
||||
**Managed Lite ($100/site/month):**
|
||||
- Initial SAG blueprint generation (one-time, first month)
|
||||
- 10 articles/month through the content pipeline
|
||||
- Basic WordPress taxonomy setup from blueprint
|
||||
- Content published to WordPress (auto or scheduled)
|
||||
- Monthly PDF report: articles published, keywords tracked, basic traffic metrics
|
||||
- Email support (48hr response)
|
||||
|
||||
**Managed Pro ($399/site/month):**
|
||||
- Everything in Lite, plus:
|
||||
- Full SAG architecture with detailed mode (not quick mode)
|
||||
- 30 articles/month (hub pages + supporting content per blueprint execution priority)
|
||||
- Taxonomy term content generation (term landing pages)
|
||||
- Backlink campaign management: plan generation, link sourcing via FatGrid, quality verification, anchor text management
|
||||
- Monthly backlink budget: separate from service fee, passed through at cost + 20% markup
|
||||
- Social media content generation + scheduling across configured platforms
|
||||
- Retroactive schema enhancement on existing site pages
|
||||
- GSC monitoring: auto-indexing, re-inspection, issue alerts
|
||||
- Weekly PDF report: content published, backlinks built, indexing status, keyword rankings, traffic, DR progress
|
||||
- Dedicated Slack channel or email thread (24hr response)
|
||||
|
||||
### 2.3 Client Requirements
|
||||
|
||||
The client must provide or enable:
|
||||
- WordPress admin access (or install IGNY8 plugin themselves)
|
||||
- Domain/hosting (Alorig does not host client sites)
|
||||
- Business data for SAG wizard (products/services, brands, locations, problems solved — per Doc A Section 9.3)
|
||||
- Approval on SAG blueprint before execution (Alorig presents, client approves)
|
||||
- Social media account access (for Managed Pro social posting)
|
||||
- Backlink budget approval (for Managed Pro, separate from service fee)
|
||||
|
||||
### 2.4 Margin Analysis
|
||||
|
||||
| Component | Managed Lite | Managed Pro |
|
||||
|-----------|-------------|-------------|
|
||||
| Service fee | $100/month | $399/month |
|
||||
| IGNY8 platform cost (credits) | ~$15-25/month (10 articles, basic images) | ~$50-80/month (30 articles, quality images, social, schema) |
|
||||
| Human time (Alorig team) | ~2-3 hrs/month | ~8-12 hrs/month |
|
||||
| Effective hourly rate | $25-40/hr | $25-35/hr |
|
||||
| Gross margin | ~75% | ~70-80% (excluding backlink pass-through) |
|
||||
|
||||
Backlink costs are pass-through with 20% markup — not included in the service fee.
|
||||
|
||||
---
|
||||
|
||||
## 3. Backlink Services — Product Definition {#3-backlink-services}
|
||||
|
||||
### 3.1 Two Modes
|
||||
|
||||
**Self-Service Mode:**
|
||||
Client uses IGNY8's backlink campaign module (Doc A Phase 10) to generate campaign plans, browse FatGrid publishers, and track links. IGNY8 provides the intelligence. Client executes link building themselves.
|
||||
|
||||
No additional subscription needed — included with Scale plan or as a credit-based add-on.
|
||||
|
||||
**Managed Mode:**
|
||||
Client submits campaign to Alorig team. Alorig sources links, manages quality, tracks progress, generates white-label reports. Client pays per-link or monthly retainer.
|
||||
|
||||
### 3.2 Service Tiers (Managed Mode)
|
||||
|
||||
| Tier | Service | IGNY8/Alorig Cost | Client Price | Margin |
|
||||
|------|---------|-------------------|-------------|--------|
|
||||
| **Basic Guest Post** | DR 30-50 via FatGrid | $30-80 | $150-300 | 3-5x |
|
||||
| **Standard Guest Post** | DR 50-70 via FatGrid | $100-300 | $400-800 | 2-3x |
|
||||
| **Premium Guest Post** | DR 70+ via FatGrid | $500-2,000 | $1,500-5,000 | 2-3x |
|
||||
| **PR Basic** | 300+ outlets via EIN Presswire | $99-499 | $500-1,500 | 3-5x |
|
||||
| **PR Premium** | Yahoo/Bloomberg/Fox via PRNews.io/Linking News | $500-5,000 | $2,000-15,000 | 3-4x |
|
||||
|
||||
### 3.3 Monthly Packages (Managed Mode)
|
||||
|
||||
| Package | Links/Month | DR Range | Monthly Cost | Typical Site Size |
|
||||
|---------|------------|----------|-------------|------------------|
|
||||
| **Starter** | 5-8 links | DR 30-50 | $800-1,500 | Small sites, PK market |
|
||||
| **Growth** | 10-15 links | DR 30-70 mix | $2,000-4,000 | Medium sites, UK/CA market |
|
||||
| **Authority** | 15-25 links | DR 40-70+ mix + PR | $4,000-8,000 | Large sites, USA market |
|
||||
| **Enterprise** | Custom | Custom | Custom | Multi-site, agency clients |
|
||||
|
||||
### 3.4 Niche Surcharges
|
||||
|
||||
| Niche | Multiplier | Reason |
|
||||
|-------|-----------|--------|
|
||||
| Crypto/Casino | 2-3x | Limited publishers, high competition |
|
||||
| Finance/Insurance | 1.5-2x | YMYL, stricter publisher requirements |
|
||||
| Health/Medical | 1.5-2x | YMYL, content quality requirements |
|
||||
| Tech/SaaS | 1.2-1.5x | Moderate competition |
|
||||
| General | 1x (baseline) | Standard pricing |
|
||||
|
||||
---
|
||||
|
||||
## 4. Implementation: Backend Models & Billing {#4-backend-models}
|
||||
|
||||
### 4.1 New Models
|
||||
|
||||
Add to existing billing module or create new services module:
|
||||
|
||||
```python
|
||||
# modules/billing/models.py or new modules/services/models.py
|
||||
|
||||
class ManagedServiceSubscription(AccountBaseModel):
|
||||
"""Managed service add-on subscription per site."""
|
||||
id = models.UUIDField(primary_key=True, default=uuid4)
|
||||
site = models.ForeignKey('auth.Site', on_delete=models.CASCADE, related_name='managed_subscriptions')
|
||||
tier = models.CharField(max_length=20, choices=[
|
||||
('lite', 'Managed Lite'),
|
||||
('pro', 'Managed Pro'),
|
||||
])
|
||||
status = models.CharField(max_length=20, choices=[
|
||||
('pending', 'Pending Setup'),
|
||||
('active', 'Active'),
|
||||
('paused', 'Paused'),
|
||||
('cancelled', 'Cancelled'),
|
||||
], default='pending')
|
||||
monthly_price = models.DecimalField(max_digits=8, decimal_places=2)
|
||||
articles_per_month = models.IntegerField()
|
||||
|
||||
# Assigned team member
|
||||
account_manager = models.ForeignKey('auth.User', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
related_name='managed_clients')
|
||||
|
||||
# Tracking
|
||||
current_month_articles_published = models.IntegerField(default=0)
|
||||
current_month_start = models.DateField(null=True, blank=True)
|
||||
|
||||
# Service config
|
||||
service_config = models.JSONField(default=dict, help_text='Per-site service configuration: automation settings, social platforms, backlink budget, report schedule')
|
||||
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
cancelled_at = models.DateTimeField(null=True, blank=True)
|
||||
next_billing_date = models.DateField(null=True, blank=True)
|
||||
|
||||
class BacklinkServiceOrder(AccountBaseModel):
|
||||
"""Individual backlink service order or monthly retainer."""
|
||||
id = models.UUIDField(primary_key=True, default=uuid4)
|
||||
site = models.ForeignKey('auth.Site', on_delete=models.CASCADE)
|
||||
campaign = models.ForeignKey('sag.SAGCampaign', null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
order_type = models.CharField(max_length=20, choices=[
|
||||
('one_time', 'One-Time Order'),
|
||||
('monthly', 'Monthly Retainer'),
|
||||
])
|
||||
package = models.CharField(max_length=30, choices=[
|
||||
('starter', 'Starter (5-8 links)'),
|
||||
('growth', 'Growth (10-15 links)'),
|
||||
('authority', 'Authority (15-25 links)'),
|
||||
('enterprise', 'Enterprise (custom)'),
|
||||
('custom', 'Custom Order'),
|
||||
], blank=True)
|
||||
|
||||
# Financial
|
||||
client_price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
actual_cost = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
margin = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
niche_surcharge = models.FloatField(default=1.0, help_text='Multiplier: 1.0 = baseline')
|
||||
|
||||
# Delivery
|
||||
links_ordered = models.IntegerField(default=0)
|
||||
links_delivered = models.IntegerField(default=0)
|
||||
links_live = models.IntegerField(default=0)
|
||||
|
||||
status = models.CharField(max_length=20, choices=[
|
||||
('draft', 'Draft'),
|
||||
('approved', 'Approved'),
|
||||
('in_progress', 'In Progress'),
|
||||
('delivered', 'Delivered'),
|
||||
('completed', 'Completed'),
|
||||
], default='draft')
|
||||
|
||||
# Tracking
|
||||
ordered_at = models.DateTimeField(null=True, blank=True)
|
||||
delivered_at = models.DateTimeField(null=True, blank=True)
|
||||
notes = models.TextField(blank=True)
|
||||
|
||||
class ServiceReport(AccountBaseModel):
|
||||
"""Generated report for a managed service client."""
|
||||
id = models.UUIDField(primary_key=True, default=uuid4)
|
||||
site = models.ForeignKey('auth.Site', on_delete=models.CASCADE)
|
||||
managed_subscription = models.ForeignKey(ManagedServiceSubscription, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
|
||||
report_type = models.CharField(max_length=20, choices=[
|
||||
('weekly', 'Weekly Report'),
|
||||
('monthly', 'Monthly Report'),
|
||||
('quarterly', 'Quarterly Review'),
|
||||
])
|
||||
period_start = models.DateField()
|
||||
period_end = models.DateField()
|
||||
|
||||
# Report data
|
||||
report_data = models.JSONField(default=dict, help_text='All metrics for this period')
|
||||
report_pdf_url = models.URLField(blank=True, help_text='Generated PDF stored in S3')
|
||||
|
||||
# White-label
|
||||
is_white_label = models.BooleanField(default=False)
|
||||
brand_name = models.CharField(max_length=200, blank=True, help_text='Client-facing brand name (if white-label)')
|
||||
brand_logo_url = models.URLField(blank=True)
|
||||
|
||||
generated_at = models.DateTimeField(auto_now_add=True)
|
||||
sent_at = models.DateTimeField(null=True, blank=True)
|
||||
```
|
||||
|
||||
### 4.2 Billing Integration
|
||||
|
||||
Managed services integrate with the existing billing system:
|
||||
|
||||
```python
|
||||
# business/billing/managed_billing_service.py # NEW
|
||||
|
||||
class ManagedBillingService:
|
||||
def create_managed_subscription(self, site, tier, payment_method):
|
||||
"""Create managed service subscription + first invoice."""
|
||||
# 1. Create ManagedServiceSubscription record
|
||||
# 2. Create Stripe/PayPal subscription or manual invoice
|
||||
# 3. Deduct from account credit balance OR charge directly
|
||||
# 4. Set next_billing_date
|
||||
pass
|
||||
|
||||
def process_monthly_billing(self):
|
||||
"""Celery task: process all active managed subscriptions monthly."""
|
||||
# For each active subscription:
|
||||
# 1. Generate invoice
|
||||
# 2. Charge payment method
|
||||
# 3. Reset current_month_articles_published
|
||||
# 4. Update current_month_start
|
||||
pass
|
||||
|
||||
def process_backlink_order(self, order):
|
||||
"""Process payment for a backlink service order."""
|
||||
# 1. Calculate total: client_price × niche_surcharge
|
||||
# 2. Charge payment method
|
||||
# 3. Update order status to 'approved'
|
||||
pass
|
||||
|
||||
def calculate_margin(self, order):
|
||||
"""Calculate and store margin on a completed backlink order."""
|
||||
order.margin = order.client_price - order.actual_cost
|
||||
order.save()
|
||||
```
|
||||
|
||||
### 4.3 Service Configuration Storage
|
||||
|
||||
The `service_config` JSON field on ManagedServiceSubscription stores per-client settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"automation": {
|
||||
"schedule": "weekly",
|
||||
"articles_per_run": 3,
|
||||
"content_types": ["post", "cluster_hub", "taxonomy_landing"],
|
||||
"auto_publish": false,
|
||||
"review_required": true
|
||||
},
|
||||
"backlinks": {
|
||||
"monthly_budget": 2000,
|
||||
"target_country": "US",
|
||||
"package": "growth",
|
||||
"auto_approve_under": 200
|
||||
},
|
||||
"social": {
|
||||
"platforms": ["linkedin", "twitter"],
|
||||
"auto_post_on_publish": true,
|
||||
"posting_schedule": "best_time"
|
||||
},
|
||||
"reporting": {
|
||||
"frequency": "weekly",
|
||||
"format": "pdf",
|
||||
"white_label": false,
|
||||
"recipients": ["client@example.com"],
|
||||
"include_backlinks": true,
|
||||
"include_rankings": true,
|
||||
"include_traffic": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation: Frontend — Service Management UI {#5-frontend-ui}
|
||||
|
||||
### 5.1 Admin-Only Service Dashboard
|
||||
|
||||
This is an internal Alorig team interface, not client-facing.
|
||||
|
||||
```
|
||||
frontend/src/pages/
|
||||
└── Services/ # NEW page (admin only)
|
||||
├── ServicesDashboard.tsx # Overview: all managed clients, revenue summary
|
||||
│ ├── ClientList.tsx # All managed subscriptions with status
|
||||
│ ├── RevenueSummary.tsx # MRR, active clients, margin totals
|
||||
│ └── UpcomingActions.tsx # Tasks due: reports, renewals, content due
|
||||
├── ClientDetail.tsx # Single client management view
|
||||
│ ├── ClientConfig.tsx # Service config editor (automation, social, backlinks)
|
||||
│ ├── ContentTracker.tsx # Articles published this month vs target
|
||||
│ ├── BacklinkTracker.tsx # Links ordered vs delivered vs live
|
||||
│ ├── ReportHistory.tsx # Generated reports with resend option
|
||||
│ └── BillingHistory.tsx # Invoices, payments, margin
|
||||
├── BacklinkOrders.tsx # All backlink orders across clients
|
||||
│ ├── OrderList.tsx # Filterable by client, status, package
|
||||
│ └── OrderDetail.tsx # Individual order with link-by-link tracking
|
||||
└── ReportGenerator.tsx # Generate reports for any client/period
|
||||
```
|
||||
|
||||
**Sidebar addition (admin only):**
|
||||
```
|
||||
ADMIN
|
||||
├── Sector Templates
|
||||
└── Managed Services (NEW — admin only)
|
||||
```
|
||||
|
||||
### 5.2 Client-Facing View (Optional)
|
||||
|
||||
Managed clients who also have IGNY8 app access see a simplified view:
|
||||
|
||||
```
|
||||
frontend/src/pages/
|
||||
└── ManagedService/ # NEW page (for managed clients)
|
||||
├── ServiceOverview.tsx # What's included, current month progress
|
||||
├── ReportViewer.tsx # View past reports
|
||||
└── ApprovalQueue.tsx # Approve blueprint, content (if review_required)
|
||||
```
|
||||
|
||||
This page appears in the client's sidebar only if they have an active `ManagedServiceSubscription`:
|
||||
```
|
||||
ACCOUNT
|
||||
├── Account Settings
|
||||
├── Plans & Billing
|
||||
├── Managed Service (NEW — if managed subscription active)
|
||||
├── Usage
|
||||
└── AI Models
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation: Service Delivery Workflow {#6-delivery-workflow}
|
||||
|
||||
### 6.1 Managed Lite — Monthly Workflow
|
||||
|
||||
```
|
||||
Month Start
|
||||
│
|
||||
├── Check: blueprint exists? If not → run SAG wizard for client (first month)
|
||||
│
|
||||
├── Run automation pipeline (10 articles):
|
||||
│ 1. Blueprint check → load unfulfilled content needs
|
||||
│ 2. Generate ideas from blueprint (execution priority order)
|
||||
│ 3. Create tasks (content_type based on blueprint)
|
||||
│ 4. Generate content (type-specific prompts)
|
||||
│ 5. Generate images
|
||||
│ 6. → Review queue
|
||||
│
|
||||
├── Alorig team reviews content (manual quality check)
|
||||
│ - Approve or edit in Writer → Review tab
|
||||
│ - Publish approved content to WordPress
|
||||
│
|
||||
├── Update ManagedServiceSubscription.current_month_articles_published
|
||||
│
|
||||
└── Month End:
|
||||
├── Generate monthly report (ServiceReport)
|
||||
├── Send to client via email
|
||||
└── Process next month billing
|
||||
```
|
||||
|
||||
### 6.2 Managed Pro — Monthly Workflow
|
||||
|
||||
```
|
||||
Month Start
|
||||
│
|
||||
├── Everything from Lite, PLUS:
|
||||
│
|
||||
├── Run automation for 30 articles (hub pages prioritized in early months)
|
||||
│
|
||||
├── Backlink campaign management:
|
||||
│ 1. Load this month's plan from SAGCampaign
|
||||
│ 2. Browse FatGrid for publishers matching plan criteria
|
||||
│ 3. Place orders on FatGrid/PR platforms
|
||||
│ 4. Track delivery: ordered → pending → live
|
||||
│ 5. Quality check each delivered link
|
||||
│ 6. Log in SAGBacklink records
|
||||
│ 7. Run dead link check on previous links
|
||||
│
|
||||
├── Social media:
|
||||
│ 1. Generate social posts for published content
|
||||
│ 2. Schedule across configured platforms
|
||||
│ 3. Monitor engagement metrics
|
||||
│
|
||||
├── Schema enhancement:
|
||||
│ 1. Run retroactive schema scan on any new/updated pages
|
||||
│ 2. Generate and push schemas to WordPress
|
||||
│
|
||||
├── GSC monitoring:
|
||||
│ 1. Check indexing status of all published content
|
||||
│ 2. Re-request indexing for pending items
|
||||
│ 3. Flag issues to client if needed
|
||||
│
|
||||
└── Week End (weekly report):
|
||||
├── Generate weekly report (ServiceReport)
|
||||
├── Include: articles published, backlinks built, indexing status, ranking changes
|
||||
└── Send to client
|
||||
```
|
||||
|
||||
### 6.3 Backlink Order Workflow (Managed Mode)
|
||||
|
||||
```
|
||||
Client approves monthly backlink budget
|
||||
│
|
||||
├── Alorig loads this month's campaign plan from IGNY8
|
||||
│ - Target pages, DR ranges, anchor text mix, link count
|
||||
│
|
||||
├── Browse FatGrid publishers:
|
||||
│ - Filter by: country, niche, DR range, budget
|
||||
│ - Compare prices across marketplaces
|
||||
│ - Select best-value publishers
|
||||
│
|
||||
├── Place orders:
|
||||
│ - Guest post orders on FatGrid
|
||||
│ - PR distribution on EIN Presswire / PRNews.io
|
||||
│ - Create BacklinkServiceOrder record
|
||||
│
|
||||
├── Track delivery:
|
||||
│ - Monitor publisher for content creation
|
||||
│ - Verify link is live (HTTP check)
|
||||
│ - Quality check: DR, traffic, niche relevance, outbound links
|
||||
│ - Log each link as SAGBacklink
|
||||
│
|
||||
├── Quality scoring:
|
||||
│ - Auto-score where possible (Ahrefs/FatGrid data)
|
||||
│ - Manual checks: content quality, relevance, link placement
|
||||
│ - Flag issues if quality below threshold
|
||||
│
|
||||
└── Update campaign:
|
||||
- BacklinkServiceOrder.links_delivered++
|
||||
- SAGCampaign KPI update
|
||||
- Calculate margin: client_price - actual_cost
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Implementation: Client Onboarding Automation {#7-onboarding}
|
||||
|
||||
### 7.1 Managed Client Onboarding Flow
|
||||
|
||||
When a managed service subscription is created:
|
||||
|
||||
```python
|
||||
# business/services/onboarding_service.py # NEW
|
||||
|
||||
class ManagedOnboardingService:
|
||||
def onboard_client(self, subscription):
|
||||
"""Automated onboarding for new managed client."""
|
||||
site = subscription.site
|
||||
|
||||
# Step 1: Ensure IGNY8 plugin is installed and connected
|
||||
# Check site.wp_api_key exists → if not, send installation guide email
|
||||
|
||||
# Step 2: Run SAG Site Builder if no blueprint exists
|
||||
if not site.sag_blueprint_id:
|
||||
# Queue Celery task to generate blueprint
|
||||
# Notification to account manager: "Blueprint ready for review"
|
||||
pass
|
||||
|
||||
# Step 3: Configure automation based on service_config
|
||||
# Set AutomationConfig schedule, stages, content types
|
||||
|
||||
# Step 4: Send welcome email to client
|
||||
# Include: what to expect, how to approve content, contact info
|
||||
|
||||
# Step 5: Create first month tasks in internal queue
|
||||
# Notification to account manager: "New client onboarded, first content due"
|
||||
|
||||
# Step 6: Update subscription status: pending → active
|
||||
```
|
||||
|
||||
### 7.2 Client Intake Form
|
||||
|
||||
For collecting business data (feeds into SAG wizard):
|
||||
|
||||
```
|
||||
frontend/src/pages/Services/
|
||||
└── components/
|
||||
└── ClientIntakeForm.tsx # NEW — admin fills this during client onboarding
|
||||
```
|
||||
|
||||
Collects per Doc A Section 9.3:
|
||||
- Site URL, industry, sectors
|
||||
- Products/services list
|
||||
- Problems solved
|
||||
- Brands (if e-commerce)
|
||||
- Locations (if service-based)
|
||||
- Competitors
|
||||
- Target countries (for backlink campaigns)
|
||||
- Social media accounts
|
||||
- Current SEO status (any existing plugins, backlink profile)
|
||||
|
||||
This data feeds directly into the SAG wizard's business data inputs.
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation: Reporting & White-Label {#8-reporting}
|
||||
|
||||
### 8.1 Report Generation Service
|
||||
|
||||
```python
|
||||
# business/services/report_service.py # NEW
|
||||
|
||||
class ReportService:
|
||||
def generate_report(self, site, period_start, period_end, report_type, white_label=False):
|
||||
"""Generate a service report for a managed client."""
|
||||
report_data = {
|
||||
'content': self._get_content_metrics(site, period_start, period_end),
|
||||
'backlinks': self._get_backlink_metrics(site, period_start, period_end),
|
||||
'indexing': self._get_indexing_metrics(site, period_start, period_end),
|
||||
'rankings': self._get_ranking_metrics(site, period_start, period_end),
|
||||
'traffic': self._get_traffic_metrics(site, period_start, period_end),
|
||||
'social': self._get_social_metrics(site, period_start, period_end),
|
||||
'health': self._get_sag_health(site),
|
||||
}
|
||||
|
||||
# Generate PDF
|
||||
pdf_url = self._render_pdf(report_data, white_label, site)
|
||||
|
||||
# Create ServiceReport record
|
||||
report = ServiceReport.objects.create(
|
||||
site=site, report_type=report_type,
|
||||
period_start=period_start, period_end=period_end,
|
||||
report_data=report_data, report_pdf_url=pdf_url,
|
||||
is_white_label=white_label,
|
||||
)
|
||||
return report
|
||||
|
||||
def _get_content_metrics(self, site, start, end):
|
||||
"""Articles published, content types breakdown, word count total."""
|
||||
pass
|
||||
|
||||
def _get_backlink_metrics(self, site, start, end):
|
||||
"""Links built, DR distribution, cost, quality scores."""
|
||||
pass
|
||||
|
||||
def _get_indexing_metrics(self, site, start, end):
|
||||
"""Pages indexed, pending, errors (from GSC module)."""
|
||||
pass
|
||||
|
||||
def _get_ranking_metrics(self, site, start, end):
|
||||
"""Keywords in top 10/20/50, position changes (from GSC module)."""
|
||||
pass
|
||||
|
||||
def _get_traffic_metrics(self, site, start, end):
|
||||
"""Organic traffic, impressions, CTR (from GSC module)."""
|
||||
pass
|
||||
|
||||
def _get_social_metrics(self, site, start, end):
|
||||
"""Posts published, engagement, clicks (from Socializer module)."""
|
||||
pass
|
||||
|
||||
def _get_sag_health(self, site):
|
||||
"""SAG health score, cluster completion (from SAG module)."""
|
||||
pass
|
||||
```
|
||||
|
||||
### 8.2 PDF Report Template
|
||||
|
||||
Reports generated as PDF using a template system. Stored in S3/media storage.
|
||||
|
||||
**Report sections (configurable per client):**
|
||||
1. Executive Summary — key wins this period
|
||||
2. Content Performance — articles published, type breakdown
|
||||
3. Indexing Status — indexed/pending/error counts with trend
|
||||
4. Keyword Rankings — top movers, new entries, lost positions
|
||||
5. Organic Traffic — clicks, impressions, CTR with month-over-month
|
||||
6. Backlink Campaign (if active) — links built, DR distribution, budget spent, quality
|
||||
7. Social Media (if active) — posts, engagement, top performers
|
||||
8. SAG Health — overall score, cluster completion, recommendations
|
||||
9. Next Month Plan — what's coming, what client needs to approve
|
||||
|
||||
### 8.3 White-Label Support
|
||||
|
||||
For agency clients reselling IGNY8 services:
|
||||
- `ServiceReport.brand_name` replaces "IGNY8" / "Alorig" throughout PDF
|
||||
- `ServiceReport.brand_logo_url` replaces logo in PDF header
|
||||
- Report footer shows agency contact info, not Alorig
|
||||
- White-label reports generated via Linking News API for backlink reporting
|
||||
|
||||
---
|
||||
|
||||
## 9. Implementation: Alorig Client Portfolio Servicing {#9-alorig-portfolio}
|
||||
|
||||
### 9.1 Current Alorig Clients (from Rich Schema doc)
|
||||
|
||||
| Client | Type | Site | Priority Services |
|
||||
|--------|------|------|------------------|
|
||||
| Banner Printing (UK) | Service/eCommerce | ~50 pages | FAQ Schema, Breadcrumbs, Local Business Schema, Review markup |
|
||||
| Aterna Advisors (CA) | Finance content | ~135 pages | Article + Author Schema (YMYL), FAQ, TL;DR, Definition blocks |
|
||||
| Seva Mattress | WooCommerce | Product catalog | Product Schema, AggregateRating, FAQ, Pros & Cons |
|
||||
| VAINO | WooCommerce + brand | Product + brand pages | Product Schema, Breadcrumbs, FAQ, Review markup |
|
||||
| African Fair Trade Society | Nonprofit content | Content + service pages | Article Schema, FAQ, Organization Schema, Breadcrumbs |
|
||||
| Halal VPN | Blog + product | Blog + product pages | Article, HowTo, FAQ, Comparison Tables, Pros & Cons |
|
||||
|
||||
### 9.2 Per-Client Service Plan
|
||||
|
||||
Each Alorig client can be onboarded as a Managed Service subscriber:
|
||||
|
||||
```
|
||||
For each client:
|
||||
1. Create ManagedServiceSubscription (Lite or Pro based on scope)
|
||||
2. Run client intake → collect business data
|
||||
3. Run SAG Site Builder → generate blueprint
|
||||
4. Install/update IGNY8 plugin on client site
|
||||
5. Run retroactive schema enhancement (Rich Schema module)
|
||||
6. Configure content pipeline based on gaps
|
||||
7. Set up reporting schedule
|
||||
8. (Pro clients) Generate backlink campaign, begin execution
|
||||
```
|
||||
|
||||
### 9.3 Internal Service Tracking
|
||||
|
||||
The admin Services Dashboard provides at-a-glance view of all Alorig clients:
|
||||
- Client name, site, tier, status
|
||||
- This month: articles target vs published, backlinks target vs delivered
|
||||
- Overdue items: reports not sent, content not published, links behind schedule
|
||||
- Revenue: MRR from managed services, backlink revenue, total margin
|
||||
|
||||
---
|
||||
|
||||
## 10. API Endpoints {#10-api-endpoints}
|
||||
|
||||
### Managed Services
|
||||
|
||||
```
|
||||
GET /api/v1/services/subscriptions/ # List managed subscriptions (admin)
|
||||
POST /api/v1/services/subscriptions/ # Create subscription
|
||||
GET /api/v1/services/subscriptions/{id}/ # Subscription detail
|
||||
PATCH /api/v1/services/subscriptions/{id}/ # Update config, status
|
||||
POST /api/v1/services/subscriptions/{id}/onboard/ # Trigger onboarding workflow
|
||||
POST /api/v1/services/subscriptions/{id}/pause/ # Pause subscription
|
||||
POST /api/v1/services/subscriptions/{id}/cancel/ # Cancel subscription
|
||||
```
|
||||
|
||||
### Backlink Orders
|
||||
|
||||
```
|
||||
GET /api/v1/services/backlink-orders/ # List orders (admin)
|
||||
POST /api/v1/services/backlink-orders/ # Create order
|
||||
GET /api/v1/services/backlink-orders/{id}/ # Order detail
|
||||
PATCH /api/v1/services/backlink-orders/{id}/ # Update status, delivery
|
||||
GET /api/v1/services/backlink-orders/{id}/links/ # Links in this order
|
||||
```
|
||||
|
||||
### Reports
|
||||
|
||||
```
|
||||
GET /api/v1/services/reports/ # List reports (admin + client)
|
||||
POST /api/v1/services/reports/generate/ # Generate report
|
||||
GET /api/v1/services/reports/{id}/ # Report detail + PDF URL
|
||||
POST /api/v1/services/reports/{id}/send/ # Email report to client
|
||||
```
|
||||
|
||||
### Dashboard (Admin)
|
||||
|
||||
```
|
||||
GET /api/v1/services/dashboard/ # Revenue summary, client overview
|
||||
GET /api/v1/services/dashboard/overdue/ # Overdue tasks across clients
|
||||
GET /api/v1/services/dashboard/margin/ # Margin tracking summary
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Celery Tasks {#11-celery-tasks}
|
||||
|
||||
| Task | Schedule | Purpose |
|
||||
|------|----------|---------|
|
||||
| `process_managed_billing` | Monthly (1st of month) | Invoice all active managed subscriptions |
|
||||
| `generate_weekly_reports` | Weekly (Monday 6am) | Generate reports for all Pro clients |
|
||||
| `generate_monthly_reports` | Monthly (1st of month) | Generate reports for all Lite + Pro clients |
|
||||
| `send_pending_reports` | Daily | Email any generated but unsent reports |
|
||||
| `check_overdue_deliverables` | Daily | Flag any managed clients behind on content/links |
|
||||
| `reset_monthly_counters` | Monthly (1st of month) | Reset current_month_articles_published |
|
||||
| `check_backlink_delivery` | Daily | Check FatGrid/publisher status on open orders |
|
||||
|
||||
---
|
||||
|
||||
## 12. Feature Flags {#12-feature-flags}
|
||||
|
||||
| Flag | Controls | Default |
|
||||
|------|---------|---------|
|
||||
| `managed_services_enabled` | Services admin page, subscription management, onboarding | `False` |
|
||||
| `backlink_orders_enabled` | Backlink order management (independent of campaign module) | `False` |
|
||||
| `white_label_reports` | White-label branding on reports | `False` |
|
||||
|
||||
---
|
||||
|
||||
## 13. Margin Tracking & Business Intelligence {#13-margin-tracking}
|
||||
|
||||
### 13.1 What to Track
|
||||
|
||||
| Metric | Source | Aggregation |
|
||||
|--------|--------|-------------|
|
||||
| Managed Services MRR | Sum of active ManagedServiceSubscription.monthly_price | Monthly |
|
||||
| Backlink Revenue | Sum of BacklinkServiceOrder.client_price (completed orders) | Monthly |
|
||||
| Backlink Cost | Sum of BacklinkServiceOrder.actual_cost | Monthly |
|
||||
| Backlink Margin | Revenue - Cost per order, aggregated | Monthly |
|
||||
| Blended Margin % | (Total Revenue - Total Cost) / Total Revenue | Monthly |
|
||||
| Cost Per Article | Platform credits used / articles published per client | Monthly |
|
||||
| Team Hours (manual tracking) | Time spent per client | Monthly |
|
||||
| Effective Hourly Rate | (Revenue - Platform Costs) / Hours | Monthly |
|
||||
| Client Retention | Active subscriptions / Total ever created | Lifetime |
|
||||
| Average Revenue Per Client | Total revenue / Active clients | Monthly |
|
||||
|
||||
### 13.2 Admin Dashboard Metrics
|
||||
|
||||
The Services Dashboard (admin-only) shows:
|
||||
- Total MRR from managed services
|
||||
- Total backlink revenue (monthly)
|
||||
- Blended margin percentage
|
||||
- Client count by tier (Lite vs Pro)
|
||||
- Top 5 clients by revenue
|
||||
- Clients behind on deliverables
|
||||
- Month-over-month growth
|
||||
|
||||
### 13.3 Future: Automated Margin Calculation
|
||||
|
||||
When FatGrid API integration is complete (Doc A Phase 10), backlink order costs can be auto-populated from actual FatGrid transaction data, making margin tracking fully automated instead of manually entered.
|
||||
|
||||
---
|
||||
|
||||
## Reference Documents
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| **IGNY8-Current-State.md** | Billing system, credit costs, subscription plans |
|
||||
| **SAG-Doc4-External-Backlink-Campaign-PLAN.md** | Campaign generation, FatGrid tiers, service pricing |
|
||||
| **IGNY8-Rich-Schema-SERP-Enhancement-Module.docx** | Client portfolio with retroactive enhancement opportunities |
|
||||
| **Doc A — SAG Architecture Dev Guide** | Campaign module (Phase 10), SAG Site Builder (Phase 4) |
|
||||
| **Doc B — Platform Modules Dev Guide** | GSC, Socializer, Rich Schema — features delivered to managed clients |
|
||||
| **Doc C — WordPress Ecosystem Dev Guide** | Plugin installation and configuration for managed sites |
|
||||
|
||||
---
|
||||
|
||||
*End of Doc D — Business & Services Development Guide*
|
||||
505
v2/Live Docs on Server/igny8-app-docs/plans/gsc_integratin.md
Normal file
505
v2/Live Docs on Server/igny8-app-docs/plans/gsc_integratin.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# Google Search Console Integration
|
||||
## Complete Implementation Plan for IGNY8
|
||||
|
||||
**Version:** 1.0 | **January 2026**
|
||||
|
||||
---
|
||||
|
||||
# 1. EXECUTIVE SUMMARY
|
||||
|
||||
## 1.1 What We're Building
|
||||
|
||||
| Capability | Description |
|
||||
|------------|-------------|
|
||||
| Connect Once | Single Google account connection manages all sites |
|
||||
| Auto-Index | Every IGNY8 article automatically submitted to Google |
|
||||
| Monitor | See which pages are indexed, pending, or have issues |
|
||||
| Performance | View clicks, impressions, keywords in IGNY8 |
|
||||
| Manual Control | Inspect and request indexing for any URL |
|
||||
|
||||
## 1.2 Problem vs Solution
|
||||
|
||||
**BEFORE:** Content published → Wait for Google → Maybe indexed in weeks/months → 43% NOT indexed after 1 year
|
||||
|
||||
**AFTER:** Content published → Auto-inspect → Auto-request indexing → Status synced to WordPress → 90%+ indexed in 2-4 weeks
|
||||
|
||||
## 1.3 API Choice: URL Inspection API
|
||||
|
||||
| Aspect | URL Inspection API (WE USE) | Indexing API (NOT USING) |
|
||||
|--------|----------------------------|--------------------------|
|
||||
| Restrictions | NONE - any URL | JobPosting/Events only |
|
||||
| Daily Quota | ~2,000/day | ~200/day |
|
||||
| Risk Level | Zero | High |
|
||||
|
||||
## 1.4 Integration Model
|
||||
|
||||
```
|
||||
IGNY8 APP (Central Hub)
|
||||
┌─────────────────────────────────────┐
|
||||
│ Google OAuth Token (ONE time) │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Site A │ │ Site B │ │ Site C │
|
||||
│(Plugin) │ │(Plugin) │ │(Plugin) │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 2. ARCHITECTURE
|
||||
|
||||
## 2.1 System Components
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ IGNY8 APP │
|
||||
│ │
|
||||
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
|
||||
│ │GSC Service │ │Queue Serv. │ │Metrics Srv │ │
|
||||
│ │• OAuth │ │• URL queue │ │• Fetch data│ │
|
||||
│ │• Tokens │ │• Rate limit│ │• Cache │ │
|
||||
│ │• Mapping │ │• Status │ │• Filter │ │
|
||||
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
|
||||
│ └──────────────┼──────────────┘ │
|
||||
│ ▼ │
|
||||
│ ┌───────────────────────┐ │
|
||||
│ │ Background Workers │ │
|
||||
│ │ (Celery) │ │
|
||||
│ │ • Process queue │ │
|
||||
│ │ • Re-inspect URLs │ │
|
||||
│ │ • Sync to plugins │ │
|
||||
│ └───────────┬───────────┘ │
|
||||
└───────────────────────┼──────────────────────────────────┘
|
||||
│
|
||||
┌─────────────┴─────────────┐
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌──────────────────────┐
|
||||
│ GOOGLE APIS │ │ WORDPRESS SITES │
|
||||
│ • URL Inspection API │ │ • IGNY8 Plugin │
|
||||
│ • Search Analytics │ │ - Receive status │
|
||||
│ • Sites API │ │ - Display in WP │
|
||||
└──────────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
## 2.2 Data Flow
|
||||
|
||||
```
|
||||
User connects Google → OAuth tokens stored → Fetch GSC sites → Map to IGNY8 sites
|
||||
│
|
||||
┌───────────────────────────────────────────┼───────────────────┐
|
||||
▼ ▼ ▼
|
||||
AUTO-INDEXING MANUAL INDEXING METRICS
|
||||
IGNY8 publishes User selects URLs User views
|
||||
│ │ performance
|
||||
▼ ▼ │
|
||||
Add to queue (P:100) Add to queue (P:50) ▼
|
||||
│ │ Fetch API
|
||||
└───────────────────┬───────────────────────┘ Cache
|
||||
▼
|
||||
Worker inspects
|
||||
│
|
||||
NOT INDEXED? → Request indexing → Schedule re-inspect
|
||||
│
|
||||
▼
|
||||
Sync to WordPress plugin
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 3. FEATURES
|
||||
|
||||
## 3.1 Feature List
|
||||
|
||||
| # | Feature | Priority |
|
||||
|---|---------|----------|
|
||||
| 1 | GSC OAuth Connection | Critical |
|
||||
| 2 | Site Mapping | Critical |
|
||||
| 3 | URL Inspection | Critical |
|
||||
| 4 | Indexing Requests | Critical |
|
||||
| 5 | Auto-Indexing | Critical |
|
||||
| 6 | Queue Management | Critical |
|
||||
| 7 | Plugin Status Sync | High |
|
||||
| 8 | Search Metrics | High |
|
||||
| 9 | Manual URL Management | Medium |
|
||||
|
||||
## 3.2 OAuth Connection
|
||||
|
||||
**Flow:** User clicks Connect → Google OAuth → User allows → Tokens stored → Sites fetched
|
||||
|
||||
**Scopes:** `webmasters.readonly`, `webmasters`
|
||||
|
||||
**Data Stored:** user_id, google_email, access_token (encrypted), refresh_token (encrypted), token_expiry, status, connected_at
|
||||
|
||||
## 3.3 Site Mapping
|
||||
|
||||
**Logic:** For each IGNY8 site, check GSC for:
|
||||
1. sc-domain:example.com (Domain property) ← Preferred
|
||||
2. https://example.com (URL prefix)
|
||||
3. http://example.com (URL prefix)
|
||||
|
||||
**States:** matched ✓, manual 🔧, not_found ✗
|
||||
|
||||
## 3.4 URL Inspection & Indexing
|
||||
|
||||
**Decision Flow:**
|
||||
```
|
||||
Inspect URL → Parse Response
|
||||
│
|
||||
┌───────────┼───────────┐
|
||||
▼ ▼ ▼
|
||||
INDEXED NOT INDEXED ERROR
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
Done! Request indexing Show details
|
||||
│
|
||||
▼
|
||||
Schedule re-inspect (24-48 hrs)
|
||||
```
|
||||
|
||||
## 3.5 Auto-Indexing Pipeline
|
||||
|
||||
```
|
||||
IGNY8 publishes content
|
||||
│
|
||||
▼
|
||||
Create URL tracking record
|
||||
│
|
||||
▼
|
||||
Add to queue (Priority: 100)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ BACKGROUND WORKER │
|
||||
│ │
|
||||
│ Pick item → Check quota │
|
||||
│ │ │
|
||||
│ YES ─┴─ NO │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ Process Skip (retry tomorrow) │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Call URL Inspection API │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ NOT INDEXED? → Request indexing │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ Sync to WordPress → Wait 3s → Next │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 3.6 Queue Management
|
||||
|
||||
**Rules:**
|
||||
1. One at a time (3 sec between calls)
|
||||
2. Priority order: 100 (IGNY8 auto) → 90 → 70 → 50 → 30 (re-inspect)
|
||||
3. Quota: 2,000/day, reset midnight PT
|
||||
4. Quota exhausted: items stay queued, resume after reset
|
||||
|
||||
## 3.7 Plugin Status Sync
|
||||
|
||||
**Flow:** Status changes → Batch collector (5 min) → POST to plugin → Store in WP DB → Display in admin
|
||||
|
||||
## 3.8 Search Metrics
|
||||
|
||||
**Metrics:** Clicks, Impressions, CTR, Position
|
||||
|
||||
**Dimensions:** Pages, Keywords, Countries, Devices
|
||||
|
||||
**Special:** Filter to show ONLY IGNY8 content performance
|
||||
|
||||
---
|
||||
|
||||
# 4. USER EXPERIENCE
|
||||
|
||||
## 4.1 First-Time Setup
|
||||
|
||||
1. **Discovery:** New "Search Console" menu in IGNY8
|
||||
2. **Welcome:** Benefits explained, "Connect" button
|
||||
3. **OAuth:** User selects Google account, grants permission
|
||||
4. **Mapping:** Shows matched/unmatched sites
|
||||
5. **Complete:** Ready to use
|
||||
|
||||
## 4.2 Indexing Dashboard
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Site: [example.com ▼] │
|
||||
│ │
|
||||
│ ┌─────────┬─────────┬─────────┬─────────┐ │
|
||||
│ │ TOTAL │ INDEXED │ PENDING │ ISSUES │ │
|
||||
│ │ 53 │ 30 │ 18 │ 5 │ │
|
||||
│ └─────────┴─────────┴─────────┴─────────┘ │
|
||||
│ │
|
||||
│ [IGNY8 Content] [All Site URLs] [Custom URL] │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────┐ │
|
||||
│ │ □ TITLE/URL PUBLISHED STATUS ACTION │ │
|
||||
│ ├───────────────────────────────────────────────────┤ │
|
||||
│ │ □ Best SEO Practices Jan 15 ✓ Indexed │ │
|
||||
│ │ □ Blue Widget Pro Jan 14 ⏳ Pending │ │
|
||||
│ │ □ Content Marketing Jan 12 ➡ Requested │ │
|
||||
│ │ □ Red Widget Jan 10 ⚠ Noindex │ │
|
||||
│ └───────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Queue: 3 processing • Quota: 1,847/2,000 remaining │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 4.3 Performance Metrics
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Site: [example.com ▼] Date: [Last 28 days ▼] │
|
||||
│ │
|
||||
│ ┌───────────┬───────────┬───────────┬───────────┐ │
|
||||
│ │ CLICKS │ IMPR │ CTR │ POSITION │ │
|
||||
│ │ 1,234 │ 45,678 │ 2.7% │ 12.4 │ │
|
||||
│ │ ▲ +15% │ ▲ +8% │ ▲ +0.3% │ ▼ -2.1 │ │
|
||||
│ └───────────┴───────────┴───────────┴───────────┘ │
|
||||
│ │
|
||||
│ [Top Pages] [Top Keywords] [IGNY8 Content ★] │
|
||||
│ │
|
||||
│ □ Show only IGNY8 content │
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────┐ │
|
||||
│ │ PAGE CLICKS IMPR CTR POSITION│ │
|
||||
│ │ ★ /blog/best-seo 342 12,456 2.7% 8.2 │ │
|
||||
│ │ ★ /product/widget 156 8,234 1.9% 15.4 │ │
|
||||
│ │ /about-us 89 5,678 1.6% 22.1 │ │
|
||||
│ └───────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ★ = IGNY8 content [Export to CSV] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 4.4 WordPress Plugin Display
|
||||
|
||||
**Content List Column:**
|
||||
```
|
||||
□ TITLE DATE STATUS INDEX STATUS
|
||||
□ Best SEO Tips Jan 15 Published ✓ Indexed
|
||||
□ Widget Guide Jan 14 Published ⏳ Pending
|
||||
□ Marketing 101 Jan 12 Published ➡ Requested
|
||||
```
|
||||
|
||||
**Edit Screen Metabox:**
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ Search Console Status │
|
||||
│ Index Status: ✓ Indexed │
|
||||
│ Last Checked: 2 hours ago │
|
||||
│ ℹ Managed via IGNY8 │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 5. DATA ARCHITECTURE
|
||||
|
||||
## 5.1 Database Schema
|
||||
|
||||
```
|
||||
USER (existing)
|
||||
│
|
||||
└──► GSC_CONNECTION (1:1)
|
||||
│ • google_email
|
||||
│ • access_token (encrypted)
|
||||
│ • refresh_token (encrypted)
|
||||
│ • status
|
||||
│
|
||||
└──► DAILY_QUOTA (1:N)
|
||||
• date
|
||||
• inspections_used
|
||||
• inspections_limit
|
||||
|
||||
SITE (existing)
|
||||
│
|
||||
└──► GSC_SITE_MAPPING (1:1)
|
||||
│ • gsc_property
|
||||
│ • mapping_type
|
||||
│ • status
|
||||
│
|
||||
└──► URL_INDEXING_RECORD (1:N)
|
||||
│ • url
|
||||
│ • source
|
||||
│ • status
|
||||
│ • last_inspection_result
|
||||
│
|
||||
└──► INDEXING_QUEUE (1:N)
|
||||
• queue_type
|
||||
• priority
|
||||
• status
|
||||
|
||||
PUBLISHED_CONTENT (existing)
|
||||
│
|
||||
└──► URL_INDEXING_RECORD (1:1)
|
||||
|
||||
METRICS_CACHE (linked to GSC_SITE_MAPPING)
|
||||
• metric_type
|
||||
• date_range
|
||||
• data
|
||||
• expires_at
|
||||
```
|
||||
|
||||
## 5.2 Status Definitions
|
||||
|
||||
| Status | Icon | Meaning |
|
||||
|--------|------|---------|
|
||||
| pending_inspection | ⏳ | In queue |
|
||||
| indexed | ✓ | In Google index |
|
||||
| not_indexed | ✗ | Not in index |
|
||||
| indexing_requested | ➡ | Request sent |
|
||||
| error_noindex | 🚫 | Has noindex tag |
|
||||
| error_blocked | 🚫 | Robots.txt blocks |
|
||||
| error_not_found | 🚫 | 404 error |
|
||||
|
||||
---
|
||||
|
||||
# 6. PROCESSING LOGIC
|
||||
|
||||
## 6.1 Queue Priority
|
||||
|
||||
| Priority | Type |
|
||||
|----------|------|
|
||||
| 100 | IGNY8 auto-inspection |
|
||||
| 90 | IGNY8 indexing request |
|
||||
| 70 | Manual indexing request |
|
||||
| 50 | Manual inspection |
|
||||
| 30 | Scheduled re-inspection |
|
||||
|
||||
## 6.2 Re-Inspection Schedule
|
||||
|
||||
| Check # | Delay | Action if not indexed |
|
||||
|---------|-------|----------------------|
|
||||
| 1 | 24 hours | Continue |
|
||||
| 2 | Day 3 | Continue |
|
||||
| 3 | Day 6 | Continue |
|
||||
| 4 | Day 13 | Final check |
|
||||
| 5 | STOP | Mark for manual review |
|
||||
|
||||
## 6.3 Quota Management
|
||||
|
||||
- Daily Limit: 2,000 inspections
|
||||
- Reset: Midnight PT
|
||||
- Exhausted: Items stay queued, resume after reset, user notified
|
||||
|
||||
---
|
||||
|
||||
# 7. API INTEGRATION
|
||||
|
||||
## 7.1 Google OAuth
|
||||
|
||||
- Redirect URI: `https://app.igny8.com/auth/google/callback`
|
||||
- Scopes: `webmasters.readonly`, `webmasters`
|
||||
|
||||
## 7.2 URL Inspection API
|
||||
|
||||
```
|
||||
POST https://searchconsole.googleapis.com/v1/urlInspection/index:inspect
|
||||
|
||||
Request:
|
||||
{
|
||||
"inspectionUrl": "https://example.com/page",
|
||||
"siteUrl": "sc-domain:example.com"
|
||||
}
|
||||
|
||||
Response fields: verdict, coverageState, robotsTxtState, indexingState, lastCrawlTime
|
||||
```
|
||||
|
||||
## 7.3 Search Analytics API
|
||||
|
||||
```
|
||||
POST https://searchconsole.googleapis.com/webmasters/v3/sites/{siteUrl}/searchAnalytics/query
|
||||
|
||||
Request:
|
||||
{
|
||||
"startDate": "2025-01-01",
|
||||
"endDate": "2025-01-28",
|
||||
"dimensions": ["page", "query"],
|
||||
"rowLimit": 1000
|
||||
}
|
||||
|
||||
Response: clicks, impressions, ctr, position
|
||||
```
|
||||
|
||||
## 7.4 Sites API
|
||||
|
||||
```
|
||||
GET https://searchconsole.googleapis.com/webmasters/v3/sites
|
||||
|
||||
Response: List of sites with siteUrl and permissionLevel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. WORDPRESS PLUGIN UPDATES
|
||||
|
||||
## 8.1 New Components
|
||||
|
||||
- **Endpoint:** `POST /wp-json/igny8/v1/index-status-sync`
|
||||
- **Table:** url, post_id, status, last_checked, details
|
||||
- **UI:** Content list column, Edit screen metabox, Dashboard widget
|
||||
|
||||
---
|
||||
|
||||
# 9. ERROR HANDLING
|
||||
|
||||
| Category | Error | Action |
|
||||
|----------|-------|--------|
|
||||
| Connection | OAuth rejected | Retry |
|
||||
| Connection | Token expired | Reconnect |
|
||||
| Mapping | Site not in GSC | Link to GSC |
|
||||
| Indexing | Quota exhausted | Queue for tomorrow |
|
||||
| Indexing | noindex tag | Show warning |
|
||||
| Sync | Plugin unreachable | Retry later |
|
||||
|
||||
---
|
||||
|
||||
# 10. IMPLEMENTATION PHASES
|
||||
|
||||
| Phase | Weeks | Focus |
|
||||
|-------|-------|-------|
|
||||
| 1 | 1-2 | Core Connection (OAuth, site mapping) |
|
||||
| 2 | 3-4 | URL Inspection (queue, status tracking) |
|
||||
| 3 | 5-6 | Auto-Indexing (hooks, re-inspection) |
|
||||
| 4 | 7 | Plugin Status Sync |
|
||||
| 5 | 8-9 | Performance Metrics |
|
||||
| 6 | 10 | Polish & Optimization |
|
||||
|
||||
---
|
||||
|
||||
# 11. SUCCESS METRICS
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| GSC connection rate | 50% of active users |
|
||||
| Sites mapped | 80% of user sites |
|
||||
| Auto-indexing enabled | 70% of connected users |
|
||||
| Indexing success rate | >80% within 7 days |
|
||||
| Sync reliability | >99% |
|
||||
| API error rate | <1% |
|
||||
|
||||
---
|
||||
|
||||
# 12. GLOSSARY
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| GSC | Google Search Console |
|
||||
| URL Inspection | Checking URL status in Google's index |
|
||||
| Indexing Request | Asking Google to index a URL |
|
||||
| Impressions | Times page appeared in search |
|
||||
| Clicks | Times users clicked from search |
|
||||
| CTR | Click-through rate |
|
||||
| Position | Average ranking in search |
|
||||
| sc-domain | Domain property type in GSC |
|
||||
| Quota | Daily API usage limits |
|
||||
|
||||
---
|
||||
|
||||
**END OF DOCUMENT**
|
||||
Reference in New Issue
Block a user