docs re-org

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-09 13:26:35 +00:00
parent 4d13a57068
commit 6a4f95c35a
231 changed files with 11353 additions and 31152 deletions

View File

@@ -1,373 +0,0 @@
#!/usr/bin/env python3
"""
Payment Workflow API Integration Examples
Demonstrates how to interact with the payment APIs programmatically
"""
import requests
import json
from decimal import Decimal
# Base URL for the API
BASE_URL = "http://localhost:8011/api/v1"
class PaymentAPIClient:
"""Example API client for payment workflow"""
def __init__(self, base_url=BASE_URL):
self.base_url = base_url
self.token = None
self.session = requests.Session()
def register_free_trial(self, email, password, first_name, last_name):
"""Register a new free trial user"""
url = f"{self.base_url}/auth/register/"
data = {
"email": email,
"password": password,
"password_confirm": password,
"first_name": first_name,
"last_name": last_name
}
response = self.session.post(url, json=data)
response.raise_for_status()
result = response.json()
print(f"✓ Free trial account created: {result['data']['account']['name']}")
print(f" Status: {result['data']['account']['status']}")
print(f" Credits: {result['data']['account']['credits']}")
return result['data']
def register_paid_user(self, email, password, first_name, last_name,
plan_slug, billing_info):
"""Register a new paid user with billing information"""
url = f"{self.base_url}/auth/register/"
data = {
"email": email,
"password": password,
"password_confirm": password,
"first_name": first_name,
"last_name": last_name,
"plan_slug": plan_slug,
**billing_info
}
response = self.session.post(url, json=data)
response.raise_for_status()
result = response.json()
print(f"✓ Paid account created: {result['data']['account']['name']}")
print(f" Status: {result['data']['account']['status']}")
print(f" Credits: {result['data']['account']['credits']}")
if 'invoice' in result['data']:
inv = result['data']['invoice']
print(f" Invoice: {inv['invoice_number']} - ${inv['total']}")
return result['data']
def login(self, email, password):
"""Login and get authentication token"""
url = f"{self.base_url}/auth/login/"
data = {
"email": email,
"password": password
}
response = self.session.post(url, json=data)
response.raise_for_status()
result = response.json()
self.token = result['data']['token']
self.session.headers.update({
'Authorization': f'Bearer {self.token}'
})
print(f"✓ Logged in as: {email}")
return result['data']
def get_payment_methods(self, country_code=None):
"""Get available payment methods for a country"""
url = f"{self.base_url}/billing/admin/payment-methods/"
params = {}
if country_code:
params['country'] = country_code
response = self.session.get(url, params=params)
response.raise_for_status()
methods = response.json()
print(f"✓ Payment methods available: {len(methods)}")
for method in methods:
print(f" - {method['display_name']} ({method['payment_method']})")
return methods
def confirm_payment(self, invoice_id, payment_method, amount,
manual_reference, manual_notes=""):
"""Submit payment confirmation for manual payments"""
url = f"{self.base_url}/billing/admin/payments/confirm/"
data = {
"invoice_id": invoice_id,
"payment_method": payment_method,
"amount": str(amount),
"manual_reference": manual_reference,
"manual_notes": manual_notes
}
response = self.session.post(url, json=data)
response.raise_for_status()
result = response.json()
payment = result['data']
print(f"✓ Payment confirmation submitted")
print(f" Payment ID: {payment['payment_id']}")
print(f" Invoice: {payment['invoice_number']}")
print(f" Status: {payment['status']}")
print(f" Reference: {payment['manual_reference']}")
return result['data']
def approve_payment(self, payment_id, admin_notes=""):
"""Approve a pending payment (admin only)"""
url = f"{self.base_url}/billing/admin/payments/{payment_id}/approve/"
data = {
"admin_notes": admin_notes
}
response = self.session.post(url, json=data)
response.raise_for_status()
result = response.json()
payment = result['data']
print(f"✓ Payment approved")
print(f" Account Status: {payment['account_status']}")
print(f" Subscription Status: {payment['subscription_status']}")
print(f" Credits Added: {payment['credits_added']}")
print(f" Total Credits: {payment['total_credits']}")
return result['data']
def reject_payment(self, payment_id, admin_notes):
"""Reject a pending payment (admin only)"""
url = f"{self.base_url}/billing/admin/payments/{payment_id}/reject/"
data = {
"admin_notes": admin_notes
}
response = self.session.post(url, json=data)
response.raise_for_status()
result = response.json()
payment = result['data']
print(f"✓ Payment rejected")
print(f" Status: {payment['status']}")
print(f" Reason: {admin_notes}")
return result['data']
def example_free_trial_workflow():
"""Example: Free trial signup workflow"""
print("\n" + "="*60)
print("EXAMPLE 1: FREE TRIAL SIGNUP")
print("="*60 + "\n")
client = PaymentAPIClient()
# Step 1: Register free trial user
user_data = client.register_free_trial(
email="freetrial_demo@example.com",
password="SecurePass123!",
first_name="Free",
last_name="Trial"
)
# Step 2: Login
login_data = client.login(
email="freetrial_demo@example.com",
password="SecurePass123!"
)
print(f"\n✓ Free trial workflow complete!")
print(f" User can now create {user_data['account']['max_sites']} site(s)")
print(f" Available credits: {user_data['account']['credits']}")
def example_paid_signup_workflow():
"""Example: Paid signup with manual payment approval"""
print("\n" + "="*60)
print("EXAMPLE 2: PAID SIGNUP WITH MANUAL PAYMENT")
print("="*60 + "\n")
client = PaymentAPIClient()
# Step 1: Check available payment methods
print("Step 1: Check Payment Methods for Pakistan")
methods = client.get_payment_methods(country_code="PK")
# Step 2: Register with paid plan
print("\nStep 2: Register Paid User")
billing_info = {
"billing_email": "billing@example.com",
"billing_address_line1": "123 Main Street",
"billing_city": "Karachi",
"billing_country": "PK",
"payment_method": "bank_transfer"
}
user_data = client.register_paid_user(
email="paiduser_demo@example.com",
password="SecurePass123!",
first_name="Paid",
last_name="User",
plan_slug="starter",
billing_info=billing_info
)
# Step 3: Login
print("\nStep 3: User Login")
login_data = client.login(
email="paiduser_demo@example.com",
password="SecurePass123!"
)
# Step 4: User makes external payment and submits confirmation
print("\nStep 4: Submit Payment Confirmation")
invoice_id = user_data['invoice']['id']
invoice_total = user_data['invoice']['total']
payment_data = client.confirm_payment(
invoice_id=invoice_id,
payment_method="bank_transfer",
amount=invoice_total,
manual_reference="DEMO-BANK-2025-001",
manual_notes="Transferred via ABC Bank on Dec 8, 2025"
)
print(f"\n✓ Payment submitted! Waiting for admin approval...")
print(f" Payment ID: {payment_data['payment_id']}")
print(f" Account remains in 'pending_payment' status")
# Step 5: Admin approves (requires admin token)
print("\nStep 5: Admin Approval (requires admin credentials)")
print(" → Admin would login separately and approve the payment")
print(f" → POST /billing/admin/payments/{payment_data['payment_id']}/approve/")
print(" → Account status changes to 'active'")
print(" → Credits allocated: 1000")
return payment_data
def example_admin_approval():
"""Example: Admin approving a payment"""
print("\n" + "="*60)
print("EXAMPLE 3: ADMIN PAYMENT APPROVAL")
print("="*60 + "\n")
# This requires admin credentials
admin_client = PaymentAPIClient()
print("Step 1: Admin Login")
try:
admin_client.login(
email="dev@igny8.com", # Replace with actual admin email
password="admin_password" # Replace with actual password
)
print("\nStep 2: Approve Payment")
# Replace with actual payment ID
payment_id = 5 # Example payment ID
result = admin_client.approve_payment(
payment_id=payment_id,
admin_notes="Verified payment in bank statement. Reference matches."
)
print(f"\n✓ Payment approval complete!")
print(f" Account activated with {result['total_credits']} credits")
except requests.exceptions.HTTPError as e:
print(f"✗ Admin approval failed: {e}")
print(" (This is expected if you don't have admin credentials)")
def example_payment_rejection():
"""Example: Admin rejecting a payment"""
print("\n" + "="*60)
print("EXAMPLE 4: ADMIN PAYMENT REJECTION")
print("="*60 + "\n")
admin_client = PaymentAPIClient()
print("Step 1: Admin Login")
try:
admin_client.login(
email="dev@igny8.com",
password="admin_password"
)
print("\nStep 2: Reject Payment")
payment_id = 7 # Example payment ID
result = admin_client.reject_payment(
payment_id=payment_id,
admin_notes="Reference number not found in bank statement. Please verify and resubmit."
)
print(f"\n✓ Payment rejected!")
print(f" User can resubmit with correct reference")
except requests.exceptions.HTTPError as e:
print(f"✗ Payment rejection failed: {e}")
print(" (This is expected if you don't have admin credentials)")
def main():
"""Run all examples"""
print("\n" + "="*60)
print("PAYMENT WORKFLOW API INTEGRATION EXAMPLES")
print("="*60)
print("\nThese examples demonstrate how to integrate with the")
print("multi-tenancy payment workflow APIs.\n")
try:
# Example 1: Free trial
example_free_trial_workflow()
# Example 2: Paid signup
# example_paid_signup_workflow()
# Example 3: Admin approval (requires admin credentials)
# example_admin_approval()
# Example 4: Payment rejection (requires admin credentials)
# example_payment_rejection()
except requests.exceptions.RequestException as e:
print(f"\n✗ API Error: {e}")
print("\nMake sure the backend is running on http://localhost:8011")
except Exception as e:
print(f"\n✗ Error: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
# Note: Uncomment examples you want to run
# Some examples may create actual data in the database
print("\n" + "="*60)
print("API INTEGRATION EXAMPLES - READ ONLY MODE")
print("="*60)
print("\nTo run examples, uncomment the desired function calls")
print("in the main() function.\n")
print("Available examples:")
print(" 1. example_free_trial_workflow()")
print(" 2. example_paid_signup_workflow()")
print(" 3. example_admin_approval()")
print(" 4. example_payment_rejection()")
print("\nWarning: Running these will create data in the database!")
print("="*60 + "\n")

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python
import os
import django
import json
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.integration.models import SiteIntegration
from igny8_core.auth.models import Site
from django.test import RequestFactory
from igny8_core.modules.integration.views import IntegrationViewSet
# Create a fake request
factory = RequestFactory()
request = factory.get('/api/v1/integration/integrations/1/content-types/')
# Create view and call the action
integration = SiteIntegration.objects.get(id=1)
viewset = IntegrationViewSet()
viewset.format_kwarg = None
viewset.request = request
viewset.kwargs = {'pk': 1}
# Get the response data
response = viewset.content_types_summary(request, pk=1)
print("Response Status:", response.status_code)
print("\nResponse Data:")
print(json.dumps(response.data, indent=2, default=str))

View File

@@ -1,149 +0,0 @@
#!/usr/bin/env python
"""
Script to check current database state for tenancy system
DO NOT MAKE ANY CHANGES - READ ONLY
"""
import os
import sys
import django
# Set up Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
sys.path.insert(0, os.path.dirname(__file__))
django.setup()
from igny8_core.auth.models import Plan, Account, User, Site, Sector, Subscription
from igny8_core.business.billing.models import CreditTransaction
print("=" * 80)
print("CURRENT DATABASE STATE ANALYSIS (READ-ONLY)")
print("=" * 80)
# Check Plans
print("\n=== EXISTING PLANS ===")
plans = Plan.objects.all()
if plans.exists():
for p in plans:
print(f"{p.id}. [{p.slug}] {p.name}")
print(f" Price: ${p.price}/{p.billing_cycle}")
print(f" Credits: {p.included_credits} (legacy: {p.credits_per_month})")
print(f" Max Sites: {p.max_sites}, Max Users: {p.max_users}, Max Industries: {p.max_industries}")
print(f" Active: {p.is_active}")
print(f" Features: {p.features}")
print()
else:
print("No plans found in database")
print(f"Total plans: {plans.count()}\n")
# Check Accounts
print("=== EXISTING ACCOUNTS ===")
accounts = Account.objects.select_related('plan', 'owner').all()[:10]
if accounts.exists():
for acc in accounts:
print(f"{acc.id}. [{acc.slug}] {acc.name}")
print(f" Owner: {acc.owner.email if acc.owner else 'None'}")
print(f" Plan: {acc.plan.slug if acc.plan else 'None'}")
print(f" Credits: {acc.credits}")
print(f" Status: {acc.status}")
print(f" Has payment_method field: {hasattr(acc, 'payment_method')}")
try:
print(f" Payment method: {acc.payment_method if hasattr(acc, 'payment_method') else 'Field does not exist'}")
except:
print(f" Payment method: Field does not exist in DB")
print()
else:
print("No accounts found in database")
print(f"Total accounts: {Account.objects.count()}\n")
# Check Users
print("=== USER ROLES ===")
users = User.objects.select_related('account').all()[:10]
if users.exists():
for u in users:
print(f"{u.id}. {u.email} - Role: {u.role}")
print(f" Account: {u.account.slug if u.account else 'None'}")
print(f" Is superuser: {u.is_superuser}")
print()
else:
print("No users found in database")
print(f"Total users: {User.objects.count()}\n")
# Check Sites
print("=== SITES AND ACCOUNT RELATIONSHIP ===")
sites = Site.objects.select_related('account', 'industry').all()[:10]
if sites.exists():
for site in sites:
print(f"{site.id}. [{site.slug}] {site.name}")
print(f" Account: {site.account.slug if site.account else 'None'}")
print(f" Industry: {site.industry.name if site.industry else 'None'}")
print(f" Active: {site.is_active}, Status: {site.status}")
print(f" Sectors: {site.sectors.filter(is_active=True).count()}")
print()
else:
print("No sites found in database")
print(f"Total sites: {Site.objects.count()}\n")
# Check Subscriptions
print("=== SUBSCRIPTIONS ===")
subscriptions = Subscription.objects.select_related('account').all()
if subscriptions.exists():
for sub in subscriptions:
print(f"{sub.id}. Account: {sub.account.slug}")
print(f" Stripe ID: {sub.stripe_subscription_id}")
print(f" Status: {sub.status}")
print(f" Period: {sub.current_period_start} to {sub.current_period_end}")
print(f" Has payment_method field: {hasattr(sub, 'payment_method')}")
try:
print(f" Payment method: {sub.payment_method if hasattr(sub, 'payment_method') else 'Field does not exist'}")
print(f" External payment ID: {sub.external_payment_id if hasattr(sub, 'external_payment_id') else 'Field does not exist'}")
except:
print(f" Payment method fields: Do not exist in DB")
print()
else:
print("No subscriptions found in database")
print(f"Total subscriptions: {Subscription.objects.count()}\n")
# Check Credit Transactions
print("=== CREDIT TRANSACTIONS (Sample) ===")
transactions = CreditTransaction.objects.select_related('account').order_by('-created_at')[:5]
if transactions.exists():
for tx in transactions:
print(f"{tx.id}. Account: {tx.account.slug}")
print(f" Type: {tx.transaction_type}, Amount: {tx.amount}")
print(f" Balance after: {tx.balance_after}")
print(f" Description: {tx.description}")
print(f" Created: {tx.created_at}")
print()
else:
print("No credit transactions found")
print(f"Total credit transactions: {CreditTransaction.objects.count()}\n")
# Model Field Analysis
print("=== MODEL FIELD ANALYSIS ===")
print("\nAccount Model Fields:")
for field in Account._meta.get_fields():
if not field.many_to_many and not field.one_to_many:
print(f" - {field.name}: {field.get_internal_type()}")
print("\nSubscription Model Fields:")
for field in Subscription._meta.get_fields():
if not field.many_to_many and not field.one_to_many:
print(f" - {field.name}: {field.get_internal_type()}")
print("\nSite Model Fields:")
for field in Site._meta.get_fields():
if not field.many_to_many and not field.one_to_many:
field_name = field.name
field_type = field.get_internal_type()
if field_name in ['account', 'industry']:
print(f" - {field_name}: {field_type} (RELATIONSHIP)")
print("\n" + "=" * 80)
print("END OF ANALYSIS")
print("=" * 80)

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env python
"""Check recent keyword creation"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.planning.models import Keywords
from django.utils import timezone
from datetime import timedelta
recent = timezone.now() - timedelta(hours=24)
recent_keywords = Keywords.objects.filter(created_at__gte=recent)
print(f'Keywords created in last 24 hours: {recent_keywords.count()}')
if recent_keywords.exists():
print('\nRecent keyword statuses:')
for k in recent_keywords[:10]:
print(f' ID {k.id}: status={k.status}, created={k.created_at}')

View File

@@ -1,38 +0,0 @@
#!/usr/bin/env python
"""
Clean up structure-based categories that were incorrectly created
This will remove categories like "Guide", "Article", etc. that match content_structure values
"""
import os
import sys
import django
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.db import transaction
from igny8_core.business.content.models import ContentTaxonomy
# List of structure values that were incorrectly added as categories
STRUCTURE_VALUES = ['Guide', 'Article', 'Listicle', 'How To', 'Tutorial', 'Review', 'Comparison']
print("=" * 80)
print("CLEANING UP STRUCTURE-BASED CATEGORIES")
print("=" * 80)
for structure_name in STRUCTURE_VALUES:
categories = ContentTaxonomy.objects.filter(
taxonomy_type='category',
name=structure_name
)
if categories.exists():
count = categories.count()
print(f"\nRemoving {count} '{structure_name}' categor{'y' if count == 1 else 'ies'}...")
categories.delete()
print(f" ✓ Deleted {count} '{structure_name}' categor{'y' if count == 1 else 'ies'}")
print("\n" + "=" * 80)
print("CLEANUP COMPLETE")
print("=" * 80)

View File

@@ -1,185 +0,0 @@
#!/usr/bin/env python
"""
Create API test data for billing endpoints
All test records are marked with 'API_TEST' in name/description/notes
"""
import os
import sys
import django
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.utils import timezone
from django.contrib.auth import get_user_model
from igny8_core.auth.models import Account, Plan
from igny8_core.business.billing.models import (
Invoice, Payment, CreditTransaction, AccountPaymentMethod, PaymentMethodConfig
)
from decimal import Decimal
from datetime import timedelta
User = get_user_model()
print("Creating API test data...")
# Get or create test account
try:
account = Account.objects.get(name__icontains='scale')
print(f"✓ Using existing account: {account.name} (ID: {account.id})")
except Account.DoesNotExist:
# Get a plan
plan = Plan.objects.filter(is_active=True).first()
account = Account.objects.create(
name='API_TEST_ACCOUNT',
slug='api-test-account',
plan=plan,
credits=5000,
status='active'
)
print(f"✓ Created test account: {account.name} (ID: {account.id})")
# Create test invoices
invoice1, created = Invoice.objects.get_or_create(
account=account,
invoice_number='INV-API-TEST-001',
defaults={
'status': 'pending',
'subtotal': Decimal('99.99'),
'tax': Decimal('0.00'),
'total': Decimal('99.99'),
'currency': 'USD',
'invoice_date': timezone.now().date(),
'due_date': (timezone.now() + timedelta(days=30)).date(),
'billing_email': 'test@igny8.com',
'notes': 'API_TEST: Invoice for approval test',
'line_items': [{'description': 'API Test Service', 'amount': 99.99, 'quantity': 1}],
}
)
if created:
print(f"✓ Created test invoice 1 (ID: {invoice1.id})")
else:
print(f"✓ Existing test invoice 1 (ID: {invoice1.id})")
invoice2, created = Invoice.objects.get_or_create(
account=account,
invoice_number='INV-API-TEST-002',
defaults={
'status': 'pending',
'subtotal': Decimal('49.99'),
'tax': Decimal('0.00'),
'total': Decimal('49.99'),
'currency': 'USD',
'invoice_date': timezone.now().date(),
'due_date': (timezone.now() + timedelta(days=30)).date(),
'billing_email': 'test@igny8.com',
'notes': 'API_TEST: Invoice for rejection test',
'line_items': [{'description': 'API Test Service', 'amount': 49.99, 'quantity': 1}],
}
)
if created:
print(f"✓ Created test invoice 2 (ID: {invoice2.id})")
else:
print(f"✓ Existing test invoice 2 (ID: {invoice2.id})")
# Create test payment for approval
pending_payment, created = Payment.objects.get_or_create(
account=account,
invoice=invoice1,
manual_reference='API_TEST_REF_001',
defaults={
'status': 'pending_approval',
'payment_method': 'bank_transfer',
'amount': Decimal('99.99'),
'currency': 'USD',
'manual_notes': 'API_TEST: Test payment for approval endpoint',
}
)
if created:
print(f"✓ Created pending payment (ID: {pending_payment.id}) for approve_payment endpoint")
else:
print(f"✓ Existing pending payment (ID: {pending_payment.id})")
# Create test payment for rejection
reject_payment, created = Payment.objects.get_or_create(
account=account,
invoice=invoice2,
manual_reference='API_TEST_REF_002',
defaults={
'status': 'pending_approval',
'payment_method': 'manual',
'amount': Decimal('49.99'),
'currency': 'USD',
'manual_notes': 'API_TEST: Test payment for rejection endpoint',
}
)
if created:
print(f"✓ Created pending payment (ID: {reject_payment.id}) for reject_payment endpoint")
else:
print(f"✓ Existing pending payment (ID: {reject_payment.id})")
# Get or create test payment method config
configs = PaymentMethodConfig.objects.filter(payment_method='bank_transfer')
if configs.exists():
config = configs.first()
print(f"✓ Using existing payment method config (ID: {config.id})")
created = False
else:
config = PaymentMethodConfig.objects.create(
payment_method='bank_transfer',
display_name='API_TEST Bank Transfer',
instructions='API_TEST: Transfer to account 123456789',
is_enabled=True,
sort_order=1,
)
print(f"✓ Created payment method config (ID: {config.id})")
created = True
# Create test account payment method
account_method, created = AccountPaymentMethod.objects.get_or_create(
account=account,
type='bank_transfer',
defaults={
'display_name': 'API_TEST Account Bank Transfer',
'instructions': 'API_TEST: Test account-specific payment method',
'is_default': True,
}
)
if created:
print(f"✓ Created account payment method (ID: {account_method.id})")
else:
print(f"✓ Existing account payment method (ID: {account_method.id})")
# Create test credit transaction
transaction, created = CreditTransaction.objects.get_or_create(
account=account,
transaction_type='adjustment',
amount=1000,
defaults={
'balance_after': account.credits,
'description': 'API_TEST: Test credit adjustment',
'metadata': {'test': True, 'reason': 'API testing'},
}
)
if created:
print(f"✓ Created credit transaction (ID: {transaction.id})")
else:
print(f"✓ Existing credit transaction (ID: {transaction.id})")
print("\n" + "="*60)
print("API Test Data Summary:")
print("="*60)
print(f"Account ID: {account.id}")
print(f"Pending Payment (approve): ID {pending_payment.id}")
print(f"Pending Payment (reject): ID {reject_payment.id}")
print(f"Payment Method Config: ID {config.id}")
print(f"Account Payment Method: ID {account_method.id}")
print(f"Credit Transaction: ID {transaction.id}")
print("="*60)
print("\nTest endpoints:")
print(f"POST /v1/admin/billing/{pending_payment.id}/approve_payment/")
print(f"POST /v1/admin/billing/{reject_payment.id}/reject_payment/")
print(f"POST /v1/admin/users/{account.id}/adjust-credits/")
print(f"GET /v1/billing/payment-methods/{account_method.id}/set_default/")
print("="*60)

View File

@@ -1,116 +0,0 @@
#!/bin/bash
# Automation System Deployment Script
# Run this script to complete the automation system deployment
set -e # Exit on error
echo "========================================="
echo "IGNY8 Automation System Deployment"
echo "========================================="
echo ""
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Check if running from correct directory
if [ ! -f "manage.py" ]; then
echo -e "${RED}Error: Please run this script from the backend directory${NC}"
echo "cd /data/app/igny8/backend && ./deploy_automation.sh"
exit 1
fi
echo -e "${YELLOW}Step 1: Creating log directory...${NC}"
mkdir -p logs/automation
chmod 755 logs/automation
echo -e "${GREEN}✓ Log directory created${NC}"
echo ""
echo -e "${YELLOW}Step 2: Running database migrations...${NC}"
python3 manage.py makemigrations
python3 manage.py migrate
echo -e "${GREEN}✓ Migrations complete${NC}"
echo ""
echo -e "${YELLOW}Step 3: Checking Celery services...${NC}"
if docker ps | grep -q celery; then
echo -e "${GREEN}✓ Celery worker is running${NC}"
else
echo -e "${RED}⚠ Celery worker is NOT running${NC}"
echo "Start with: docker-compose up -d celery"
fi
if docker ps | grep -q beat; then
echo -e "${GREEN}✓ Celery beat is running${NC}"
else
echo -e "${RED}⚠ Celery beat is NOT running${NC}"
echo "Start with: docker-compose up -d celery-beat"
fi
echo ""
echo -e "${YELLOW}Step 4: Verifying cache backend...${NC}"
python3 -c "
from django.core.cache import cache
try:
cache.set('test_key', 'test_value', 10)
if cache.get('test_key') == 'test_value':
print('${GREEN}✓ Cache backend working${NC}')
else:
print('${RED}⚠ Cache backend not working properly${NC}')
except Exception as e:
print('${RED}⚠ Cache backend error:', str(e), '${NC}')
" || echo -e "${RED}⚠ Could not verify cache backend${NC}"
echo ""
echo -e "${YELLOW}Step 5: Testing automation API...${NC}"
python3 manage.py shell << EOF
from igny8_core.business.automation.services import AutomationService
from igny8_core.modules.system.models import Account, Site
try:
account = Account.objects.first()
site = Site.objects.first()
if account and site:
service = AutomationService(account, site)
estimate = service.estimate_credits()
print('${GREEN}✓ AutomationService working - Estimated credits:', estimate, '${NC}')
else:
print('${YELLOW}⚠ No account or site found - create one first${NC}')
except Exception as e:
print('${RED}⚠ AutomationService error:', str(e), '${NC}')
EOF
echo ""
echo -e "${YELLOW}Step 6: Checking Celery beat schedule...${NC}"
if docker ps | grep -q celery; then
CELERY_CONTAINER=$(docker ps | grep celery | grep -v beat | awk '{print $1}')
docker exec $CELERY_CONTAINER celery -A igny8_core inspect scheduled 2>/dev/null | grep -q "check-scheduled-automations" && \
echo -e "${GREEN}✓ Automation task scheduled in Celery beat${NC}" || \
echo -e "${YELLOW}⚠ Automation task not found in schedule (may need restart)${NC}"
else
echo -e "${YELLOW}⚠ Celery worker not running - cannot check schedule${NC}"
fi
echo ""
echo "========================================="
echo -e "${GREEN}Deployment Steps Completed!${NC}"
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Restart Celery services to pick up new tasks:"
echo " docker-compose restart celery celery-beat"
echo ""
echo "2. Access the frontend at /automation page"
echo ""
echo "3. Test the automation:"
echo " - Click [Configure] to set up schedule"
echo " - Click [Run Now] to start automation"
echo " - Monitor progress in real-time"
echo ""
echo "4. Check logs:"
echo " tail -f logs/automation/{account_id}/{site_id}/{run_id}/automation_run.log"
echo ""
echo -e "${YELLOW}For troubleshooting, see: AUTOMATION-DEPLOYMENT-CHECKLIST.md${NC}"

View File

@@ -1,393 +0,0 @@
#!/usr/bin/env python
"""
Diagnostic script for generate_content function issues
Tests each layer of the content generation pipeline to identify where it's failing
"""
import os
import sys
import django
import logging
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.auth.models import Account
from igny8_core.modules.writer.models import Tasks, Content
from igny8_core.modules.system.models import IntegrationSettings
from igny8_core.ai.registry import get_function_instance
from igny8_core.ai.engine import AIEngine
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
logger = logging.getLogger(__name__)
def print_section(title):
"""Print a section header"""
print("\n" + "=" * 80)
print(f" {title}")
print("=" * 80 + "\n")
def test_prerequisites():
"""Test that prerequisites are met"""
print_section("1. TESTING PREREQUISITES")
# Check if account exists
try:
account = Account.objects.first()
if not account:
print("❌ FAIL: No account found in database")
return None
print(f"✅ PASS: Found account: {account.id} ({account.email})")
except Exception as e:
print(f"❌ FAIL: Error getting account: {e}")
return None
# Check OpenAI integration settings
try:
openai_settings = IntegrationSettings.objects.filter(
integration_type='openai',
account=account,
is_active=True
).first()
if not openai_settings:
print("❌ FAIL: No active OpenAI integration settings found")
return None
if not openai_settings.config or not openai_settings.config.get('apiKey'):
print("❌ FAIL: OpenAI API key not configured in IntegrationSettings")
return None
api_key_preview = openai_settings.config['apiKey'][:10] + "..." if openai_settings.config.get('apiKey') else "None"
model = openai_settings.config.get('model', 'Not set')
print(f"✅ PASS: OpenAI settings found (API key: {api_key_preview}, Model: {model})")
except Exception as e:
print(f"❌ FAIL: Error checking OpenAI settings: {e}")
return None
# Check if tasks exist
try:
tasks = Tasks.objects.filter(account=account, status='pending')[:5]
task_count = tasks.count()
if task_count == 0:
print("⚠️ WARNING: No pending tasks found, will try to use any task")
tasks = Tasks.objects.filter(account=account)[:5]
task_count = tasks.count()
if task_count == 0:
print("❌ FAIL: No tasks found at all")
return None
print(f"✅ PASS: Found {task_count} task(s)")
for task in tasks:
print(f" - Task {task.id}: {task.title or 'Untitled'} (status: {task.status})")
except Exception as e:
print(f"❌ FAIL: Error getting tasks: {e}")
return None
return {
'account': account,
'tasks': list(tasks),
'openai_settings': openai_settings
}
def test_function_registry():
"""Test that the generate_content function is registered"""
print_section("2. TESTING FUNCTION REGISTRY")
try:
fn = get_function_instance('generate_content')
if not fn:
print("❌ FAIL: generate_content function not found in registry")
return False
print(f"✅ PASS: Function registered: {fn.get_name()}")
metadata = fn.get_metadata()
print(f" - Display name: {metadata.get('display_name')}")
print(f" - Description: {metadata.get('description')}")
return True
except Exception as e:
print(f"❌ FAIL: Error loading function: {e}")
import traceback
traceback.print_exc()
return False
def test_function_validation(context):
"""Test function validation"""
print_section("3. TESTING FUNCTION VALIDATION")
try:
fn = get_function_instance('generate_content')
account = context['account']
task = context['tasks'][0]
payload = {'ids': [task.id]}
print(f"Testing with payload: {payload}")
result = fn.validate(payload, account)
if result['valid']:
print(f"✅ PASS: Validation succeeded")
else:
print(f"❌ FAIL: Validation failed: {result.get('error')}")
return False
return True
except Exception as e:
print(f"❌ FAIL: Error during validation: {e}")
import traceback
traceback.print_exc()
return False
def test_function_prepare(context):
"""Test function prepare phase"""
print_section("4. TESTING FUNCTION PREPARE")
try:
fn = get_function_instance('generate_content')
account = context['account']
task = context['tasks'][0]
payload = {'ids': [task.id]}
print(f"Preparing task {task.id}: {task.title or 'Untitled'}")
data = fn.prepare(payload, account)
if not data:
print("❌ FAIL: Prepare returned no data")
return False
if isinstance(data, list):
print(f"✅ PASS: Prepared {len(data)} task(s)")
for t in data:
print(f" - Task {t.id}: {t.title or 'Untitled'}")
print(f" Cluster: {t.cluster.name if t.cluster else 'None'}")
print(f" Taxonomy: {t.taxonomy_term.name if t.taxonomy_term else 'None'}")
print(f" Keywords: {t.keywords.count()} keyword(s)")
else:
print(f"✅ PASS: Prepared data: {type(data)}")
context['prepared_data'] = data
return True
except Exception as e:
print(f"❌ FAIL: Error during prepare: {e}")
import traceback
traceback.print_exc()
return False
def test_function_build_prompt(context):
"""Test prompt building"""
print_section("5. TESTING PROMPT BUILDING")
try:
fn = get_function_instance('generate_content')
account = context['account']
data = context['prepared_data']
prompt = fn.build_prompt(data, account)
if not prompt:
print("❌ FAIL: No prompt generated")
return False
print(f"✅ PASS: Prompt generated ({len(prompt)} characters)")
print("\nPrompt preview (first 500 chars):")
print("-" * 80)
print(prompt[:500])
if len(prompt) > 500:
print(f"\n... ({len(prompt) - 500} more characters)")
print("-" * 80)
context['prompt'] = prompt
return True
except Exception as e:
print(f"❌ FAIL: Error building prompt: {e}")
import traceback
traceback.print_exc()
return False
def test_model_config(context):
"""Test model configuration"""
print_section("6. TESTING MODEL CONFIGURATION")
try:
from igny8_core.ai.settings import get_model_config
account = context['account']
model_config = get_model_config('generate_content', account=account)
if not model_config:
print("❌ FAIL: No model config returned")
return False
print(f"✅ PASS: Model configuration loaded")
print(f" - Model: {model_config.get('model')}")
print(f" - Max tokens: {model_config.get('max_tokens')}")
print(f" - Temperature: {model_config.get('temperature')}")
print(f" - Response format: {model_config.get('response_format')}")
context['model_config'] = model_config
return True
except Exception as e:
print(f"❌ FAIL: Error getting model config: {e}")
import traceback
traceback.print_exc()
return False
def test_ai_core_request(context):
"""Test AI core request (actual API call)"""
print_section("7. TESTING AI CORE REQUEST (ACTUAL API CALL)")
# Ask user for confirmation
print("⚠️ WARNING: This will make an actual API call to OpenAI and cost money!")
print("Do you want to proceed? (yes/no): ", end='')
response = input().strip().lower()
if response != 'yes':
print("Skipping API call test")
return True
try:
from igny8_core.ai.ai_core import AICore
account = context['account']
prompt = context['prompt']
model_config = context['model_config']
# Use a shorter test prompt to save costs
test_prompt = prompt[:1000] + "\n\n[TEST MODE - Generate only title and first paragraph]"
print(f"Making test API call with shortened prompt ({len(test_prompt)} chars)...")
ai_core = AICore(account=account)
result = ai_core.run_ai_request(
prompt=test_prompt,
model=model_config['model'],
max_tokens=500, # Limit tokens for testing
temperature=model_config.get('temperature', 0.7),
response_format=model_config.get('response_format'),
function_name='generate_content_test'
)
if result.get('error'):
print(f"❌ FAIL: API call returned error: {result['error']}")
return False
if not result.get('content'):
print(f"❌ FAIL: API call returned no content")
return False
print(f"✅ PASS: API call successful")
print(f" - Tokens: {result.get('total_tokens', 0)}")
print(f" - Cost: ${result.get('cost', 0):.6f}")
print(f" - Model: {result.get('model')}")
print(f"\nContent preview (first 300 chars):")
print("-" * 80)
print(result['content'][:300])
print("-" * 80)
context['ai_response'] = result
return True
except Exception as e:
print(f"❌ FAIL: Error during API call: {e}")
import traceback
traceback.print_exc()
return False
def test_service_layer(context):
"""Test the content generation service"""
print_section("8. TESTING CONTENT GENERATION SERVICE")
print("⚠️ WARNING: This will make a full API call and create content!")
print("Do you want to proceed? (yes/no): ", end='')
response = input().strip().lower()
if response != 'yes':
print("Skipping service test")
return True
try:
account = context['account']
task = context['tasks'][0]
service = ContentGenerationService()
print(f"Calling generate_content with task {task.id}...")
result = service.generate_content([task.id], account)
if not result:
print("❌ FAIL: Service returned None")
return False
if not result.get('success'):
print(f"❌ FAIL: Service failed: {result.get('error')}")
return False
print(f"✅ PASS: Service call successful")
if 'task_id' in result:
print(f" - Celery task ID: {result['task_id']}")
print(f" - Message: {result.get('message')}")
print("\n⚠️ Note: Content generation is running in background (Celery)")
print(" Check Celery logs for actual execution status")
else:
print(f" - Content created: {result.get('content_id')}")
print(f" - Word count: {result.get('word_count')}")
return True
except Exception as e:
print(f"❌ FAIL: Error in service layer: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Run all diagnostic tests"""
print("\n" + "=" * 80)
print(" GENERATE_CONTENT DIAGNOSTIC TOOL")
print("=" * 80)
print("\nThis tool will test each layer of the content generation pipeline")
print("to identify where the function is failing.")
# Run tests
context = test_prerequisites()
if not context:
print("\n❌ FATAL: Prerequisites test failed. Cannot continue.")
return
if not test_function_registry():
print("\n❌ FATAL: Function registry test failed. Cannot continue.")
return
if not test_function_validation(context):
print("\n❌ FATAL: Validation test failed. Cannot continue.")
return
if not test_function_prepare(context):
print("\n❌ FATAL: Prepare test failed. Cannot continue.")
return
if not test_function_build_prompt(context):
print("\n❌ FATAL: Prompt building test failed. Cannot continue.")
return
if not test_model_config(context):
print("\n❌ FATAL: Model config test failed. Cannot continue.")
return
# Optional tests (require API calls)
test_ai_core_request(context)
test_service_layer(context)
print_section("DIAGNOSTIC COMPLETE")
print("Review the results above to identify where the generate_content")
print("function is failing.\n")
if __name__ == '__main__':
main()

View File

@@ -1,67 +0,0 @@
#!/usr/bin/env python
"""
Final verification that the WordPress content types are properly synced
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.integration.models import SiteIntegration
from igny8_core.auth.models import Site
import json
print("=" * 70)
print("WORDPRESS SYNC FIX VERIFICATION")
print("=" * 70)
# Get site 5
site = Site.objects.get(id=5)
print(f"\n✓ Site: {site.name} (ID: {site.id})")
# Get WordPress integration
integration = SiteIntegration.objects.get(site=site, platform='wordpress')
print(f"✓ Integration: {integration.platform.upper()} (ID: {integration.id})")
print(f"✓ Active: {integration.is_active}")
print(f"✓ Sync Enabled: {integration.sync_enabled}")
# Verify config data
config = integration.config_json or {}
content_types = config.get('content_types', {})
print("\n" + "=" * 70)
print("CONTENT TYPES STRUCTURE")
print("=" * 70)
# Post Types
post_types = content_types.get('post_types', {})
print(f"\n📝 Post Types: ({len(post_types)} total)")
for pt_name, pt_data in post_types.items():
print(f"{pt_data['label']} ({pt_name})")
print(f" - Count: {pt_data['count']}")
print(f" - Enabled: {pt_data['enabled']}")
print(f" - Fetch Limit: {pt_data['fetch_limit']}")
# Taxonomies
taxonomies = content_types.get('taxonomies', {})
print(f"\n🏷️ Taxonomies: ({len(taxonomies)} total)")
for tax_name, tax_data in taxonomies.items():
print(f"{tax_data['label']} ({tax_name})")
print(f" - Count: {tax_data['count']}")
print(f" - Enabled: {tax_data['enabled']}")
print(f" - Fetch Limit: {tax_data['fetch_limit']}")
# Last fetch time
last_fetch = content_types.get('last_structure_fetch')
print(f"\n🕐 Last Structure Fetch: {last_fetch}")
print("\n" + "=" * 70)
print("✅ SUCCESS! WordPress content types are properly configured")
print("=" * 70)
print("\nNext Steps:")
print("1. Refresh the IGNY8 app page in your browser")
print("2. Navigate to Sites → Settings → Content Types tab")
print("3. You should now see all Post Types and Taxonomies listed")
print("=" * 70)

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env python
"""Fix remaining cluster with old status"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.planning.models import Clusters
cluster = Clusters.objects.filter(status='active').first()
if cluster:
print(f"Found cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}")
print(f"Ideas count: {cluster.ideas.count()}")
if cluster.ideas.exists():
cluster.status = 'mapped'
else:
cluster.status = 'new'
cluster.save()
print(f"Updated to: {cluster.status}")
else:
print("No clusters with 'active' status found")

View File

@@ -1,88 +0,0 @@
#!/usr/bin/env python
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.integration.models import SiteIntegration
from igny8_core.auth.models import Site
from django.utils import timezone
try:
# Get site 5
site = Site.objects.get(id=5)
print(f"✓ Site found: {site.name}")
# Get or create WordPress integration
integration, created = SiteIntegration.objects.get_or_create(
site=site,
platform='wordpress',
defaults={
'is_active': True,
'sync_enabled': True,
'config_json': {}
}
)
print(f"✓ Integration ID: {integration.id} (created: {created})")
# Add structure data
integration.config_json = {
'content_types': {
'post_types': {
'post': {
'label': 'Posts',
'count': 150,
'enabled': True,
'fetch_limit': 100
},
'page': {
'label': 'Pages',
'count': 25,
'enabled': True,
'fetch_limit': 100
},
'product': {
'label': 'Products',
'count': 89,
'enabled': True,
'fetch_limit': 100
}
},
'taxonomies': {
'category': {
'label': 'Categories',
'count': 15,
'enabled': True,
'fetch_limit': 100
},
'post_tag': {
'label': 'Tags',
'count': 234,
'enabled': True,
'fetch_limit': 100
},
'product_cat': {
'label': 'Product Categories',
'count': 12,
'enabled': True,
'fetch_limit': 100
}
},
'last_structure_fetch': timezone.now().isoformat()
},
'plugin_connection_enabled': True,
'two_way_sync_enabled': True
}
integration.save()
print("✓ Structure data saved successfully!")
print(f"✓ Integration ID: {integration.id}")
print("\n✅ READY: Refresh the page to see the content types!")
except Exception as e:
print(f"❌ ERROR: {str(e)}")
import traceback
traceback.print_exc()

View File

@@ -1,76 +0,0 @@
#!/usr/bin/env python
"""
Fix missing site_url in integration config
Adds site_url to config_json from site.domain or site.wp_url
"""
import os
import sys
import django
# Setup Django environment
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.integration.models import SiteIntegration
from igny8_core.auth.models import Site
def fix_integration_site_urls():
"""Add site_url to integration config if missing"""
integrations = SiteIntegration.objects.filter(platform='wordpress')
fixed_count = 0
skipped_count = 0
error_count = 0
for integration in integrations:
try:
config = integration.config_json or {}
# Check if site_url is already set
if config.get('site_url'):
print(f"✓ Integration {integration.id} already has site_url: {config.get('site_url')}")
skipped_count += 1
continue
# Try to get site URL from multiple sources
site_url = None
# First, try legacy wp_url
if integration.site.wp_url:
site_url = integration.site.wp_url
print(f"→ Using legacy wp_url for integration {integration.id}: {site_url}")
# Fallback to domain
elif integration.site.domain:
site_url = integration.site.domain
print(f"→ Using domain for integration {integration.id}: {site_url}")
if site_url:
# Update config
config['site_url'] = site_url
integration.config_json = config
integration.save(update_fields=['config_json'])
print(f"✓ Updated integration {integration.id} with site_url: {site_url}")
fixed_count += 1
else:
print(f"✗ Integration {integration.id} has no site URL available (site: {integration.site.name}, id: {integration.site.id})")
error_count += 1
except Exception as e:
print(f"✗ Error fixing integration {integration.id}: {e}")
error_count += 1
print("\n" + "="*60)
print(f"Summary:")
print(f" Fixed: {fixed_count}")
print(f" Skipped (already set): {skipped_count}")
print(f" Errors: {error_count}")
print("="*60)
if __name__ == '__main__':
print("Fixing WordPress integration site URLs...")
print("="*60)
fix_integration_site_urls()

View File

@@ -1,90 +0,0 @@
#!/usr/bin/env python
"""Script to inject WordPress structure data into the backend"""
from igny8_core.business.integration.models import SiteIntegration
from igny8_core.auth.models import Site
from django.utils import timezone
# Get site 5
try:
site = Site.objects.get(id=5)
print(f"✓ Found site: {site.name}")
except Site.DoesNotExist:
print("✗ Site with ID 5 not found!")
exit(1)
# Get or create WordPress integration for this site
integration, created = SiteIntegration.objects.get_or_create(
site=site,
platform='wordpress',
defaults={
'is_active': True,
'sync_enabled': True,
'config_json': {}
}
)
print(f"✓ Integration ID: {integration.id} (newly created: {created})")
# Add structure data
integration.config_json = {
'content_types': {
'post_types': {
'post': {
'label': 'Posts',
'count': 150,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'page': {
'label': 'Pages',
'count': 25,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'product': {
'label': 'Products',
'count': 89,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
}
},
'taxonomies': {
'category': {
'label': 'Categories',
'count': 15,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'post_tag': {
'label': 'Tags',
'count': 234,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'product_cat': {
'label': 'Product Categories',
'count': 12,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
}
},
'last_structure_fetch': timezone.now().isoformat()
},
'plugin_connection_enabled': True,
'two_way_sync_enabled': True
}
integration.save()
print("✓ Structure data saved!")
print(f"✓ Post Types: {len(integration.config_json['content_types']['post_types'])}")
print(f"✓ Taxonomies: {len(integration.config_json['content_types']['taxonomies'])}")
print(f"✓ Last fetch: {integration.config_json['content_types']['last_structure_fetch']}")
print("\n🎉 SUCCESS! Now refresh: https://app.igny8.com/sites/5/settings?tab=content-types")

View File

@@ -1,106 +0,0 @@
#!/usr/bin/env python
"""
Fix missing taxonomy relationships for existing content
This script will:
1. Find content that should have tags/categories based on their keywords
2. Create appropriate taxonomy terms
3. Link them to the content
"""
import os
import sys
import django
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.db import transaction
from django.utils.text import slugify
from igny8_core.business.content.models import Content, ContentTaxonomy
print("=" * 80)
print("FIXING MISSING TAXONOMY RELATIONSHIPS")
print("=" * 80)
# Get all content without taxonomy terms
content_without_tags = Content.objects.filter(taxonomy_terms__isnull=True).distinct()
print(f"\nFound {content_without_tags.count()} content items without tags/categories")
fixed_count = 0
for content in content_without_tags:
print(f"\nProcessing Content #{content.id}: {content.title[:50]}...")
# Generate tags from keywords
tags_to_add = []
categories_to_add = []
# Use primary keyword as a tag
if content.primary_keyword:
tags_to_add.append(content.primary_keyword)
# Use secondary keywords as tags
if content.secondary_keywords and isinstance(content.secondary_keywords, list):
tags_to_add.extend(content.secondary_keywords[:3]) # Limit to 3
# Create category based on cluster only
if content.cluster:
categories_to_add.append(content.cluster.name)
with transaction.atomic():
# Process tags
for tag_name in tags_to_add:
if tag_name and isinstance(tag_name, str):
tag_name = tag_name.strip()
if tag_name:
try:
tag_obj, created = ContentTaxonomy.objects.get_or_create(
site=content.site,
name=tag_name,
taxonomy_type='tag',
defaults={
'slug': slugify(tag_name),
'sector': content.sector,
'account': content.account,
'description': '',
'external_taxonomy': '',
'sync_status': '',
'count': 0,
'metadata': {},
}
)
content.taxonomy_terms.add(tag_obj)
print(f" + Tag: {tag_name} ({'created' if created else 'existing'})")
except Exception as e:
print(f" ✗ Failed to add tag '{tag_name}': {e}")
# Process categories
for category_name in categories_to_add:
if category_name and isinstance(category_name, str):
category_name = category_name.strip()
if category_name:
try:
category_obj, created = ContentTaxonomy.objects.get_or_create(
site=content.site,
name=category_name,
taxonomy_type='category',
defaults={
'slug': slugify(category_name),
'sector': content.sector,
'account': content.account,
'description': '',
'external_taxonomy': '',
'sync_status': '',
'count': 0,
'metadata': {},
}
)
content.taxonomy_terms.add(category_obj)
print(f" + Category: {category_name} ({'created' if created else 'existing'})")
except Exception as e:
print(f" ✗ Failed to add category '{category_name}': {e}")
fixed_count += 1
print("\n" + "=" * 80)
print(f"FIXED {fixed_count} CONTENT ITEMS")
print("=" * 80)

View File

@@ -1,57 +0,0 @@
#!/usr/bin/env python3
"""Force cancel stuck automation runs and clear cache locks"""
import os
import sys
import django
# Setup Django
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.automation.models import AutomationRun
from django.core.cache import cache
from django.utils import timezone
print("=" * 80)
print("AUTOMATION RUN FORCE CANCEL & CLEANUP")
print("=" * 80)
# Check and cancel active runs
runs = AutomationRun.objects.filter(status__in=['running', 'paused']).order_by('-started_at')
print(f"\nFound {runs.count()} active run(s)")
if runs.count() == 0:
print(" No runs to cancel\n")
else:
for r in runs:
duration = (timezone.now() - r.started_at).total_seconds() / 60
print(f"\nRun ID: {r.run_id}")
print(f" Site: {r.site_id}")
print(f" Status: {r.status}")
print(f" Stage: {r.current_stage}")
print(f" Started: {r.started_at} ({duration:.1f}m ago)")
print(f" Credits: {r.total_credits_used}")
# Force cancel
print(f" >>> FORCE CANCELLING...")
r.status = 'cancelled'
r.save()
print(f" >>> Status: {r.status}")
# Clear cache lock
lock_key = f'automation_lock_{r.site_id}'
cache.delete(lock_key)
print(f" >>> Lock cleared: {lock_key}")
print("\n" + "=" * 40)
print("Cache lock status:")
for site_id in [5, 16]:
lock_key = f'automation_lock_{site_id}'
lock_val = cache.get(lock_key)
status = lock_val or 'UNLOCKED ✓'
print(f" Site {site_id}: {status}")
print("\n" + "=" * 80)
print("✓ CLEANUP COMPLETE - You can now start a new automation run")
print("=" * 80)

View File

@@ -99,8 +99,9 @@ class SiteSerializer(serializers.ModelSerializer):
- If domain has no protocol, add https://
- Validates that the final URL is valid
"""
if not value:
return value
# Allow empty/None values
if not value or value.strip() == '':
return None
value = value.strip()

View File

@@ -496,8 +496,9 @@ class SiteViewSet(AccountModelViewSet):
from rest_framework.permissions import AllowAny
return [AllowAny()]
if self.action == 'create':
# For create, only require authentication - not active account status
return [permissions.IsAuthenticated()]
return [IsEditorOrAbove()]
return [IsAuthenticatedAndActive(), HasTenantAccess(), IsEditorOrAbove()]
def get_queryset(self):
"""Return sites accessible to the current user."""

View File

@@ -1,87 +0,0 @@
"""
Django Management Command to Manually Add WordPress Structure Data
Run this in Django shell or as a management command
"""
from igny8_core.business.integration.models import SiteIntegration
from igny8_core.auth.models import Site
from django.utils import timezone
# Get site 5
site = Site.objects.get(id=5)
print(f"Site: {site.name}")
# Get or create WordPress integration for this site
integration, created = SiteIntegration.objects.get_or_create(
site=site,
platform='wordpress',
defaults={
'is_active': True,
'sync_enabled': True,
'config_json': {}
}
)
print(f"Integration: {integration.id} (created: {created})")
# Add structure data
integration.config_json = {
'content_types': {
'post_types': {
'post': {
'label': 'Posts',
'count': 150,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'page': {
'label': 'Pages',
'count': 25,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'product': {
'label': 'Products',
'count': 89,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
}
},
'taxonomies': {
'category': {
'label': 'Categories',
'count': 15,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'post_tag': {
'label': 'Tags',
'count': 234,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
},
'product_cat': {
'label': 'Product Categories',
'count': 12,
'enabled': True,
'fetch_limit': 100,
'synced_count': 0
}
},
'last_structure_fetch': timezone.now().isoformat()
},
'plugin_connection_enabled': True,
'two_way_sync_enabled': True
}
integration.save()
print("✓ Structure data saved!")
print(f"Integration ID: {integration.id}")
print(f"Content Types: {len(integration.config_json['content_types']['post_types'])} post types, {len(integration.config_json['content_types']['taxonomies'])} taxonomies")
print("\nNow refresh: https://app.igny8.com/sites/5/settings?tab=content-types")

View File

@@ -1,53 +0,0 @@
-- COMPREHENSIVE FIELD RENAME MIGRATION
-- Renames all entity_type, cluster_role, site_entity_type columns to content_type and content_structure
-- Date: 2025-11-26
BEGIN;
-- 1. ContentIdeas table (igny8_content_ideas)
ALTER TABLE igny8_content_ideas RENAME COLUMN site_entity_type TO content_type;
ALTER TABLE igny8_content_ideas RENAME COLUMN cluster_role TO content_structure;
-- Update index names for ContentIdeas
DROP INDEX IF EXISTS igny8_content_ideas_site_entity_type_idx;
DROP INDEX IF EXISTS igny8_content_ideas_cluster_role_idx;
CREATE INDEX igny8_content_ideas_content_type_idx ON igny8_content_ideas(content_type);
CREATE INDEX igny8_content_ideas_content_structure_idx ON igny8_content_ideas(content_structure);
-- 2. Tasks table (igny8_tasks)
ALTER TABLE igny8_tasks RENAME COLUMN entity_type TO content_type;
-- cluster_role already mapped via db_column, but let's check if column exists
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_tasks' AND column_name = 'cluster_role') THEN
ALTER TABLE igny8_tasks RENAME COLUMN cluster_role TO content_structure;
END IF;
END $$;
-- 3. Content table (igny8_content)
ALTER TABLE igny8_content RENAME COLUMN entity_type TO content_type;
-- cluster_role already mapped via db_column, but let's check if column exists
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_content' AND column_name = 'cluster_role') THEN
ALTER TABLE igny8_content RENAME COLUMN cluster_role TO content_structure;
END IF;
END $$;
-- 4. ContentTaxonomy table (igny8_content_taxonomy)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_content_taxonomy' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_content_taxonomy RENAME COLUMN entity_type TO content_type;
END IF;
END $$;
-- 5. AITaskExecution table (igny8_ai_task_execution)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_ai_task_execution' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_ai_task_execution RENAME COLUMN entity_type TO content_type;
END IF;
END $$;
COMMIT;

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env python
"""
Sync idea status from completed tasks
One-time script to fix existing data
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.content.models import Tasks
from igny8_core.business.planning.models import ContentIdeas
# Find all completed tasks with ideas
completed_tasks = Tasks.objects.filter(status='completed', idea__isnull=False)
synced = 0
for task in completed_tasks:
if task.idea and task.idea.status != 'completed':
task.idea.status = 'completed'
task.idea.save(update_fields=['status', 'updated_at'])
synced += 1
print(f"Synced idea {task.idea.id} to completed (from task {task.id})")
print(f"\nTotal synced: {synced} ideas to completed status")

View File

@@ -1,444 +0,0 @@
#!/usr/bin/env python3
"""
End-to-End Payment Workflow Test Script
Tests the complete manual payment approval flow
"""
import os
import sys
import django
# Setup Django
sys.path.insert(0, os.path.dirname(__file__))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils import timezone
from decimal import Decimal
from datetime import timedelta
from igny8_core.auth.models import Account, Subscription, Plan
from igny8_core.business.billing.models import (
Invoice, Payment, AccountPaymentMethod, CreditTransaction
)
from igny8_core.business.billing.services.invoice_service import InvoiceService
User = get_user_model()
class Colors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
def print_header(text):
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}")
print(f"{Colors.HEADER}{Colors.BOLD}{text:^60}{Colors.ENDC}")
print(f"{Colors.HEADER}{Colors.BOLD}{'='*60}{Colors.ENDC}\n")
def print_success(text):
print(f"{Colors.OKGREEN}{text}{Colors.ENDC}")
def print_error(text):
print(f"{Colors.FAIL}{text}{Colors.ENDC}")
def print_info(text):
print(f"{Colors.OKCYAN}{text}{Colors.ENDC}")
def cleanup_test_data():
"""Remove test data from previous runs"""
print_header("CLEANUP TEST DATA")
# Delete test accounts
test_emails = [
'workflow_test_free@example.com',
'workflow_test_paid@example.com'
]
for email in test_emails:
try:
user = User.objects.filter(email=email).first()
if user:
# Delete associated account (cascade will handle related objects)
account = Account.objects.filter(owner=user).first()
if account:
account.delete()
print_success(f"Deleted account for {email}")
user.delete()
print_success(f"Deleted user {email}")
except Exception as e:
print_error(f"Error cleaning up {email}: {e}")
def test_free_trial_signup():
"""Test free trial user registration"""
print_header("TEST 1: FREE TRIAL SIGNUP")
try:
# Get free plan
free_plan = Plan.objects.get(slug='free')
print_info(f"Free Plan: {free_plan.name} - {free_plan.included_credits} credits")
# Create user
with transaction.atomic():
user = User.objects.create_user(
username='workflow_test_free',
email='workflow_test_free@example.com',
password='TestPass123!',
first_name='Free',
last_name='Trial'
)
print_success(f"Created user: {user.email}")
# Create account
account = Account.objects.create(
name=f"{user.first_name}'s Account",
slug=f'free-trial-{timezone.now().timestamp()}',
owner=user,
plan=free_plan,
status='trial',
credits=free_plan.included_credits
)
print_success(f"Created account: {account.name} (ID: {account.id})")
# Create credit transaction
CreditTransaction.objects.create(
account=account,
transaction_type='plan_allocation',
amount=free_plan.included_credits,
balance_after=account.credits,
description=f'Initial credits from {free_plan.name} plan'
)
print_success(f"Allocated {free_plan.included_credits} credits")
# Verify
account.refresh_from_db()
assert account.status == 'trial', "Status should be 'trial'"
assert account.credits == 1000, "Credits should be 1000"
assert account.plan.slug == 'free', "Plan should be 'free'"
# Check no subscription or invoice created
sub_count = Subscription.objects.filter(account=account).count()
invoice_count = Invoice.objects.filter(account=account).count()
assert sub_count == 0, "Free trial should not have subscription"
assert invoice_count == 0, "Free trial should not have invoice"
print_success("No subscription created (correct for free trial)")
print_success("No invoice created (correct for free trial)")
print_success("\nFREE TRIAL TEST PASSED ✓")
return account
except Exception as e:
print_error(f"Free trial test failed: {e}")
raise
def test_paid_signup():
"""Test paid user registration with manual payment"""
print_header("TEST 2: PAID SIGNUP WORKFLOW")
try:
# Get starter plan
starter_plan = Plan.objects.get(slug='starter')
print_info(f"Starter Plan: {starter_plan.name} - ${starter_plan.price} - {starter_plan.included_credits} credits")
# Step 1: Create user with billing info
print_info("\nStep 1: User Registration")
with transaction.atomic():
user = User.objects.create_user(
username='workflow_test_paid',
email='workflow_test_paid@example.com',
password='TestPass123!',
first_name='Paid',
last_name='User'
)
print_success(f"Created user: {user.email}")
# Create account with billing info
account = Account.objects.create(
name=f"{user.first_name}'s Account",
slug=f'paid-user-{timezone.now().timestamp()}',
owner=user,
plan=starter_plan,
status='pending_payment',
credits=0, # No credits until payment approved
billing_email='billing@example.com',
billing_address_line1='123 Main Street',
billing_city='Karachi',
billing_country='PK'
)
print_success(f"Created account: {account.name} (ID: {account.id})")
print_info(f" Status: {account.status}")
print_info(f" Credits: {account.credits}")
# Create subscription
subscription = Subscription.objects.create(
account=account,
plan=starter_plan,
status='pending_payment',
current_period_start=timezone.now(),
current_period_end=timezone.now() + timedelta(days=30)
)
print_success(f"Created subscription (ID: {subscription.id})")
print_info(f" Status: {subscription.status}")
# Create invoice
invoice_service = InvoiceService()
invoice = invoice_service.create_subscription_invoice(
subscription=subscription,
billing_period_start=subscription.current_period_start,
billing_period_end=subscription.current_period_end
)
print_success(f"Created invoice: {invoice.invoice_number}")
print_info(f" Status: {invoice.status}")
print_info(f" Total: ${invoice.total}")
print_info(f" Has billing snapshot: {'billing_snapshot' in invoice.metadata}")
# Create payment method
payment_method = AccountPaymentMethod.objects.create(
account=account,
type='bank_transfer',
is_default=True
)
print_success(f"Created payment method: {payment_method.type}")
# Step 2: User submits payment confirmation
print_info("\nStep 2: User Payment Confirmation")
payment = Payment.objects.create(
invoice=invoice,
account=account,
amount=invoice.total,
currency=invoice.currency,
payment_method='bank_transfer',
status='pending_approval',
manual_reference='BT-TEST-20251208-001',
manual_notes='Test payment via ABC Bank'
)
print_success(f"Created payment (ID: {payment.id})")
print_info(f" Status: {payment.status}")
print_info(f" Reference: {payment.manual_reference}")
# Verify pending state
account.refresh_from_db()
subscription.refresh_from_db()
invoice.refresh_from_db()
assert account.status == 'pending_payment', "Account should be pending_payment"
assert account.credits == 0, "Credits should be 0 before approval"
assert subscription.status == 'pending_payment', "Subscription should be pending_payment"
assert invoice.status == 'pending', "Invoice should be pending"
assert payment.status == 'pending_approval', "Payment should be pending_approval"
print_success("\nPending state verified ✓")
# Step 3: Admin approves payment
print_info("\nStep 3: Admin Payment Approval")
# Create admin user for approval
admin_user = User.objects.filter(is_superuser=True).first()
if not admin_user:
admin_user = User.objects.create_superuser(
username='test_admin',
email='test_admin@example.com',
password='admin123',
first_name='Test',
last_name='Admin'
)
print_info(f"Created admin user: {admin_user.email}")
with transaction.atomic():
# Update payment
payment.status = 'succeeded'
payment.approved_by = admin_user
payment.approved_at = timezone.now()
payment.admin_notes = 'Verified in bank statement'
payment.save()
print_success("Payment approved")
# Update invoice
invoice.status = 'paid'
invoice.paid_at = timezone.now()
invoice.save()
print_success("Invoice marked as paid")
# Update subscription
subscription.status = 'active'
subscription.save()
print_success("Subscription activated")
# Update account and add credits
account.status = 'active'
account.credits = starter_plan.included_credits
account.save()
print_success(f"Account activated with {starter_plan.included_credits} credits")
# Log credit transaction
credit_txn = CreditTransaction.objects.create(
account=account,
transaction_type='plan_allocation',
amount=starter_plan.included_credits,
balance_after=account.credits,
description=f'Credits from approved payment (Invoice: {invoice.invoice_number})'
)
print_success("Credit transaction logged")
# Final verification
print_info("\nStep 4: Final Verification")
account.refresh_from_db()
subscription.refresh_from_db()
invoice.refresh_from_db()
payment.refresh_from_db()
assert account.status == 'active', "Account should be active"
assert account.credits == 1000, "Credits should be 1000"
assert subscription.status == 'active', "Subscription should be active"
assert invoice.status == 'paid', "Invoice should be paid"
assert payment.status == 'succeeded', "Payment should be succeeded"
assert payment.approved_by == admin_user, "Payment should have approved_by"
print_success(f"Account: {account.status}")
print_success(f"Credits: {account.credits}")
print_success(f"Subscription: {subscription.status}")
print_success(f"Invoice: {invoice.status}")
print_success(f"Payment: {payment.status}")
print_success(f"Approved by: {payment.approved_by.email}")
# Check credit transaction
txn = CreditTransaction.objects.filter(account=account).latest('created_at')
print_success(f"Credit Transaction: {txn.transaction_type} | {txn.amount} credits ✓")
print_success("\nPAID SIGNUP TEST PASSED ✓")
return account
except Exception as e:
print_error(f"Paid signup test failed: {e}")
raise
def test_payment_rejection():
"""Test payment rejection flow"""
print_header("TEST 3: PAYMENT REJECTION")
try:
# Use the paid account from previous test
account = Account.objects.get(owner__email='workflow_test_paid@example.com')
# Create a second invoice for testing rejection
print_info("Creating second invoice for rejection test")
subscription = Subscription.objects.get(account=account)
invoice_service = InvoiceService()
with transaction.atomic():
invoice2 = invoice_service.create_subscription_invoice(
subscription=subscription,
billing_period_start=subscription.current_period_start + timedelta(days=30),
billing_period_end=subscription.current_period_end + timedelta(days=30)
)
print_success(f"Created invoice: {invoice2.invoice_number}")
# Submit payment
payment2 = Payment.objects.create(
invoice=invoice2,
account=account,
amount=invoice2.total,
currency=invoice2.currency,
payment_method='bank_transfer',
status='pending_approval',
manual_reference='BT-INVALID-REF',
manual_notes='Test invalid payment reference'
)
print_success(f"Created payment (ID: {payment2.id})")
# Admin rejects payment
print_info("\nRejecting payment...")
admin_user = User.objects.filter(is_superuser=True).first()
with transaction.atomic():
payment2.status = 'failed'
payment2.approved_by = admin_user
payment2.approved_at = timezone.now()
payment2.admin_notes = 'Reference number not found in bank statement'
payment2.save()
print_success("Payment rejected")
# Verify rejection
payment2.refresh_from_db()
invoice2.refresh_from_db()
assert payment2.status == 'failed', "Payment should be failed"
assert invoice2.status == 'pending', "Invoice should remain pending"
print_success(f"Payment status: {payment2.status}")
print_success(f"Invoice status: {invoice2.status}")
print_success(f"Rejection reason: {payment2.admin_notes}")
print_success("\nPAYMENT REJECTION TEST PASSED ✓")
except Exception as e:
print_error(f"Payment rejection test failed: {e}")
raise
def print_summary():
"""Print test summary"""
print_header("TEST SUMMARY")
# Count accounts by status
from django.db.models import Count
status_counts = Account.objects.values('status').annotate(count=Count('id'))
print_info("Account Status Distribution:")
for item in status_counts:
print(f" {item['status']:20} {item['count']} account(s)")
# Count payments by status
payment_counts = Payment.objects.values('status').annotate(count=Count('id'))
print_info("\nPayment Status Distribution:")
for item in payment_counts:
print(f" {item['status']:20} {item['count']} payment(s)")
# Count subscriptions by status
sub_counts = Subscription.objects.values('status').annotate(count=Count('id'))
print_info("\nSubscription Status Distribution:")
for item in sub_counts:
print(f" {item['status']:20} {item['count']} subscription(s)")
print()
def main():
"""Run all tests"""
print_header("PAYMENT WORKFLOW E2E TEST SUITE")
print(f"{Colors.BOLD}Date: {timezone.now().strftime('%Y-%m-%d %H:%M:%S')}{Colors.ENDC}\n")
try:
# Cleanup
cleanup_test_data()
# Run tests
test_free_trial_signup()
test_paid_signup()
test_payment_rejection()
# Summary
print_summary()
# Final success
print_header("ALL TESTS PASSED ✓")
print(f"{Colors.OKGREEN}{Colors.BOLD}The payment workflow is functioning correctly!{Colors.ENDC}\n")
return 0
except Exception as e:
print_header("TESTS FAILED ✗")
print(f"{Colors.FAIL}{Colors.BOLD}Error: {e}{Colors.ENDC}\n")
import traceback
traceback.print_exc()
return 1
if __name__ == '__main__':
sys.exit(main())

View File

@@ -1,141 +0,0 @@
#!/usr/bin/env python
"""
Test script to detect and reproduce session contamination bugs
Usage: docker exec igny8_backend python test_session_contamination.py
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.contrib.sessions.models import Session
from django.contrib.auth import get_user_model
from django.test import RequestFactory
from django.contrib.sessions.middleware import SessionMiddleware
from igny8_core.auth.middleware import AccountContextMiddleware
from datetime import datetime, timedelta
User = get_user_model()
def test_session_isolation():
"""Test that sessions are properly isolated between users"""
print("\n=== SESSION CONTAMINATION TEST ===\n")
# Get test users
try:
developer = User.objects.get(username='developer')
scale_user = User.objects.filter(account__slug='scale-account').first()
if not scale_user:
print("⚠️ No scale account user found, creating one...")
from igny8_core.auth.models import Account
scale_account = Account.objects.filter(slug='scale-account').first()
if scale_account:
scale_user = User.objects.create_user(
username='scale_test',
email='scale@test.com',
password='testpass123',
account=scale_account,
role='owner'
)
else:
print("❌ No scale account found")
return False
print(f"✓ Developer user: {developer.username} (account: {developer.account.slug})")
print(f"✓ Scale user: {scale_user.username} (account: {scale_user.account.slug if scale_user.account else 'None'})")
except Exception as e:
print(f"❌ Failed to get test users: {e}")
return False
# Check active sessions
active_sessions = Session.objects.filter(expire_date__gte=datetime.now())
print(f"\n📊 Total active sessions: {active_sessions.count()}")
# Count sessions by user
user_sessions = {}
for session in active_sessions:
try:
data = session.get_decoded()
user_id = data.get('_auth_user_id')
if user_id:
user = User.objects.get(id=user_id)
key = f"{user.username} ({user.account.slug if user.account else 'no-account'})"
user_sessions[key] = user_sessions.get(key, 0) + 1
except:
pass
print("\n📈 Sessions by user:")
for user_key, count in sorted(user_sessions.items(), key=lambda x: x[1], reverse=True):
print(f" {user_key}: {count} sessions")
# Check for session contamination patterns
contamination_found = False
# Pattern 1: Too many sessions for one user
for user_key, count in user_sessions.items():
if count > 20:
print(f"\n⚠️ WARNING: {user_key} has {count} sessions (possible proliferation)")
contamination_found = True
# Pattern 2: Check session cookie settings
from django.conf import settings
print(f"\n🔧 Session Configuration:")
print(f" SESSION_COOKIE_NAME: {settings.SESSION_COOKIE_NAME}")
print(f" SESSION_COOKIE_DOMAIN: {getattr(settings, 'SESSION_COOKIE_DOMAIN', 'Not set (good)')}")
print(f" SESSION_COOKIE_SAMESITE: {getattr(settings, 'SESSION_COOKIE_SAMESITE', 'Not set')}")
print(f" SESSION_COOKIE_HTTPONLY: {settings.SESSION_COOKIE_HTTPONLY}")
print(f" SESSION_ENGINE: {settings.SESSION_ENGINE}")
if getattr(settings, 'SESSION_COOKIE_SAMESITE', None) != 'Strict':
print(f"\n⚠️ WARNING: SESSION_COOKIE_SAMESITE should be 'Strict' (currently: {getattr(settings, 'SESSION_COOKIE_SAMESITE', 'Not set')})")
contamination_found = True
# Test middleware isolation
print(f"\n🧪 Testing Middleware Isolation...")
factory = RequestFactory()
# Simulate two requests from different users
request1 = factory.get('/api/v1/test/')
request1.user = developer
request1.session = {}
request2 = factory.get('/api/v1/test/')
request2.user = scale_user
request2.session = {}
middleware = AccountContextMiddleware(lambda x: None)
# Process requests
middleware.process_request(request1)
middleware.process_request(request2)
# Check isolation
account1 = getattr(request1, 'account', None)
account2 = getattr(request2, 'account', None)
print(f" Request 1 account: {account1.slug if account1 else 'None'}")
print(f" Request 2 account: {account2.slug if account2 else 'None'}")
if account1 and account2 and account1.id == account2.id:
print(f"\n❌ CONTAMINATION DETECTED: Both requests have same account!")
contamination_found = True
else:
print(f"\n✓ Middleware isolation working correctly")
# Final result
if contamination_found:
print(f"\n❌ SESSION CONTAMINATION DETECTED")
print(f"\nRecommended fixes:")
print(f"1. Set SESSION_COOKIE_SAMESITE='Strict' in settings.py")
print(f"2. Clear all existing sessions: Session.objects.all().delete()")
print(f"3. Ensure users logout and re-login with fresh cookies")
return False
else:
print(f"\n✅ No contamination detected - sessions appear isolated")
return True
if __name__ == '__main__':
result = test_session_isolation()
exit(0 if result else 1)

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env python
import os
import sys
import django
# Add the backend directory to the path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.modules.writer.models import Content
from igny8_core.modules.writer.serializers import ContentSerializer
print("Testing ContentSerializer tags and categories fields...")
print("=" * 60)
# Get a content record
content = Content.objects.first()
if content:
serializer = ContentSerializer(content)
data = serializer.data
print(f"Content ID: {data['id']}")
print(f"Title: {data.get('title', 'N/A')}")
print(f"Tags: {data.get('tags', [])}")
print(f"Categories: {data.get('categories', [])}")
print(f"Taxonomy Terms Data: {len(data.get('taxonomy_terms_data', []))} items")
# Show taxonomy terms breakdown
taxonomy_terms = data.get('taxonomy_terms_data', [])
if taxonomy_terms:
print("\nTaxonomy Terms Details:")
for term in taxonomy_terms:
print(f" - {term['name']} ({term['taxonomy_type']})")
print("\n✓ Serializer fields test passed!")
else:
print("No content found in database")
print("This is expected if no content has been generated yet")

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env python
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.integration.models import SiteIntegration
import json
integration = SiteIntegration.objects.get(id=1)
print("Current config_json:")
print(json.dumps(integration.config_json, indent=2))
print("\nIntegration ID:", integration.id)
print("Site:", integration.site.name)
print("Platform:", integration.platform)
print("Is Active:", integration.is_active)
print("Sync Enabled:", integration.sync_enabled)

View File

@@ -1,204 +0,0 @@
#!/usr/bin/env python3
"""
Database Migration Verification Script
Checks for orphaned SiteBlueprint tables and verifies new migrations
"""
import os
import sys
import django
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from django.db import connection
from django.core.management import call_command
def check_orphaned_tables():
"""Check for orphaned blueprint tables"""
print("\n" + "="*60)
print("CHECKING FOR ORPHANED SITEBLUEPRINT TABLES")
print("="*60 + "\n")
with connection.cursor() as cursor:
cursor.execute("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name LIKE '%blueprint%'
ORDER BY table_name;
""")
tables = cursor.fetchall()
if tables:
print("⚠️ Found blueprint-related tables:")
for table in tables:
print(f" - {table[0]}")
print("\n💡 These tables can be safely dropped if no longer needed.")
else:
print("✅ No orphaned blueprint tables found.")
return len(tables) if tables else 0
def verify_cluster_constraint():
"""Verify cluster unique constraint is per-site/sector"""
print("\n" + "="*60)
print("VERIFYING CLUSTER UNIQUE CONSTRAINT")
print("="*60 + "\n")
with connection.cursor() as cursor:
cursor.execute("""
SELECT
tc.constraint_name,
tc.constraint_type,
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) as columns
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
AND tc.table_schema = kcu.table_schema
WHERE tc.table_name = 'igny8_clusters'
AND tc.constraint_type = 'UNIQUE'
GROUP BY tc.constraint_name, tc.constraint_type;
""")
constraints = cursor.fetchall()
if constraints:
print("Found unique constraints on igny8_clusters:")
for constraint in constraints:
name, ctype, columns = constraint
print(f" {name}: {columns}")
# Check if it includes site and sector
if 'site' in columns.lower() and 'sector' in columns.lower():
print(f" ✅ Constraint is scoped per-site/sector")
else:
print(f" ⚠️ Constraint may need updating")
else:
print("⚠️ No unique constraints found on igny8_clusters")
def verify_automation_delays():
"""Verify automation delay fields exist"""
print("\n" + "="*60)
print("VERIFYING AUTOMATION DELAY CONFIGURATION")
print("="*60 + "\n")
with connection.cursor() as cursor:
cursor.execute("""
SELECT
column_name,
data_type,
column_default
FROM information_schema.columns
WHERE table_name = 'igny8_automationconfig'
AND column_name IN ('within_stage_delay', 'between_stage_delay')
ORDER BY column_name;
""")
columns = cursor.fetchall()
if len(columns) == 2:
print("✅ Delay configuration fields found:")
for col in columns:
name, dtype, default = col
print(f" {name}: {dtype} (default: {default})")
else:
print(f"⚠️ Expected 2 delay fields, found {len(columns)}")
def check_migration_status():
"""Check migration status"""
print("\n" + "="*60)
print("CHECKING MIGRATION STATUS")
print("="*60 + "\n")
with connection.cursor() as cursor:
cursor.execute("""
SELECT app, name, applied
FROM django_migrations
WHERE name LIKE '%cluster%' OR name LIKE '%delay%'
ORDER BY applied DESC
LIMIT 10;
""")
migrations = cursor.fetchall()
if migrations:
print("Recent relevant migrations:")
for mig in migrations:
app, name, applied = mig
status = "" if applied else ""
print(f" {status} {app}.{name}")
print(f" Applied: {applied}")
else:
print("No relevant migrations found in history")
def check_data_integrity():
"""Check for data integrity issues"""
print("\n" + "="*60)
print("DATA INTEGRITY CHECKS")
print("="*60 + "\n")
from igny8_core.business.planning.models import Clusters, Keywords
# Check for clusters with 'active' status (should all be 'new' or 'mapped')
active_clusters = Clusters.objects.filter(status='active').count()
if active_clusters > 0:
print(f"⚠️ Found {active_clusters} clusters with status='active'")
print(" These should be updated to 'new' or 'mapped'")
else:
print("✅ No clusters with invalid 'active' status")
# Check for duplicate cluster names in same site/sector
with connection.cursor() as cursor:
cursor.execute("""
SELECT name, site_id, sector_id, COUNT(*) as count
FROM igny8_clusters
GROUP BY name, site_id, sector_id
HAVING COUNT(*) > 1;
""")
duplicates = cursor.fetchall()
if duplicates:
print(f"\n⚠️ Found {len(duplicates)} duplicate cluster names in same site/sector:")
for dup in duplicates[:5]: # Show first 5
print(f" - '{dup[0]}' (site={dup[1]}, sector={dup[2]}): {dup[3]} duplicates")
else:
print("✅ No duplicate cluster names within same site/sector")
def main():
print("\n" + "#"*60)
print("# IGNY8 DATABASE MIGRATION VERIFICATION")
print("# Date:", __import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
print("#"*60)
try:
orphaned = check_orphaned_tables()
verify_cluster_constraint()
verify_automation_delays()
check_migration_status()
check_data_integrity()
print("\n" + "="*60)
print("VERIFICATION COMPLETE")
print("="*60)
if orphaned > 0:
print(f"\n⚠️ {orphaned} orphaned table(s) found - review recommended")
else:
print("\n✅ All verifications passed!")
print("\n")
except Exception as e:
print(f"\n❌ ERROR: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == '__main__':
main()

View File

@@ -1,71 +0,0 @@
#!/usr/bin/env python
"""Verify all status fixes"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.planning.models import Keywords, Clusters, ContentIdeas
from igny8_core.business.content.models import Tasks
from django.db.models import Count
print("=" * 60)
print("STATUS VERIFICATION REPORT")
print("=" * 60)
# Keywords
print("\n1. KEYWORDS STATUS:")
kw_status = Keywords.objects.values('status').annotate(count=Count('id')).order_by('status')
for item in kw_status:
print(f" {item['status']}: {item['count']}")
print(f" Total: {Keywords.objects.count()}")
# Clusters
print("\n2. CLUSTERS STATUS:")
cl_status = Clusters.objects.values('status').annotate(count=Count('id')).order_by('status')
for item in cl_status:
print(f" {item['status']}: {item['count']}")
print(f" Total: {Clusters.objects.count()}")
# Content Ideas
print("\n3. IDEAS STATUS:")
idea_status = ContentIdeas.objects.values('status').annotate(count=Count('id')).order_by('status')
for item in idea_status:
print(f" {item['status']}: {item['count']}")
print(f" Total: {ContentIdeas.objects.count()}")
# Verify idea-task sync
print("\n4. IDEA-TASK STATUS SYNC:")
completed_tasks = Tasks.objects.filter(status='completed', idea__isnull=False)
mismatched = 0
for task in completed_tasks:
if task.idea and task.idea.status != 'completed':
mismatched += 1
print(f" MISMATCH: Task {task.id} completed, Idea {task.idea.id} is {task.idea.status}")
if mismatched == 0:
print(f" ✓ All {completed_tasks.count()} completed tasks have ideas with 'completed' status")
else:
print(f"{mismatched} mismatches found")
# Check for old status values
print("\n5. OLD STATUS VALUES CHECK:")
old_keywords = Keywords.objects.filter(status__in=['pending', 'active', 'archived']).count()
old_clusters = Clusters.objects.filter(status__in=['active']).exclude(status='mapped').exclude(status='new').count()
old_ideas = ContentIdeas.objects.filter(status__in=['scheduled', 'published']).count()
if old_keywords == 0 and old_clusters == 0 and old_ideas == 0:
print(" ✓ No old status values found")
else:
print(f" ✗ Found old values:")
if old_keywords > 0:
print(f" Keywords with pending/active/archived: {old_keywords}")
if old_clusters > 0:
print(f" Clusters with old 'active': {old_clusters}")
if old_ideas > 0:
print(f" Ideas with scheduled/published: {old_ideas}")
print("\n" + "=" * 60)
print("VERIFICATION COMPLETE")
print("=" * 60)

View File

@@ -1,129 +0,0 @@
#!/usr/bin/env python
"""
Verify Tags and Categories Implementation
Tests that ContentTaxonomy integration is working correctly
"""
import os
import sys
import django
# Add the backend directory to the path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.content.models import Content, ContentTaxonomy
from igny8_core.modules.writer.serializers import ContentSerializer
print("=" * 80)
print("VERIFYING TAGS AND CATEGORIES IMPLEMENTATION")
print("=" * 80)
# Check if ContentTaxonomy model is accessible
print("\n1. ContentTaxonomy Model Check:")
try:
taxonomy_count = ContentTaxonomy.objects.count()
print(f" ✓ ContentTaxonomy model accessible")
print(f" ✓ Total taxonomy terms in database: {taxonomy_count}")
# Show breakdown by type
tag_count = ContentTaxonomy.objects.filter(taxonomy_type='tag').count()
category_count = ContentTaxonomy.objects.filter(taxonomy_type='category').count()
print(f" - Tags: {tag_count}")
print(f" - Categories: {category_count}")
except Exception as e:
print(f" ✗ Error accessing ContentTaxonomy: {e}")
sys.exit(1)
# Check Content model has taxonomy_terms field
print("\n2. Content Model Taxonomy Field Check:")
try:
content = Content.objects.first()
if content:
taxonomy_terms = content.taxonomy_terms.all()
print(f" ✓ Content.taxonomy_terms field accessible")
print(f" ✓ Sample content (ID: {content.id}) has {taxonomy_terms.count()} taxonomy terms")
for term in taxonomy_terms:
print(f" - {term.name} ({term.taxonomy_type})")
else:
print(" ⚠ No content found in database")
except Exception as e:
print(f" ✗ Error accessing Content.taxonomy_terms: {e}")
sys.exit(1)
# Check serializer includes tags and categories
print("\n3. ContentSerializer Tags/Categories Check:")
try:
if content:
serializer = ContentSerializer(content)
data = serializer.data
# Check if fields exist
has_tags_field = 'tags' in data
has_categories_field = 'categories' in data
has_taxonomy_data = 'taxonomy_terms_data' in data
print(f" ✓ Serializer includes 'tags' field: {has_tags_field}")
print(f" ✓ Serializer includes 'categories' field: {has_categories_field}")
print(f" ✓ Serializer includes 'taxonomy_terms_data' field: {has_taxonomy_data}")
if has_tags_field:
print(f" - Tags: {data.get('tags', [])}")
if has_categories_field:
print(f" - Categories: {data.get('categories', [])}")
else:
print(" ⚠ No content to serialize")
except Exception as e:
print(f" ✗ Error serializing content: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
# Check if we can create taxonomy terms
print("\n4. Creating Test Taxonomy Terms:")
try:
from django.utils.text import slugify
# Try to create a test tag
test_tag, created = ContentTaxonomy.objects.get_or_create(
name="Test Tag",
taxonomy_type='tag',
defaults={
'slug': slugify("Test Tag"),
}
)
if created:
print(f" ✓ Created new test tag: {test_tag.name}")
else:
print(f" ✓ Test tag already exists: {test_tag.name}")
# Try to create a test category
test_category, created = ContentTaxonomy.objects.get_or_create(
name="Test Category",
taxonomy_type='category',
defaults={
'slug': slugify("Test Category"),
}
)
if created:
print(f" ✓ Created new test category: {test_category.name}")
else:
print(f" ✓ Test category already exists: {test_category.name}")
except Exception as e:
print(f" ✗ Error creating taxonomy terms: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
print("\n" + "=" * 80)
print("VERIFICATION COMPLETE")
print("=" * 80)
print("\nNext steps:")
print("1. Access Django admin at /admin/writer/contenttaxonomy/")
print("2. Generate content via AI and check if tags/categories are saved")
print("3. Check API response includes 'tags' and 'categories' fields")
print("=" * 80)