Files
igny8/docs/00-SYSTEM/TENANCY.md
IGNY8 VPS (Salman) 4bffede052 docs & ux improvmeents
2025-12-25 20:31:58 +00:00

300 lines
7.0 KiB
Markdown

# Multi-Tenancy Architecture
**Last Verified:** December 25, 2025
**Backend Path:** `backend/igny8_core/auth/models.py`
---
## Data Hierarchy
```
Account (Tenant)
├── Users (team members)
├── Subscription (plan binding)
├── Sites
│ ├── Sectors
│ │ ├── Keywords
│ │ ├── Clusters
│ │ ├── ContentIdeas
│ │ ├── Tasks
│ │ ├── Content
│ │ └── Images
│ └── Integrations (WordPress)
└── Billing (credits, transactions)
```
---
## Core Models
### Account
| Field | Type | Purpose |
|-------|------|---------|
| name | CharField | Account/organization name |
| is_active | Boolean | Enable/disable account |
| credits | Decimal | Current credit balance |
| stripe_customer_id | CharField | Stripe integration |
| paypal_customer_id | CharField | PayPal integration |
| created_at | DateTime | Registration date |
### Plan
| Field | Type | Purpose |
|-------|------|---------|
| name | CharField | Plan name (Free, Starter, Growth, Scale) |
| included_credits | Integer | Monthly credit allocation |
| max_sites | Integer | Site limit |
| max_users | Integer | User limit |
| max_keywords | Integer | Keyword limit |
| max_clusters | Integer | Cluster limit |
| max_content_ideas | Integer | Monthly idea limit |
| max_content_words | Integer | Monthly word limit |
| max_images_basic | Integer | Monthly basic image limit |
| max_images_premium | Integer | Monthly premium image limit |
| is_active | Boolean | Available for purchase |
| is_internal | Boolean | Internal/test plan |
### Site
| Field | Type | Purpose |
|-------|------|---------|
| account | ForeignKey | Owner account |
| name | CharField | Site name |
| domain | CharField | Primary domain |
| is_active | Boolean | Enable/disable |
### Sector
| Field | Type | Purpose |
|-------|------|---------|
| site | ForeignKey | Parent site |
| account | ForeignKey | Owner account |
| industry | ForeignKey | Industry template |
| name | CharField | Sector name |
| is_active | Boolean | Enable/disable |
---
## Base Model Classes
### AccountBaseModel
**File:** `auth/models.py`
All account-scoped models inherit from this:
```python
class AccountBaseModel(models.Model):
account = models.ForeignKey(Account, on_delete=models.CASCADE)
class Meta:
abstract = True
```
**Used by:** Billing records, Settings, API keys
### SiteSectorBaseModel
**File:** `auth/models.py`
All content models inherit from this:
```python
class SiteSectorBaseModel(AccountBaseModel):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
sector = models.ForeignKey(Sector, on_delete=models.CASCADE)
class Meta:
abstract = True
```
**Used by:** Keywords, Clusters, Ideas, Tasks, Content, Images
---
## ViewSet Base Classes
### AccountModelViewSet
**File:** `api/base.py`
Automatically filters queryset by account:
```python
class AccountModelViewSet(ModelViewSet):
def get_queryset(self):
qs = super().get_queryset()
if not self.request.user.is_admin_or_developer:
qs = qs.filter(account=self.request.account)
return qs
```
### SiteSectorModelViewSet
**File:** `api/base.py`
Filters by account + site + sector:
```python
class SiteSectorModelViewSet(AccountModelViewSet):
def get_queryset(self):
qs = super().get_queryset()
site_id = self.request.query_params.get('site_id')
sector_id = self.request.query_params.get('sector_id')
if site_id:
qs = qs.filter(site_id=site_id)
if sector_id:
qs = qs.filter(sector_id=sector_id)
return qs
```
---
## Where Tenancy Applies
### Uses Site/Sector Filtering
| Module | Filter |
|--------|--------|
| Planner (Keywords, Clusters, Ideas) | site + sector |
| Writer (Tasks, Content, Images) | site + sector |
| Linker | site + sector |
| Optimizer | site + sector |
| Setup/Add Keywords | site + sector |
### Account-Level Only (No Site/Sector)
| Module | Filter |
|--------|--------|
| Billing/Plans | account only |
| Account Settings | account only |
| Team Management | account only |
| User Profile | user only |
| System Settings | account only |
---
## Frontend Implementation
### Site Selection
**Store:** `store/siteStore.ts`
```typescript
const useSiteStore = create({
activeSite: Site | null,
sites: Site[],
loadSites: () => Promise<void>,
setActiveSite: (site: Site) => void,
});
```
### Sector Selection
**Store:** `store/sectorStore.ts`
```typescript
const useSectorStore = create({
activeSector: Sector | null,
sectors: Sector[],
loadSectorsForSite: (siteId: number) => Promise<void>,
setActiveSector: (sector: Sector) => void,
});
```
### Sector Loading Pattern
Sectors are loaded by `PageHeader` component, not `AppLayout`:
```typescript
// PageHeader.tsx
useEffect(() => {
if (hideSiteSector) return; // Skip for account pages
if (activeSite?.id && activeSite?.is_active) {
loadSectorsForSite(activeSite.id);
}
}, [activeSite?.id, hideSiteSector]);
```
**Pages with `hideSiteSector={true}`:**
- `/account/*` (settings, team, billing)
- User profile pages
---
## Global Resources (No Tenancy)
These are system-wide, not tenant-specific:
| Model | Purpose |
|-------|---------|
| Industry | Global industry taxonomy |
| IndustrySector | Sub-categories within industries |
| SeedKeyword | Global keyword database |
| GlobalIntegrationSettings | Platform API keys |
| GlobalAIPrompt | Default prompt templates |
| GlobalAuthorProfile | Author persona templates |
---
## Tenant Data vs System Data
### System Data (KEEP on reset)
- Plans, CreditPackages
- Industries, IndustrySectors
- GlobalIntegrationSettings
- GlobalAIPrompt, GlobalAuthorProfile
- CreditCostConfig
- PaymentMethodConfig
### Tenant Data (CLEAN on reset)
- Accounts, Users, Sites, Sectors
- Keywords, Clusters, Ideas, Tasks, Content, Images
- Subscriptions, Invoices, Payments
- CreditTransactions, CreditUsageLogs
- SiteIntegrations, SyncEvents
- AutomationConfigs, AutomationRuns
---
## Admin/Developer Bypass
Admin and developer users can access all tenants:
```python
# In auth/models.py
class User:
@property
def is_admin_or_developer(self):
return self.role in ['admin', 'developer']
```
**Bypass Rules:**
- Admin: Full access to own account
- Developer: Full access to ALL accounts
- System accounts protected from deletion
---
## Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Data leak between accounts | Missing account filter | Use AccountModelViewSet |
| Wrong site data | Site not validated for account | Validate site.account == request.account |
| Sector not loading | Site changed but sector not reset | Clear activeSector when activeSite changes |
| Admin can't see all data | Incorrect role check | Check is_admin_or_developer |
---
## Planned Changes
| Feature | Status | Description |
|---------|--------|-------------|
| Account switching | 🔜 Planned | Allow users to switch between accounts |
| Sub-accounts | 🔜 Planned | Hierarchical account structure |