8 Phases refactor

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-03 16:08:02 +00:00
parent 30bbcb08a1
commit 39df00e5ae
55 changed files with 2120 additions and 5527 deletions

View File

@@ -1,530 +0,0 @@
"""
Sites Renderer Adapter
Phase 5: Sites Renderer & Publishing
Stage 4: Enhanced with Stage 3 metadata (clusters, taxonomies, internal links)
Adapter for deploying sites to IGNY8 Sites renderer.
"""
import logging
import json
import os
from typing import Dict, Any, Optional, List
from pathlib import Path
from datetime import datetime
from igny8_core.business.site_building.models import SiteBlueprint
from igny8_core.business.publishing.models import DeploymentRecord
from igny8_core.business.publishing.services.adapters.base_adapter import BaseAdapter
logger = logging.getLogger(__name__)
class SitesRendererAdapter(BaseAdapter):
"""
Adapter for deploying sites to IGNY8 Sites renderer.
Writes site definitions to filesystem for Sites container to serve.
"""
def __init__(self):
self.sites_data_path = os.getenv('SITES_DATA_PATH', '/data/app/sites-data')
def deploy(self, site_blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Deploy site blueprint to Sites renderer.
Args:
site_blueprint: SiteBlueprint instance to deploy
Returns:
dict: Deployment result with status and deployment record
"""
try:
# Create deployment record
deployment = DeploymentRecord.objects.create(
account=site_blueprint.account,
site=site_blueprint.site,
sector=site_blueprint.sector,
site_blueprint=site_blueprint,
version=site_blueprint.version,
status='deploying'
)
# Build site definition
site_definition = self._build_site_definition(site_blueprint)
# Write to filesystem
deployment_path = self._write_site_definition(
site_blueprint,
site_definition,
deployment.version
)
# Update deployment record
deployment.status = 'deployed'
deployment.deployed_version = site_blueprint.version
deployment.deployment_url = self._get_deployment_url(site_blueprint)
deployment.metadata = {
'deployment_path': str(deployment_path),
'site_definition': site_definition
}
deployment.save()
# Update blueprint
site_blueprint.deployed_version = site_blueprint.version
site_blueprint.status = 'deployed'
site_blueprint.save(update_fields=['deployed_version', 'status', 'updated_at'])
logger.info(
f"[SitesRendererAdapter] Successfully deployed site {site_blueprint.id} v{deployment.version}"
)
return {
'success': True,
'deployment_id': deployment.id,
'version': deployment.version,
'deployment_url': deployment.deployment_url,
'deployment_path': str(deployment_path)
}
except Exception as e:
logger.error(
f"[SitesRendererAdapter] Error deploying site {site_blueprint.id}: {str(e)}",
exc_info=True
)
# Update deployment record with error
if 'deployment' in locals():
deployment.status = 'failed'
deployment.error_message = str(e)
deployment.save()
return {
'success': False,
'error': str(e)
}
def _build_site_definition(self, site_blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Build site definition JSON from blueprint.
Merges actual Content from Writer into PageBlueprint blocks.
Stage 4: Enhanced with Stage 3 metadata (clusters, taxonomies, internal links).
Args:
site_blueprint: SiteBlueprint instance
Returns:
dict: Site definition structure
"""
from igny8_core.business.content.models import Tasks, Content, ContentClusterMap
# Get all pages
pages = []
content_id_to_page = {} # Map content IDs to pages for metadata lookup
for page in site_blueprint.pages.all().order_by('order'):
# Get blocks from blueprint (placeholders)
blocks = page.blocks_json or []
page_metadata = {
'content_type': page.content_type if hasattr(page, 'content_type') else None,
'cluster_id': None,
'cluster_name': None,
'content_structure': None,
'taxonomy_terms': [], # Changed from taxonomy_id/taxonomy_name to list of terms
'internal_links': []
}
# Try to find actual Content from Writer
# PageBlueprint -> Task (by title pattern) -> Content
task_title = f"[Site Builder] {page.title or page.slug.replace('-', ' ').title()}"
task = Tasks.objects.filter(
account=page.account,
site=page.site,
sector=page.sector,
title=task_title
).first()
# If task exists, get its Content
if task and hasattr(task, 'content_record'):
content = task.content_record
# If content is published, merge its blocks
if content and content.status == 'publish' and content.json_blocks:
# Merge Content.json_blocks into PageBlueprint.blocks_json
# Content blocks take precedence over blueprint placeholders
blocks = content.json_blocks
logger.info(
f"[SitesRendererAdapter] Using published Content blocks for page {page.slug} "
f"(Content ID: {content.id})"
)
elif content and content.status == 'publish' and content.html_content:
# If no json_blocks but has html_content, convert to text block
blocks = [{
'type': 'text',
'data': {
'content': content.html_content,
'title': content.title or page.title
}
}]
logger.info(
f"[SitesRendererAdapter] Converted HTML content to text block for page {page.slug}"
)
# Stage 4: Add Stage 3 metadata if content exists
if content:
content_id_to_page[content.id] = page.slug
# Get cluster mapping
cluster_map = ContentClusterMap.objects.filter(content=content).first()
if cluster_map and cluster_map.cluster:
page_metadata['cluster_id'] = cluster_map.cluster.id
page_metadata['cluster_name'] = cluster_map.cluster.name
page_metadata['content_structure'] = cluster_map.role or task.content_structure if task else None
# Get taxonomy terms using M2M relationship
taxonomy_terms = content.taxonomy_terms.all()
if taxonomy_terms.exists():
page_metadata['taxonomy_terms'] = [
{'id': term.id, 'name': term.name, 'type': term.taxonomy_type}
for term in taxonomy_terms
]
# Get internal links from content
if content.internal_links:
page_metadata['internal_links'] = content.internal_links
# Use content_type if available
if content.content_type:
page_metadata['content_type'] = content.content_type
# Fallback to task metadata if content not found
if task and not page_metadata.get('cluster_id'):
if task.cluster:
page_metadata['cluster_id'] = task.cluster.id
page_metadata['cluster_name'] = task.cluster.name
page_metadata['content_structure'] = task.content_structure
if task.taxonomy:
page_metadata['taxonomy_id'] = task.taxonomy.id
page_metadata['taxonomy_name'] = task.taxonomy.name
if task.content_type:
page_metadata['content_type'] = task.content_type
pages.append({
'id': page.id,
'slug': page.slug,
'title': page.title,
'type': page.type,
'blocks': blocks,
'status': page.status,
'metadata': page_metadata, # Stage 4: Add metadata
})
# Stage 4: Build navigation with cluster grouping
navigation = self._build_navigation_with_metadata(site_blueprint, pages)
# Stage 4: Build taxonomy tree for breadcrumbs
taxonomy_tree = self._build_taxonomy_tree(site_blueprint)
# Build site definition
definition = {
'id': site_blueprint.id,
'name': site_blueprint.name,
'description': site_blueprint.description,
'version': site_blueprint.version,
'layout': site_blueprint.structure_json.get('layout', 'default'),
'theme': site_blueprint.structure_json.get('theme', {}),
'navigation': navigation, # Stage 4: Enhanced navigation
'taxonomy_tree': taxonomy_tree, # Stage 4: Taxonomy tree for breadcrumbs
'pages': pages,
'config': site_blueprint.config_json,
'created_at': site_blueprint.created_at.isoformat(),
'updated_at': site_blueprint.updated_at.isoformat(),
}
return definition
def _build_navigation_with_metadata(
self,
site_blueprint: SiteBlueprint,
pages: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""
Build navigation structure with cluster grouping.
Stage 4: Groups pages by cluster for better navigation.
Args:
site_blueprint: SiteBlueprint instance
pages: List of page dictionaries
Returns:
List of navigation items
"""
# If explicit navigation exists in structure_json, use it
explicit_nav = site_blueprint.structure_json.get('navigation', [])
if explicit_nav:
return explicit_nav
# Otherwise, build navigation from pages grouped by cluster
navigation = []
# Group pages by cluster
cluster_groups = {}
ungrouped_pages = []
for page in pages:
if page.get('status') in ['published', 'ready']:
cluster_id = page.get('metadata', {}).get('cluster_id')
if cluster_id:
if cluster_id not in cluster_groups:
cluster_groups[cluster_id] = {
'cluster_id': cluster_id,
'cluster_name': page.get('metadata', {}).get('cluster_name', 'Unknown'),
'pages': []
}
cluster_groups[cluster_id]['pages'].append({
'slug': page['slug'],
'title': page['title'],
'type': page['type']
})
else:
ungrouped_pages.append({
'slug': page['slug'],
'title': page['title'],
'type': page['type']
})
# Add cluster groups to navigation
for cluster_group in cluster_groups.values():
navigation.append({
'type': 'cluster',
'name': cluster_group['cluster_name'],
'items': cluster_group['pages']
})
# Add ungrouped pages
if ungrouped_pages:
navigation.extend(ungrouped_pages)
return navigation if navigation else [
{'slug': page['slug'], 'title': page['title']}
for page in pages
if page.get('status') in ['published', 'ready']
]
def _build_taxonomy_tree(self, site_blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Build taxonomy tree structure for breadcrumbs.
Stage 4: Creates hierarchical taxonomy structure.
Args:
site_blueprint: SiteBlueprint instance
Returns:
dict: Taxonomy tree structure
"""
taxonomies = site_blueprint.taxonomies.all()
tree = {
'categories': [],
'tags': [],
'product_categories': [],
'product_attributes': []
}
for taxonomy in taxonomies:
taxonomy_item = {
'id': taxonomy.id,
'name': taxonomy.name,
'slug': taxonomy.slug,
'type': taxonomy.taxonomy_type,
'description': taxonomy.description
}
if taxonomy.taxonomy_type in ['blog_category', 'product_category']:
category_key = 'product_categories' if 'product' in taxonomy.taxonomy_type else 'categories'
tree[category_key].append(taxonomy_item)
elif taxonomy.taxonomy_type in ['blog_tag', 'product_tag']:
tag_key = 'product_tags' if 'product' in taxonomy.taxonomy_type else 'tags'
if tag_key not in tree:
tree[tag_key] = []
tree[tag_key].append(taxonomy_item)
elif taxonomy.taxonomy_type == 'product_attribute':
tree['product_attributes'].append(taxonomy_item)
return tree
def _write_site_definition(
self,
site_blueprint: SiteBlueprint,
site_definition: Dict[str, Any],
version: int
) -> Path:
"""
Write site definition to filesystem.
Args:
site_blueprint: SiteBlueprint instance
site_definition: Site definition dict
version: Version number
Returns:
Path: Deployment path
"""
# Build path: /data/app/sites-data/clients/{site_id}/v{version}/
site_id = site_blueprint.site.id
deployment_dir = Path(self.sites_data_path) / 'clients' / str(site_id) / f'v{version}'
deployment_dir.mkdir(parents=True, exist_ok=True)
# Write site.json
site_json_path = deployment_dir / 'site.json'
with open(site_json_path, 'w', encoding='utf-8') as f:
json.dump(site_definition, f, indent=2, ensure_ascii=False)
# Write pages
pages_dir = deployment_dir / 'pages'
pages_dir.mkdir(exist_ok=True)
for page in site_definition.get('pages', []):
page_json_path = pages_dir / f"{page['slug']}.json"
with open(page_json_path, 'w', encoding='utf-8') as f:
json.dump(page, f, indent=2, ensure_ascii=False)
# Ensure assets directory exists
assets_dir = deployment_dir / 'assets'
assets_dir.mkdir(exist_ok=True)
(assets_dir / 'images').mkdir(exist_ok=True)
(assets_dir / 'documents').mkdir(exist_ok=True)
(assets_dir / 'media').mkdir(exist_ok=True)
logger.info(f"[SitesRendererAdapter] Wrote site definition to {deployment_dir}")
return deployment_dir
def _get_deployment_url(self, site_blueprint: SiteBlueprint) -> str:
"""
Get deployment URL for site.
Args:
site_blueprint: SiteBlueprint instance
Returns:
str: Deployment URL
"""
site_id = site_blueprint.site.id
# Get Sites Renderer URL from environment or use default
sites_renderer_host = os.getenv('SITES_RENDERER_HOST', '31.97.144.105')
sites_renderer_port = os.getenv('SITES_RENDERER_PORT', '8024')
sites_renderer_protocol = os.getenv('SITES_RENDERER_PROTOCOL', 'http')
# Construct URL: http://31.97.144.105:8024/{site_id}
# Sites Renderer routes: /:siteId/* -> SiteRenderer component
return f"{sites_renderer_protocol}://{sites_renderer_host}:{sites_renderer_port}/{site_id}"
# BaseAdapter interface implementation
def publish(
self,
content: Any,
destination_config: Dict[str, Any]
) -> Dict[str, Any]:
"""
Publish content to destination (implements BaseAdapter interface).
Args:
content: SiteBlueprint to publish
destination_config: Destination-specific configuration
Returns:
dict: Publishing result
"""
if not isinstance(content, SiteBlueprint):
return {
'success': False,
'error': 'SitesRendererAdapter only accepts SiteBlueprint instances'
}
result = self.deploy(content)
if result.get('success'):
return {
'success': True,
'external_id': str(result.get('deployment_id')),
'url': result.get('deployment_url'),
'published_at': datetime.now(),
'metadata': {
'deployment_path': result.get('deployment_path'),
'version': result.get('version')
}
}
else:
return {
'success': False,
'error': result.get('error'),
'metadata': {}
}
def test_connection(
self,
config: Dict[str, Any]
) -> Dict[str, Any]:
"""
Test connection to Sites renderer (implements BaseAdapter interface).
Args:
config: Destination configuration
Returns:
dict: Connection test result
"""
sites_data_path = config.get('sites_data_path', os.getenv('SITES_DATA_PATH', '/data/app/sites-data'))
try:
path = Path(sites_data_path)
if path.exists() and path.is_dir():
return {
'success': True,
'message': 'Sites data directory is accessible',
'details': {'path': str(path)}
}
else:
return {
'success': False,
'message': f'Sites data directory does not exist: {sites_data_path}',
'details': {}
}
except Exception as e:
return {
'success': False,
'message': f'Error accessing sites data directory: {str(e)}',
'details': {}
}
def get_status(
self,
published_id: str,
config: Dict[str, Any]
) -> Dict[str, Any]:
"""
Get publishing status for published content (implements BaseAdapter interface).
Args:
published_id: Deployment record ID
config: Destination configuration
Returns:
dict: Status information
"""
try:
deployment = DeploymentRecord.objects.get(id=published_id)
return {
'status': deployment.status,
'url': deployment.deployment_url,
'updated_at': deployment.updated_at,
'metadata': deployment.metadata or {}
}
except DeploymentRecord.DoesNotExist:
return {
'status': 'not_found',
'url': None,
'updated_at': None,
'metadata': {}
}

View File

@@ -1,422 +0,0 @@
"""
Deployment Readiness Service
Stage 4: Checks if site blueprint is ready for deployment
Validates cluster coverage, content validation, sync status, and taxonomy completeness.
"""
import logging
from typing import Dict, Any, List
from igny8_core.business.site_building.models import SiteBlueprint
from igny8_core.business.content.services.validation_service import ContentValidationService
from igny8_core.business.integration.services.sync_health_service import SyncHealthService
logger = logging.getLogger(__name__)
class DeploymentReadinessService:
"""
Service for checking deployment readiness.
"""
def __init__(self):
self.validation_service = ContentValidationService()
self.sync_health_service = SyncHealthService()
def check_readiness(self, site_blueprint_id: int) -> Dict[str, Any]:
"""
Check if site blueprint is ready for deployment.
Args:
site_blueprint_id: SiteBlueprint ID
Returns:
dict: {
'ready': bool,
'checks': {
'cluster_coverage': bool,
'content_validation': bool,
'sync_status': bool,
'taxonomy_completeness': bool
},
'errors': List[str],
'warnings': List[str],
'details': {
'cluster_coverage': dict,
'content_validation': dict,
'sync_status': dict,
'taxonomy_completeness': dict
}
}
"""
try:
blueprint = SiteBlueprint.objects.get(id=site_blueprint_id)
except SiteBlueprint.DoesNotExist:
return {
'ready': False,
'checks': {},
'errors': [f'SiteBlueprint {site_blueprint_id} not found'],
'warnings': [],
'details': {}
}
checks = {}
errors = []
warnings = []
details = {}
# Check 1: Cluster Coverage
cluster_check = self._check_cluster_coverage(blueprint)
checks['cluster_coverage'] = cluster_check['ready']
details['cluster_coverage'] = cluster_check
if not cluster_check['ready']:
errors.extend(cluster_check.get('errors', []))
if cluster_check.get('warnings'):
warnings.extend(cluster_check['warnings'])
# Check 2: Content Validation
content_check = self._check_content_validation(blueprint)
checks['content_validation'] = content_check['ready']
details['content_validation'] = content_check
if not content_check['ready']:
errors.extend(content_check.get('errors', []))
if content_check.get('warnings'):
warnings.extend(content_check['warnings'])
# Check 3: Sync Status (if WordPress integration exists)
sync_check = self._check_sync_status(blueprint)
checks['sync_status'] = sync_check['ready']
details['sync_status'] = sync_check
if not sync_check['ready']:
warnings.extend(sync_check.get('warnings', []))
if sync_check.get('errors'):
errors.extend(sync_check['errors'])
# Check 4: Taxonomy Completeness
taxonomy_check = self._check_taxonomy_completeness(blueprint)
checks['taxonomy_completeness'] = taxonomy_check['ready']
details['taxonomy_completeness'] = taxonomy_check
if not taxonomy_check['ready']:
warnings.extend(taxonomy_check.get('warnings', []))
if taxonomy_check.get('errors'):
errors.extend(taxonomy_check['errors'])
# Overall readiness: all critical checks must pass
ready = (
checks.get('cluster_coverage', False) and
checks.get('content_validation', False)
)
return {
'ready': ready,
'checks': checks,
'errors': errors,
'warnings': warnings,
'details': details
}
def _check_cluster_coverage(self, blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Check if all clusters have required coverage.
Returns:
dict: {
'ready': bool,
'total_clusters': int,
'covered_clusters': int,
'incomplete_clusters': List[Dict],
'errors': List[str],
'warnings': List[str]
}
"""
try:
cluster_links = blueprint.cluster_links.all()
total_clusters = cluster_links.count()
if total_clusters == 0:
return {
'ready': False,
'total_clusters': 0,
'covered_clusters': 0,
'incomplete_clusters': [],
'errors': ['No clusters attached to blueprint'],
'warnings': []
}
incomplete_clusters = []
covered_count = 0
for cluster_link in cluster_links:
if cluster_link.coverage_status == 'complete':
covered_count += 1
else:
incomplete_clusters.append({
'cluster_id': cluster_link.cluster_id,
'cluster_name': getattr(cluster_link.cluster, 'name', 'Unknown'),
'status': cluster_link.coverage_status,
'role': cluster_link.role
})
ready = covered_count == total_clusters
errors = []
warnings = []
if not ready:
if covered_count == 0:
errors.append('No clusters have complete coverage')
else:
warnings.append(
f'{total_clusters - covered_count} of {total_clusters} clusters need coverage'
)
return {
'ready': ready,
'total_clusters': total_clusters,
'covered_clusters': covered_count,
'incomplete_clusters': incomplete_clusters,
'errors': errors,
'warnings': warnings
}
except Exception as e:
logger.error(f"Error checking cluster coverage: {e}", exc_info=True)
return {
'ready': False,
'total_clusters': 0,
'covered_clusters': 0,
'incomplete_clusters': [],
'errors': [f'Error checking cluster coverage: {str(e)}'],
'warnings': []
}
def _check_content_validation(self, blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Check if all published content passes validation.
Returns:
dict: {
'ready': bool,
'total_content': int,
'valid_content': int,
'invalid_content': List[Dict],
'errors': List[str],
'warnings': List[str]
}
"""
try:
from igny8_core.business.content.models import Content, Tasks
# Get all content associated with this blueprint
# Content is linked via Tasks -> PageBlueprint -> SiteBlueprint
page_ids = blueprint.pages.values_list('id', flat=True)
# Find tasks that match page blueprints
tasks = Tasks.objects.filter(
account=blueprint.account,
site=blueprint.site,
sector=blueprint.sector
)
# Filter tasks that might be related to this blueprint
# (This is a simplified check - in practice, tasks should have blueprint reference)
content_items = Content.objects.filter(
task__in=tasks,
status='publish',
source='igny8'
)
total_content = content_items.count()
if total_content == 0:
return {
'ready': True, # No content to validate is OK
'total_content': 0,
'valid_content': 0,
'invalid_content': [],
'errors': [],
'warnings': ['No published content found for validation']
}
invalid_content = []
valid_count = 0
for content in content_items:
errors = self.validation_service.validate_for_publish(content)
if errors:
invalid_content.append({
'content_id': content.id,
'title': content.title or 'Untitled',
'errors': errors
})
else:
valid_count += 1
ready = len(invalid_content) == 0
errors = []
warnings = []
if not ready:
errors.append(
f'{len(invalid_content)} of {total_content} content items have validation errors'
)
return {
'ready': ready,
'total_content': total_content,
'valid_content': valid_count,
'invalid_content': invalid_content,
'errors': errors,
'warnings': warnings
}
except Exception as e:
logger.error(f"Error checking content validation: {e}", exc_info=True)
return {
'ready': False,
'total_content': 0,
'valid_content': 0,
'invalid_content': [],
'errors': [f'Error checking content validation: {str(e)}'],
'warnings': []
}
def _check_sync_status(self, blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Check sync status for WordPress integrations.
Returns:
dict: {
'ready': bool,
'has_integration': bool,
'sync_status': str,
'mismatch_count': int,
'errors': List[str],
'warnings': List[str]
}
"""
try:
from igny8_core.business.integration.models import SiteIntegration
integrations = SiteIntegration.objects.filter(
site=blueprint.site,
is_active=True,
platform='wordpress'
)
if not integrations.exists():
return {
'ready': True, # No WordPress integration is OK
'has_integration': False,
'sync_status': None,
'mismatch_count': 0,
'errors': [],
'warnings': []
}
# Get sync status from SyncHealthService
sync_status = self.sync_health_service.get_sync_status(blueprint.site.id)
overall_status = sync_status.get('overall_status', 'error')
is_healthy = overall_status == 'healthy'
# Count total mismatches
mismatch_count = sum(
i.get('mismatch_count', 0) for i in sync_status.get('integrations', [])
)
errors = []
warnings = []
if not is_healthy:
if overall_status == 'error':
errors.append('WordPress sync has errors')
else:
warnings.append('WordPress sync has warnings')
if mismatch_count > 0:
warnings.append(f'{mismatch_count} sync mismatches detected')
# Sync status doesn't block deployment, but should be warned
return {
'ready': True, # Sync issues are warnings, not blockers
'has_integration': True,
'sync_status': overall_status,
'mismatch_count': mismatch_count,
'errors': errors,
'warnings': warnings
}
except Exception as e:
logger.error(f"Error checking sync status: {e}", exc_info=True)
return {
'ready': True, # Don't block on sync check errors
'has_integration': False,
'sync_status': None,
'mismatch_count': 0,
'errors': [],
'warnings': [f'Could not check sync status: {str(e)}']
}
def _check_taxonomy_completeness(self, blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Check if taxonomies are complete for the site type.
Returns:
dict: {
'ready': bool,
'total_taxonomies': int,
'required_taxonomies': List[str],
'missing_taxonomies': List[str],
'errors': List[str],
'warnings': List[str]
}
"""
try:
taxonomies = blueprint.taxonomies.all()
total_taxonomies = taxonomies.count()
# Determine required taxonomies based on site type
site_type = blueprint.site.site_type if hasattr(blueprint.site, 'site_type') else None
required_types = []
if site_type == 'blog':
required_types = ['blog_category', 'blog_tag']
elif site_type == 'ecommerce':
required_types = ['product_category', 'product_tag', 'product_attribute']
elif site_type == 'company':
required_types = ['service_category']
existing_types = set(taxonomies.values_list('taxonomy_type', flat=True))
missing_types = set(required_types) - existing_types
ready = len(missing_types) == 0
errors = []
warnings = []
if not ready:
warnings.append(
f'Missing required taxonomies for {site_type} site: {", ".join(missing_types)}'
)
if total_taxonomies == 0:
warnings.append('No taxonomies defined')
return {
'ready': ready,
'total_taxonomies': total_taxonomies,
'required_taxonomies': required_types,
'missing_taxonomies': list(missing_types),
'errors': errors,
'warnings': warnings
}
except Exception as e:
logger.error(f"Error checking taxonomy completeness: {e}", exc_info=True)
return {
'ready': True, # Don't block on taxonomy check errors
'total_taxonomies': 0,
'required_taxonomies': [],
'missing_taxonomies': [],
'errors': [],
'warnings': [f'Could not check taxonomy completeness: {str(e)}']
}

View File

@@ -1,140 +1,17 @@
"""
Deployment Service
Phase 5: Sites Renderer & Publishing
Deployment Service - DEPRECATED
Manages deployment lifecycle for sites.
Legacy SiteBlueprint deployment functionality removed.
Use WordPress integration sync for publishing.
"""
import logging
from typing import Optional
from igny8_core.business.site_building.models import SiteBlueprint
from igny8_core.business.publishing.models import DeploymentRecord
logger = logging.getLogger(__name__)
class DeploymentService:
"""
Service for managing site deployment lifecycle.
DEPRECATED: Legacy SiteBlueprint deployment service.
Use integration sync services instead.
"""
def get_status(self, site_blueprint: SiteBlueprint) -> Optional[DeploymentRecord]:
"""
Get current deployment status for a site.
Args:
site_blueprint: SiteBlueprint instance
Returns:
DeploymentRecord or None
"""
return DeploymentRecord.objects.filter(
site_blueprint=site_blueprint,
status='deployed'
).order_by('-deployed_at').first()
def get_latest_deployment(
self,
site_blueprint: SiteBlueprint
) -> Optional[DeploymentRecord]:
"""
Get latest deployment record (any status).
Args:
site_blueprint: SiteBlueprint instance
Returns:
DeploymentRecord or None
"""
return DeploymentRecord.objects.filter(
site_blueprint=site_blueprint
).order_by('-created_at').first()
def rollback(
self,
site_blueprint: SiteBlueprint,
target_version: int
) -> dict:
"""
Rollback site to a previous version.
Args:
site_blueprint: SiteBlueprint instance
target_version: Version to rollback to
Returns:
dict: Rollback result
"""
try:
# Find deployment record for target version
target_deployment = DeploymentRecord.objects.filter(
site_blueprint=site_blueprint,
version=target_version,
status='deployed'
).first()
if not target_deployment:
return {
'success': False,
'error': f'Deployment for version {target_version} not found'
}
# Create new deployment record for rollback
rollback_deployment = DeploymentRecord.objects.create(
account=site_blueprint.account,
site=site_blueprint.site,
sector=site_blueprint.sector,
site_blueprint=site_blueprint,
version=target_version,
status='deployed',
deployed_version=target_version,
deployment_url=target_deployment.deployment_url,
metadata={
'rollback_from': site_blueprint.version,
'rollback_to': target_version
}
)
# Update blueprint
site_blueprint.deployed_version = target_version
site_blueprint.save(update_fields=['deployed_version', 'updated_at'])
logger.info(
f"[DeploymentService] Rolled back site {site_blueprint.id} to version {target_version}"
)
return {
'success': True,
'deployment_id': rollback_deployment.id,
'version': target_version
}
except Exception as e:
logger.error(
f"[DeploymentService] Error rolling back site {site_blueprint.id}: {str(e)}",
exc_info=True
)
return {
'success': False,
'error': str(e)
}
def list_deployments(
self,
site_blueprint: SiteBlueprint
) -> list:
"""
List all deployments for a site.
Args:
site_blueprint: SiteBlueprint instance
Returns:
list: List of DeploymentRecord instances
"""
return list(
DeploymentRecord.objects.filter(
site_blueprint=site_blueprint
).order_by('-created_at')
)
pass

View File

@@ -368,10 +368,8 @@ class PublisherService:
Adapter instance or None
"""
# Lazy import to avoid circular dependencies
if destination == 'sites':
from igny8_core.business.publishing.services.adapters.sites_renderer_adapter import SitesRendererAdapter
return SitesRendererAdapter()
elif destination == 'wordpress':
# REMOVED: 'sites' destination (SitesRendererAdapter) - legacy SiteBlueprint functionality
if destination == 'wordpress':
from igny8_core.business.publishing.services.adapters.wordpress_adapter import WordPressAdapter
return WordPressAdapter()
elif destination == 'shopify':