1- from sqlalchemy import Column , String , Integer , Boolean , ForeignKey , Text , JSON , UniqueConstraint , TIMESTAMP
1+ from sqlalchemy import Column , String , Integer , Boolean , ForeignKey , Text , JSON , UniqueConstraint , TIMESTAMP , DateTime
22import sqlalchemy
33from sqlalchemy .orm import relationship
44from sqlalchemy .sql import func
5+ from datetime import datetime , UTC
6+ import json
57
68from app .models .base import Base
79
10+
811class Plugin (Base ):
912 """SQLAlchemy model for plugins."""
1013
@@ -44,6 +47,7 @@ class Plugin(Base):
4447 config_fields = Column (Text ) # Stored as JSON string
4548 messages = Column (Text ) # Stored as JSON string
4649 dependencies = Column (Text ) # Stored as JSON string
50+ required_services_runtime = Column (Text , nullable = True )
4751
4852 # Timestamps
4953 created_at = Column (String , default = func .now ())
@@ -60,6 +64,7 @@ class Plugin(Base):
6064
6165 # Relationships
6266 modules = relationship ("Module" , back_populates = "plugin" , cascade = "all, delete-orphan" )
67+ service_runtimes = relationship ("PluginServiceRuntime" , back_populates = "plugin" , cascade = "all, delete-orphan" )
6368
6469 def to_dict (self ):
6570 """Convert model to dictionary."""
@@ -118,6 +123,11 @@ def to_dict(self):
118123 else :
119124 result ["permissions" ] = []
120125
126+ if self .required_services_runtime :
127+ result ["requiredServicesRuntime" ] = json .loads (self .required_services_runtime )
128+ else :
129+ result ["requiredServicesRuntime" ] = []
130+
121131 return result
122132
123133 @classmethod
@@ -162,10 +172,81 @@ def from_dict(cls, data):
162172 # Remove modules from data as they are handled separately
163173 if "modules" in db_data :
164174 db_data .pop ("modules" )
175+
176+ # Handle service runtimes (only store names in plugin table)
177+ if "requiredServicesRuntime" in db_data and db_data ["requiredServicesRuntime" ] is not None :
178+ db_data ["required_services_runtime" ] = json .dumps ([
179+ r ["name" ] for r in db_data ["requiredServicesRuntime" ]
180+ ])
181+ db_data .pop ("requiredServicesRuntime" )
165182
166183 return cls (** db_data )
167184
168185
186+ class PluginServiceRuntime (Base ):
187+ """SQLAlchemy model for plugin service runtimes."""
188+
189+ __tablename__ = "plugin_service_runtime"
190+
191+ id = Column (String , primary_key = True , index = True )
192+ plugin_id = Column (String , ForeignKey ("plugin.id" ), nullable = False , index = True )
193+ plugin_slug = Column (String , nullable = False , index = True )
194+
195+ name = Column (String , nullable = False )
196+ source_url = Column (String )
197+ type = Column (String )
198+ install_command = Column (Text )
199+ start_command = Column (Text )
200+ healthcheck_url = Column (String )
201+ required_env_vars = Column (Text ) # store as JSON string
202+ status = Column (String , default = "pending" )
203+
204+ created_at = Column (DateTime , default = datetime .now (UTC ))
205+ updated_at = Column (DateTime , default = datetime .now (UTC ), onupdate = datetime .now (UTC ))
206+
207+ user_id = Column (String (32 ), ForeignKey ("users.id" , name = "fk_plugin_service_runtime_user_id" ), nullable = False )
208+ user = relationship ("User" )
209+
210+ # Relationship back to plugin
211+ plugin = relationship ("Plugin" , back_populates = "service_runtimes" )
212+
213+ def to_dict (self ):
214+ """
215+ Convert the model instance to a dictionary, handling JSON fields.
216+ """
217+ return {
218+ "id" : self .id ,
219+ "plugin_id" : self .plugin_id ,
220+ "plugin_slug" : self .plugin_slug ,
221+ "name" : self .name ,
222+ "source_url" : self .source_url ,
223+ "type" : self .type ,
224+ "install_command" : self .install_command ,
225+ "start_command" : self .start_command ,
226+ "healthcheck_url" : self .healthcheck_url ,
227+ "required_env_vars" : json .loads (self .required_env_vars ) if self .required_env_vars else [],
228+ "status" : self .status ,
229+ "user_id" : self .user_id ,
230+ "created_at" : self .created_at .isoformat () if self .created_at else None ,
231+ "updated_at" : self .updated_at .isoformat () if self .updated_at else None ,
232+ }
233+
234+ @classmethod
235+ def from_dict (cls , data : dict ):
236+ """
237+ Create a new instance from a dictionary, serializing JSON fields.
238+ """
239+ db_data = data .copy ()
240+
241+ if "required_env_vars" in db_data and db_data ["required_env_vars" ] is not None :
242+ db_data ["required_env_vars" ] = json .dumps (db_data ["required_env_vars" ])
243+
244+ # Handle conversion from camelCase to snake_case if necessary
245+ # For simplicity, we are assuming keys in the incoming dict match model attributes
246+
247+ return cls (** db_data )
248+
249+
169250class Module (Base ):
170251 """SQLAlchemy model for plugin modules."""
171252
0 commit comments