diff --git a/go.mod b/go.mod index 59dbdda..f8e19ba 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/frankban/quicktest v1.14.6 github.com/go-resty/resty/v2 v2.12.0 github.com/gofrs/uuid v4.4.0+incompatible + github.com/gogo/status v1.1.0 github.com/gojuno/minimock/v3 v3.3.6 github.com/golang-migrate/migrate/v4 v4.17.0 github.com/google/go-cmp v0.6.0 @@ -46,6 +47,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect github.com/getsentry/sentry-go v0.12.0 // indirect github.com/go-ini/ini v1.67.0 // indirect + github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/milvus-io/milvus-proto/go-api/v2 v2.4.3 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect diff --git a/go.sum b/go.sum index 9ffde57..775c5ff 100644 --- a/go.sum +++ b/go.sum @@ -207,11 +207,13 @@ github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0 h1:+eIkrewn5q6b30y+g/BJINVVdi2xH7je5MPJ3ZPK3JA= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/gojuno/minimock/v3 v3.3.6 h1:tZQQaDgKSxsKiVia9vt6zZ/qsKNGBw2D0ubHQPr+mHc= github.com/gojuno/minimock/v3 v3.3.6/go.mod h1:kjvubEBVT8aUQ9e+g8x/hPfAhiOoqW7WinzzJgzr4ws= @@ -342,8 +344,6 @@ github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0 github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/instill-ai/protogen-go v0.3.3-alpha.0.20241012090311-e872dc0b511d h1:jf2RQtRFNxnPMkjTD0AAqXDXO8lHYOrWU3Hrr+yGEzY= -github.com/instill-ai/protogen-go v0.3.3-alpha.0.20241012090311-e872dc0b511d/go.mod h1:rf0UY7VpEgpaLudYEcjx5rnbuwlBaaLyD4FQmWLtgAY= github.com/instill-ai/protogen-go v0.3.3-alpha.0.20241018045010-dcc80f850d9d h1:6/5voyjeqeeeYszZ7XjifG2pekRDYdqWD0bgeKz9LHw= github.com/instill-ai/protogen-go v0.3.3-alpha.0.20241018045010-dcc80f850d9d/go.mod h1:rf0UY7VpEgpaLudYEcjx5rnbuwlBaaLyD4FQmWLtgAY= github.com/instill-ai/usage-client v0.3.0-alpha.0.20240319060111-4a3a39f2fd61 h1:smPTvmXDhn/QC7y/TPXyMTqbbRd0gvzmFgWBChwTfhE= diff --git a/pkg/db/migration/000017_create_object_and_object_url_table.up.sql b/pkg/db/migration/000017_create_object_and_object_url_table.up.sql index 608cae0..63693e1 100644 --- a/pkg/db/migration/000017_create_object_and_object_url_table.up.sql +++ b/pkg/db/migration/000017_create_object_and_object_url_table.up.sql @@ -2,9 +2,9 @@ BEGIN; -- Create object table CREATE TABLE IF NOT EXISTS object ( uid UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name VARCHAR(1040) NOT NULL, - size BIGINT NOT NULL, - content_type VARCHAR(255) NOT NULL, + name VARCHAR(1040), + size BIGINT , + content_type VARCHAR(255), namespace_uid UUID NOT NULL, creator_uid UUID NOT NULL, is_uploaded BOOLEAN NOT NULL DEFAULT FALSE, @@ -36,7 +36,7 @@ CREATE TABLE IF NOT EXISTS object_url ( uid UUID PRIMARY KEY DEFAULT gen_random_uuid(), namespace_uid UUID NOT NULL, object_uid UUID NOT NULL REFERENCES object(uid) ON DELETE CASCADE, - url_expire_at TIMESTAMP NOT NULL, + url_expire_at TIMESTAMP , minio_url_path TEXT NOT NULL, encoded_url_path TEXT NOT NULL, type VARCHAR(10) NOT NULL CHECK (type IN ('upload', 'download')), diff --git a/pkg/minio/main_test.go b/pkg/minio/main_test.go index 64af655..f8d4fdc 100644 --- a/pkg/minio/main_test.go +++ b/pkg/minio/main_test.go @@ -19,7 +19,6 @@ package minio // Port: "19000", // RootUser: "minioadmin", // RootPwd: "minioadmin", -// BucketName: "instill-ai-knowledge-bases", // }) // if err != nil { // log.Fatalf("Failed to initialize Minio client for testing: %v", err) diff --git a/pkg/minio/minio.go b/pkg/minio/minio.go index 0b4cbe0..30aa689 100644 --- a/pkg/minio/minio.go +++ b/pkg/minio/minio.go @@ -35,16 +35,18 @@ type MinioI interface { GetFilesByPaths(ctx context.Context, bucket string, filePaths []string) ([]FileContent, error) // KnowledgeBase KnowledgeBaseI + // Object + ObjectI } type Minio struct { - client *minio.Client + client *minio.Client } const ( // Note: this bucket is for storing the blob of the file and not changeable // in different environment so we dont put it in config - BlobBucketName = "instill-ai-blob" + BlobBucketName = "instill-ai-blob" KnowledgeBaseBucketName = "instill-ai-knowledge-bases" ) diff --git a/pkg/minio/object.go b/pkg/minio/object.go new file mode 100644 index 0000000..770ee65 --- /dev/null +++ b/pkg/minio/object.go @@ -0,0 +1,68 @@ +package minio + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "github.com/gofrs/uuid" + "github.com/instill-ai/artifact-backend/pkg/logger" + "go.uber.org/zap" +) + +// ObjectI is the interface for object-related operations. +type ObjectI interface { + // MakePresignedURLForUpload creates a presigned URL for uploading an object. + MakePresignedURLForUpload(ctx context.Context, namespaceUUID uuid.UUID, objectUUID uuid.UUID, expiration time.Duration) (*url.URL, error) + // MakePresignedURLForDownload creates a presigned URL for downloading an object. + MakePresignedURLForDownload(ctx context.Context, namespaceUUID uuid.UUID, objectUUID uuid.UUID, expiration time.Duration) (*url.URL, error) +} + +// MakePresignedURLForUpload creates a presigned URL for uploading an object. +func (m *Minio) MakePresignedURLForUpload(ctx context.Context, namespaceUUID uuid.UUID, objectUUID uuid.UUID, expiration time.Duration) (*url.URL, error) { + log, err := logger.GetZapLogger(ctx) + if err != nil { + return nil, err + } + // check if the expiration is within the range of 1sec to 7 days. + if expiration > time.Hour*24*7 { + return nil, errors.New("expiration time must be within 1sec to 7 days") + } + // Get presigned URL for uploading object + presignedURL, err := m.client.PresignedPutObject(BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID), expiration) + if err != nil { + log.Error("Failed to make presigned URL for upload", zap.Error(err)) + return nil, err + } + + return presignedURL, nil +} + +// MakePresignedURLForDownload creates a presigned URL for downloading an object. +func (m *Minio) MakePresignedURLForDownload(ctx context.Context, namespaceUUID uuid.UUID, objectUUID uuid.UUID, expiration time.Duration) (*url.URL, error) { + log, err := logger.GetZapLogger(ctx) + if err != nil { + return nil, err + } + // check if the expiration is within the range of 1sec to 7 days. + if expiration > time.Hour*24*7 { + return nil, errors.New("expiration time must be within 1sec to 7 days") + } + + // Get presigned URL for downloading object + presignedURL, err := m.client.PresignedGetObject(BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID), expiration, url.Values{}) + if err != nil { + log.Error("Failed to make presigned URL for download", zap.Error(err)) + return nil, err + } + + return presignedURL, nil +} + +// make object path from objectUUID. +// namespaceUUID / objectUUID +func GetBlobObjectPath(namespaceUUID uuid.UUID, objectUUID uuid.UUID) string { + return fmt.Sprintf("ns-%s/obj-%s", namespaceUUID.String(), objectUUID.String()) +} diff --git a/pkg/minio/object_test.go b/pkg/minio/object_test.go new file mode 100644 index 0000000..2145ea8 --- /dev/null +++ b/pkg/minio/object_test.go @@ -0,0 +1,143 @@ +package minio + +// import ( +// "context" +// "fmt" +// "io" +// "log" +// "net/http" +// "strings" +// "testing" +// "time" + +// "github.com/gofrs/uuid" +// "github.com/instill-ai/artifact-backend/config" +// "github.com/minio/minio-go" +// "github.com/minio/minio-go/pkg/encrypt" +// ) + +// // Test presigned URL for upload +// func TestMinio_TestMakePresignedURLForUpload(t *testing.T) { +// log.Println("Setting up Minio client for testing") +// var err error +// testMinioClient, err := NewMinioClientAndInitBucket(config.MinioConfig{ +// Host: "localhost", +// Port: "19000", +// RootUser: "minioadmin", +// RootPwd: "minioadmin", +// }) +// if err != nil { +// log.Fatalf("Failed to initialize Minio client for testing: %v", err) +// } +// // create a namespaceUUID and objectUUID +// namespaceUUID, err := uuid.NewV4() +// if err != nil { +// t.Fatalf("failed to create namespaceUUID: %v", err) +// } +// objectUUID, err := uuid.NewV4() +// if err != nil { +// t.Fatalf("failed to create objectUUID: %v", err) +// } +// presignedURL, err := testMinioClient.MakePresignedURLForUpload(context.TODO(), namespaceUUID, objectUUID, 1*time.Hour) +// if err != nil { +// t.Fatalf("failed to make presigned URL for upload: %v", err) +// } +// // just get path and query from presignedURL +// // parsedURL, err := url.Parse(presignedURL) +// // if err != nil { +// // t.Fatalf("failed to parse presigned URL: %v", err) +// // } +// // fmt.Println("presignedURL: ", parsedURL.Path, parsedURL.Query()) + +// // upload a test file with content "test" to the presigned URL using http client +// client := &http.Client{} +// req, err := http.NewRequest("PUT", presignedURL.String(), strings.NewReader("test")) +// if err != nil { +// t.Fatalf("failed to create request: %v", err) +// } +// req.Header.Set("Content-Type", "text/plain") +// resp, err := client.Do(req) +// if err != nil { +// t.Fatalf("failed to upload file: %v", err) +// } +// fmt.Println("resp code: ", resp.StatusCode) +// // resp range in header +// fmt.Println("resp range: ", resp.Header.Get("Content-Range")) +// // read the body 100 bytes at a time until EOF +// buf := make([]byte, 100) +// for { +// n, err := resp.Body.Read(buf) +// if err != nil && err != io.EOF { +// t.Fatalf("failed to read response body: %v", err) +// } +// fmt.Println("read: ", string(buf[:n])) +// if err == io.EOF { +// break +// } +// } + +// // check if the file is uploaded to the Minio bucket +// objectInfo, err := testMinioClient.client.StatObject(BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID), minio.StatObjectOptions{}) +// if err != nil { +// t.Fatalf("failed to stat object: %v", err) +// } +// // list objectsInfo field by field +// // print name of object +// fmt.Println("ObjectInfo:") +// fmt.Printf(" Key: %v\n", objectInfo.Key) +// fmt.Printf(" Size: %v\n", objectInfo.Size) +// fmt.Printf(" ContentType: %v\n", objectInfo.ContentType) +// // fmt.Printf("Metadata: %v\n", objectInfo.Metadata) +// // fmt.Printf("Owner: %v\n", objectInfo.Owner) +// // fmt.Printf("StorageClass: %v\n", objectInfo.StorageClass) + +// // check if the content is "test" +// data, err := testMinioClient.GetFile(context.TODO(), BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID)) +// if err != nil { +// t.Fatalf("failed to get file: %v", err) +// } +// fmt.Println("data: ", string(data)) + +// // delete the test file from the Minio bucket +// err = testMinioClient.client.RemoveObject(BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID)) +// if err != nil { +// t.Fatalf("failed to delete object: %v", err) +// } +// } + +// // TestMinio_TestMakePresignedURLForDownload +// func TestMinio_TestMakePresignedURLForDownload(t *testing.T) { +// } + +// // TestMinio_TestMultiPartUpload +// func TestMinio_TestMultiPartUpload(t *testing.T) { +// log.Println("Setting up Minio client for testing") +// var err error +// testMinioClient, err := NewMinioClientAndInitBucket(config.MinioConfig{ +// Host: "localhost", +// Port: "19000", +// RootUser: "minioadmin", +// RootPwd: "minioadmin", +// }) +// if err != nil { +// log.Fatalf("Failed to initialize Minio client for testing: %v", err) +// } +// client := testMinioClient.GetClient() +// namespaceUUID, err := uuid.NewV4() +// if err != nil { +// t.Fatalf("failed to create namespaceUUID: %v", err) +// } +// objectUUID, err := uuid.NewV4() +// if err != nil { +// t.Fatalf("failed to create objectUUID: %v", err) +// } +// dest, err := minio.NewDestinationInfo(BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID), encrypt.NewSSE(), nil) +// if err != nil { +// t.Fatalf("failed to create destination info: %v", err) +// } +// sources := []minio.SourceInfo{ +// minio.NewSourceInfo(BlobBucketName, GetBlobObjectPath(namespaceUUID, objectUUID), nil), +// } +// client.ComposeObjectWithProgress(dest, sources, nil) + +// } diff --git a/pkg/repository/object.go b/pkg/repository/object.go index cf39955..fffe173 100644 --- a/pkg/repository/object.go +++ b/pkg/repository/object.go @@ -19,12 +19,12 @@ type ObjectI interface { type Object struct { UID uuid.UUID `gorm:"column:uid;type:uuid;default:gen_random_uuid();primaryKey" json:"uid"` - Name string `gorm:"column:name;size:1040;not null" json:"name"` - Size int64 `gorm:"column:size;not null" json:"size"` - ContentType string `gorm:"column:content_type;size:255;not null" json:"content_type"` - NamespaceUID uuid.UUID `gorm:"column:namespace_uid;type:uuid;not null" json:"namespace_uid"` - CreatorUID uuid.UUID `gorm:"column:creator_uid;type:uuid;not null" json:"creator_uid"` - IsUploaded bool `gorm:"column:is_uploaded;not null;default:false" json:"is_uploaded"` + Name string `gorm:"column:name;size:1040" json:"name"` + Size int64 `gorm:"column:size;" json:"size"` + ContentType string `gorm:"column:content_type;size:255" json:"content_type"` + NamespaceUID uuid.UUID `gorm:"column:namespace_uid;type:uuid;not null" json:"namespace_uid"` + CreatorUID uuid.UUID `gorm:"column:creator_uid;type:uuid;not null" json:"creator_uid"` + IsUploaded bool `gorm:"column:is_uploaded;not null;default:false" json:"is_uploaded"` // BucketName/ns:/obj: Destination string `gorm:"column:destination;size:255" json:"destination"` ObjectExpireDays *int `gorm:"column:object_expire_days" json:"object_expire_days"` diff --git a/pkg/repository/objectUrl.go b/pkg/repository/objectUrl.go index 0abe100..dbd3c1f 100644 --- a/pkg/repository/objectUrl.go +++ b/pkg/repository/objectUrl.go @@ -26,7 +26,7 @@ type ObjectURL struct { UID uuid.UUID `gorm:"column:uid;type:uuid;default:gen_random_uuid();primaryKey" json:"uid"` NamespaceUID uuid.UUID `gorm:"column:namespace_uid;type:uuid;not null" json:"namespace_uid"` ObjectUID uuid.UUID `gorm:"column:object_uid;type:uuid;not null" json:"object_uid"` - URLExpireAt time.Time `gorm:"column:url_expire_at;not null" json:"url_expire_at"` + URLExpireAt time.Time `gorm:"column:url_expire_at" json:"url_expire_at"` MinioURLPath string `gorm:"column:minio_url_path;type:text;not null" json:"minio_url_path"` EncodedURLPath string `gorm:"column:encoded_url_path;type:text;not null" json:"encoded_url_path"` // download or upload @@ -68,8 +68,8 @@ var ObjectURLColumn = ObjectURLColumns{ } const ( - objectURLTypeDownload = "download" - objectURLTypeUpload = "upload" + ObjectURLTypeDownload = "download" + ObjectURLTypeUpload = "upload" ) @@ -151,7 +151,7 @@ func (r *Repository) GetObjectURLCountByObject(ctx context.Context, objectUID uu func (r *Repository) GetObjectUploadURL(ctx context.Context, objectUID uuid.UUID) (*ObjectURL, error) { var objectURL ObjectURL whereString := fmt.Sprintf("%v = ? AND %v = ? AND %v IS NULL", ObjectURLColumn.ObjectUID, ObjectURLColumn.Type, ObjectURLColumn.DeleteTime) - if err := r.db.WithContext(ctx).Where(whereString, objectUID, objectURLTypeUpload).First(&objectURL).Error; err != nil { + if err := r.db.WithContext(ctx).Where(whereString, objectUID, ObjectURLTypeUpload).First(&objectURL).Error; err != nil { return nil, err } return &objectURL, nil @@ -161,7 +161,7 @@ func (r *Repository) GetObjectUploadURL(ctx context.Context, objectUID uuid.UUID func (r *Repository) GetObjectDownloadURL(ctx context.Context, objectUID uuid.UUID) (*ObjectURL, error) { var objectURL ObjectURL whereString := fmt.Sprintf("%v = ? AND %v = ? AND %v IS NULL", ObjectURLColumn.ObjectUID, ObjectURLColumn.Type, ObjectURLColumn.DeleteTime) - if err := r.db.WithContext(ctx).Where(whereString, objectUID, objectURLTypeDownload).First(&objectURL).Error; err != nil { + if err := r.db.WithContext(ctx).Where(whereString, objectUID, ObjectURLTypeDownload).First(&objectURL).Error; err != nil { return nil, err } return &objectURL, nil diff --git a/pkg/service/object.go b/pkg/service/object.go new file mode 100644 index 0000000..4bc442d --- /dev/null +++ b/pkg/service/object.go @@ -0,0 +1,124 @@ +package service + +import ( + "context" + "time" + + "github.com/gofrs/uuid" + "github.com/gogo/status" + "github.com/instill-ai/artifact-backend/pkg/minio" + "github.com/instill-ai/artifact-backend/pkg/repository" + artifactpb "github.com/instill-ai/protogen-go/artifact/artifact/v1alpha" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// GetUploadURL get the upload url of the object +// this function will create a new object and object_url record in the database +func (s *Service) GetUploadURL( + ctx context.Context, + req *artifactpb.GetObjectUploadURLRequest, + namespaceUID uuid.UUID, + contentType string, +) (*artifactpb.GetObjectUploadURLResponse, error) { + // name cannot be empty + if req.GetObjectName() == "" { + return nil, status.Errorf(codes.InvalidArgument, "name cannot be empty") + } + + // check expiration_time is valid. if it is lower than 60, we will use 60 as the expiration_time + if req.GetExpirationTime() < 60 { + req.ExpirationTime = 60 + } + + // check expiration_time is valid. if it is greater than 7 days, we will use 7 days as the expiration_time + if req.GetExpirationTime() > 7*24*60 { + req.ExpirationTime = 7 * 24 * 60 + } + + objectExpireDays := int(req.GetObjectExpireDays()) + lastModifiedTime := req.GetLastModifiedTime().AsTime() + // create object + object := &repository.Object{ + Name: req.GetObjectName(), + NamespaceUID: namespaceUID, + ContentType: contentType, + Size: 0, // we will update the size when the object is uploaded. when trying to get download url, we will check the size of the object in minio. + IsUploaded: false, // we will check and update the is_uploaded when trying to get download url. + Destination: "", + ObjectExpireDays: &objectExpireDays, + LastModifiedTime: &lastModifiedTime, + } + + // create object + createdObject, err := s.Repository.CreateObject(ctx, *object) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to create object: %v", err) + } + + // get path of the object + minioPath := minio.GetBlobObjectPath(createdObject.NamespaceUID, createdObject.UID) + // update the object destination + createdObject.Destination = minioPath + _, err = s.Repository.UpdateObject(ctx, *createdObject) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to update object: %v", err) + } + + // get presigned url for uploading object + presignedURL, err := s.MinIO.MakePresignedURLForUpload(ctx, namespaceUID, createdObject.UID, time.Duration(req.GetExpirationTime())*time.Minute) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to make presigned url for upload: %v", err) + } + + // remove the protocol and host from the presignedURL + path := presignedURL.Path + "?" + presignedURL.RawQuery + + // create object_url and update the encoded_url_path + objectURL := &repository.ObjectURL{ + NamespaceUID: namespaceUID, + ObjectUID: object.UID, + URLExpireAt: time.Now().UTC().Add(time.Duration(req.GetExpirationTime()) * time.Minute), + MinioURLPath: path, + EncodedURLPath: "", + Type: repository.ObjectURLTypeUpload, + } + + createdObjectURL, err := s.Repository.CreateObjectURL(ctx, *objectURL) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to create object url: %v", err) + } + createdObjectURL.EncodedURLPath = getEncodedMinioURLPath(createdObjectURL.UID) + _, err = s.Repository.UpdateObjectURL(ctx, *createdObjectURL) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to update object url: %v", err) + } + + objectInProto := turnObjectInDBToObjectInProto(createdObject) + + return &artifactpb.GetObjectUploadURLResponse{ + UploadUrl: createdObjectURL.EncodedURLPath, + UrlExpireAt: timestamppb.New(createdObjectURL.URLExpireAt), + Object: objectInProto, + }, nil +} + +// getEncodedMinioURLPath get the encoded minio url path +func getEncodedMinioURLPath(objectURLUUID uuid.UUID) string { + return "/" + "v1alpha" + "/" + "object-urls" + "/" + objectURLUUID.String() +} + +// turn object in db to object in proto +func turnObjectInDBToObjectInProto(object *repository.Object) *artifactpb.Object { + return &artifactpb.Object{ + Uid: object.UID.String(), + Name: object.Name, + ContentType: object.ContentType, + Size: object.Size, + IsUploaded: object.IsUploaded, + Path: &object.Destination, + ObjectExpireDays: int32(*object.ObjectExpireDays), + CreatedTime: timestamppb.New(object.CreateTime), + UpdatedTime: timestamppb.New(object.UpdateTime), + } +}