358 lines
16 KiB
Python
358 lines
16 KiB
Python
"""
|
|
Unified Site Settings API
|
|
Consolidates AI & Automation settings into a single endpoint.
|
|
|
|
Per SETTINGS-CONSOLIDATION-PLAN.md:
|
|
GET/PUT /api/v1/sites/{site_id}/unified-settings/
|
|
"""
|
|
import logging
|
|
from rest_framework import viewsets, status
|
|
from rest_framework.response import Response
|
|
from rest_framework.decorators import action
|
|
from django.shortcuts import get_object_or_404
|
|
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
|
|
|
|
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsEditorOrAbove
|
|
from igny8_core.api.response import success_response, error_response
|
|
from igny8_core.api.throttles import DebugScopedRateThrottle
|
|
from igny8_core.auth.models import Site
|
|
from igny8_core.business.automation.models import AutomationConfig
|
|
from igny8_core.business.integration.models import PublishingSettings
|
|
from igny8_core.business.billing.models import AIModelConfig
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# Default stage configuration
|
|
DEFAULT_STAGE_CONFIG = {
|
|
'1': {'enabled': True, 'batch_size': 50, 'per_run_limit': 0, 'use_testing': False, 'budget_pct': 15},
|
|
'2': {'enabled': True, 'batch_size': 1, 'per_run_limit': 10, 'use_testing': False, 'budget_pct': 10},
|
|
'3': {'enabled': True, 'batch_size': 20, 'per_run_limit': 0}, # No AI
|
|
'4': {'enabled': True, 'batch_size': 1, 'per_run_limit': 5, 'use_testing': False, 'budget_pct': 40},
|
|
'5': {'enabled': True, 'batch_size': 1, 'per_run_limit': 5, 'use_testing': False, 'budget_pct': 5},
|
|
'6': {'enabled': True, 'batch_size': 1, 'per_run_limit': 20, 'use_testing': False, 'budget_pct': 30},
|
|
'7': {'enabled': True, 'per_run_limit': 10}, # No AI
|
|
}
|
|
|
|
STAGE_INFO = [
|
|
{'number': 1, 'name': 'Keywords → Clusters', 'has_ai': True},
|
|
{'number': 2, 'name': 'Clusters → Ideas', 'has_ai': True},
|
|
{'number': 3, 'name': 'Ideas → Tasks', 'has_ai': False},
|
|
{'number': 4, 'name': 'Tasks → Content', 'has_ai': True},
|
|
{'number': 5, 'name': 'Content → Prompts', 'has_ai': True},
|
|
{'number': 6, 'name': 'Prompts → Images', 'has_ai': True},
|
|
{'number': 7, 'name': 'Review → Approved', 'has_ai': False},
|
|
]
|
|
|
|
|
|
@extend_schema_view(
|
|
retrieve=extend_schema(
|
|
tags=['Site Settings'],
|
|
summary='Get unified site settings',
|
|
description='Get all AI & Automation settings for a site in one response',
|
|
parameters=[
|
|
OpenApiParameter(name='site_id', location='path', type=int, required=True),
|
|
]
|
|
),
|
|
update=extend_schema(
|
|
tags=['Site Settings'],
|
|
summary='Update unified site settings',
|
|
description='Update all AI & Automation settings for a site atomically',
|
|
parameters=[
|
|
OpenApiParameter(name='site_id', location='path', type=int, required=True),
|
|
]
|
|
),
|
|
)
|
|
class UnifiedSiteSettingsViewSet(viewsets.ViewSet):
|
|
"""
|
|
Unified API for all site AI & automation settings.
|
|
|
|
GET /api/v1/sites/{site_id}/unified-settings/
|
|
PUT /api/v1/sites/{site_id}/unified-settings/
|
|
"""
|
|
permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove]
|
|
throttle_scope = 'settings'
|
|
throttle_classes = [DebugScopedRateThrottle]
|
|
|
|
def retrieve(self, request, site_id=None):
|
|
"""Get all settings for a site in one response"""
|
|
site = get_object_or_404(Site, id=site_id, account=request.user.account)
|
|
|
|
# Get or create AutomationConfig
|
|
automation_config, _ = AutomationConfig.objects.get_or_create(
|
|
site=site,
|
|
defaults={
|
|
'account': site.account,
|
|
'is_enabled': False,
|
|
'frequency': 'daily',
|
|
'scheduled_time': '02:00',
|
|
}
|
|
)
|
|
|
|
# Get or create PublishingSettings
|
|
publishing_settings, _ = PublishingSettings.get_or_create_for_site(site)
|
|
|
|
# Get available models (Testing vs Live)
|
|
text_testing = AIModelConfig.get_testing_model('text')
|
|
text_live = AIModelConfig.get_live_model('text')
|
|
image_testing = AIModelConfig.get_testing_model('image')
|
|
image_live = AIModelConfig.get_live_model('image')
|
|
|
|
# Build stage configuration from AutomationConfig
|
|
stage_config = self._build_stage_config_from_automation(automation_config)
|
|
|
|
# Handle scheduled_time which might be a string or time object
|
|
scheduled_time = automation_config.scheduled_time
|
|
if scheduled_time:
|
|
if hasattr(scheduled_time, 'strftime'):
|
|
time_str = scheduled_time.strftime('%H:%M')
|
|
else:
|
|
time_str = str(scheduled_time)[:5] # Get HH:MM from string
|
|
else:
|
|
time_str = '02:00'
|
|
|
|
response_data = {
|
|
'site_id': site.id,
|
|
'site_name': site.name,
|
|
'automation': {
|
|
'enabled': automation_config.is_enabled,
|
|
'frequency': automation_config.frequency,
|
|
'time': time_str,
|
|
'last_run_at': automation_config.last_run_at.isoformat() if automation_config.last_run_at else None,
|
|
'next_run_at': automation_config.next_run_at.isoformat() if automation_config.next_run_at else None,
|
|
},
|
|
'stages': self._build_stage_matrix(stage_config),
|
|
'delays': {
|
|
'within_stage': automation_config.within_stage_delay,
|
|
'between_stage': automation_config.between_stage_delay,
|
|
},
|
|
'publishing': {
|
|
'auto_approval_enabled': publishing_settings.auto_approval_enabled,
|
|
'auto_publish_enabled': publishing_settings.auto_publish_enabled,
|
|
'publish_days': publishing_settings.publish_days,
|
|
'time_slots': publishing_settings.publish_time_slots,
|
|
# Calculated capacity (read-only)
|
|
'daily_capacity': publishing_settings.daily_capacity,
|
|
'weekly_capacity': publishing_settings.weekly_capacity,
|
|
'monthly_capacity': publishing_settings.monthly_capacity,
|
|
},
|
|
'available_models': {
|
|
'text': {
|
|
'testing': {
|
|
'id': text_testing.id if text_testing else None,
|
|
'name': text_testing.display_name if text_testing else None,
|
|
'model_name': text_testing.model_name if text_testing else None,
|
|
} if text_testing else None,
|
|
'live': {
|
|
'id': text_live.id if text_live else None,
|
|
'name': text_live.display_name if text_live else None,
|
|
'model_name': text_live.model_name if text_live else None,
|
|
} if text_live else None,
|
|
},
|
|
'image': {
|
|
'testing': {
|
|
'id': image_testing.id if image_testing else None,
|
|
'name': image_testing.display_name if image_testing else None,
|
|
'model_name': image_testing.model_name if image_testing else None,
|
|
} if image_testing else None,
|
|
'live': {
|
|
'id': image_live.id if image_live else None,
|
|
'name': image_live.display_name if image_live else None,
|
|
'model_name': image_live.model_name if image_live else None,
|
|
} if image_live else None,
|
|
},
|
|
},
|
|
}
|
|
|
|
return success_response(response_data, request=request)
|
|
|
|
def update(self, request, site_id=None):
|
|
"""Update all settings for a site atomically"""
|
|
site = get_object_or_404(Site, id=site_id, account=request.user.account)
|
|
data = request.data
|
|
|
|
try:
|
|
# Get or create AutomationConfig
|
|
automation_config, _ = AutomationConfig.objects.get_or_create(
|
|
site=site,
|
|
defaults={'account': site.account}
|
|
)
|
|
|
|
# Get or create PublishingSettings
|
|
publishing_settings, _ = PublishingSettings.get_or_create_for_site(site)
|
|
|
|
# Update automation settings
|
|
if 'automation' in data:
|
|
auto = data['automation']
|
|
if 'enabled' in auto:
|
|
automation_config.is_enabled = auto['enabled']
|
|
if 'frequency' in auto:
|
|
automation_config.frequency = auto['frequency']
|
|
if 'time' in auto:
|
|
from datetime import datetime
|
|
automation_config.scheduled_time = datetime.strptime(auto['time'], '%H:%M').time()
|
|
|
|
# Update stage configuration
|
|
if 'stages' in data:
|
|
self._update_stage_config(automation_config, data['stages'])
|
|
|
|
# Update delays
|
|
if 'delays' in data:
|
|
delays = data['delays']
|
|
if 'within_stage' in delays:
|
|
automation_config.within_stage_delay = delays['within_stage']
|
|
if 'between_stage' in delays:
|
|
automation_config.between_stage_delay = delays['between_stage']
|
|
|
|
automation_config.save()
|
|
|
|
# Update publishing settings
|
|
if 'publishing' in data:
|
|
pub = data['publishing']
|
|
if 'auto_approval_enabled' in pub:
|
|
publishing_settings.auto_approval_enabled = pub['auto_approval_enabled']
|
|
if 'auto_publish_enabled' in pub:
|
|
publishing_settings.auto_publish_enabled = pub['auto_publish_enabled']
|
|
if 'publish_days' in pub:
|
|
publishing_settings.publish_days = pub['publish_days']
|
|
if 'time_slots' in pub:
|
|
publishing_settings.publish_time_slots = pub['time_slots']
|
|
|
|
publishing_settings.save()
|
|
|
|
# Return the updated settings
|
|
return self.retrieve(request, site_id)
|
|
|
|
except Exception as e:
|
|
logger.exception(f"Error updating unified settings for site {site_id}")
|
|
return error_response(
|
|
f"Failed to update settings: {str(e)}",
|
|
None,
|
|
status.HTTP_400_BAD_REQUEST,
|
|
request
|
|
)
|
|
|
|
def _build_stage_config_from_automation(self, automation_config):
|
|
"""Build stage config dict from AutomationConfig model fields"""
|
|
return {
|
|
'1': {
|
|
'enabled': automation_config.stage_1_enabled,
|
|
'batch_size': automation_config.stage_1_batch_size,
|
|
'per_run_limit': automation_config.max_keywords_per_run,
|
|
'use_testing': False, # Default, can be stored in metadata later
|
|
},
|
|
'2': {
|
|
'enabled': automation_config.stage_2_enabled,
|
|
'batch_size': automation_config.stage_2_batch_size,
|
|
'per_run_limit': automation_config.max_clusters_per_run,
|
|
'use_testing': False,
|
|
},
|
|
'3': {
|
|
'enabled': automation_config.stage_3_enabled,
|
|
'batch_size': automation_config.stage_3_batch_size,
|
|
'per_run_limit': automation_config.max_ideas_per_run,
|
|
},
|
|
'4': {
|
|
'enabled': automation_config.stage_4_enabled,
|
|
'batch_size': automation_config.stage_4_batch_size,
|
|
'per_run_limit': automation_config.max_tasks_per_run,
|
|
'use_testing': False,
|
|
},
|
|
'5': {
|
|
'enabled': automation_config.stage_5_enabled,
|
|
'batch_size': automation_config.stage_5_batch_size,
|
|
'per_run_limit': automation_config.max_content_per_run,
|
|
'use_testing': False,
|
|
},
|
|
'6': {
|
|
'enabled': automation_config.stage_6_enabled,
|
|
'batch_size': automation_config.stage_6_batch_size,
|
|
'per_run_limit': automation_config.max_images_per_run,
|
|
'use_testing': False,
|
|
},
|
|
'7': {
|
|
'enabled': automation_config.stage_7_enabled,
|
|
'per_run_limit': automation_config.max_approvals_per_run,
|
|
},
|
|
}
|
|
|
|
def _build_stage_matrix(self, stage_config):
|
|
"""Build stage configuration matrix for frontend"""
|
|
result = []
|
|
for stage in STAGE_INFO:
|
|
num = str(stage['number'])
|
|
config = stage_config.get(num, DEFAULT_STAGE_CONFIG.get(num, {}))
|
|
|
|
stage_data = {
|
|
'number': stage['number'],
|
|
'name': stage['name'],
|
|
'has_ai': stage['has_ai'],
|
|
'enabled': config.get('enabled', True),
|
|
'batch_size': config.get('batch_size', 1),
|
|
'per_run_limit': config.get('per_run_limit', 0),
|
|
}
|
|
|
|
# Only include AI-related fields for stages that use AI
|
|
if stage['has_ai']:
|
|
stage_data['use_testing'] = config.get('use_testing', False)
|
|
stage_data['budget_pct'] = config.get('budget_pct', 20)
|
|
|
|
result.append(stage_data)
|
|
|
|
return result
|
|
|
|
def _update_stage_config(self, automation_config, stages):
|
|
"""Update AutomationConfig from stages array"""
|
|
for stage in stages:
|
|
num = stage.get('number')
|
|
if not num:
|
|
continue
|
|
|
|
if num == 1:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_1_enabled = stage['enabled']
|
|
if 'batch_size' in stage:
|
|
automation_config.stage_1_batch_size = stage['batch_size']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_keywords_per_run = stage['per_run_limit']
|
|
elif num == 2:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_2_enabled = stage['enabled']
|
|
if 'batch_size' in stage:
|
|
automation_config.stage_2_batch_size = stage['batch_size']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_clusters_per_run = stage['per_run_limit']
|
|
elif num == 3:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_3_enabled = stage['enabled']
|
|
if 'batch_size' in stage:
|
|
automation_config.stage_3_batch_size = stage['batch_size']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_ideas_per_run = stage['per_run_limit']
|
|
elif num == 4:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_4_enabled = stage['enabled']
|
|
if 'batch_size' in stage:
|
|
automation_config.stage_4_batch_size = stage['batch_size']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_tasks_per_run = stage['per_run_limit']
|
|
elif num == 5:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_5_enabled = stage['enabled']
|
|
if 'batch_size' in stage:
|
|
automation_config.stage_5_batch_size = stage['batch_size']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_content_per_run = stage['per_run_limit']
|
|
elif num == 6:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_6_enabled = stage['enabled']
|
|
if 'batch_size' in stage:
|
|
automation_config.stage_6_batch_size = stage['batch_size']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_images_per_run = stage['per_run_limit']
|
|
elif num == 7:
|
|
if 'enabled' in stage:
|
|
automation_config.stage_7_enabled = stage['enabled']
|
|
if 'per_run_limit' in stage:
|
|
automation_config.max_approvals_per_run = stage['per_run_limit']
|