55import os
66import pickle
77import re
8+ import requests
9+ import tempfile
10+ import zipfile
811from typing import Dict
912
1013import uuid_utils .compat as uuid
@@ -124,7 +127,7 @@ class Meta:
124127 model = Tool
125128 fields = ['id' , 'name' , 'icon' , 'desc' , 'code' , 'input_field_list' , 'init_field_list' , 'init_params' ,
126129 'scope' , 'is_active' , 'user_id' , 'template_id' , 'workspace_id' , 'folder_id' , 'tool_type' , 'label' ,
127- 'create_time' , 'update_time' ]
130+ 'version' , ' create_time' , 'update_time' ]
128131
129132
130133class ToolExportModelSerializer (serializers .ModelSerializer ):
@@ -705,6 +708,101 @@ def add(self, instance, with_valid=True):
705708 tool_type = ToolType .CUSTOM ,
706709 folder_id = instance .get ('folder_id' , self .data .get ('workspace_id' )),
707710 template_id = internal_tool .id ,
711+ label = internal_tool .label ,
712+ is_active = False
713+ )
714+ tool .save ()
715+
716+ # 自动授权给创建者
717+ UserResourcePermissionSerializer (data = {
718+ 'workspace_id' : self .data .get ('workspace_id' ),
719+ 'user_id' : self .data .get ('user_id' ),
720+ 'auth_target_type' : AuthTargetType .TOOL .value
721+ }).auth_resource (str (tool_id ))
722+
723+ return ToolModelSerializer (tool ).data
724+
725+ class StoreTool (serializers .Serializer ):
726+ user_id = serializers .UUIDField (required = True , label = _ ("User ID" ))
727+ name = serializers .CharField (required = False , label = _ ("tool name" ), allow_null = True , allow_blank = True )
728+
729+ def get_appstore_tools (self ):
730+ self .is_valid (raise_exception = True )
731+ # 下载zip文件
732+ try :
733+ res = requests .get ('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip' , timeout = 5 )
734+ res .raise_for_status ()
735+ # 创建临时文件保存zip
736+ with tempfile .NamedTemporaryFile (delete = False , suffix = '.zip' ) as temp_zip :
737+ temp_zip .write (res .content )
738+ temp_zip_path = temp_zip .name
739+
740+ try :
741+ # 解压zip文件
742+ with zipfile .ZipFile (temp_zip_path , 'r' ) as zip_ref :
743+ # 获取zip中的第一个文件(假设只有一个json文件)
744+ json_filename = zip_ref .namelist ()[0 ]
745+ json_content = zip_ref .read (json_filename )
746+
747+ # 将json转换为字典
748+ tool_store = json .loads (json_content .decode ('utf-8' ))
749+ tag_dict = {tag ['name' ]: tag ['key' ] for tag in tool_store ['additionalProperties' ]['tags' ]}
750+ filter_apps = []
751+ for tool in tool_store ['apps' ]:
752+ if self .data .get ('name' , '' ) != '' :
753+ if self .data .get ('name' ).lower () not in tool .get ('name' , '' ).lower ():
754+ continue
755+ versions = tool .get ('versions' , [])
756+ tool ['label' ] = tag_dict [tool .get ('tags' )[0 ]] if tool .get ('tags' ) else ''
757+ tool ['version' ] = next (
758+ (version .get ('name' ) for version in versions if version .get ('downloadUrl' ) == tool ['downloadUrl' ]),
759+ )
760+ filter_apps .append (tool )
761+
762+ tool_store ['apps' ] = filter_apps
763+ return tool_store
764+ finally :
765+ # 清理临时文件
766+ os .unlink (temp_zip_path )
767+ except requests .RequestException as e :
768+ maxkb_logger .error (f"fetch appstore tools error: { e } " )
769+ return []
770+
771+ class AddStoreTool (serializers .Serializer ):
772+ user_id = serializers .UUIDField (required = True , label = _ ("User ID" ))
773+ workspace_id = serializers .CharField (required = True , label = _ ("workspace id" ))
774+ tool_id = serializers .CharField (required = True , label = _ ("tool id" ))
775+
776+ def add (self , instance : Dict , with_valid = True ):
777+ if with_valid :
778+ self .is_valid (raise_exception = True )
779+ AddInternalToolRequest (data = instance ).is_valid (raise_exception = True )
780+
781+ versions = instance .get ('versions' , [])
782+ download_url = instance .get ('download_url' )
783+ # 查找匹配的版本名称
784+ version_name = next (
785+ (version .get ('name' ) for version in versions if version .get ('downloadUrl' ) == download_url ),
786+ )
787+ res = requests .get (download_url , timeout = 5 )
788+ tool_data = RestrictedUnpickler (io .BytesIO (res .content )).load ().tool
789+ tool_id = uuid .uuid7 ()
790+ tool = Tool (
791+ id = tool_id ,
792+ name = tool_data .get ('name' ),
793+ desc = tool_data .get ('desc' ),
794+ code = tool_data .get ('code' ),
795+ user_id = self .data .get ('user_id' ),
796+ icon = instance .get ('icon' , '' ),
797+ workspace_id = self .data .get ('workspace_id' ),
798+ input_field_list = tool_data .get ('input_field_list' , []),
799+ init_field_list = tool_data .get ('init_field_list' , []),
800+ scope = ToolScope .WORKSPACE ,
801+ tool_type = ToolType .CUSTOM ,
802+ folder_id = instance .get ('folder_id' , self .data .get ('workspace_id' )),
803+ template_id = self .data .get ('tool_id' ),
804+ label = instance .get ('label' ),
805+ version = version_name ,
708806 is_active = False
709807 )
710808 tool .save ()
@@ -715,10 +813,50 @@ def add(self, instance, with_valid=True):
715813 'user_id' : self .data .get ('user_id' ),
716814 'auth_target_type' : AuthTargetType .TOOL .value
717815 }).auth_resource (str (tool_id ))
816+ try :
817+ requests .get (instance .get ('download_callback_url' ), timeout = 5 )
818+ except Exception as e :
819+ maxkb_logger .error (f"callback appstore tool download error: { e } " )
820+ return ToolModelSerializer (tool ).data
718821
822+ class UpdateStoreTool (serializers .Serializer ):
823+ user_id = serializers .UUIDField (required = True , label = _ ("User ID" ))
824+ workspace_id = serializers .CharField (required = True , label = _ ("workspace id" ))
825+ tool_id = serializers .UUIDField (required = True , label = _ ("tool id" ))
826+ download_url = serializers .CharField (required = True , label = _ ("download url" ))
827+ download_callback_url = serializers .CharField (required = True , label = _ ("download callback url" ))
828+ icon = serializers .CharField (required = True , label = _ ("icon" ), allow_null = True , allow_blank = True )
829+ versions = serializers .ListField (required = True , label = _ ("versions" ), child = serializers .DictField ())
830+
831+ def update_tool (self , with_valid = True ):
832+ if with_valid :
833+ self .is_valid (raise_exception = True )
834+ tool = QuerySet (Tool ).filter (id = self .data .get ('tool_id' )).first ()
835+ if tool is None :
836+ raise AppApiException (500 , _ ('Tool does not exist' ))
837+ # 查找匹配的版本名称
838+ version_name = next (
839+ (version .get ('name' ) for version in self .data .get ('versions' ) if version .get ('downloadUrl' ) == self .data .get ('download_url' )),
840+ )
841+ res = requests .get (self .data .get ('download_url' ), timeout = 5 )
842+ tool_data = RestrictedUnpickler (io .BytesIO (res .content )).load ().tool
843+ tool .name = tool_data .get ('name' )
844+ tool .desc = tool_data .get ('desc' )
845+ tool .code = tool_data .get ('code' )
846+ tool .input_field_list = tool_data .get ('input_field_list' , [])
847+ tool .init_field_list = tool_data .get ('init_field_list' , [])
848+ tool .icon = self .data .get ('icon' , tool .icon )
849+ tool .version = version_name
850+ # tool.is_active = False
851+ tool .save ()
852+ try :
853+ requests .get (self .data .get ('download_callback_url' ), timeout = 5 )
854+ except Exception as e :
855+ maxkb_logger .error (f"callback appstore tool download error: { e } " )
719856 return ToolModelSerializer (tool ).data
720857
721858
859+
722860class ToolTreeSerializer (serializers .Serializer ):
723861 class Query (serializers .Serializer ):
724862 workspace_id = serializers .CharField (required = True , label = _ ('workspace id' ))
0 commit comments