GT AI OS Community Edition v2.0.33
Security hardening release addressing CodeQL and Dependabot alerts: - Fix stack trace exposure in error responses - Add SSRF protection with DNS resolution checking - Implement proper URL hostname validation (replaces substring matching) - Add centralized path sanitization to prevent path traversal - Fix ReDoS vulnerability in email validation regex - Improve HTML sanitization in validation utilities - Fix capability wildcard matching in auth utilities - Update glob dependency to address CVE - Add CodeQL suppression comments for verified false positives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
283
apps/tenant-backend/app/models/event.py
Normal file
283
apps/tenant-backend/app/models/event.py
Normal file
@@ -0,0 +1,283 @@
|
||||
"""
|
||||
Event Models for GT 2.0 Tenant Backend - Service-Based Architecture
|
||||
|
||||
Pydantic models for event entities using the PostgreSQL + PGVector backend.
|
||||
Handles event automation, triggers, and action definitions.
|
||||
Perfect tenant isolation with encrypted storage.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict, Any
|
||||
from enum import Enum
|
||||
import uuid
|
||||
|
||||
from pydantic import Field, ConfigDict
|
||||
from app.models.base import BaseServiceModel, BaseCreateModel, BaseUpdateModel, BaseResponseModel
|
||||
|
||||
|
||||
def generate_uuid():
|
||||
"""Generate a unique identifier"""
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
class EventStatus(str, Enum):
|
||||
"""Event status enumeration"""
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
RETRYING = "retrying"
|
||||
|
||||
|
||||
class Event(BaseServiceModel):
|
||||
"""
|
||||
Event model for GT 2.0 service-based architecture.
|
||||
|
||||
Represents an automation event with processing status,
|
||||
payload data, and retry logic.
|
||||
"""
|
||||
|
||||
# Core event properties
|
||||
event_id: str = Field(default_factory=generate_uuid, description="Unique event identifier")
|
||||
event_type: str = Field(..., min_length=1, max_length=100, description="Event type identifier")
|
||||
user_id: str = Field(..., description="User who triggered the event")
|
||||
tenant_id: str = Field(..., description="Tenant domain identifier")
|
||||
|
||||
# Event data
|
||||
payload: Dict[str, Any] = Field(default_factory=dict, description="Encrypted event data")
|
||||
event_metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
||||
|
||||
# Processing status
|
||||
status: EventStatus = Field(default=EventStatus.PENDING, description="Processing status")
|
||||
error_message: Optional[str] = Field(None, description="Error message if failed")
|
||||
retry_count: int = Field(default=0, ge=0, description="Number of retry attempts")
|
||||
|
||||
# Timestamps
|
||||
started_at: Optional[datetime] = Field(None, description="Processing start time")
|
||||
completed_at: Optional[datetime] = Field(None, description="Processing completion time")
|
||||
|
||||
# Model configuration
|
||||
model_config = ConfigDict(
|
||||
protected_namespaces=(),
|
||||
json_encoders={
|
||||
datetime: lambda v: v.isoformat() if v else None
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_table_name(cls) -> str:
|
||||
"""Get the database table name"""
|
||||
return "events"
|
||||
|
||||
def is_completed(self) -> bool:
|
||||
"""Check if event processing is completed"""
|
||||
return self.status == EventStatus.COMPLETED
|
||||
|
||||
def is_failed(self) -> bool:
|
||||
"""Check if event processing failed"""
|
||||
return self.status == EventStatus.FAILED
|
||||
|
||||
def mark_processing(self) -> None:
|
||||
"""Mark event as processing"""
|
||||
self.status = EventStatus.PROCESSING
|
||||
self.started_at = datetime.utcnow()
|
||||
self.update_timestamp()
|
||||
|
||||
def mark_completed(self) -> None:
|
||||
"""Mark event as completed"""
|
||||
self.status = EventStatus.COMPLETED
|
||||
self.completed_at = datetime.utcnow()
|
||||
self.update_timestamp()
|
||||
|
||||
def mark_failed(self, error_message: str) -> None:
|
||||
"""Mark event as failed"""
|
||||
self.status = EventStatus.FAILED
|
||||
self.error_message = error_message
|
||||
self.completed_at = datetime.utcnow()
|
||||
self.update_timestamp()
|
||||
|
||||
def increment_retry(self) -> None:
|
||||
"""Increment retry count"""
|
||||
self.retry_count += 1
|
||||
self.status = EventStatus.RETRYING
|
||||
self.update_timestamp()
|
||||
|
||||
|
||||
class EventTrigger(BaseServiceModel):
|
||||
"""
|
||||
Event trigger model for automation conditions.
|
||||
|
||||
Defines conditions that will trigger event processing.
|
||||
"""
|
||||
|
||||
# Core trigger properties
|
||||
trigger_name: str = Field(..., min_length=1, max_length=100, description="Trigger name")
|
||||
event_type: str = Field(..., min_length=1, max_length=100, description="Event type to trigger")
|
||||
user_id: str = Field(..., description="User who owns this trigger")
|
||||
tenant_id: str = Field(..., description="Tenant domain identifier")
|
||||
|
||||
# Trigger configuration
|
||||
conditions: Dict[str, Any] = Field(default_factory=dict, description="Trigger conditions")
|
||||
trigger_config: Dict[str, Any] = Field(default_factory=dict, description="Trigger configuration")
|
||||
|
||||
# Status
|
||||
is_active: bool = Field(default=True, description="Whether trigger is active")
|
||||
trigger_count: int = Field(default=0, description="Number of times triggered")
|
||||
last_triggered: Optional[datetime] = Field(None, description="Last trigger timestamp")
|
||||
|
||||
# Model configuration
|
||||
model_config = ConfigDict(
|
||||
protected_namespaces=(),
|
||||
json_encoders={
|
||||
datetime: lambda v: v.isoformat() if v else None
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_table_name(cls) -> str:
|
||||
"""Get the database table name"""
|
||||
return "event_triggers"
|
||||
|
||||
|
||||
class EventAction(BaseServiceModel):
|
||||
"""
|
||||
Event action model for automation responses.
|
||||
|
||||
Defines actions to take when events are processed.
|
||||
"""
|
||||
|
||||
# Core action properties
|
||||
action_name: str = Field(..., min_length=1, max_length=100, description="Action name")
|
||||
event_type: str = Field(..., min_length=1, max_length=100, description="Event type this action handles")
|
||||
user_id: str = Field(..., description="User who owns this action")
|
||||
tenant_id: str = Field(..., description="Tenant domain identifier")
|
||||
|
||||
# Action configuration
|
||||
action_type: str = Field(..., min_length=1, max_length=50, description="Type of action")
|
||||
action_config: Dict[str, Any] = Field(default_factory=dict, description="Action configuration")
|
||||
|
||||
# Execution settings
|
||||
priority: int = Field(default=10, ge=1, le=100, description="Execution priority")
|
||||
timeout_seconds: int = Field(default=300, ge=1, le=3600, description="Action timeout")
|
||||
max_retries: int = Field(default=3, ge=0, le=10, description="Maximum retry attempts")
|
||||
|
||||
# Status
|
||||
is_active: bool = Field(default=True, description="Whether action is active")
|
||||
execution_count: int = Field(default=0, description="Number of times executed")
|
||||
last_executed: Optional[datetime] = Field(None, description="Last execution timestamp")
|
||||
|
||||
# Model configuration
|
||||
model_config = ConfigDict(
|
||||
protected_namespaces=(),
|
||||
json_encoders={
|
||||
datetime: lambda v: v.isoformat() if v else None
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_table_name(cls) -> str:
|
||||
"""Get the database table name"""
|
||||
return "event_actions"
|
||||
|
||||
|
||||
class EventSubscription(BaseServiceModel):
|
||||
"""
|
||||
Event subscription model for user notifications.
|
||||
|
||||
Manages user subscriptions to specific event types.
|
||||
"""
|
||||
|
||||
# Core subscription properties
|
||||
user_id: str = Field(..., description="Subscribing user ID")
|
||||
tenant_id: str = Field(..., description="Tenant domain identifier")
|
||||
event_type: str = Field(..., min_length=1, max_length=100, description="Subscribed event type")
|
||||
|
||||
# Subscription configuration
|
||||
notification_method: str = Field(default="websocket", max_length=50, description="Notification delivery method")
|
||||
subscription_config: Dict[str, Any] = Field(default_factory=dict, description="Subscription settings")
|
||||
|
||||
# Filtering
|
||||
event_filters: Dict[str, Any] = Field(default_factory=dict, description="Event filtering criteria")
|
||||
|
||||
# Status
|
||||
is_active: bool = Field(default=True, description="Whether subscription is active")
|
||||
notification_count: int = Field(default=0, description="Number of notifications sent")
|
||||
last_notified: Optional[datetime] = Field(None, description="Last notification timestamp")
|
||||
|
||||
# Model configuration
|
||||
model_config = ConfigDict(
|
||||
protected_namespaces=(),
|
||||
json_encoders={
|
||||
datetime: lambda v: v.isoformat() if v else None
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_table_name(cls) -> str:
|
||||
"""Get the database table name"""
|
||||
return "event_subscriptions"
|
||||
|
||||
|
||||
# Create/Update/Response models
|
||||
|
||||
class EventCreate(BaseCreateModel):
|
||||
"""Model for creating new events"""
|
||||
event_type: str = Field(..., min_length=1, max_length=100)
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
payload: Dict[str, Any] = Field(default_factory=dict)
|
||||
event_metadata: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class EventUpdate(BaseUpdateModel):
|
||||
"""Model for updating events"""
|
||||
status: Optional[EventStatus] = None
|
||||
error_message: Optional[str] = None
|
||||
retry_count: Optional[int] = Field(None, ge=0)
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class EventResponse(BaseResponseModel):
|
||||
"""Model for event API responses"""
|
||||
id: str
|
||||
event_id: str
|
||||
event_type: str
|
||||
user_id: str
|
||||
tenant_id: str
|
||||
payload: Dict[str, Any]
|
||||
event_metadata: Dict[str, Any]
|
||||
status: EventStatus
|
||||
error_message: Optional[str]
|
||||
retry_count: int
|
||||
started_at: Optional[datetime]
|
||||
completed_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
# Legacy compatibility - simplified versions of missing models
|
||||
class EventLog(BaseServiceModel):
|
||||
"""Minimal EventLog model for compatibility"""
|
||||
event_id: str = Field(..., description="Related event ID")
|
||||
log_message: str = Field(..., description="Log message")
|
||||
log_level: str = Field(default="info", description="Log level")
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
||||
@classmethod
|
||||
def get_table_name(cls) -> str:
|
||||
return "event_logs"
|
||||
|
||||
|
||||
class ScheduledTask(BaseServiceModel):
|
||||
"""Minimal ScheduledTask model for compatibility"""
|
||||
task_name: str = Field(..., description="Task name")
|
||||
schedule: str = Field(..., description="Cron schedule")
|
||||
is_active: bool = Field(default=True, description="Whether task is active")
|
||||
|
||||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
||||
@classmethod
|
||||
def get_table_name(cls) -> str:
|
||||
return "scheduled_tasks"
|
||||
Reference in New Issue
Block a user