mirror of https://github.com/usememos/memos
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			171 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
			
		
		
	
	
			171 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			Go
		
	
package store
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"log/slog"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
 | 
						|
	"github.com/pkg/errors"
 | 
						|
 | 
						|
	"github.com/usememos/memos/internal/base"
 | 
						|
	"github.com/usememos/memos/plugin/storage/s3"
 | 
						|
	storepb "github.com/usememos/memos/proto/gen/store"
 | 
						|
)
 | 
						|
 | 
						|
type Attachment struct {
 | 
						|
	// ID is the system generated unique identifier for the attachment.
 | 
						|
	ID int32
 | 
						|
	// UID is the user defined unique identifier for the attachment.
 | 
						|
	UID string
 | 
						|
 | 
						|
	// Standard fields
 | 
						|
	CreatorID int32
 | 
						|
	CreatedTs int64
 | 
						|
	UpdatedTs int64
 | 
						|
 | 
						|
	// Domain specific fields
 | 
						|
	Filename    string
 | 
						|
	Blob        []byte
 | 
						|
	Type        string
 | 
						|
	Size        int64
 | 
						|
	StorageType storepb.AttachmentStorageType
 | 
						|
	Reference   string
 | 
						|
	Payload     *storepb.AttachmentPayload
 | 
						|
 | 
						|
	// The related memo ID.
 | 
						|
	MemoID *int32
 | 
						|
 | 
						|
	// Composed field
 | 
						|
	MemoUID *string
 | 
						|
}
 | 
						|
 | 
						|
type FindAttachment struct {
 | 
						|
	GetBlob        bool
 | 
						|
	ID             *int32
 | 
						|
	UID            *string
 | 
						|
	CreatorID      *int32
 | 
						|
	Filename       *string
 | 
						|
	FilenameSearch *string
 | 
						|
	MemoID         *int32
 | 
						|
	MemoIDList     []int32
 | 
						|
	HasRelatedMemo bool
 | 
						|
	StorageType    *storepb.AttachmentStorageType
 | 
						|
	Limit          *int
 | 
						|
	Offset         *int
 | 
						|
}
 | 
						|
 | 
						|
type UpdateAttachment struct {
 | 
						|
	ID        int32
 | 
						|
	UID       *string
 | 
						|
	UpdatedTs *int64
 | 
						|
	Filename  *string
 | 
						|
	MemoID    *int32
 | 
						|
	Reference *string
 | 
						|
	Payload   *storepb.AttachmentPayload
 | 
						|
}
 | 
						|
 | 
						|
type DeleteAttachment struct {
 | 
						|
	ID     int32
 | 
						|
	MemoID *int32
 | 
						|
}
 | 
						|
 | 
						|
func (s *Store) CreateAttachment(ctx context.Context, create *Attachment) (*Attachment, error) {
 | 
						|
	if !base.UIDMatcher.MatchString(create.UID) {
 | 
						|
		return nil, errors.New("invalid uid")
 | 
						|
	}
 | 
						|
	return s.driver.CreateAttachment(ctx, create)
 | 
						|
}
 | 
						|
 | 
						|
func (s *Store) ListAttachments(ctx context.Context, find *FindAttachment) ([]*Attachment, error) {
 | 
						|
	// Set default limits to prevent loading too many attachments at once
 | 
						|
	if find.Limit == nil && find.GetBlob {
 | 
						|
		// When fetching blobs, we should be especially careful with limits
 | 
						|
		defaultLimit := 10
 | 
						|
		find.Limit = &defaultLimit
 | 
						|
	} else if find.Limit == nil {
 | 
						|
		// Even without blobs, let's default to a reasonable limit
 | 
						|
		defaultLimit := 100
 | 
						|
		find.Limit = &defaultLimit
 | 
						|
	}
 | 
						|
 | 
						|
	return s.driver.ListAttachments(ctx, find)
 | 
						|
}
 | 
						|
 | 
						|
func (s *Store) GetAttachment(ctx context.Context, find *FindAttachment) (*Attachment, error) {
 | 
						|
	attachments, err := s.ListAttachments(ctx, find)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if len(attachments) == 0 {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return attachments[0], nil
 | 
						|
}
 | 
						|
 | 
						|
func (s *Store) UpdateAttachment(ctx context.Context, update *UpdateAttachment) error {
 | 
						|
	if update.UID != nil && !base.UIDMatcher.MatchString(*update.UID) {
 | 
						|
		return errors.New("invalid uid")
 | 
						|
	}
 | 
						|
	return s.driver.UpdateAttachment(ctx, update)
 | 
						|
}
 | 
						|
 | 
						|
func (s *Store) DeleteAttachment(ctx context.Context, delete *DeleteAttachment) error {
 | 
						|
	attachment, err := s.GetAttachment(ctx, &FindAttachment{ID: &delete.ID})
 | 
						|
	if err != nil {
 | 
						|
		return errors.Wrap(err, "failed to get attachment")
 | 
						|
	}
 | 
						|
	if attachment == nil {
 | 
						|
		return errors.New("attachment not found")
 | 
						|
	}
 | 
						|
 | 
						|
	if attachment.StorageType == storepb.AttachmentStorageType_LOCAL {
 | 
						|
		if err := func() error {
 | 
						|
			p := filepath.FromSlash(attachment.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 attachment.StorageType == storepb.AttachmentStorageType_S3 {
 | 
						|
		if err := func() error {
 | 
						|
			s3ObjectPayload := attachment.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 := s3ObjectPayload.S3Config
 | 
						|
			if s3Config == nil {
 | 
						|
				if workspaceStorageSetting.S3Config == nil {
 | 
						|
					return errors.Errorf("S3 config is not found")
 | 
						|
				}
 | 
						|
				s3Config = workspaceStorageSetting.S3Config
 | 
						|
			}
 | 
						|
 | 
						|
			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", slog.Any("err", err))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return s.driver.DeleteAttachment(ctx, delete)
 | 
						|
}
 |