""" Plugin System Database Models Defines the database schema for the isolated plugin architecture """ from sqlalchemy import ( Column, Integer, String, Text, DateTime, Boolean, JSON, ForeignKey, Index, ) from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship from sqlalchemy.sql import func import uuid from app.db.database import Base class Plugin(Base): """Plugin registry - tracks all installed plugins""" __tablename__ = "plugins" # Primary identification id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String(100), unique=True, nullable=False, index=True) slug = Column( String(100), unique=True, nullable=False, index=True ) # URL-safe identifier # Metadata display_name = Column(String(200), nullable=False) description = Column(Text) version = Column(String(50), nullable=False) author = Column(String(200)) homepage = Column(String(500)) repository = Column(String(500)) # Plugin file information package_path = Column(String(500), nullable=False) # Path to plugin package manifest_hash = Column(String(64), nullable=False) # SHA256 of manifest file package_hash = Column(String(64), nullable=False) # SHA256 of plugin package # Status and lifecycle status = Column(String(20), nullable=False, default="installed", index=True) # Statuses: installing, installed, enabled, disabled, error, uninstalling enabled = Column(Boolean, default=False, nullable=False, index=True) auto_enable = Column(Boolean, default=False, nullable=False) # Installation tracking installed_at = Column(DateTime, nullable=False, default=func.now()) enabled_at = Column(DateTime) last_updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) installed_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Configuration and requirements manifest_data = Column(JSON) # Complete plugin.yaml content config_schema = Column(JSON) # JSON schema for plugin configuration default_config = Column(JSON) # Default configuration values # Security and permissions required_permissions = Column(JSON) # List of required permission scopes api_scopes = Column(JSON) # Required API access scopes resource_limits = Column(JSON) # Memory, CPU, storage limits # Database isolation database_name = Column(String(100), unique=True) # Isolated database name database_url = Column(String(1000)) # Connection string for plugin database # Error tracking last_error = Column(Text) error_count = Column(Integer, default=0) last_error_at = Column(DateTime) # Relationships installed_by_user = relationship("User", back_populates="installed_plugins") configurations = relationship( "PluginConfiguration", back_populates="plugin", cascade="all, delete-orphan" ) instances = relationship( "PluginInstance", back_populates="plugin", cascade="all, delete-orphan" ) audit_logs = relationship( "PluginAuditLog", back_populates="plugin", cascade="all, delete-orphan" ) cron_jobs = relationship( "PluginCronJob", back_populates="plugin", cascade="all, delete-orphan" ) # Indexes for performance __table_args__ = ( Index("idx_plugin_status_enabled", "status", "enabled"), Index("idx_plugin_user_status", "installed_by_user_id", "status"), ) class PluginConfiguration(Base): """Plugin configuration instances - per user/environment configs""" __tablename__ = "plugin_configurations" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Configuration data name = Column(String(200), nullable=False) # Human-readable config name description = Column(Text) config_data = Column(JSON, nullable=False) # Non-sensitive configuration values encrypted_data = Column(Text) # Encrypted sensitive fields (JSON string) schema_version = Column(String(50)) # Schema version for migration support is_active = Column(Boolean, default=False, nullable=False) is_default = Column(Boolean, default=False, nullable=False) # Metadata created_at = Column(DateTime, nullable=False, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Relationships plugin = relationship("Plugin", back_populates="configurations") user = relationship("User", foreign_keys=[user_id]) created_by_user = relationship("User", foreign_keys=[created_by_user_id]) # Constraints __table_args__ = ( Index("idx_plugin_config_user_active", "plugin_id", "user_id", "is_active"), ) class PluginInstance(Base): """Plugin runtime instances - tracks running plugin processes""" __tablename__ = "plugin_instances" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False) configuration_id = Column( UUID(as_uuid=True), ForeignKey("plugin_configurations.id") ) # Runtime information instance_name = Column(String(200), nullable=False) process_id = Column(String(100)) # Docker container ID or process ID status = Column(String(20), nullable=False, default="starting", index=True) # Statuses: starting, running, stopping, stopped, error, crashed # Performance tracking start_time = Column(DateTime, nullable=False, default=func.now()) last_heartbeat = Column(DateTime, default=func.now()) stop_time = Column(DateTime) restart_count = Column(Integer, default=0) # Resource usage memory_usage_mb = Column(Integer) cpu_usage_percent = Column(Integer) # Health monitoring health_status = Column(String(20), default="unknown") # healthy, warning, critical health_message = Column(Text) last_health_check = Column(DateTime) # Error tracking last_error = Column(Text) error_count = Column(Integer, default=0) # Relationships plugin = relationship("Plugin", back_populates="instances") configuration = relationship("PluginConfiguration") __table_args__ = (Index("idx_plugin_instance_status", "plugin_id", "status"),) class PluginAuditLog(Base): """Audit logging for all plugin activities""" __tablename__ = "plugin_audit_logs" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False) instance_id = Column(UUID(as_uuid=True), ForeignKey("plugin_instances.id")) # Event details event_type = Column( String(50), nullable=False, index=True ) # api_call, config_change, error, etc. action = Column(String(100), nullable=False) resource = Column(String(200)) # Resource being accessed # Context information user_id = Column(Integer, ForeignKey("users.id")) api_key_id = Column(Integer, ForeignKey("api_keys.id")) ip_address = Column(String(45)) # IPv4 or IPv6 user_agent = Column(String(500)) # Request/response data request_data = Column(JSON) # Sanitized request data response_status = Column(Integer) response_data = Column(JSON) # Sanitized response data # Performance metrics duration_ms = Column(Integer) # Status and errors success = Column(Boolean, nullable=False, index=True) error_message = Column(Text) # Timestamps timestamp = Column(DateTime, nullable=False, default=func.now(), index=True) # Relationships plugin = relationship("Plugin", back_populates="audit_logs") instance = relationship("PluginInstance") user = relationship("User") api_key = relationship("APIKey") __table_args__ = ( Index("idx_plugin_audit_plugin_time", "plugin_id", "timestamp"), Index("idx_plugin_audit_user_time", "user_id", "timestamp"), Index("idx_plugin_audit_event_type", "event_type", "timestamp"), ) class PluginCronJob(Base): """Plugin scheduled jobs and cron tasks""" __tablename__ = "plugin_cron_jobs" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False) # Job identification job_name = Column(String(200), nullable=False) job_id = Column( String(100), nullable=False, unique=True, index=True ) # Unique scheduler ID # Schedule configuration schedule = Column(String(100), nullable=False) # Cron expression timezone = Column(String(50), default="UTC") enabled = Column(Boolean, default=True, nullable=False, index=True) # Job details description = Column(Text) function_name = Column(String(200), nullable=False) # Plugin function to call job_data = Column(JSON) # Parameters for the job function # Execution tracking last_run_at = Column(DateTime) next_run_at = Column(DateTime, index=True) last_duration_ms = Column(Integer) run_count = Column(Integer, default=0) success_count = Column(Integer, default=0) error_count = Column(Integer, default=0) # Error handling last_error = Column(Text) last_error_at = Column(DateTime) max_retries = Column(Integer, default=3) retry_delay_seconds = Column(Integer, default=60) # Lifecycle created_at = Column(DateTime, nullable=False, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Relationships plugin = relationship("Plugin", back_populates="cron_jobs") created_by_user = relationship("User") __table_args__ = ( Index("idx_plugin_cron_next_run", "enabled", "next_run_at"), Index("idx_plugin_cron_plugin", "plugin_id", "enabled"), ) class PluginAPIGateway(Base): """API gateway configuration for plugin routing""" __tablename__ = "plugin_api_gateways" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) plugin_id = Column( UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False, unique=True ) # API routing configuration base_path = Column( String(200), nullable=False, unique=True ) # /api/v1/plugins/zammad internal_url = Column(String(500), nullable=False) # http://plugin-zammad:8000 # Security settings require_authentication = Column(Boolean, default=True, nullable=False) allowed_methods = Column( JSON, default=["GET", "POST", "PUT", "DELETE"] ) # HTTP methods rate_limit_per_minute = Column(Integer, default=60) rate_limit_per_hour = Column(Integer, default=1000) # CORS settings cors_enabled = Column(Boolean, default=True, nullable=False) cors_origins = Column(JSON, default=["*"]) cors_methods = Column(JSON, default=["GET", "POST", "PUT", "DELETE", "OPTIONS"]) cors_headers = Column(JSON, default=["*"]) # Circuit breaker settings circuit_breaker_enabled = Column(Boolean, default=True, nullable=False) failure_threshold = Column(Integer, default=5) recovery_timeout_seconds = Column(Integer, default=60) # Monitoring enabled = Column(Boolean, default=True, nullable=False, index=True) last_health_check = Column(DateTime) health_status = Column(String(20), default="unknown") # healthy, unhealthy, timeout # Timestamps created_at = Column(DateTime, nullable=False, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) # Relationships plugin = relationship("Plugin") # Add relationships to existing User model (import this in user.py) """ Add to User model: installed_plugins = relationship("Plugin", back_populates="installed_by_user") """ # Add relationships to existing APIKey model (import this in api_key.py) """ Add to APIKey model: plugin_audit_logs = relationship("PluginAuditLog", back_populates="api_key") """ class PluginPermission(Base): """Plugin permission grants - tracks user permissions for plugins""" __tablename__ = "plugin_permissions" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) # Permission details permission_name = Column( String(200), nullable=False ) # e.g., 'chatbot:invoke', 'rag:query' granted = Column( Boolean, default=True, nullable=False ) # True=granted, False=revoked # Grant/revoke tracking granted_at = Column(DateTime, nullable=False, default=func.now()) granted_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False) revoked_at = Column(DateTime) revoked_by_user_id = Column(Integer, ForeignKey("users.id")) # Metadata reason = Column(Text) # Reason for grant/revocation expires_at = Column(DateTime) # Optional expiration time # Relationships plugin = relationship("Plugin") user = relationship("User", foreign_keys=[user_id]) granted_by_user = relationship("User", foreign_keys=[granted_by_user_id]) revoked_by_user = relationship("User", foreign_keys=[revoked_by_user_id]) __table_args__ = ( Index("idx_plugin_permission_user_plugin", "user_id", "plugin_id"), Index("idx_plugin_permission_plugin_name", "plugin_id", "permission_name"), Index("idx_plugin_permission_active", "plugin_id", "user_id", "granted"), )