""" Integration ViewSet Phase 6: Site Integration & Multi-Destination Publishing """ from rest_framework import status from rest_framework.decorators import action from rest_framework.response import Response from igny8_core.api.base import SiteSectorModelViewSet 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.business.integration.models import SiteIntegration from igny8_core.business.integration.services.integration_service import IntegrationService from igny8_core.business.integration.services.sync_service import SyncService from igny8_core.business.integration.services.sync_health_service import SyncHealthService from igny8_core.business.integration.services.content_sync_service import ContentSyncService class IntegrationViewSet(SiteSectorModelViewSet): """ ViewSet for SiteIntegration model. """ queryset = SiteIntegration.objects.select_related('site') permission_classes = [IsAuthenticatedAndActive, IsEditorOrAbove] throttle_scope = 'integration' throttle_classes = [DebugScopedRateThrottle] def get_serializer_class(self): from rest_framework import serializers class SiteIntegrationSerializer(serializers.ModelSerializer): class Meta: model = SiteIntegration fields = '__all__' read_only_fields = ['created_at', 'updated_at', 'last_sync_at'] return SiteIntegrationSerializer @action(detail=True, methods=['post']) def test_connection(self, request, pk=None): """ Test connection to integrated platform. POST /api/v1/integration/integrations/{id}/test_connection/ """ integration = self.get_object() service = IntegrationService() result = service.test_connection(integration) if result.get('success'): return success_response(result, request=request) else: return error_response( result.get('message', 'Connection test failed'), status.HTTP_400_BAD_REQUEST, request ) @action(detail=True, methods=['post']) def sync(self, request, pk=None): """ Trigger synchronization with integrated platform. POST /api/v1/integration/integrations/{id}/sync/ Request body: { "direction": "both", # 'both', 'to_external', 'from_external' "content_types": ["blog_post", "page"] # Optional } """ integration = self.get_object() direction = request.data.get('direction', 'both') content_types = request.data.get('content_types') sync_service = SyncService() result = sync_service.sync(integration, direction=direction, content_types=content_types) response_status = status.HTTP_200_OK if result.get('success') else status.HTTP_400_BAD_REQUEST return success_response(result, request=request, status_code=response_status) @action(detail=True, methods=['get']) def sync_status(self, request, pk=None): """ Get sync status for integration. GET /api/v1/integration/integrations/{id}/sync_status/ """ integration = self.get_object() sync_service = SyncService() status_data = sync_service.get_sync_status(integration) return success_response(status_data, request=request) # Stage 4: Site-level sync endpoints @action(detail=False, methods=['get'], url_path='sites/(?P[^/.]+)/sync/status') def sync_status_by_site(self, request, site_id=None): """ Get sync status for all integrations on a site. Stage 4: Site-level sync health endpoint. GET /api/v1/integration/integrations/sites/{site_id}/sync/status/ """ try: site_id_int = int(site_id) except (ValueError, TypeError): return error_response( 'Invalid site_id', status.HTTP_400_BAD_REQUEST, request ) # Verify site belongs to user's account from igny8_core.auth.models import Site try: site = Site.objects.get(id=site_id_int, account=request.user.account) except Site.DoesNotExist: return error_response( 'Site not found', status.HTTP_404_NOT_FOUND, request ) sync_health_service = SyncHealthService() status_data = sync_health_service.get_sync_status(site_id_int) return success_response(status_data, request=request) @action(detail=False, methods=['post'], url_path='sites/(?P[^/.]+)/sync/run') def run_sync(self, request, site_id=None): """ Trigger sync for all integrations on a site. Stage 4: Site-level sync trigger endpoint. POST /api/v1/integration/integrations/sites/{site_id}/sync/run/ Request body: { "direction": "both", # Optional: 'both', 'to_external', 'from_external' "content_types": ["blog_post", "product"] # Optional } """ try: site_id_int = int(site_id) except (ValueError, TypeError): return error_response( 'Invalid site_id', status.HTTP_400_BAD_REQUEST, request ) # Verify site belongs to user's account from igny8_core.auth.models import Site try: site = Site.objects.get(id=site_id_int, account=request.user.account) except Site.DoesNotExist: return error_response( 'Site not found', status.HTTP_404_NOT_FOUND, request ) direction = request.data.get('direction', 'both') content_types = request.data.get('content_types') # Get all active integrations for this site integrations = SiteIntegration.objects.filter( site_id=site_id_int, is_active=True, sync_enabled=True ) if not integrations.exists(): return error_response( 'No active integrations found for this site', status.HTTP_400_BAD_REQUEST, request ) sync_service = SyncService() sync_health_service = SyncHealthService() results = [] for integration in integrations: result = sync_service.sync(integration, direction=direction, content_types=content_types) # Record sync run sync_health_service.record_sync_run(integration.id, result) results.append({ 'integration_id': integration.id, 'platform': integration.platform, 'result': result }) return success_response({ 'site_id': site_id_int, 'sync_results': results, 'total_integrations': len(results) }, request=request) @action(detail=False, methods=['get'], url_path='sites/(?P[^/.]+)/sync/mismatches') def get_mismatches(self, request, site_id=None): """ Get sync mismatches for a site. Stage 4: Detailed mismatch information. GET /api/v1/integration/integrations/sites/{site_id}/sync/mismatches/ """ try: site_id_int = int(site_id) except (ValueError, TypeError): return error_response( 'Invalid site_id', status.HTTP_400_BAD_REQUEST, request ) # Verify site belongs to user's account from igny8_core.auth.models import Site try: site = Site.objects.get(id=site_id_int, account=request.user.account) except Site.DoesNotExist: return error_response( 'Site not found', status.HTTP_404_NOT_FOUND, request ) sync_health_service = SyncHealthService() mismatches = sync_health_service.get_mismatches(site_id_int) return success_response(mismatches, request=request) @action(detail=False, methods=['get'], url_path='sites/(?P[^/.]+)/sync/logs') def get_sync_logs(self, request, site_id=None): """ Get sync logs for a site. Stage 4: Sync history and logs. GET /api/v1/integration/integrations/sites/{site_id}/sync/logs/ Query params: - limit: Number of logs to return (default: 100) - integration_id: Filter by specific integration """ try: site_id_int = int(site_id) except (ValueError, TypeError): return error_response( 'Invalid site_id', status.HTTP_400_BAD_REQUEST, request ) # Verify site belongs to user's account from igny8_core.auth.models import Site try: site = Site.objects.get(id=site_id_int, account=request.user.account) except Site.DoesNotExist: return error_response( 'Site not found', status.HTTP_404_NOT_FOUND, request ) limit = int(request.query_params.get('limit', 100)) integration_id = request.query_params.get('integration_id') sync_health_service = SyncHealthService() logs = sync_health_service.get_sync_logs( site_id_int, integration_id=int(integration_id) if integration_id else None, limit=limit ) return success_response({ 'site_id': site_id_int, 'logs': logs, 'count': len(logs) }, request=request)