import os from sqlalchemy import ( Column, String, UUID, DateTime, ForeignKey, JSON, Text, CheckConstraint, Boolean, ) from sqlalchemy.sql import func from sqlalchemy.orm import relationship, backref from src.config.database import Base import uuid class Client(Base): __tablename__ = "clients" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String, nullable=False) email = Column(String, unique=True, index=True, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) class User(Base): __tablename__ = "users" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) email = Column(String, unique=True, index=True, nullable=False) password_hash = Column(String, nullable=False) client_id = Column( UUID(as_uuid=True), ForeignKey("clients.id", ondelete="CASCADE"), nullable=True ) is_active = Column(Boolean, default=False) is_admin = Column(Boolean, default=False) email_verified = Column(Boolean, default=False) verification_token = Column(String, nullable=True) verification_token_expiry = Column(DateTime(timezone=True), nullable=True) password_reset_token = Column(String, nullable=True) password_reset_expiry = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) client = relationship( "Client", backref=backref("user", uselist=False, cascade="all, delete-orphan") ) class AgentFolder(Base): __tablename__ = "agent_folders" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) client_id = Column(UUID(as_uuid=True), ForeignKey("clients.id", ondelete="CASCADE")) name = Column(String, nullable=False) description = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) client = relationship("Client", backref="agent_folders") agents = relationship("Agent", back_populates="folder") class Agent(Base): __tablename__ = "agents" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) client_id = Column(UUID(as_uuid=True), ForeignKey("clients.id", ondelete="CASCADE")) name = Column(String, nullable=False) description = Column(Text, nullable=True) type = Column(String, nullable=False) model = Column(String, nullable=True, default="") api_key_id = Column( UUID(as_uuid=True), ForeignKey("api_keys.id", ondelete="SET NULL"), nullable=True, ) instruction = Column(Text) agent_card_url = Column(String, nullable=True) folder_id = Column( UUID(as_uuid=True), ForeignKey("agent_folders.id", ondelete="SET NULL"), nullable=True, ) config = Column(JSON, default={}) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) __table_args__ = ( CheckConstraint( "type IN ('llm', 'sequential', 'parallel', 'loop', 'a2a', 'workflow')", name="check_agent_type", ), ) folder = relationship("AgentFolder", back_populates="agents") api_key_ref = relationship("ApiKey", foreign_keys=[api_key_id]) @property def agent_card_url_property(self) -> str: """Virtual URL for the agent card""" if self.agent_card_url: return self.agent_card_url return f"{os.getenv('API_URL', '')}/api/v1/a2a/{self.id}/.well-known/agent.json" def to_dict(self): """Converts the object to a dictionary, converting UUIDs to strings""" result = {} for key, value in self.__dict__.items(): if key.startswith("_"): continue if isinstance(value, uuid.UUID): result[key] = str(value) elif isinstance(value, dict): result[key] = self._convert_dict(value) elif isinstance(value, list): result[key] = [ ( self._convert_dict(item) if isinstance(item, dict) else str(item) if isinstance(item, uuid.UUID) else item ) for item in value ] else: result[key] = value result["agent_card_url"] = self.agent_card_url_property return result def _convert_dict(self, d): """Converts UUIDs to a dictionary for strings""" result = {} for key, value in d.items(): if isinstance(value, uuid.UUID): result[key] = str(value) elif isinstance(value, dict): result[key] = self._convert_dict(value) elif isinstance(value, list): result[key] = [ ( self._convert_dict(item) if isinstance(item, dict) else str(item) if isinstance(item, uuid.UUID) else item ) for item in value ] else: result[key] = value return result class MCPServer(Base): __tablename__ = "mcp_servers" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String, nullable=False) description = Column(Text, nullable=True) config_type = Column(String, nullable=False, default="studio") config_json = Column(JSON, nullable=False, default={}) environments = Column(JSON, nullable=False, default={}) tools = Column(JSON, nullable=False, default=[]) type = Column(String, nullable=False, default="official") created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) __table_args__ = ( CheckConstraint( "type IN ('official', 'community')", name="check_mcp_server_type" ), CheckConstraint( "config_type IN ('studio', 'sse')", name="check_mcp_server_config_type" ), ) class Tool(Base): __tablename__ = "tools" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name = Column(String, nullable=False) description = Column(Text, nullable=True) config_json = Column(JSON, nullable=False, default={}) environments = Column(JSON, nullable=False, default={}) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) class Session(Base): __tablename__ = "sessions" __table_args__ = {"extend_existing": True, "info": {"skip_autogenerate": True}} id = Column(String, primary_key=True) app_name = Column(String) user_id = Column(String) state = Column(JSON) create_time = Column(DateTime(timezone=True)) update_time = Column(DateTime(timezone=True)) class AuditLog(Base): __tablename__ = "audit_logs" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) action = Column(String, nullable=False) resource_type = Column(String, nullable=False) resource_id = Column(String, nullable=True) details = Column(JSON, nullable=True) ip_address = Column(String, nullable=True) user_agent = Column(String, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) user = relationship("User", backref="audit_logs") class ApiKey(Base): __tablename__ = "api_keys" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) client_id = Column(UUID(as_uuid=True), ForeignKey("clients.id", ondelete="CASCADE")) name = Column(String, nullable=False) provider = Column(String, nullable=False) encrypted_key = Column(String, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) is_active = Column(Boolean, default=True) client = relationship("Client", backref="api_keys")