1122 lines
34 KiB
Markdown
1122 lines
34 KiB
Markdown
# Feature Modification & Development Guide
|
|
## Complete File Reference for Adding, Modifying, and Removing Features
|
|
|
|
**Version:** 1.0.0
|
|
**Last Updated:** November 29, 2025
|
|
**Purpose:** Quick reference for developers to locate exactly which files to modify for specific feature types, across both frontend and backend.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Quick Navigation by Feature Type](#quick-navigation-by-feature-type)
|
|
2. [Feature Type Reference Matrix](#feature-type-reference-matrix)
|
|
3. [Frontend Feature Modifications](#frontend-feature-modifications)
|
|
4. [Backend Feature Modifications](#backend-feature-modifications)
|
|
5. [Cross-Module Modifications](#cross-module-modifications)
|
|
6. [Database Schema Changes](#database-schema-changes)
|
|
7. [Common Workflow Patterns](#common-workflow-patterns)
|
|
8. [File Organization Index](#file-organization-index)
|
|
|
|
---
|
|
|
|
## Quick Navigation by Feature Type
|
|
|
|
### 🎯 Find Your Feature Type
|
|
|
|
- **Adding/Removing/Modifying a Database Column** → [Database Changes](#database-changes)
|
|
- **Adding a New Page/List View** → [New Page Feature](#new-page-feature)
|
|
- **Adding a Column to a Table/List** → [Table Column Addition](#table-column-addition)
|
|
- **Adding a Form Field** → [Form Field Addition](#form-field-addition)
|
|
- **Adding an API Endpoint** → [API Endpoint Addition](#api-endpoint-addition)
|
|
- **Adding a Filter/Search** → [Filter/Search Addition](#filtersearch-addition)
|
|
- **Adding a Status/Choice Field** → [Status Field Addition](#status-field-addition)
|
|
- **Modifying API Response** → [API Response Modification](#api-response-modification)
|
|
- **Adding Validation** → [Validation Addition](#validation-addition)
|
|
- **Adding Background Task** → [Background Task Addition](#background-task-addition)
|
|
- **Adding Webhook** → [Webhook Addition](#webhook-addition)
|
|
- **Adding Integration** → [Integration Addition](#integration-addition)
|
|
|
|
---
|
|
|
|
## Feature Type Reference Matrix
|
|
|
|
| Feature Type | Frontend Files | Backend Files | Database | Global Config |
|
|
|--------------|---|---|---|---|
|
|
| **Database Column** | - | Model + Serializer | Migration | - |
|
|
| **Table Column** | List Component | ViewSet Filter | - | - |
|
|
| **Form Field** | Form Component | Serializer | Model Migration | - |
|
|
| **Page/View** | Page Component | ViewSet | - | Routes |
|
|
| **API Endpoint** | API Hook | ViewSet + Router | - | urls.py |
|
|
| **Status Field** | Select/Enum | Model Choice | Migration | - |
|
|
| **Filter/Search** | Filter UI | filterset_fields | Index | - |
|
|
| **Validation** | Form Rules | Validator Service | - | - |
|
|
| **Background Task** | UI Trigger | Celery Task | - | settings.py |
|
|
| **Webhook** | - | Handler Function | WebhookLog Model | urls.py |
|
|
| **Integration** | - | Adapter Class | Integration Model | - |
|
|
|
|
---
|
|
|
|
## Frontend Feature Modifications
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
frontend/src/
|
|
├── pages/
|
|
│ ├── Planner/
|
|
│ │ ├── Dashboard.tsx
|
|
│ │ ├── Keywords.tsx
|
|
│ │ ├── Clusters.tsx
|
|
│ │ ├── ClusterDetail.tsx
|
|
│ │ ├── Ideas.tsx
|
|
│ │ └── KeywordOpportunities.tsx
|
|
│ ├── Writer/
|
|
│ │ ├── Dashboard.tsx
|
|
│ │ ├── Tasks.tsx
|
|
│ │ ├── Content.tsx
|
|
│ │ ├── ContentView.tsx
|
|
│ │ ├── Drafts.tsx
|
|
│ │ ├── Images.tsx
|
|
│ │ ├── Published.tsx
|
|
│ │ └── Review.tsx
|
|
│ ├── Linker/
|
|
│ │ └── [Linker Pages]
|
|
│ ├── Optimizer/
|
|
│ │ └── [Optimizer Pages]
|
|
│ ├── Settings/
|
|
│ │ └── [Settings Pages]
|
|
│ └── [Other Modules]
|
|
├── components/
|
|
│ ├── common/
|
|
│ │ ├── Table.tsx
|
|
│ │ ├── Modal.tsx
|
|
│ │ ├── Form.tsx
|
|
│ │ ├── Button.tsx
|
|
│ │ └── [Shared UI Components]
|
|
│ ├── [Module-Specific Components]
|
|
│ └── [Feature Components]
|
|
├── api/
|
|
│ ├── client.ts # Axios setup
|
|
│ ├── auth.ts # Auth API calls
|
|
│ ├── planner.ts # Planner endpoints
|
|
│ ├── writer.ts # Writer endpoints
|
|
│ ├── [module].ts # Module-specific APIs
|
|
│ └── hooks/
|
|
│ └── useApi.ts # API hook
|
|
├── store/
|
|
│ ├── authStore.ts # Auth state
|
|
│ ├── siteStore.ts # Site/Sector state
|
|
│ ├── [module]Store.ts # Module state
|
|
│ └── globalStore.ts # Global state
|
|
├── hooks/
|
|
│ ├── useAuth.ts
|
|
│ ├── useApi.ts
|
|
│ ├── useSite.ts
|
|
│ └── [Custom Hooks]
|
|
├── types/
|
|
│ ├── api.ts # API type definitions
|
|
│ ├── models.ts # Data model types
|
|
│ └── [Module Types]
|
|
└── utils/
|
|
├── formatters.ts
|
|
├── validators.ts
|
|
└── [Utility Functions]
|
|
```
|
|
|
|
### Feature: Adding a Column to a List/Table
|
|
|
|
**Module Example: Writer/Tasks List**
|
|
|
|
#### Files to Modify:
|
|
|
|
| Layer | File | Change Type | Action |
|
|
|-------|------|-------------|--------|
|
|
| **Data Type** | `frontend/src/types/models.ts` | Modify | Add field to Task interface |
|
|
| **API Response** | `frontend/src/api/writer.ts` | Auto | API will return new field from backend |
|
|
| **Page Component** | `frontend/src/pages/Writer/Tasks.tsx` | Modify | Add column to table columns config |
|
|
| **Table Component** | `frontend/src/components/Writer/TasksTable.tsx` | Modify | Render new column in JSX |
|
|
| **Backend ViewSet** | `backend/igny8_core/modules/writer/views.py` | Modify | Add to `filterset_fields` if filterable, `ordering_fields` if sortable |
|
|
| **Backend Serializer** | `backend/igny8_core/modules/writer/serializers.py` | Modify | Add field to serializer `fields` list |
|
|
| **Backend Model** | `backend/igny8_core/business/content/models.py` | Modify | Add field to Tasks model class |
|
|
| **Backend Migration** | `backend/igny8_core/migrations/` | Create | `python manage.py makemigrations` |
|
|
|
|
#### Step-by-Step for "Adding 'priority' column to Tasks list":
|
|
|
|
```python
|
|
# Step 1: Backend Model (models.py)
|
|
class Tasks(SiteSectorBaseModel):
|
|
# ... existing fields ...
|
|
priority = models.IntegerField(
|
|
choices=[(1, 'Low'), (2, 'Medium'), (3, 'High')],
|
|
default=2
|
|
)
|
|
|
|
# Step 2: Backend Serializer (serializers.py)
|
|
class TasksSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Tasks
|
|
fields = ['id', 'title', 'status', 'priority', ...] # Add 'priority'
|
|
|
|
# Step 3: Backend ViewSet (views.py) - if filterable/sortable
|
|
class TasksViewSet(SiteSectorModelViewSet):
|
|
filterset_fields = ['status', 'cluster_id', 'priority'] # Add 'priority'
|
|
ordering_fields = ['title', 'created_at', 'priority'] # Add 'priority'
|
|
|
|
# Step 4: Frontend Type (types/models.ts)
|
|
interface Task {
|
|
id: number;
|
|
title: string;
|
|
status: string;
|
|
priority: number; // Add this
|
|
// ... other fields
|
|
}
|
|
|
|
# Step 5: Frontend API (api/writer.ts) - No changes needed if using generic fetch
|
|
|
|
# Step 6: Frontend Component (pages/Writer/Tasks.tsx)
|
|
const columns = [
|
|
{ key: 'id', label: 'ID', width: '10%' },
|
|
{ key: 'title', label: 'Title', width: '40%' },
|
|
{ key: 'status', label: 'Status', width: '20%' },
|
|
{ key: 'priority', label: 'Priority', width: '15%' }, // Add this
|
|
];
|
|
|
|
# Step 7: Database Migration
|
|
# bash: python manage.py makemigrations
|
|
# bash: python manage.py migrate
|
|
```
|
|
|
|
### Feature: Adding a Form Field
|
|
|
|
**Module Example: Writer/Tasks Create Form**
|
|
|
|
#### Files to Modify:
|
|
|
|
| Layer | File | Change Type | Description |
|
|
|-------|------|-------------|-------------|
|
|
| **Backend Model** | `backend/.../models.py` | Add | New model field with validators |
|
|
| **Backend Serializer** | `backend/.../serializers.py` | Add | Serializer field with validation |
|
|
| **Backend ViewSet** | `backend/.../views.py` | Modify | Add to `filterset_fields` if needed |
|
|
| **Frontend Type** | `frontend/src/types/models.ts` | Add | Add to interface |
|
|
| **Frontend Form Component** | `frontend/src/pages/Writer/[FormPage].tsx` | Add | Add form input JSX |
|
|
| **Frontend Validation** | `frontend/src/utils/validators.ts` | Add | Client-side validation rule |
|
|
| **Frontend State** | `frontend/src/store/[module]Store.ts` | Add | Add to form state if needed |
|
|
| **Database Migration** | `backend/migrations/` | Create | If model field is added |
|
|
|
|
#### Example: "Adding 'estimatedWordCount' field to Tasks form":
|
|
|
|
```python
|
|
# Step 1: Backend Model
|
|
class Tasks(SiteSectorBaseModel):
|
|
# ... existing fields ...
|
|
word_count = models.IntegerField(
|
|
default=1000,
|
|
validators=[MinValueValidator(100), MaxValueValidator(10000)],
|
|
help_text="Target word count (100-10000)"
|
|
)
|
|
|
|
# Step 2: Backend Serializer
|
|
class TasksSerializer(serializers.ModelSerializer):
|
|
word_count = serializers.IntegerField(
|
|
required=False,
|
|
min_value=100,
|
|
max_value=10000
|
|
)
|
|
|
|
class Meta:
|
|
model = Tasks
|
|
fields = [..., 'word_count']
|
|
|
|
# Step 3: Frontend Type
|
|
interface Task {
|
|
id: number;
|
|
title: string;
|
|
wordCount: number; // Add this (camelCase)
|
|
}
|
|
|
|
# Step 4: Frontend Form Component
|
|
function TaskForm() {
|
|
const [formData, setFormData] = useState({
|
|
title: '',
|
|
wordCount: 1000, // Add this
|
|
});
|
|
|
|
return (
|
|
<form>
|
|
{/* ... other fields ... */}
|
|
<input
|
|
type="number"
|
|
name="wordCount"
|
|
label="Word Count"
|
|
min={100}
|
|
max={10000}
|
|
value={formData.wordCount}
|
|
onChange={e => setFormData({...formData, wordCount: e.target.value})}
|
|
/>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
# Step 5: Frontend Validation
|
|
export const validateTaskForm = (data) => {
|
|
if (data.wordCount < 100 || data.wordCount > 10000) {
|
|
return 'Word count must be between 100 and 10000';
|
|
}
|
|
return null;
|
|
};
|
|
```
|
|
|
|
### Feature: Adding a New Page/View
|
|
|
|
**Module Example: Adding "Writer/Scheduled" Page**
|
|
|
|
#### Files to Create/Modify:
|
|
|
|
| Layer | File | Change Type | Action |
|
|
|-------|------|-------------|--------|
|
|
| **New Page Component** | `frontend/src/pages/Writer/Scheduled.tsx` | Create | New React component |
|
|
| **Page Router** | `frontend/src/App.tsx` | Modify | Add route entry |
|
|
| **Navigation** | `frontend/src/layout/Sidebar.tsx` | Modify | Add menu item |
|
|
| **API Hooks** | `frontend/src/api/writer.ts` | Modify | Add API call if needed |
|
|
| **Type Definitions** | `frontend/src/types/models.ts` | Modify | Add types if needed |
|
|
|
|
#### Step-by-Step for "Adding Writer/Scheduled Page":
|
|
|
|
```typescript
|
|
// Step 1: Create new page component
|
|
// File: frontend/src/pages/Writer/Scheduled.tsx
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { getScheduledContent } from '../../api/writer';
|
|
import ContentTable from '../../components/Writer/ContentTable';
|
|
|
|
export default function ScheduledPage() {
|
|
const [content, setContent] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const loadContent = async () => {
|
|
try {
|
|
const response = await getScheduledContent({
|
|
siteId: currentSite.id,
|
|
sectorId: currentSector.id,
|
|
status: 'scheduled'
|
|
});
|
|
setContent(response.data);
|
|
} catch (error) {
|
|
console.error('Failed to load scheduled content:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
loadContent();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<h1 className="text-2xl font-bold mb-6">Scheduled Content</h1>
|
|
<ContentTable content={content} loading={loading} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Step 2: Add route in App.tsx
|
|
import ScheduledPage from './pages/Writer/Scheduled';
|
|
|
|
function App() {
|
|
return (
|
|
<Routes>
|
|
{/* ... other routes ... */}
|
|
<Route path="/writer/scheduled" element={<ScheduledPage />} />
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
// Step 3: Add to Sidebar navigation
|
|
// File: frontend/src/layout/Sidebar.tsx
|
|
<NavItem
|
|
to="/writer/scheduled"
|
|
label="Scheduled"
|
|
icon={<CalendarIcon />}
|
|
/>
|
|
|
|
// Step 4: Add API function if needed
|
|
// File: frontend/src/api/writer.ts
|
|
export const getScheduledContent = async (params) => {
|
|
return fetchAPI('/api/v1/writer/content/', {
|
|
query: { ...params, status: 'scheduled' }
|
|
});
|
|
};
|
|
```
|
|
|
|
### Feature: Adding a Filter/Search
|
|
|
|
**Module Example: Adding "Priority Filter" to Tasks**
|
|
|
|
#### Files to Modify:
|
|
|
|
| Layer | File | Change Type | Description |
|
|
|-------|------|-------------|-------------|
|
|
| **Backend ViewSet** | `backend/.../views.py` | Modify | Add to `filterset_fields` |
|
|
| **Backend Model** | `backend/.../models.py` | Check | Field must have choices |
|
|
| **Frontend Filter UI** | `frontend/src/components/Writer/TasksFilter.tsx` | Add | Add filter control |
|
|
| **Frontend State** | `frontend/src/store/writerStore.ts` | Modify | Add filter state |
|
|
|
|
#### Example:
|
|
|
|
```python
|
|
# Backend (views.py)
|
|
class TasksViewSet(SiteSectorModelViewSet):
|
|
filterset_fields = ['status', 'cluster_id', 'priority'] # Add 'priority'
|
|
|
|
# Frontend - Add Filter Component
|
|
interface TaskFilter {
|
|
priority?: number;
|
|
status?: string;
|
|
}
|
|
|
|
function TasksFilter() {
|
|
const [filters, setFilters] = useState<TaskFilter>({});
|
|
|
|
return (
|
|
<div className="flex gap-4 mb-6">
|
|
<select
|
|
value={filters.priority || ''}
|
|
onChange={(e) => setFilters({...filters, priority: e.target.value})}
|
|
>
|
|
<option value="">All Priorities</option>
|
|
<option value="1">Low</option>
|
|
<option value="2">Medium</option>
|
|
<option value="3">High</option>
|
|
</select>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Backend Feature Modifications
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
backend/igny8_core/
|
|
├── modules/ # API ViewSets (REST endpoints)
|
|
│ ├── planner/
|
|
│ │ ├── views.py # ViewSets
|
|
│ │ ├── serializers.py # Serializers
|
|
│ │ ├── urls.py # Route definitions
|
|
│ │ └── apps.py
|
|
│ ├── writer/
|
|
│ │ ├── views.py
|
|
│ │ ├── serializers.py
|
|
│ │ ├── urls.py
|
|
│ │ └── apps.py
|
|
│ ├── [other modules]
|
|
│ └── urls.py # Root routing
|
|
├── business/ # Business logic & models
|
|
│ ├── planning/
|
|
│ │ ├── models.py # Database models
|
|
│ │ └── services/ # Business logic
|
|
│ ├── content/
|
|
│ │ ├── models.py
|
|
│ │ └── services/
|
|
│ ├── [other domains]
|
|
│ └── models.py
|
|
├── auth/
|
|
│ ├── models.py # User, Account, Site, Sector
|
|
│ ├── views.py
|
|
│ ├── serializers.py
|
|
│ └── urls.py
|
|
├── api/
|
|
│ ├── base.py # Base ViewSet classes
|
|
│ ├── response.py # Response utilities
|
|
│ ├── pagination.py
|
|
│ ├── permissions.py
|
|
│ ├── throttles.py
|
|
│ └── exception_handlers.py
|
|
├── ai/ # AI Engine
|
|
│ ├── engine.py
|
|
│ ├── ai_core.py
|
|
│ ├── registry.py
|
|
│ └── functions/
|
|
├── middleware/
|
|
├── utils/
|
|
├── tasks/ # Celery tasks
|
|
├── migrations/ # Database migrations
|
|
├── settings.py # Django settings
|
|
├── urls.py # Root URL config
|
|
└── wsgi.py
|
|
```
|
|
|
|
### Feature: Adding an API Endpoint
|
|
|
|
**Module Example: Adding "GET /writer/tasks/{id}/brief/" endpoint**
|
|
|
|
#### Files to Create/Modify:
|
|
|
|
| File | Change Type | Description |
|
|
|------|-------------|-------------|
|
|
| `backend/igny8_core/modules/writer/views.py` | Add | Add `@action` method to ViewSet |
|
|
| `backend/igny8_core/modules/writer/serializers.py` | Check | Serializer for response |
|
|
| `backend/igny8_core/modules/writer/urls.py` | Auto | Routes auto-generated by DRF `DefaultRouter` |
|
|
|
|
#### Step-by-Step Example:
|
|
|
|
```python
|
|
# Step 1: Add action to ViewSet (views.py)
|
|
class TasksViewSet(SiteSectorModelViewSet):
|
|
# ... existing code ...
|
|
|
|
@action(detail=True, methods=['get'])
|
|
def brief(self, request, pk=None):
|
|
"""
|
|
Get task brief
|
|
GET /api/v1/writer/tasks/{id}/brief/
|
|
"""
|
|
task = self.get_object()
|
|
|
|
# Business logic to generate brief
|
|
brief_data = {
|
|
'task_id': task.id,
|
|
'title': task.title,
|
|
'keywords': task.keywords,
|
|
'cluster': task.cluster.name if task.cluster else None,
|
|
'description': task.description
|
|
}
|
|
|
|
return success_response(
|
|
success=True,
|
|
data=brief_data,
|
|
message='Task brief retrieved'
|
|
)
|
|
|
|
# Step 2: That's it! DRF auto-generates the route
|
|
# URL will be: /api/v1/writer/tasks/123/brief/
|
|
# Method: GET
|
|
# Response format: { success: true, data: {...}, message: '...' }
|
|
|
|
# Step 3: Frontend API hook
|
|
// frontend/src/api/writer.ts
|
|
export const getTaskBrief = async (taskId: number) => {
|
|
return fetchAPI(`/api/v1/writer/tasks/${taskId}/brief/`);
|
|
};
|
|
```
|
|
|
|
### Feature: Adding a Status/Choice Field
|
|
|
|
**Module Example: Adding "priority" choice field to Tasks**
|
|
|
|
#### Files to Modify:
|
|
|
|
| Layer | File | Change Type | Action |
|
|
|-------|------|-------------|--------|
|
|
| **Backend Model** | `backend/.../models.py` | Add | Add field with CHOICES |
|
|
| **Database Migration** | `backend/migrations/` | Create | Auto from model |
|
|
| **Backend Serializer** | `backend/.../serializers.py` | Auto | Choices auto-included |
|
|
| **Backend ViewSet** | `backend/.../views.py` | Modify | Add to filters if needed |
|
|
| **Frontend Types** | `frontend/src/types/models.ts` | Add | Add type definition |
|
|
| **Frontend Enum/Constant** | `frontend/src/config/constants.ts` | Add | Add choice options |
|
|
| **Frontend UI** | Components using field | Use | Render as select/dropdown |
|
|
|
|
#### Example:
|
|
|
|
```python
|
|
# Step 1: Backend Model (models.py)
|
|
class Tasks(SiteSectorBaseModel):
|
|
PRIORITY_CHOICES = [
|
|
(1, 'Low'),
|
|
(2, 'Medium'),
|
|
(3, 'High'),
|
|
(4, 'Urgent'),
|
|
]
|
|
|
|
priority = models.IntegerField(
|
|
choices=PRIORITY_CHOICES,
|
|
default=2,
|
|
help_text="Task priority level"
|
|
)
|
|
|
|
# Step 2: Backend Serializer (serializers.py) - No changes needed!
|
|
# DRF automatically includes choices in API response as:
|
|
# {
|
|
# "priority": 2,
|
|
# "priority_display": "Medium",
|
|
# "priority_choices": [[1, "Low"], [2, "Medium"], ...]
|
|
# }
|
|
|
|
# Step 3: Frontend Constant (config/constants.ts)
|
|
export const TASK_PRIORITIES = {
|
|
LOW: 1,
|
|
MEDIUM: 2,
|
|
HIGH: 3,
|
|
URGENT: 4,
|
|
};
|
|
|
|
export const TASK_PRIORITY_LABELS = {
|
|
1: 'Low',
|
|
2: 'Medium',
|
|
3: 'High',
|
|
4: 'Urgent',
|
|
};
|
|
|
|
# Step 4: Frontend Type (types/models.ts)
|
|
interface Task {
|
|
id: number;
|
|
priority: 1 | 2 | 3 | 4; // Use enum values
|
|
}
|
|
|
|
# Step 5: Frontend Component
|
|
function TaskSelect() {
|
|
return (
|
|
<select value={task.priority}>
|
|
<option value={1}>Low</option>
|
|
<option value={2}>Medium</option>
|
|
<option value={3}>High</option>
|
|
<option value={4}>Urgent</option>
|
|
</select>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Feature: Adding Validation
|
|
|
|
**Module Example: Validating task word count (100-10000)**
|
|
|
|
#### Files to Create/Modify:
|
|
|
|
| File | Change Type | Description |
|
|
|------|-------------|-------------|
|
|
| `backend/igny8_core/business/content/services/validation_service.py` | Create | Validation logic |
|
|
| `backend/igny8_core/modules/writer/serializers.py` | Modify | Add field validation |
|
|
| `frontend/src/utils/validators.ts` | Add | Client-side validation |
|
|
|
|
#### Example:
|
|
|
|
```python
|
|
# Step 1: Backend Model Field Validators (models.py)
|
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
|
|
|
class Tasks(SiteSectorBaseModel):
|
|
word_count = models.IntegerField(
|
|
default=1000,
|
|
validators=[
|
|
MinValueValidator(100, message="Minimum word count is 100"),
|
|
MaxValueValidator(10000, message="Maximum word count is 10000")
|
|
]
|
|
)
|
|
|
|
# Step 2: Backend Serializer Validation (serializers.py)
|
|
class TasksSerializer(serializers.ModelSerializer):
|
|
word_count = serializers.IntegerField(
|
|
min_value=100,
|
|
max_value=10000,
|
|
required=False
|
|
)
|
|
|
|
def validate_word_count(self, value):
|
|
if value and (value < 100 or value > 10000):
|
|
raise serializers.ValidationError(
|
|
"Word count must be between 100 and 10000"
|
|
)
|
|
return value
|
|
|
|
class Meta:
|
|
model = Tasks
|
|
fields = ['id', 'title', 'word_count', ...]
|
|
|
|
# Step 3: Frontend Validation (utils/validators.ts)
|
|
export const validateTaskWordCount = (wordCount: number): string | null => {
|
|
if (wordCount < 100) {
|
|
return 'Word count must be at least 100';
|
|
}
|
|
if (wordCount > 10000) {
|
|
return 'Word count cannot exceed 10000';
|
|
}
|
|
return null;
|
|
};
|
|
|
|
# Step 4: Frontend Component
|
|
function TaskForm() {
|
|
const [wordCount, setWordCount] = useState(1000);
|
|
const [error, setError] = useState('');
|
|
|
|
const handleChange = (value) => {
|
|
setWordCount(value);
|
|
const err = validateTaskWordCount(value);
|
|
setError(err || '');
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<input
|
|
type="number"
|
|
value={wordCount}
|
|
onChange={(e) => handleChange(Number(e.target.value))}
|
|
/>
|
|
{error && <span className="text-red-500">{error}</span>}
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Feature: Adding a Background Task (Celery)
|
|
|
|
**Module Example: Adding "Async content generation" task**
|
|
|
|
#### Files to Create/Modify:
|
|
|
|
| File | Change Type | Description |
|
|
|------|-------------|-------------|
|
|
| `backend/igny8_core/tasks/[domain]_tasks.py` | Create | Celery task function |
|
|
| `backend/igny8_core/modules/[module]/views.py` | Modify | Queue task from ViewSet |
|
|
| `backend/igny8_core/settings.py` | Check | Celery config (usually pre-configured) |
|
|
|
|
#### Example: Adding "generate_content_images" async task
|
|
|
|
```python
|
|
# Step 1: Create Celery Task (tasks/writer_tasks.py)
|
|
from celery import shared_task
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@shared_task(bind=True, max_retries=3)
|
|
def generate_content_images(self, content_id):
|
|
"""
|
|
Generate AI images for content
|
|
"""
|
|
from igny8_core.business.content.models import Content
|
|
from igny8_core.ai.engine import AIEngine
|
|
|
|
logger.info(f"[generate_content_images] 🎬 Starting image generation for content {content_id}")
|
|
|
|
try:
|
|
content = Content.objects.get(id=content_id)
|
|
|
|
# Generate images
|
|
engine = AIEngine()
|
|
images = engine.generate_images(
|
|
content_id=content.id,
|
|
title=content.title,
|
|
keywords=content.secondary_keywords
|
|
)
|
|
|
|
# Save images
|
|
for image_data in images:
|
|
Image.objects.create(
|
|
content=content,
|
|
url=image_data['url'],
|
|
prompt=image_data['prompt']
|
|
)
|
|
|
|
logger.info(f"[generate_content_images] ✅ Successfully generated {len(images)} images")
|
|
return {'success': True, 'image_count': len(images)}
|
|
|
|
except Content.DoesNotExist:
|
|
logger.error(f"[generate_content_images] ❌ Content {content_id} not found")
|
|
return {'success': False, 'error': 'Content not found'}
|
|
except Exception as exc:
|
|
logger.error(f"[generate_content_images] ❌ Task failed: {str(exc)}")
|
|
# Retry with exponential backoff
|
|
raise self.retry(exc=exc, countdown=2 ** self.request.retries)
|
|
|
|
# Step 2: Queue task from ViewSet (views.py)
|
|
class ContentViewSet(SiteSectorModelViewSet):
|
|
@action(detail=True, methods=['post'])
|
|
def generate_images(self, request, pk=None):
|
|
"""
|
|
Trigger async image generation
|
|
POST /api/v1/writer/content/{id}/generate_images/
|
|
"""
|
|
content = self.get_object()
|
|
|
|
# Queue background task
|
|
from igny8_core.tasks.writer_tasks import generate_content_images
|
|
|
|
task = generate_content_images.delay(content.id)
|
|
|
|
return success_response(
|
|
success=True,
|
|
data={'task_id': task.id},
|
|
message='Image generation started'
|
|
)
|
|
|
|
# Step 3: Frontend to trigger task
|
|
// frontend/src/api/writer.ts
|
|
export const generateContentImages = async (contentId: number) => {
|
|
return fetchAPI(`/api/v1/writer/content/${contentId}/generate_images/`, {
|
|
method: 'POST'
|
|
});
|
|
};
|
|
|
|
// frontend/src/pages/Writer/Content.tsx
|
|
async function handleGenerateImages(contentId) {
|
|
try {
|
|
const response = await generateContentImages(contentId);
|
|
toast.success('Image generation started');
|
|
} catch (error) {
|
|
toast.error('Failed to start image generation');
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Database Schema Changes
|
|
|
|
### Feature: Adding/Modifying a Database Column
|
|
|
|
#### Complete Workflow:
|
|
|
|
```bash
|
|
# Step 1: Add field to model
|
|
# File: backend/igny8_core/business/content/models.py
|
|
class Content(SiteSectorBaseModel):
|
|
# ... existing fields ...
|
|
seo_score = models.IntegerField(
|
|
default=0,
|
|
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
|
help_text="SEO optimization score (0-100)"
|
|
)
|
|
|
|
# Step 2: Create migration
|
|
$ python manage.py makemigrations writer
|
|
|
|
# Output: Migrations for 'writer':
|
|
# migrations/0015_content_seo_score.py
|
|
|
|
# Step 3: Review migration (optional)
|
|
$ cat migrations/0015_content_seo_score.py
|
|
|
|
# Step 4: Apply migration
|
|
$ python manage.py migrate
|
|
|
|
# Step 5: Update serializer to include new field
|
|
# File: backend/igny8_core/modules/writer/serializers.py
|
|
class ContentSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Content
|
|
fields = [
|
|
'id', 'title', 'content_html', 'status',
|
|
'seo_score', # Add this
|
|
# ... other fields ...
|
|
]
|
|
|
|
# Step 6: Update frontend types
|
|
// frontend/src/types/models.ts
|
|
interface Content {
|
|
id: number;
|
|
title: string;
|
|
contentHtml: string;
|
|
seoScore: number; // Add this
|
|
}
|
|
|
|
# Step 7: Update components to use new field
|
|
```
|
|
|
|
### Feature: Removing a Database Column
|
|
|
|
```python
|
|
# Step 1: Create migration to remove field
|
|
$ python manage.py makemigrations writer --remove-field
|
|
|
|
# Step 2: Or manually edit model - comment out field
|
|
class Content(SiteSectorBaseModel):
|
|
# ... existing fields ...
|
|
# seo_score = models.IntegerField(...) # REMOVED
|
|
|
|
# Step 3: Auto-create migration
|
|
$ python manage.py makemigrations writer
|
|
|
|
# Step 4: Apply migration
|
|
$ python manage.py migrate
|
|
|
|
# Step 5: Remove from serializer
|
|
# backend/igny8_core/modules/writer/serializers.py
|
|
# Remove 'seo_score' from fields list
|
|
|
|
# Step 6: Remove from frontend types and components
|
|
```
|
|
|
|
### Feature: Modifying a Database Column
|
|
|
|
```python
|
|
# Example: Change word_count max value from 10000 to 20000
|
|
|
|
# Step 1: Update model
|
|
class Tasks(SiteSectorBaseModel):
|
|
word_count = models.IntegerField(
|
|
default=1000,
|
|
validators=[MinValueValidator(100), MaxValueValidator(20000)] # Changed
|
|
)
|
|
|
|
# Step 2: Create migration
|
|
$ python manage.py makemigrations writer
|
|
|
|
# Step 3: Apply migration
|
|
$ python manage.py migrate
|
|
|
|
# Step 4: Update serializer if validation changed
|
|
class TasksSerializer(serializers.ModelSerializer):
|
|
word_count = serializers.IntegerField(
|
|
min_value=100,
|
|
max_value=20000 # Update
|
|
)
|
|
|
|
# Step 5: Update frontend validation constants
|
|
export const MAX_WORD_COUNT = 20000; // Update
|
|
```
|
|
|
|
---
|
|
|
|
## Cross-Module Modifications
|
|
|
|
### Feature: Adding a Relationship Between Modules
|
|
|
|
**Example: Link Content to Ideas (Writer → Planner)**
|
|
|
|
#### Files to Modify:
|
|
|
|
```python
|
|
# Step 1: Backend Model - Add ForeignKey
|
|
# backend/igny8_core/business/content/models.py
|
|
class Content(SiteSectorBaseModel):
|
|
# ... existing fields ...
|
|
idea = models.ForeignKey(
|
|
'planner.ContentIdeas',
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='contents',
|
|
help_text="Source content idea"
|
|
)
|
|
|
|
# Step 2: Create migration
|
|
$ python manage.py makemigrations writer
|
|
|
|
# Step 3: Apply migration
|
|
$ python manage.py migrate
|
|
|
|
# Step 4: Update serializer to include nested relationship
|
|
# backend/igny8_core/modules/writer/serializers.py
|
|
from igny8_core.modules.planner.serializers import ContentIdeasSerializer
|
|
|
|
class ContentSerializer(serializers.ModelSerializer):
|
|
idea = ContentIdeasSerializer(read_only=True)
|
|
idea_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=ContentIdeas.objects.all(),
|
|
source='idea',
|
|
write_only=True
|
|
)
|
|
|
|
class Meta:
|
|
model = Content
|
|
fields = ['id', 'title', 'idea', 'idea_id', ...]
|
|
|
|
# Step 5: Update ViewSet to support filtering by idea
|
|
# backend/igny8_core/modules/writer/views.py
|
|
class ContentViewSet(SiteSectorModelViewSet):
|
|
filterset_fields = ['status', 'cluster_id', 'idea_id'] # Add idea_id
|
|
ordering_fields = ['title', 'created_at', 'idea_id'] # Add idea_id
|
|
|
|
# Step 6: Frontend - Update type
|
|
// frontend/src/types/models.ts
|
|
interface Content {
|
|
id: number;
|
|
title: string;
|
|
idea?: Idea; // Add nested object
|
|
ideaId?: number; // Add ID for create/update
|
|
}
|
|
|
|
# Step 7: Frontend - Display related idea
|
|
// frontend/src/pages/Writer/Content.tsx
|
|
{content.idea && (
|
|
<div>Idea: {content.idea.ideaTitle}</div>
|
|
)}
|
|
```
|
|
|
|
---
|
|
|
|
## Common Workflow Patterns
|
|
|
|
### Pattern 1: Adding a Simple Field to List Page
|
|
|
|
**Timeline: ~15 minutes**
|
|
|
|
```
|
|
1. Backend Model → Add field with validators
|
|
2. DB Migration → makemigrations + migrate
|
|
3. Backend Serializer → Add to fields list
|
|
4. Backend ViewSet → Add to filters/ordering if needed
|
|
5. Frontend Type → Add to interface
|
|
6. Frontend Component → Add to table columns
|
|
7. Test → Verify in both UI and API
|
|
```
|
|
|
|
**Files Modified:**
|
|
- `backend/igny8_core/business/*/models.py`
|
|
- `backend/igny8_core/migrations/XXXX_*.py`
|
|
- `backend/igny8_core/modules/*/serializers.py`
|
|
- `backend/igny8_core/modules/*/views.py`
|
|
- `frontend/src/types/models.ts`
|
|
- `frontend/src/pages/*/[Page].tsx`
|
|
|
|
### Pattern 2: Adding a New Feature Page
|
|
|
|
**Timeline: ~1 hour**
|
|
|
|
```
|
|
1. Backend ViewSet/Action → Create endpoint
|
|
2. Backend Serializer → Define response format
|
|
3. Frontend API Hook → Create fetch function
|
|
4. Frontend Type → Define data interface
|
|
5. Frontend Page Component → Create page
|
|
6. Frontend Router → Add route
|
|
7. Frontend Navigation → Add menu item
|
|
8. Test → End-to-end flow
|
|
```
|
|
|
|
**Files Modified:**
|
|
- `backend/igny8_core/modules/*/views.py`
|
|
- `backend/igny8_core/modules/*/serializers.py`
|
|
- `frontend/src/api/*.ts`
|
|
- `frontend/src/types/models.ts`
|
|
- `frontend/src/pages/*/*.tsx`
|
|
- `frontend/src/App.tsx`
|
|
- `frontend/src/layout/Sidebar.tsx`
|
|
|
|
### Pattern 3: Adding Async Processing
|
|
|
|
**Timeline: ~30 minutes**
|
|
|
|
```
|
|
1. Backend Celery Task → Create task function
|
|
2. Backend ViewSet Action → Queue task from endpoint
|
|
3. Frontend API Hook → Create trigger function
|
|
4. Frontend Component → Add button to trigger
|
|
5. Frontend Polling/Polling → Show task status
|
|
6. Test → Verify task execution and completion
|
|
```
|
|
|
|
**Files Modified:**
|
|
- `backend/igny8_core/tasks/*_tasks.py`
|
|
- `backend/igny8_core/modules/*/views.py`
|
|
- `frontend/src/api/*.ts`
|
|
- `frontend/src/pages/*/*.tsx`
|
|
|
|
---
|
|
|
|
## File Organization Index
|
|
|
|
### Module File Structure Template
|
|
|
|
For each module (Planner, Writer, Linker, etc.):
|
|
|
|
```
|
|
backend/igny8_core/
|
|
├── modules/[module]/
|
|
│ ├── views.py # ViewSets, @actions
|
|
│ ├── serializers.py # Serializers
|
|
│ ├── urls.py # Route registration
|
|
│ └── apps.py
|
|
└── business/[domain]/
|
|
├── models.py # Data models
|
|
├── services/
|
|
│ ├── service1.py # Business logic
|
|
│ └── service2.py
|
|
└── migrations/
|
|
└── XXXX_*.py # DB migrations
|
|
|
|
frontend/src/
|
|
├── pages/[Module]/
|
|
│ ├── Dashboard.tsx # Overview page
|
|
│ ├── [Feature].tsx # Feature pages
|
|
│ └── [Detail].tsx # Detail views
|
|
├── components/[Module]/
|
|
│ ├── [Feature]Table.tsx # Table components
|
|
│ ├── [Feature]Form.tsx # Form components
|
|
│ └── [Feature]Filter.tsx # Filter components
|
|
├── api/
|
|
│ └── [module].ts # API calls
|
|
├── store/
|
|
│ └── [module]Store.ts # State management
|
|
└── types/
|
|
└── models.ts # Type definitions
|
|
```
|
|
|
|
### Global Files (All Modules)
|
|
|
|
| File | Purpose | Where Located |
|
|
|------|---------|---------------|
|
|
| **API Base Classes** | ViewSet, Serializer base | `backend/igny8_core/api/base.py` |
|
|
| **Response Format** | Unified responses | `backend/igny8_core/api/response.py` |
|
|
| **Permissions** | Role-based access | `backend/igny8_core/api/permissions.py` |
|
|
| **Pagination** | List pagination | `backend/igny8_core/api/pagination.py` |
|
|
| **Root Router** | Route registration | `backend/igny8_core/urls.py` |
|
|
| **Settings** | Django config | `backend/igny8_core/settings.py` |
|
|
| **Auth Models** | User, Site, Sector | `backend/igny8_core/auth/models.py` |
|
|
| **API Client** | Frontend HTTP | `frontend/src/api/client.ts` |
|
|
| **Auth Store** | User state | `frontend/src/store/authStore.ts` |
|
|
| **Site Store** | Site/Sector context | `frontend/src/store/siteStore.ts` |
|
|
| **App Router** | Frontend routes | `frontend/src/App.tsx` |
|
|
| **Sidebar** | Navigation menu | `frontend/src/layout/Sidebar.tsx` |
|
|
| **Constants** | Global constants | `frontend/src/config/constants.ts` |
|
|
| **Validators** | Validation rules | `frontend/src/utils/validators.ts` |
|
|
|
|
---
|
|
|
|
## Quick Checklists
|
|
|
|
### Adding a New Column: Checklist
|
|
|
|
- [ ] Add field to Backend Model (`models.py`)
|
|
- [ ] Run `makemigrations`
|
|
- [ ] Run `migrate`
|
|
- [ ] Add field to Backend Serializer (`serializers.py`)
|
|
- [ ] Add to ViewSet `filterset_fields` if filterable
|
|
- [ ] Add to ViewSet `ordering_fields` if sortable
|
|
- [ ] Add to Frontend Type (`types/models.ts`)
|
|
- [ ] Add to Frontend Component columns config
|
|
- [ ] Add to Frontend Table JSX rendering
|
|
- [ ] Test API response includes field
|
|
- [ ] Test frontend displays column
|
|
- [ ] Test filter/sort works (if applicable)
|
|
|
|
### Adding a New Page: Checklist
|
|
|
|
- [ ] Create new Page component (`pages/[Module]/[Page].tsx`)
|
|
- [ ] Create or update API hook (`api/[module].ts`)
|
|
- [ ] Add route to router (`App.tsx`)
|
|
- [ ] Add menu item to navigation (`layout/Sidebar.tsx`)
|
|
- [ ] Add types if needed (`types/models.ts`)
|
|
- [ ] Test page loads
|
|
- [ ] Test data fetches
|
|
- [ ] Test filters/search work
|
|
- [ ] Test pagination works
|
|
- [ ] Test error states
|
|
|
|
### Adding an API Endpoint: Checklist
|
|
|
|
- [ ] Define endpoint action in ViewSet (`views.py`)
|
|
- [ ] Use `@action(detail=...)` decorator
|
|
- [ ] Define response format using serializer
|
|
- [ ] Add permission check if needed
|
|
- [ ] Document docstring
|
|
- [ ] Test endpoint with API client
|
|
- [ ] Verify unified response format
|
|
- [ ] Create frontend API hook (`api/[module].ts`)
|
|
- [ ] Test from frontend component
|
|
- [ ] Verify error handling
|
|
|
|
---
|
|
|
|
**End of Document**
|
|
|
|
For questions on specific features, refer to:
|
|
- Architecture: `00-SYSTEM-ARCHITECTURE-MASTER-REFERENCE.md`
|
|
- API Details: `01-IGNY8-REST-API-COMPLETE-REFERENCE.md`
|
|
- Workflows: `02-PLANNER-WRITER-WORKFLOW-TECHNICAL-GUIDE.md`
|