github.com/instill-ai/component@v0.16.0-beta/pkg/connector/numbers/v0/main.go (about)

     1  //go:generate compogen readme --connector ./config ./README.mdx
     2  package numbers
     3  
     4  import (
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"mime/multipart"
    10  	"net/http"
    11  	"sync"
    12  
    13  	_ "embed"
    14  	b64 "encoding/base64"
    15  
    16  	"github.com/gabriel-vasile/mimetype"
    17  	"github.com/gofrs/uuid"
    18  	"go.uber.org/zap"
    19  	"google.golang.org/protobuf/types/known/structpb"
    20  
    21  	"github.com/instill-ai/component/pkg/base"
    22  )
    23  
    24  const urlRegisterAsset = "https://api.numbersprotocol.io/api/v3/assets/"
    25  const urlUserMe = "https://api.numbersprotocol.io/api/v3/auth/users/me"
    26  
    27  var once sync.Once
    28  var con *connector
    29  
    30  //go:embed config/definition.json
    31  var definitionJSON []byte
    32  
    33  //go:embed config/tasks.json
    34  var tasksJSON []byte
    35  
    36  type connector struct {
    37  	base.BaseConnector
    38  }
    39  
    40  type execution struct {
    41  	base.BaseConnectorExecution
    42  }
    43  
    44  type CommitCustomLicense struct {
    45  	Name     *string `json:"name,omitempty"`
    46  	Document *string `json:"document,omitempty"`
    47  }
    48  type CommitCustom struct {
    49  	AssetCreator      *string               `json:"assetCreator,omitempty"`
    50  	DigitalSourceType *string               `json:"digitalSourceType,omitempty"`
    51  	MiningPreference  *string               `json:"miningPreference,omitempty"`
    52  	GeneratedThrough  string                `json:"generatedThrough"`
    53  	GeneratedBy       *string               `json:"generatedBy,omitempty"`
    54  	CreatorWallet     *string               `json:"creatorWallet,omitempty"`
    55  	License           *CommitCustomLicense  `json:"license,omitempty"`
    56  	Metadata          *CommitCustomMetadata `json:"instillMetadata,omitempty"`
    57  }
    58  
    59  type CommitCustomMetadata struct {
    60  	Pipeline struct {
    61  		UID    string
    62  		Recipe interface{}
    63  	}
    64  }
    65  
    66  type Meta struct {
    67  	Proof struct {
    68  		Hash      string `json:"hash"`
    69  		MIMEType  string `json:"mimeType"`
    70  		Timestamp string `json:"timestamp"`
    71  	} `json:"proof"`
    72  }
    73  
    74  type Register struct {
    75  	Caption         *string       `json:"caption,omitempty"`
    76  	Headline        *string       `json:"headline,omitempty"`
    77  	NITCommitCustom *CommitCustom `json:"nit_commit_custom,omitempty"`
    78  	Meta
    79  }
    80  
    81  type Input struct {
    82  	Images            []string `json:"images"`
    83  	AssetCreator      *string  `json:"asset_creator,omitempty"`
    84  	Caption           *string  `json:"caption,omitempty"`
    85  	Headline          *string  `json:"headline,omitempty"`
    86  	DigitalSourceType *string  `json:"digital_source_type,omitempty"`
    87  	MiningPreference  *string  `json:"mining_preference,omitempty"`
    88  	GeneratedBy       *string  `json:"generated_by,omitempty"`
    89  	License           *struct {
    90  		Name     *string `json:"name,omitempty"`
    91  		Document *string `json:"document,omitempty"`
    92  	} `json:"license,omitempty"`
    93  }
    94  
    95  type Output struct {
    96  	AssetUrls []string `json:"asset_urls"`
    97  }
    98  
    99  func Init(l *zap.Logger, u base.UsageHandler) *connector {
   100  	once.Do(func() {
   101  		con = &connector{
   102  			BaseConnector: base.BaseConnector{
   103  				Logger:       l,
   104  				UsageHandler: u,
   105  			},
   106  		}
   107  		err := con.LoadConnectorDefinition(definitionJSON, tasksJSON, nil)
   108  		if err != nil {
   109  			panic(err)
   110  		}
   111  	})
   112  	return con
   113  }
   114  
   115  func (c *connector) CreateExecution(sysVars map[string]any, connection *structpb.Struct, task string) (*base.ExecutionWrapper, error) {
   116  	return &base.ExecutionWrapper{Execution: &execution{
   117  		BaseConnectorExecution: base.BaseConnectorExecution{Connector: c, SystemVariables: sysVars, Connection: connection, Task: task},
   118  	}}, nil
   119  }
   120  
   121  func getToken(config *structpb.Struct) string {
   122  	return fmt.Sprintf("token %s", config.GetFields()["capture_token"].GetStringValue())
   123  }
   124  
   125  func (e *execution) registerAsset(data []byte, reg Register) (string, error) {
   126  
   127  	var b bytes.Buffer
   128  
   129  	w := multipart.NewWriter(&b)
   130  	var fw io.Writer
   131  	var err error
   132  
   133  	fileName, _ := uuid.NewV4()
   134  	if fw, err = w.CreateFormFile("asset_file", fileName.String()+mimetype.Detect(data).Extension()); err != nil {
   135  		return "", err
   136  	}
   137  
   138  	if _, err := io.Copy(fw, bytes.NewReader(data)); err != nil {
   139  		return "", err
   140  	}
   141  
   142  	if reg.Caption != nil {
   143  		_ = w.WriteField("caption", *reg.Caption)
   144  	}
   145  
   146  	if reg.Headline != nil {
   147  		_ = w.WriteField("headline", *reg.Headline)
   148  	}
   149  
   150  	if reg.NITCommitCustom != nil {
   151  		commitBytes, _ := json.Marshal(*reg.NITCommitCustom)
   152  		_ = w.WriteField("nit_commit_custom", string(commitBytes))
   153  	}
   154  	metaBytes, _ := json.Marshal(Meta{})
   155  	_ = w.WriteField("meta", string(metaBytes))
   156  
   157  	w.Close()
   158  
   159  	req, err := http.NewRequest("POST", urlRegisterAsset, &b)
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  	req.Header.Set("Content-Type", w.FormDataContentType())
   164  	req.Header.Set("Authorization", getToken(e.Connection))
   165  
   166  	tr := &http.Transport{
   167  		DisableKeepAlives: true,
   168  	}
   169  	client := &http.Client{Transport: tr}
   170  	res, err := client.Do(req)
   171  	if res != nil && res.Body != nil {
   172  		defer res.Body.Close()
   173  	}
   174  	if err != nil {
   175  		return "", err
   176  	}
   177  
   178  	if res.StatusCode == http.StatusCreated {
   179  		bodyBytes, err := io.ReadAll(res.Body)
   180  		if err != nil {
   181  			return "", err
   182  		}
   183  		var jsonRes map[string]interface{}
   184  		_ = json.Unmarshal(bodyBytes, &jsonRes)
   185  		if cid, ok := jsonRes["cid"]; ok {
   186  			return cid.(string), nil
   187  		} else {
   188  			return "", fmt.Errorf("register file failed")
   189  		}
   190  
   191  	} else {
   192  		bodyBytes, err := io.ReadAll(res.Body)
   193  		if err != nil {
   194  			return "", err
   195  		}
   196  		return "", fmt.Errorf(string(bodyBytes))
   197  	}
   198  }
   199  
   200  func (e *execution) Execute(inputs []*structpb.Struct) ([]*structpb.Struct, error) {
   201  
   202  	var outputs []*structpb.Struct
   203  
   204  	for _, input := range inputs {
   205  
   206  		assetUrls := []string{}
   207  
   208  		inputStruct := Input{}
   209  		err := base.ConvertFromStructpb(input, &inputStruct)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  
   214  		for _, image := range inputStruct.Images {
   215  			imageBytes, err := b64.StdEncoding.DecodeString(base.TrimBase64Mime(image))
   216  			if err != nil {
   217  				return nil, err
   218  			}
   219  
   220  			var commitLicense *CommitCustomLicense
   221  			if inputStruct.License != nil {
   222  				commitLicense = &CommitCustomLicense{
   223  					Name:     inputStruct.License.Name,
   224  					Document: inputStruct.License.Document,
   225  				}
   226  			}
   227  
   228  			meta := CommitCustomMetadata{
   229  				Pipeline: struct {
   230  					UID    string
   231  					Recipe interface{}
   232  				}{
   233  					UID:    e.GetSystemVariables()["__PIPELINE_UID"].(string),
   234  					Recipe: e.GetSystemVariables()["__PIPELINE_RECIPE"],
   235  				},
   236  			}
   237  			commitCustom := CommitCustom{
   238  				AssetCreator:      inputStruct.AssetCreator,
   239  				DigitalSourceType: inputStruct.DigitalSourceType,
   240  				MiningPreference:  inputStruct.MiningPreference,
   241  				GeneratedThrough:  "https://instill.tech", //TODO: support Core Host
   242  				GeneratedBy:       inputStruct.GeneratedBy,
   243  				License:           commitLicense,
   244  				Metadata:          &meta,
   245  			}
   246  
   247  			reg := Register{
   248  				Caption:         inputStruct.Caption,
   249  				Headline:        inputStruct.Headline,
   250  				NITCommitCustom: &commitCustom,
   251  			}
   252  			assetCid, err := e.registerAsset(imageBytes, reg)
   253  			if err != nil {
   254  				return nil, err
   255  			}
   256  
   257  			assetUrls = append(assetUrls, fmt.Sprintf("https://verify.numbersprotocol.io/asset-profile?nid=%s", assetCid))
   258  		}
   259  
   260  		outputStruct := Output{
   261  			AssetUrls: assetUrls,
   262  		}
   263  
   264  		output, err := base.ConvertToStructpb(outputStruct)
   265  		if err != nil {
   266  			return nil, err
   267  		}
   268  		outputs = append(outputs, output)
   269  
   270  	}
   271  
   272  	return outputs, nil
   273  
   274  }
   275  
   276  func (c *connector) Test(sysVars map[string]any, connection *structpb.Struct) error {
   277  
   278  	req, err := http.NewRequest("GET", urlUserMe, nil)
   279  	if err != nil {
   280  		return err
   281  	}
   282  	req.Header.Set("Authorization", getToken(connection))
   283  
   284  	tr := &http.Transport{
   285  		DisableKeepAlives: true,
   286  	}
   287  	client := &http.Client{Transport: tr}
   288  	res, err := client.Do(req)
   289  	if res != nil && res.Body != nil {
   290  		defer res.Body.Close()
   291  	}
   292  	if err != nil {
   293  		return err
   294  	}
   295  	if res.StatusCode == http.StatusOK {
   296  		return fmt.Errorf("connection error")
   297  	}
   298  	return nil
   299  }