# 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.