# IGNY8 Account, User, Plan, Sites, Credits Documentation

**Version:** 1.0  
**Last Updated:** 2025-01-XX  
**Purpose:** Complete documentation of multi-tenancy architecture, access control, plans, credits, and related systems.

---

## Table of Contents

1. [Overview](#overview)
2. [Account Model](#account-model)
3. [User Model](#user-model)
4. [Plan Model](#plan-model)
5. [Subscription Model](#subscription-model)
6. [Site Model](#site-model)
7. [Sector Model](#sector-model)
8. [Industry & IndustrySector Models](#industry--industrysector-models)
9. [Access Control](#access-control)
10. [Credits System](#credits-system)
11. [Plan Limits](#plan-limits)
12. [Multi-Tenancy Architecture](#multi-tenancy-architecture)

---

## Overview

The IGNY8 platform uses a multi-account architecture with the following core entities:

- **Account**: Top-level organization/workspace
- **User**: Individual user accounts with roles
- **Plan**: Subscription plan templates with limits
- **Subscription**: Active subscription linking Account to Plan
- **Site**: Workspace within an Account (1-N relationship)
- **Sector**: Content category within a Site (1-5 per site)
- **Industry**: Global industry templates
- **IndustrySector**: Industry sector templates

### Key Relationships

```
Account (1) ──< (N) User
Account (1) ──< (1) Subscription ──> (1) Plan
Account (1) ──< (N) Site
Site (1) ──< (1-5) Sector
Industry (1) ──< (N) IndustrySector
Site (1) ──> (1) Industry (optional)
Sector (1) ──> (1) IndustrySector (optional, template reference)
```

---

## Account Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_accounts`

### Fields

- `id`: Primary key
- `name`: CharField - Account name
- `slug`: SlugField - Unique slug identifier
- `owner`: ForeignKey to User - Account owner
- `stripe_customer_id`: CharField - Stripe customer ID (optional)
- `plan`: ForeignKey to Plan - Current subscription plan
- `credits`: IntegerField - Current credit balance (default: 0)
- `status`: CharField - Account status (choices: active, suspended, trial, cancelled)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Status Values

- `active`: Normal operation
- `suspended`: Access temporarily revoked
- `trial`: Limited trial access
- `cancelled`: Account terminated

### Methods

#### is_system_account()
**Returns**: `True` if account is a system account (aws-admin, default-account, default)

**Purpose**: System accounts bypass all filtering restrictions


### Relationships

- **One-to-Many**: Users (via `user.account`)
- **One-to-One**: Subscription (via `account.subscription`)
- **One-to-Many**: Sites (via `site.account`)


---

## User Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_users`

**Inherits**: `AbstractUser` (Django's built-in user model)

### Fields

- `id`: Primary key (inherited from AbstractUser)
- `email`: EmailField - Unique, USERNAME_FIELD
- `username`: CharField - Username
- `password`: Hashed password (inherited)
- `account`: ForeignKey to Account - User's account (null=True, blank=True)
- `role`: CharField - User role (choices: developer, owner, admin, editor, viewer, system_bot)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Role Hierarchy (from highest to lowest)

1. **developer**: Full system access, bypasses all restrictions
2. **owner**: Full access to account, can manage users and billing
3. **admin**: Admin access to account, can manage content and users
4. **editor**: Can edit content, manage clusters/tasks
5. **viewer**: Read-only access
6. **system_bot**: System automation user

### Methods

#### has_role(*roles)
**Parameters**: `*roles` - Variable number of role strings

**Returns**: `True` if user has any of the specified roles

#### is_owner_or_admin()
**Returns**: `True` if role is owner or admin

#### is_developer()
**Returns**: `True` if role is developer or is_superuser

#### is_admin_or_developer()
**Returns**: `True` if role is admin or developer (bypasses restrictions)

**Purpose**: Admin/Developer users bypass account/site/sector filtering

#### is_system_account_user()
**Returns**: `True` if user belongs to system account

**Purpose**: System account users bypass all filtering restrictions

#### get_accessible_sites()
**Returns**: QuerySet of sites user can access

**Access Control Logic**:
- **System Account Users**: All active sites across all accounts
- **Developers**: All active sites across all accounts
- **Owners/Admins**: All sites in their account
- **Editors/Viewers**: Only sites explicitly granted via `SiteUserAccess`


### Relationships

- **Many-to-One**: Account (via `user.account`)
- **Many-to-Many**: Sites (via `SiteUserAccess`)

---

## Plan Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_plans`

### Fields

#### Plan Information
- `id`: Primary key
- `name`: CharField - Plan name (e.g., "Free", "Starter", "Growth")
- `slug`: SlugField - Unique slug identifier
- `price`: DecimalField - Monthly price
- `billing_cycle`: CharField - Billing cycle (choices: monthly, annual)
- `features`: JSONField - Array of feature strings (e.g., ['ai_writer', 'image_gen', 'auto_publish'])
- `is_active`: BooleanField - Whether plan is available for subscription

#### User/Site/Scope Limits
- `max_users`: IntegerField - Total users allowed per account (default: 1, min: 1)
- `max_sites`: IntegerField - Maximum sites allowed (default: 1, min: 1)
- `max_industries`: IntegerField - Optional limit for industries/sectors (nullable)
- `max_author_profiles`: IntegerField - Limit for saved writing styles (default: 5)

#### Planner Limits
- `max_keywords`: IntegerField - Total keywords allowed globally (default: 1000)
- `max_clusters`: IntegerField - Total clusters allowed globally (default: 100)
- `max_content_ideas`: IntegerField - Total content ideas allowed globally (default: 300)
- `daily_cluster_limit`: IntegerField - Max clusters per day (default: 10)
- `daily_keyword_import_limit`: IntegerField - Seed keywords import limit per day (default: 100)
- `monthly_cluster_ai_credits`: IntegerField - AI credits for clustering (default: 50)

#### Writer Limits
- `daily_content_tasks`: IntegerField - Max content tasks per day (default: 10)
- `daily_ai_requests`: IntegerField - Total AI executions per day (default: 50)
- `monthly_word_count_limit`: IntegerField - Monthly word limit for generated content (default: 50000)
- `monthly_content_ai_credits`: IntegerField - AI credit pool for content generation (default: 200)

#### Image Generation Limits
- `monthly_image_count`: IntegerField - Max images per month (default: 100)
- `daily_image_generation_limit`: IntegerField - Max images per day (default: 25)
- `monthly_image_ai_credits`: IntegerField - AI credit pool for images (default: 100)
- `max_images_per_task`: IntegerField - Max images per content task (default: 4, min: 1)
- `image_model_choices`: JSONField - Allowed image models (e.g., ['dalle3', 'hidream'])

#### AI Request Controls
- `daily_ai_request_limit`: IntegerField - Global daily AI request cap (default: 100)
- `monthly_ai_credit_limit`: IntegerField - Unified credit ceiling per month (default: 500)

#### Billing & Credits
- `included_credits`: IntegerField - Monthly credits included (default: 0)
- `extra_credit_price`: DecimalField - Price per additional credit (default: 0.01)
- `allow_credit_topup`: BooleanField - Can user purchase more credits? (default: True)
- `auto_credit_topup_threshold`: IntegerField - Auto top-up trigger point (nullable)
- `auto_credit_topup_amount`: IntegerField - Credits to auto-buy (nullable)

#### Stripe Integration
- `stripe_product_id`: CharField - Stripe product ID (nullable)
- `stripe_price_id`: CharField - Stripe price ID (nullable)

#### Legacy Fields
- `credits_per_month`: IntegerField - DEPRECATED: Use included_credits instead (default: 0)

### Methods

#### clean()
**Purpose**: Validates plan limits

**Validations**:
- `max_sites` must be >= 1
- `included_credits` must be >= 0

#### get_effective_credits_per_month()
**Returns**: `included_credits` if set, otherwise `credits_per_month` (backward compatibility)

### Relationships

- **One-to-Many**: Accounts (via `account.plan`)

### Plan Limits Enforcement

**Current Status**: Plan limits are defined but **not currently enforced** in AI functions.

**Future Implementation**: Plan limits should be checked before allowing operations.

---

## Subscription Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_subscriptions`

### Fields

- `id`: Primary key
- `account`: OneToOneField to Account - Account subscription
- `stripe_subscription_id`: CharField - Unique Stripe subscription ID
- `status`: CharField - Subscription status (choices: active, past_due, canceled, trialing)
- `current_period_start`: DateTimeField - Current billing period start
- `current_period_end`: DateTimeField - Current billing period end
- `cancel_at_period_end`: BooleanField - Cancel at period end flag (default: False)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Status Values

- `active`: Subscription is active and paid
- `past_due`: Payment failed, subscription past due
- `canceled`: Subscription canceled
- `trialing`: In trial period

### Methods


### Relationships

- **One-to-One**: Account (via `account.subscription`)

---

## Site Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_sites`

**Inherits**: `AccountBaseModel`

### Fields

- `id`: Primary key
- `account`: ForeignKey to Account - Account this site belongs to
- `name`: CharField - Site name
- `slug`: SlugField - Unique slug per account
- `domain`: URLField - Primary domain URL (optional)
- `description`: TextField - Site description (optional)
- `industry`: ForeignKey to Industry - Industry this site belongs to (optional)
- `is_active`: BooleanField - Whether site is active (default: True)
- `status`: CharField - Site status (choices: active, inactive, suspended, default: active)
- `wp_url`: URLField - WordPress site URL (optional, for WordPress integration)
- `wp_username`: CharField - WordPress username (optional)
- `wp_app_password`: CharField - WordPress app password (optional)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Unique Constraint

- `(account, slug)` - Slug unique per account

### Methods

#### get_active_sectors_count()
**Returns**: Count of active sectors for this site

#### can_add_sector()
**Returns**: `True` if site can add another sector (max 5 sectors per site)

### Relationships

- **Many-to-One**: Account (via `site.account`)
- **Many-to-One**: Industry (via `site.industry`, optional)
- **One-to-Many**: Sectors (via `sector.site`)
- **Many-to-Many**: Users (via `SiteUserAccess`)

### Site Activation

**Important**: Multiple sites can be active simultaneously. The previous restriction of "only 1 site can be active at one time" has been removed.

---

## Sector Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_sectors`

**Inherits**: `AccountBaseModel`

### Fields

- `id`: Primary key
- `account`: ForeignKey to Account - Account this sector belongs to
- `site`: ForeignKey to Site - Site this sector belongs to
- `industry_sector`: ForeignKey to IndustrySector - Reference to industry sector template (optional)
- `name`: CharField - Sector name
- `slug`: SlugField - Unique slug per site
- `description`: TextField - Sector description (optional)
- `is_active`: BooleanField - Whether sector is active (default: True)
- `status`: CharField - Sector status (choices: active, inactive, default: active)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Unique Constraint

- `(site, slug)` - Slug unique per site

### Validation

**On Save**:
1. Automatically sets `account` from `site.account`
2. Validates that `industry_sector.industry` matches `site.industry` (if both set)
3. Validates sector limit: Maximum 5 active sectors per site

### Methods

#### industry (property)
**Returns**: Industry for this sector (from `industry_sector.industry` if set)

### Relationships

- **Many-to-One**: Account (via `sector.account`)
- **Many-to-One**: Site (via `sector.site`)
- **Many-to-One**: IndustrySector (via `sector.industry_sector`, optional, template reference)
- **One-to-Many**: Keywords, Clusters, ContentIdeas, Tasks (via their `sector` field)

---

## Industry & IndustrySector Models

### Industry Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_industries`

**Purpose**: Global industry templates.

### Fields

- `id`: Primary key
- `name`: CharField - Industry name (unique)
- `slug`: SlugField - Unique slug identifier
- `description`: TextField - Industry description (optional)
- `is_active`: BooleanField - Whether industry is active (default: True)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Relationships

- **One-to-Many**: Sites (via `site.industry`)
- **One-to-Many**: IndustrySectors (via `industry_sector.industry`)

### IndustrySector Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_industry_sectors`

**Purpose**: Sector templates within industries.

### Fields

- `id`: Primary key
- `industry`: ForeignKey to Industry - Industry this sector belongs to
- `name`: CharField - Sector name
- `slug`: SlugField - Unique slug per industry
- `description`: TextField - Sector description (optional)
- `suggested_keywords`: JSONField - List of suggested keywords for this sector template
- `is_active`: BooleanField - Whether sector is active (default: True)
- `created_at`: DateTimeField - Creation timestamp
- `updated_at`: DateTimeField - Last update timestamp

### Unique Constraint

- `(industry, slug)` - Slug unique per industry

### Relationships

- **Many-to-One**: Industry (via `industry_sector.industry`)
- **One-to-Many**: Sectors (via `sector.industry_sector`, template reference)
- **One-to-Many**: SeedKeywords (via `seed_keyword.sector`)

---

## Access Control

### Account Isolation

**Principle**: All data is isolated by account.

**Implementation**:
- All models inherit `AccountBaseModel` (has `account` ForeignKey)
- All ViewSets inherit `AccountModelViewSet` (filters by `request.account`)
- Middleware sets `request.account` from JWT token

**Access Control**:
- **Admin/Developer users**: Bypass account filtering (see all accounts)
- **System account users**: Bypass account filtering (see all accounts)
- **Regular users**: Only see data from their account

### Site Access Control

**Principle**: Users can only access sites they have permission to access.

**Implementation**:
- `User.get_accessible_sites()` returns sites user can access
- ViewSets filter by accessible sites
- `SiteUserAccess` model controls explicit site access for editors/viewers

**Access Levels**:
- **System Account Users**: All active sites across all accounts
- **Developers**: All active sites across all accounts
- **Owners/Admins**: All sites in their account
- **Editors/Viewers**: Only sites explicitly granted via `SiteUserAccess`

### SiteUserAccess Model

**File**: `backend/igny8_core/auth/models.py`

**Table**: `igny8_site_user_access`

**Purpose**: Many-to-many relationship between Users and Sites for explicit access control.

### Fields

- `id`: Primary key
- `user`: ForeignKey to User
- `site`: ForeignKey to Site
- `granted_at`: DateTimeField - When access was granted
- `granted_by`: ForeignKey to User - Who granted access (optional)

### Unique Constraint

- `(user, site)` - One access record per user-site pair

### Usage

- Owners and Admins have automatic access to all sites in their account
- Editors and Viewers require explicit `SiteUserAccess` records
- System account users and Developers bypass this check

### Role-Based Access Control (RBAC)

**Role Hierarchy**:
1. **developer**: Full system access, bypasses all restrictions
2. **owner**: Full account access, can manage users and billing
3. **admin**: Account admin access, can manage content and users
4. **editor**: Content editing access, can manage clusters/tasks
5. **viewer**: Read-only access

**Permission Checks**:
- `user.has_role(*roles)`: Check if user has any of the specified roles
- `user.is_owner_or_admin()`: Check if user is owner or admin
- `user.is_developer()`: Check if user is developer
- `user.is_admin_or_developer()`: Check if user is admin or developer (bypasses restrictions)

---

## Credits System

### Account Credits

**Field**: `Account.credits` (IntegerField, default: 0)

**Purpose**: Current credit balance for the account.

**Usage**: Credits are deducted for AI operations and added through purchases or subscriptions.

### Credit Models

#### CreditTransaction

**File**: `backend/igny8_core/modules/billing/models.py`

**Table**: `igny8_credit_transactions`

**Purpose**: Track all credit transactions (additions, deductions).

### Fields

- `id`: Primary key
- `account`: ForeignKey to Account
- `transaction_type`: CharField - Type of transaction (choices: purchase, subscription, refund, deduction, adjustment)
- `amount`: IntegerField - Positive for additions, negative for deductions
- `balance_after`: IntegerField - Credit balance after this transaction
- `description`: CharField - Transaction description
- `metadata`: JSONField - Additional context (AI call details, etc.)
- `created_at`: DateTimeField - Creation timestamp

#### CreditUsageLog

**File**: `backend/igny8_core/modules/billing/models.py`

**Table**: `igny8_credit_usage_logs`

**Purpose**: Detailed log of credit usage per AI operation.

### Fields

- `id`: Primary key
- `account`: ForeignKey to Account
- `operation_type`: CharField - Type of operation (choices: clustering, ideas, content, images, reparse)
- `credits_used`: IntegerField - Number of credits used
- `cost_usd`: DecimalField - Cost in USD (optional)
- `model_used`: CharField - AI model used (optional)
- `tokens_input`: IntegerField - Input tokens (optional)
- `tokens_output`: IntegerField - Output tokens (optional)
- `related_object_type`: CharField - Related object type (optional)
- `related_object_id`: IntegerField - Related object ID (optional)
- `metadata`: JSONField - Additional metadata
- `created_at`: DateTimeField - Creation timestamp

### CreditService

**File**: `backend/igny8_core/modules/billing/services.py`

**Purpose**: Service for managing credits.

#### Methods

##### check_credits(account, required_credits)
**Purpose**: Check if account has enough credits.

**Raises**: `InsufficientCreditsError` if account doesn't have enough credits

##### deduct_credits(account, amount, operation_type, description, ...)
**Purpose**: Deduct credits and log transaction.

**Parameters**:
- `account`: Account instance
- `amount`: Number of credits to deduct
- `operation_type`: Type of operation
- `description`: Description of the transaction
- `metadata`: Optional metadata dict
- `cost_usd`: Optional cost in USD
- `model_used`: Optional AI model used
- `tokens_input`: Optional input tokens
- `tokens_output`: Optional output tokens
- `related_object_type`: Optional related object type
- `related_object_id`: Optional related object ID

**Returns**: New credit balance

**Creates**:
- `CreditTransaction` record
- `CreditUsageLog` record

##### add_credits(account, amount, transaction_type, description, ...)
**Purpose**: Add credits (purchase, subscription, etc.).

**Parameters**:
- `account`: Account instance
- `amount`: Number of credits to add
- `transaction_type`: Type of transaction
- `description`: Description of the transaction
- `metadata`: Optional metadata dict

**Returns**: New credit balance

**Creates**: `CreditTransaction` record

##### calculate_credits_for_operation(operation_type, **kwargs)
**Purpose**: Calculate credits needed for an operation.

**Parameters**:
- `operation_type`: Type of operation
- `**kwargs`: Operation-specific parameters

**Returns**: Number of credits required

**Raises**: `CreditCalculationError` if calculation fails

**Credit Costs**:
- `clustering`: 1 credit per 30 keywords
- `ideas`: 1 credit per idea
- `content`: 3 credits per content piece
- `images`: 1 credit per image
- `reparse`: 1 credit per reparse

### Credit Integration

**Current Status**: Credits system is implemented but **not currently enforced** in AI functions.

**Future Implementation**: Credits should be checked and deducted before allowing AI operations.

---

## Plan Limits

### Planner Limits

- `max_keywords`: Total keywords allowed (not enforced)
- `max_clusters`: Total clusters allowed (not enforced)
- `max_content_ideas`: Total content ideas allowed (not enforced)
- `daily_cluster_limit`: Max clusters per day (not enforced)
- `monthly_cluster_ai_credits`: AI credits for clustering (not enforced)

### Writer Limits

- `daily_content_tasks`: Max content tasks per day (not enforced)
- `daily_ai_requests`: Total AI executions per day (not enforced)
- `monthly_word_count_limit`: Monthly word limit (not enforced)
- `monthly_content_ai_credits`: AI credits for content (not enforced)

### Image Limits

- `monthly_image_count`: Max images per month (not enforced)
- `daily_image_generation_limit`: Max images per day (not enforced)
- `monthly_image_ai_credits`: AI credits for images (not enforced)
- `max_images_per_task`: Max images per task (not enforced)
- `image_model_choices`: Allowed image models (not enforced)

### Feature Flags

- `features`: JSON array of enabled features (e.g., `['ai_writer', 'image_gen', 'auto_publish']`)
- **Not enforced**: Feature flags are not checked before allowing AI operations

### Plan Limits Enforcement

**Current Status**: Plan limits are defined but **not currently enforced** in AI functions.

**Future Implementation**: Plan limits should be checked before allowing operations.

---

## Multi-Tenancy Architecture

### Account Isolation

**Principle**: All data is isolated by account.

**Implementation**:
1. **Model Level**: All models inherit `AccountBaseModel` (has `account` ForeignKey)
2. **ViewSet Level**: All ViewSets inherit `AccountModelViewSet` (filters by `request.account`)
3. **Middleware Level**: `AccountContextMiddleware` sets `request.account` from JWT token

**Access Control**:
- Admin/Developer users: Bypass account filtering (see all accounts)
- System account users: Bypass account filtering (see all accounts)
- Regular users: Only see data from their account

### Site/Sector Hierarchy

**Structure**:
```
Account (1) ──< (N) Site
Site (1) ──< (1-5) Sector
Sector (1) ──< (N) Keywords, Clusters, ContentIdeas, Tasks
```

**Implementation**:
- Models inherit `SiteSectorBaseModel` (has `site` and `sector` ForeignKeys)
- ViewSets inherit `SiteSectorModelViewSet` (filters by accessible sites)
- User access control via `User.get_accessible_sites()`

**Site Access Control**:
- System account users: All active sites
- Developers: All active sites
- Owners/Admins: All sites in their account
- Editors/Viewers: Only sites granted via `SiteUserAccess`

### Data Isolation Flow

1. **Request Arrives**: JWT token contains account ID
2. **Middleware**: `AccountContextMiddleware` extracts account ID and sets `request.account`
3. **ViewSet**: `AccountModelViewSet.get_queryset()` filters by `request.account`
4. **Database**: All queries automatically filtered by account

**Override**: Admin/Developer users bypass account filtering

---

## Summary

The IGNY8 multi-tenancy architecture provides:

1. **Complete Account Isolation**: All data isolated by account with automatic filtering
2. **Site/Sector Hierarchy**: Hierarchical organization with access control
3. **Role-Based Access Control**: Granular permissions based on user roles
4. **Plan-Based Limits**: Comprehensive plan limits (defined but not enforced)
5. **Credits System**: Credit tracking and deduction system (implemented but not enforced)
6. **Flexible Site Access**: Multiple sites can be active simultaneously
7. **Industry Templates**: Global industry and sector templates

This architecture ensures data security, scalability, and flexibility while providing a solid foundation for subscription management and billing.

