diff --git a/plugin/storage/s3/s3.go b/plugin/storage/s3/s3.go index 8ad973bd..3455744d 100644 --- a/plugin/storage/s3/s3.go +++ b/plugin/storage/s3/s3.go @@ -45,10 +45,10 @@ func NewClient(ctx context.Context, s3Config *storepb.WorkspaceStorageSetting_S3 } // UploadObject uploads an object to S3. -func (client *Client) UploadObject(ctx context.Context, key string, fileType string, content io.Reader) (string, error) { - uploader := manager.NewUploader(client.Client) +func (c *Client) UploadObject(ctx context.Context, key string, fileType string, content io.Reader) (string, error) { + uploader := manager.NewUploader(c.Client) putInput := s3.PutObjectInput{ - Bucket: client.Bucket, + Bucket: c.Bucket, Key: aws.String(key), ContentType: aws.String(fileType), Body: content, @@ -66,10 +66,10 @@ func (client *Client) UploadObject(ctx context.Context, key string, fileType str } // PresignGetObject presigns an object in S3. -func (client *Client) PresignGetObject(ctx context.Context, bucket, key string) (string, error) { - presignClient := s3.NewPresignClient(client.Client) +func (c *Client) PresignGetObject(ctx context.Context, key string) (string, error) { + presignClient := s3.NewPresignClient(c.Client) presignResult, err := presignClient.PresignGetObject(context.TODO(), &s3.GetObjectInput{ - Bucket: aws.String(bucket), + Bucket: aws.String(*c.Bucket), Key: aws.String(key), }, func(opts *s3.PresignOptions) { opts.Expires = time.Duration(presignLifetimeSecs * int64(time.Second)) @@ -79,3 +79,15 @@ func (client *Client) PresignGetObject(ctx context.Context, bucket, key string) } return presignResult.URL, nil } + +// DeleteObject deletes an object in S3. +func (c *Client) DeleteObject(ctx context.Context, key string) error { + _, err := c.Client.DeleteObject(ctx, &s3.DeleteObjectInput{ + Bucket: c.Bucket, + Key: aws.String(key), + }) + if err != nil { + return errors.Wrap(err, "failed to delete object") + } + return nil +} diff --git a/plugin/webhook/webhook.go b/plugin/webhook/webhook.go index 113fabd8..65d1874d 100644 --- a/plugin/webhook/webhook.go +++ b/plugin/webhook/webhook.go @@ -8,6 +8,8 @@ import ( "time" "github.com/pkg/errors" + + v1pb "github.com/usememos/memos/proto/gen/api/v1" ) var ( @@ -22,44 +24,19 @@ type Memo struct { UpdatedTs int64 `json:"updatedTs"` // Domain specific fields - Content string `json:"content"` - Visibility string `json:"visibility"` - Pinned bool `json:"pinned"` - ResourceList []*Resource `json:"resourceList"` - RelationList []*MemoRelation `json:"relationList"` -} - -type Resource struct { - ID int32 `json:"id"` - UID string `json:"uid"` - - // Standard fields - CreatorID int32 `json:"creatorId"` - CreatedTs int64 `json:"createdTs"` - UpdatedTs int64 `json:"updatedTs"` - - // Domain specific fields - Filename string `json:"filename"` - InternalPath string `json:"internalPath"` - ExternalLink string `json:"externalLink"` - Type string `json:"type"` - Size int64 `json:"size"` -} - -type MemoRelation struct { - MemoID int32 `json:"memoId"` - RelatedMemoID int32 `json:"relatedMemoId"` - Type string `json:"type"` + Content string `json:"content"` + Visibility string `json:"visibility"` + Pinned bool `json:"pinned"` } // WebhookPayload is the payload of webhook request. // nolint type WebhookPayload struct { - URL string `json:"url"` - ActivityType string `json:"activityType"` - CreatorID int32 `json:"creatorId"` - CreatedTs int64 `json:"createdTs"` - Memo *Memo `json:"memo"` + URL string `json:"url"` + ActivityType string `json:"activityType"` + CreatorID int32 `json:"creatorId"` + CreatedTs int64 `json:"createdTs"` + Memo *v1pb.Memo `json:"memo"` } // WebhookResponse is the response of webhook request. diff --git a/server/router/api/v1/memo_service.go b/server/router/api/v1/memo_service.go index 915b2322..8ac95c94 100644 --- a/server/router/api/v1/memo_service.go +++ b/server/router/api/v1/memo_service.go @@ -314,6 +314,17 @@ func (s *APIV1Service) DeleteMemo(ctx context.Context, request *v1pb.DeleteMemoR return nil, status.Errorf(codes.Internal, "failed to delete memo") } + // Delete related resources. + resources, err := s.Store.ListResources(ctx, &store.FindResource{MemoID: &id}) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to list resources") + } + for _, resource := range resources { + if err := s.Store.DeleteResource(ctx, &store.DeleteResource{ID: resource.ID}); err != nil { + return nil, status.Errorf(codes.Internal, "failed to delete resource") + } + } + return &emptypb.Empty{}, nil } @@ -841,34 +852,9 @@ func convertMemoToWebhookPayload(memo *v1pb.Memo) (*webhook.WebhookPayload, erro if err != nil { return nil, errors.Wrap(err, "invalid memo creator") } - id, err := ExtractMemoIDFromName(memo.Name) - if err != nil { - return nil, errors.Wrap(err, "invalid memo name") - } return &webhook.WebhookPayload{ CreatorID: creatorID, CreatedTs: time.Now().Unix(), - Memo: &webhook.Memo{ - ID: id, - CreatorID: creatorID, - CreatedTs: memo.CreateTime.Seconds, - UpdatedTs: memo.UpdateTime.Seconds, - Content: memo.Content, - Visibility: memo.Visibility.String(), - Pinned: memo.Pinned, - ResourceList: func() []*webhook.Resource { - resources := []*webhook.Resource{} - for _, resource := range memo.Resources { - resources = append(resources, &webhook.Resource{ - UID: resource.Uid, - Filename: resource.Filename, - ExternalLink: resource.ExternalLink, - Type: resource.Type, - Size: resource.Size, - }) - } - return resources - }(), - }, + Memo: memo, }, nil } diff --git a/server/router/api/v1/resource_service.go b/server/router/api/v1/resource_service.go index b9535989..a37468ef 100644 --- a/server/router/api/v1/resource_service.go +++ b/server/router/api/v1/resource_service.go @@ -365,7 +365,7 @@ func SaveResourceBlob(ctx context.Context, s *store.Store, create *store.Resourc if err != nil { return errors.Wrap(err, "Failed to upload via s3 client") } - presignURL, err := s3Client.PresignGetObject(ctx, s3Config.Bucket, key) + presignURL, err := s3Client.PresignGetObject(ctx, key) if err != nil { return errors.Wrap(err, "Failed to presign via s3 client") } diff --git a/store/resource.go b/store/resource.go index b09eb173..e6daa8fa 100644 --- a/store/resource.go +++ b/store/resource.go @@ -2,12 +2,14 @@ package store import ( "context" + "log/slog" "os" "path/filepath" "github.com/pkg/errors" "github.com/usememos/memos/internal/util" + "github.com/usememos/memos/plugin/storage/s3" storepb "github.com/usememos/memos/proto/gen/store" ) @@ -100,13 +102,45 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro return errors.Wrap(nil, "resource not found") } - // Delete the local file. if resource.StorageType == storepb.ResourceStorageType_LOCAL { - p := filepath.FromSlash(resource.Reference) - if !filepath.IsAbs(p) { - p = filepath.Join(s.Profile.Data, p) + if err := func() error { + p := filepath.FromSlash(resource.Reference) + if !filepath.IsAbs(p) { + p = filepath.Join(s.Profile.Data, p) + } + err := os.Remove(p) + if err != nil { + return errors.Wrap(err, "failed to delete local file") + } + return nil + }(); err != nil { + return errors.Wrap(err, "failed to delete local file") + } + } else if resource.StorageType == storepb.ResourceStorageType_S3 { + if err := func() error { + s3ObjectPayload := resource.Payload.GetS3Object() + if s3ObjectPayload == nil { + return errors.Errorf("No s3 object found") + } + workspaceStorageSetting, err := s.GetWorkspaceStorageSetting(ctx) + if err != nil { + return errors.Wrap(err, "failed to get workspace storage setting") + } + s3Config := workspaceStorageSetting.S3Config + if s3Config == nil { + return errors.Errorf("No actived external storage found") + } + s3Client, err := s3.NewClient(ctx, s3Config) + if err != nil { + return errors.Wrap(err, "Failed to create s3 client") + } + if err := s3Client.DeleteObject(ctx, s3ObjectPayload.Key); err != nil { + return errors.Wrap(err, "Failed to delete s3 object") + } + return nil + }(); err != nil { + slog.Warn("Failed to delete s3 object", err) } - _ = os.Remove(p) } return s.driver.DeleteResource(ctx, delete)