# Notifications Module

**Last Verified:** December 28, 2025  
**Status:** ✅ Active (v1.2.2 - Notifications Page Added)  
**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
