""" Integration Models Phase 6: Site Integration & Multi-Destination Publishing """ from django.db import models from django.core.validators import MinValueValidator from igny8_core.auth.models import AccountBaseModel class SiteIntegration(AccountBaseModel): """ Store integration configurations for sites. Each site can have multiple integrations (WordPress, Shopify, etc.). """ PLATFORM_CHOICES = [ ('wordpress', 'WordPress'), ('shopify', 'Shopify'), ('custom', 'Custom API'), ] PLATFORM_TYPE_CHOICES = [ ('cms', 'CMS'), ('ecommerce', 'Ecommerce'), ('custom_api', 'Custom API'), ] SYNC_STATUS_CHOICES = [ ('success', 'Success'), ('failed', 'Failed'), ('pending', 'Pending'), ('syncing', 'Syncing'), ] site = models.ForeignKey( 'igny8_core_auth.Site', on_delete=models.CASCADE, related_name='integrations', help_text="Site this integration belongs to" ) platform = models.CharField( max_length=50, choices=PLATFORM_CHOICES, db_index=True, help_text="Platform name: 'wordpress', 'shopify', 'custom'" ) platform_type = models.CharField( max_length=50, choices=PLATFORM_TYPE_CHOICES, default='cms', help_text="Platform type: 'cms', 'ecommerce', 'custom_api'" ) config_json = models.JSONField( default=dict, help_text="Platform-specific configuration (URLs, endpoints, etc.)" ) # Credentials stored as JSON (encryption handled at application level) credentials_json = models.JSONField( default=dict, help_text="Encrypted credentials (API keys, tokens, etc.)" ) is_active = models.BooleanField( default=True, db_index=True, help_text="Whether this integration is active" ) sync_enabled = models.BooleanField( default=False, help_text="Whether two-way sync is enabled" ) last_sync_at = models.DateTimeField( null=True, blank=True, help_text="Last successful sync timestamp" ) sync_status = models.CharField( max_length=20, choices=SYNC_STATUS_CHOICES, default='pending', db_index=True, help_text="Current sync status" ) sync_error = models.TextField( blank=True, null=True, help_text="Last sync error message" ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: app_label = 'integration' db_table = 'igny8_site_integrations' ordering = ['-created_at'] unique_together = [['site', 'platform']] indexes = [ models.Index(fields=['site', 'platform']), models.Index(fields=['site', 'is_active']), models.Index(fields=['account', 'platform']), models.Index(fields=['sync_status']), ] def __str__(self): return f"{self.site.name} - {self.get_platform_display()}" def get_credentials(self) -> dict: """ Get decrypted credentials. In production, this should decrypt credentials_json. For now, return as-is (encryption to be implemented). """ return self.credentials_json or {} def set_credentials(self, credentials: dict): """ Set encrypted credentials. In production, this should encrypt before storing. For now, store as-is (encryption to be implemented). """ self.credentials_json = credentials class SyncEvent(AccountBaseModel): """ Track sync events for debugging and monitoring. Stores real-time events for the debug status page. """ EVENT_TYPE_CHOICES = [ ('publish', 'Content Published'), ('sync', 'Status Synced'), ('metadata_sync', 'Metadata Synced'), ('error', 'Error'), ('webhook', 'Webhook Received'), ('test', 'Connection Test'), ] ACTION_CHOICES = [ ('content_publish', 'Content Publish'), ('status_update', 'Status Update'), ('metadata_update', 'Metadata Update'), ('test_connection', 'Test Connection'), ('webhook_received', 'Webhook Received'), ] integration = models.ForeignKey( SiteIntegration, on_delete=models.CASCADE, related_name='sync_events', help_text="Integration this event belongs to" ) site = models.ForeignKey( 'igny8_core_auth.Site', on_delete=models.CASCADE, related_name='sync_events', help_text="Site this event belongs to" ) event_type = models.CharField( max_length=50, choices=EVENT_TYPE_CHOICES, db_index=True, help_text="Type of sync event" ) action = models.CharField( max_length=100, choices=ACTION_CHOICES, db_index=True, help_text="Specific action performed" ) description = models.TextField( help_text="Human-readable description of the event" ) success = models.BooleanField( default=True, db_index=True, help_text="Whether the event was successful" ) # Related object references content_id = models.IntegerField( null=True, blank=True, db_index=True, help_text="IGNY8 content ID if applicable" ) external_id = models.CharField( max_length=255, null=True, blank=True, db_index=True, help_text="External platform ID (e.g., WordPress post ID)" ) # Event details (JSON for flexibility) details = models.JSONField( default=dict, help_text="Additional event details (request/response data, errors, etc.)" ) error_message = models.TextField( null=True, blank=True, help_text="Error message if event failed" ) # Duration tracking duration_ms = models.IntegerField( null=True, blank=True, help_text="Event duration in milliseconds" ) created_at = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: app_label = 'integration' db_table = 'igny8_sync_events' verbose_name = 'Sync Event' verbose_name_plural = 'Sync Events' ordering = ['-created_at'] indexes = [ models.Index(fields=['integration', '-created_at'], name='idx_integration_events'), models.Index(fields=['site', '-created_at'], name='idx_site_events'), models.Index(fields=['content_id'], name='idx_content_events'), models.Index(fields=['event_type', '-created_at'], name='idx_event_type_time'), ] def __str__(self): return f"{self.get_event_type_display()} - {self.description[:50]}" class PublishingSettings(AccountBaseModel): """ Site-level publishing configuration settings. Controls automatic approval, publishing limits, and scheduling. """ DEFAULT_PUBLISH_DAYS = ['mon', 'tue', 'wed', 'thu', 'fri'] DEFAULT_TIME_SLOTS = ['09:00', '14:00', '18:00'] site = models.OneToOneField( 'igny8_core_auth.Site', on_delete=models.CASCADE, related_name='publishing_settings', help_text="Site these publishing settings belong to" ) # Auto-approval settings auto_approval_enabled = models.BooleanField( default=True, help_text="Automatically approve content after review (moves to 'approved' status)" ) # Auto-publish settings auto_publish_enabled = models.BooleanField( default=True, help_text="Automatically publish approved content to the external site" ) # Publishing limits daily_publish_limit = models.PositiveIntegerField( default=3, validators=[MinValueValidator(1)], help_text="Maximum number of articles to publish per day" ) weekly_publish_limit = models.PositiveIntegerField( default=15, validators=[MinValueValidator(1)], help_text="Maximum number of articles to publish per week" ) monthly_publish_limit = models.PositiveIntegerField( default=50, validators=[MinValueValidator(1)], help_text="Maximum number of articles to publish per month" ) # Publishing schedule publish_days = models.JSONField( default=list, help_text="Days of the week to publish (mon, tue, wed, thu, fri, sat, sun)" ) publish_time_slots = models.JSONField( default=list, help_text="Times of day to publish (HH:MM format, e.g., ['09:00', '14:00', '18:00'])" ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: app_label = 'integration' db_table = 'igny8_publishing_settings' verbose_name = 'Publishing Settings' verbose_name_plural = 'Publishing Settings' def __str__(self): return f"Publishing Settings for {self.site.name}" def save(self, *args, **kwargs): """Set defaults for JSON fields if empty""" if not self.publish_days: self.publish_days = self.DEFAULT_PUBLISH_DAYS if not self.publish_time_slots: self.publish_time_slots = self.DEFAULT_TIME_SLOTS super().save(*args, **kwargs) @classmethod def get_or_create_for_site(cls, site): """Get or create publishing settings for a site with defaults""" settings, created = cls.objects.get_or_create( site=site, defaults={ 'account': site.account, 'auto_approval_enabled': True, 'auto_publish_enabled': True, 'daily_publish_limit': 3, 'weekly_publish_limit': 15, 'monthly_publish_limit': 50, 'publish_days': cls.DEFAULT_PUBLISH_DAYS, 'publish_time_slots': cls.DEFAULT_TIME_SLOTS, } ) return settings, created