Commit 938dcc37 authored by brent's avatar brent

add watermark

parent 212b50f0
No preview for this file type
......@@ -18,5 +18,6 @@ FROM alpine
WORKDIR /root
COPY --from=build /aon-app-server /usr/bin/aon-app-server
COPY --from=build /build/aon-app-server/watermark.png /root/watermark.png
ENTRYPOINT [ "aon-app-server" ]
\ No newline at end of file
......@@ -14,6 +14,10 @@ imageUrl = "https://tmp-file.aigic.ai/api/v1/upload/persistence"
imageTransferUrl = "https://tmp-file.aigic.ai/api/v1/down/put"
replicateToken = "r8_9OCCea50go2Qkh0f0jhu3DbNjyzuyt61VNVI6"
replicateTimeout = 10
bucketName = "ai-watermark"
region = "us-west-2"
awsAccessKeyID = "AKIAYS2NSY7MVGYC2W7M"
awsSecretAccessKey = "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"
[test]
whoisApi = "aonet"
......@@ -23,6 +27,7 @@ imageUrl = "https://tmp-file.aigic.ai/api/v1/upload/persistence"
imageTransferUrl = "https://tmp-file.aigic.ai/api/v1/down/put"
replicateToken = "r8_9OCCea50go2Qkh0f0jhu3DbNjyzuyt61VNVI6"
replicateTimeout = 10
bucketName = "ai-watermark"
[prod]
whoisApi = "aonet"
......@@ -32,6 +37,7 @@ imageUrl = "https://tmp-file.aigic.ai/api/v1/upload/persistence"
imageTransferUrl = "https://tmp-file.aigic.ai/api/v1/down/put"
replicateToken = "r8_9OCCea50go2Qkh0f0jhu3DbNjyzuyt61VNVI6"
replicateTimeout = 10
bucketName = "ai-watermark"
......
......@@ -18,6 +18,11 @@ lllama3:0.0.8:
url: "https://api.replicate.com/v1/models/meta/meta-llama-3-8b/predictions"
stream: false
meta-llama-3-8b:
version: ""
url: "https://api.replicate.com/v1/models/meta/meta-llama-3-8b/predictions"
stream: false
meta-llama-3-8b-instruct:
version: ""
url: "https://api.replicate.com/v1/models/meta/meta-llama-3-8b-instruct/predictions"
......
package controllers
import (
"aon_app_server/models"
"aon_app_server/utils/mongo"
"encoding/json"
"github.com/beego/beego/v2/core/logs"
"go.mongodb.org/mongo-driver/bson"
"net/http"
"time"
)
type AppController struct {
......@@ -15,120 +9,120 @@ type AppController struct {
}
func (server *AppController) Add() {
body := server.Ctx.Input.RequestBody
template := models.App{}
err := json.Unmarshal(body, &template) //解析body中数据
logs.Debug("appRequest", template)
if err != nil {
server.respond(models.NoRequestBody, err.Error())
return
}
template.CreatedTime = time.Now().UTC()
template.UpdatedTime = template.CreatedTime
_, err = mongo.Insert(&template)
if err != nil {
server.respond(models.BusinessFailed, err.Error())
return
}
//body := server.Ctx.Input.RequestBody
//template := models.App{}
//err := json.Unmarshal(body, &template) //解析body中数据
//logs.Debug("appRequest", template)
//if err != nil {
// server.respond(models.NoRequestBody, err.Error())
// return
//}
//
//template.CreatedTime = time.Now().UTC()
//template.UpdatedTime = template.CreatedTime
//_, err = mongo.Insert(&template)
//if err != nil {
// server.respond(models.BusinessFailed, err.Error())
// return
//}
server.respond(http.StatusOK, "")
}
func (server *AppController) Update() {
body := server.Ctx.Input.RequestBody
template := models.App{}
err := json.Unmarshal(body, &template) //解析body中数据
logs.Debug("appRequest", template)
if err != nil {
server.respond(models.NoRequestBody, err.Error())
return
}
if template.Id == nil {
server.respond(models.MissingParameter, "id param is null")
return
}
template.UpdatedTime = time.Now().UTC()
_, err = mongo.Update(template)
if err != nil {
server.respond(models.BusinessFailed, err.Error())
return
}
//body := server.Ctx.Input.RequestBody
//template := models.App{}
//err := json.Unmarshal(body, &template) //解析body中数据
//logs.Debug("appRequest", template)
//if err != nil {
// server.respond(models.NoRequestBody, err.Error())
// return
//}
//
//if template.Id == nil {
// server.respond(models.MissingParameter, "id param is null")
// return
//}
//template.UpdatedTime = time.Now().UTC()
//
//_, err = mongo.Update(template)
//if err != nil {
// server.respond(models.BusinessFailed, err.Error())
// return
//}
server.respond(http.StatusOK, "")
}
func (server *AppController) Delete() {
body := server.Ctx.Input.RequestBody
template := models.App{}
err := json.Unmarshal(body, &template) //解析body中数据
logs.Debug("appRequest", template)
if err != nil {
server.respond(models.NoRequestBody, err.Error())
return
}
if template.Id == nil {
server.respond(models.MissingParameter, "id param is null")
return
}
template.UpdatedTime = time.Now().UTC()
template.Deleted = 1
_, err = mongo.Update(template)
if err != nil {
server.respond(models.BusinessFailed, err.Error())
return
}
//body := server.Ctx.Input.RequestBody
//template := models.App{}
//err := json.Unmarshal(body, &template) //解析body中数据
//logs.Debug("appRequest", template)
//if err != nil {
// server.respond(models.NoRequestBody, err.Error())
// return
//}
//
//if template.Id == nil {
// server.respond(models.MissingParameter, "id param is null")
// return
//}
//
//template.UpdatedTime = time.Now().UTC()
//template.Deleted = 1
//_, err = mongo.Update(template)
//if err != nil {
// server.respond(models.BusinessFailed, err.Error())
// return
//}
server.respond(http.StatusOK, "")
}
func (server *AppController) List() {
body := server.Ctx.Input.RequestBody
request := models.ListRequest{}
err := json.Unmarshal(body, &request) //解析body中数据
logs.Debug("appRequest", request)
if err != nil {
server.respond(models.NoRequestBody, err.Error())
return
}
if request.Page == 0 {
request.Page = 1
}
if request.Size == 0 {
request.Size = 10
}
total, data, err := mongo.Query("App", request.Page, request.Size, request.Filter)
if err != nil {
logs.Info("List Error:", err)
server.respond(models.BusinessFailed, err.Error())
}
var apps []models.App
for _, bsonD := range data {
var app models.App
// 将 bson.D 转换为 bson.Raw
bsonRaw, err := bson.Marshal(bsonD)
if err != nil {
server.respond(models.BusinessFailed, err.Error())
}
// 将 bson.Raw 解码为 User 结构体
err = bson.Unmarshal(bsonRaw, &app)
if err != nil {
server.respond(models.BusinessFailed, err.Error())
}
apps = append(apps, app)
}
responseData := struct {
Total int64 `json:"total"`
Data interface{} `json:"data,omitempty"`
}{
Total: total,
Data: apps,
}
server.respond(http.StatusOK, "", responseData)
//body := server.Ctx.Input.RequestBody
//request := models.ListRequest{}
//err := json.Unmarshal(body, &request) //解析body中数据
//logs.Debug("appRequest", request)
//if err != nil {
// server.respond(models.NoRequestBody, err.Error())
// return
//}
//
//if request.Page == 0 {
// request.Page = 1
//}
//if request.Size == 0 {
// request.Size = 10
//}
//
//total, data, err := mongo.Query("App", request.Page, request.Size, request.Filter)
//if err != nil {
// logs.Info("List Error:", err)
// server.respond(models.BusinessFailed, err.Error())
//}
//
//var apps []models.App
//for _, bsonD := range data {
// var app models.App
// // 将 bson.D 转换为 bson.Raw
// bsonRaw, err := bson.Marshal(bsonD)
// if err != nil {
// server.respond(models.BusinessFailed, err.Error())
// }
// // 将 bson.Raw 解码为 User 结构体
// err = bson.Unmarshal(bsonRaw, &app)
// if err != nil {
// server.respond(models.BusinessFailed, err.Error())
// }
// apps = append(apps, app)
//}
//responseData := struct {
// Total int64 `json:"total"`
// Data interface{} `json:"data,omitempty"`
//}{
// Total: total,
// Data: apps,
//}
server.respond(http.StatusOK, "")
}
//func (server *AppController) BindTemplate() {
......
......@@ -2,17 +2,30 @@ package controllers
import (
"aon_app_server/models"
"aon_app_server/utils/aonsupabase"
"aon_app_server/utils/mongo"
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/beego/beego/v2/core/logs"
beego "github.com/beego/beego/v2/server/web"
"github.com/fogleman/gg"
"go.mongodb.org/mongo-driver/bson"
"gopkg.in/yaml.v2"
"image"
"image/jpeg"
"image/png"
"io"
"net/http"
"net/url"
"os"
"path"
"reflect"
"strings"
"time"
......@@ -108,6 +121,7 @@ func copyImages(images []string) []string {
}
func transferImages(images []string) []string {
imagesToCopy := models.ImagesToCopy{
Sources: images,
}
......@@ -144,6 +158,243 @@ func transferImages(images []string) []string {
return images
}
func FindWatermarkFields(node models.JSONNode, prefix string, result *[]string) {
for key, value := range node {
newPrefix := key
if prefix != "" {
newPrefix = prefix + "." + key
}
if subNode, ok := value.(map[string]interface{}); ok {
FindWatermarkFields(subNode, newPrefix, result)
} else if key == "watermark" && value == true {
*result = append(*result, prefix)
}
}
}
func checkFileIsImage(files []string) bool {
for _, value := range files {
_, ext := parseUrl(value)
if ext == "jpeg" || ext == ".jpeg" || ext == ".jpg" || ext == "jpg" || ext == "png" || ext == ".png" || ext == "webp" || ext == ".webp" {
return true
}
}
return false
}
func findValueByPath(data interface{}, path string) (interface{}, bool) {
// 分割路径成键的切片
keys := strings.Split(path, ".")
if len(keys) == 0 {
return nil, false
}
// 遍历路径中的键
for _, key := range keys {
if key == "" || key == "properties" {
continue
}
switch v := data.(type) {
case map[string]interface{}:
if nextData, ok := v[key]; ok {
data = nextData
} else {
return nil, false
}
case models.JSONNode:
if nextData, ok := v[key]; ok {
data = nextData
} else {
return nil, false
}
default:
return nil, false
}
}
return data, true
}
func transferImagesToS3(images []string, task *models.Task) []string {
watermarkURL := ""
apps, count, err := aonsupabase.MyClient.From("app").Select("", "exact", false).Eq("app_id", task.AppId).Execute()
if err == nil {
var temp []models.App
if err := json.Unmarshal(apps, &temp); err != nil {
logs.Debug("apps Unmarshal err = ", err)
}
if len(temp) > 0 {
app := temp[0]
logs.Debug("app = ", count, app)
var result []string
FindWatermarkFields(app.TemplateParams, "", &result)
for _, path := range result {
fmt.Println(path)
value, found := findValueByPath(app.ParamsValue, path)
if found {
if str, ok := value.(string); ok {
watermarkURL = str
}
fmt.Printf("Value at '%s': %v\n", path, value)
}
}
}
}
var backImages []string
for _, value := range images {
url, _ := addWatermark(value, watermarkURL)
if url != "" {
backImages = append(backImages, url)
}
}
if len(backImages) > 0 {
return backImages
}
return images
}
func downloadImage(url string) (image.Image, string, error) {
resp, err := http.Get(url)
if err != nil {
return nil, "", err
}
defer resp.Body.Close()
img, format, err := image.Decode(resp.Body)
if err != nil {
return nil, "", err
}
return img, format, nil
}
func uploadToS3(bucket, key string, img image.Image, format string) (string, error) {
awsAccessKeyID, _ := beego.AppConfig.String("awsAccessKeyID")
awsSecretAccessKey, _ := beego.AppConfig.String("awsSecretAccessKey")
region, _ := beego.AppConfig.String("region")
creds := credentials.NewStaticCredentialsProvider(awsAccessKeyID, awsSecretAccessKey, "")
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion(region), config.WithCredentialsProvider(creds))
//sess, err := session.NewSession(&aws.Config{
// Region: region,
// Credentials: credentials.NewStaticCredentials(awsAccessKeyID, awsSecretAccessKey, ""),
//})
if err != nil {
return "", err
}
client := s3.NewFromConfig(cfg)
buf := new(bytes.Buffer)
if format == "jpeg" || format == ".jpeg" || format == ".jpg" || format == "jpg" {
err = jpeg.Encode(buf, img, nil)
} else if format == "png" || format == ".png" {
err = png.Encode(buf, img)
} else {
return "", err
}
if err != nil {
return "", err
}
filePath := "watermark"
key = filePath + "/" + key
_, err = client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
Body: bytes.NewReader(buf.Bytes()),
ACL: types.ObjectCannedACLPublicRead, // 可根据需要更改
})
returnUrl := "https://" + bucket + ".s3.amazonaws.com/" + key
return returnUrl, err
//presignClient := s3.NewPresignClient(client)
//presignResult, err := presignClient.PresignGetObject(context.TODO(), &s3.GetObjectInput{
// Bucket: &bucket,
// Key: &key,
//})
//if err != nil {
// logs.Debug("failed to presign request, %v", err)
// return "", err
//}
//return presignResult.URL, err
}
func addWatermark(sourceURL string, watermarkURL string) (string, error) {
bucketName, _ := beego.AppConfig.String("bucketName")
outputKey, ext := parseUrl(sourceURL)
// 下载源图
srcImg, _, err := downloadImage(sourceURL)
if err != nil {
logs.Debug("addWatermark downloadImage faild =", sourceURL)
return "", err
}
// 下载水印图
var watermarkImg image.Image
if watermarkURL == "" {
imgFile, err := os.Open("./watermark.png")
if err != nil {
logs.Debug("read watermark faild")
}
defer imgFile.Close()
watermarkImg, err = png.Decode(imgFile)
if err != nil {
logs.Debug("Decode watermark faild")
}
} else {
watermarkImg, _, err = downloadImage(watermarkURL)
if err != nil {
logs.Debug("downloadImage watermark faild", err)
}
}
// 创建绘图上下文
dc := gg.NewContextForImage(srcImg)
// 添加水印
if watermarkImg != nil {
dc.DrawImageAnchored(watermarkImg, dc.Width()/2, dc.Height()-80, 0.5, 0.5) // 在中心添加水印
}
// 获取合成后的图像
outputImg := dc.Image()
// 上传到 S3
s3Url, err := uploadToS3(bucketName, outputKey, outputImg, ext)
if err != nil {
logs.Debug("uploadToS3 faild", err)
return "", err
}
return s3Url, nil
}
func parseUrl(urlStr string) (string, string) {
// 解析 URL
u, err := url.Parse(urlStr)
if err != nil {
fmt.Println("解析 URL 时出错:", err)
return "", ""
}
// 从 URL 中提取路径
filePath := u.Path
// 提取文件名
fileName := path.Base(filePath)
// 提取扩展名
ext := path.Ext(fileName)
// 提取文件名(不包含扩展名)
//nameWithoutExt := strings.TrimSuffix(fileName, ext)
return fileName, ext
}
func sendTask(task *models.Task, async bool) (*models.TaskResponse, error) {
host, _ := beego.AppConfig.String("taskUrl")
url := host + task.ApiPath
......@@ -474,9 +725,16 @@ func doGetReplicate(url string, task *models.Task, taskResponse *models.TaskResp
for _, value := range slice {
output = append(output, value)
}
}
task.Status = 2
task.Output = transferImages(output)
isImage := checkFileIsImage(output)
if isImage {
task.Output = transferImagesToS3(output, task)
} else {
task.Output = transferImages(output)
}
mongo.Update(task)
taskResponse.Output = task.Output
taskResponse.Task.IsSuccess = true
......@@ -660,6 +918,17 @@ func (server *TaskController) List() {
}
if task.Error != nil {
if str, ok := task.Error.(string); ok {
fmt.Println("The string is:", str)
taskError := models.TaskReturn{
IsSuccess: false,
TaskError: str,
ExecCode: 0,
ExecError: "",
}
task.Error = taskError
continue
}
raw, err := bson.Marshal(task.Error)
if err != nil {
server.respond(models.BusinessFailed, err.Error())
......
......@@ -5,19 +5,39 @@ go 1.19
require github.com/beego/beego/v2 v2.0.1
require (
github.com/beego/beego v1.12.12
github.com/go-sql-driver/mysql v1.5.0
github.com/aws/aws-sdk-go-v2 v1.30.3
github.com/aws/aws-sdk-go-v2/config v1.27.27
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2
github.com/fogleman/gg v1.3.0
github.com/robfig/cron/v3 v3.0.1
github.com/smartystreets/goconvey v1.6.4
github.com/supabase-community/supabase-go v0.0.4
go.mongodb.org/mongo-driver v1.15.1
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/beego/swagger v4.6.2+incompatible // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/protobuf v1.4.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
......@@ -32,17 +52,22 @@ require (
github.com/prometheus/procfs v0.1.3 // indirect
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/supabase-community/functions-go v0.0.0-20220927045802-22373e6cb51d // indirect
github.com/supabase-community/gotrue-go v1.2.0 // indirect
github.com/supabase-community/postgrest-go v0.0.11 // indirect
github.com/supabase-community/storage-go v0.7.0 // indirect
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.6.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.23.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
This diff is collapsed.
{"/Users/brent/Documents/wubanWork/aon_app_server/controllers":1721199888568212138}
\ No newline at end of file
{"/Users/brent/Documents/wubanWork/aon_app_server/controllers":1721643676253627085}
\ No newline at end of file
......@@ -13,14 +13,28 @@ import "time"
// Deleted int `json:"deleted" bson:"deleted"`
//}
//type App struct {
// Id interface{} `json:"id" bson:"_id,omitempty"`
// Logo string `json:"logo,omitempty" bson:"logo"`
// Name string `json:"name,omitempty" bson:"name"`
// Desc string `json:"desc,omitempty" bson:"desc"`
// TemplateId interface{} `json:"template_id" bson:"template_id"`
// UserId string `json:"user_id,omitempty" bson:"user_id"`
// CreatedTime time.Time `json:"created_time" bson:"created_time"`
// UpdatedTime time.Time `json:"updated_time" bson:"updated_time"`
// Deleted int `json:"deleted" bson:"deleted"`
//}
type JSONNode map[string]interface{}
type App struct {
Id interface{} `json:"id" bson:"_id,omitempty"`
Logo string `json:"logo,omitempty" bson:"logo"`
Name string `json:"name,omitempty" bson:"name"`
Desc string `json:"desc,omitempty" bson:"desc"`
TemplateId interface{} `json:"template_id" bson:"template_id"`
UserId string `json:"user_id,omitempty" bson:"user_id"`
CreatedTime time.Time `json:"created_time" bson:"created_time"`
UpdatedTime time.Time `json:"updated_time" bson:"updated_time"`
Deleted int `json:"deleted" bson:"deleted"`
Id int `json:"id"`
CreatedTime time.Time `json:"created_time"`
TemplateId int `json:"template_id"`
TemplateParams JSONNode `json:"template_params"`
ParamsValue JSONNode `json:"params_value"`
UserId string `json:"user_id"`
UpdatedTime time.Time `json:"updated_time"`
Deleted bool `json:"deleted"`
AppId string `json:"app_id"`
}
......@@ -23,6 +23,7 @@ type Task struct {
Output []string `json:"output" bson:"output"`
Error interface{} `json:"error" bson:"error"`
ExcuteId string `json:"excute_id" bson:"excute_id"`
AppId string `json:"app_id" bson:"app_id"`
CreatedTime time.Time `json:"created_time" bson:"created_time"`
UpdatedTime time.Time `json:"updated_time" bson:"updated_time"`
Deleted int `json:"deleted" bson:"deleted"`
......
File added
package aonsupabase
import (
"github.com/beego/beego/v2/core/logs"
"github.com/supabase-community/supabase-go"
)
var API_URL = "http://43.198.54.207:8000"
var API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE"
var MyClient *supabase.Client
func init() {
var err error
MyClient, err = supabase.NewClient(API_URL, API_KEY, nil)
if err != nil {
logs.Debug("cannot initalize client:", err)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment