diff --git a/server/runner/s3presign/runner.go b/server/runner/s3presign/runner.go index a18ede22a..991d1ab51 100644 --- a/server/runner/s3presign/runner.go +++ b/server/runner/s3presign/runner.go @@ -5,6 +5,7 @@ import ( "log/slog" "time" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" "github.com/usememos/memos/internal/storage/s3" @@ -75,7 +76,8 @@ func (r *Runner) CheckAndPresign(ctx context.Context) { // Process batch of attachments presignCount := 0 for _, attachment := range attachments { - s3ObjectPayload := attachment.Payload.GetS3Object() + payload := cloneAttachmentPayload(attachment.Payload) + s3ObjectPayload := payload.GetS3Object() if s3ObjectPayload == nil { continue } @@ -114,11 +116,7 @@ func (r *Runner) CheckAndPresign(ctx context.Context) { if err := r.Store.UpdateAttachment(ctx, &store.UpdateAttachment{ ID: attachment.ID, Reference: &presignURL, - Payload: &storepb.AttachmentPayload{ - Payload: &storepb.AttachmentPayload_S3Object_{ - S3Object: s3ObjectPayload, - }, - }, + Payload: payload, }); err != nil { slog.Error("Failed to update attachment", "error", err, "attachmentID", attachment.ID) continue @@ -132,3 +130,14 @@ func (r *Runner) CheckAndPresign(ctx context.Context) { offset += len(attachments) } } + +func cloneAttachmentPayload(payload *storepb.AttachmentPayload) *storepb.AttachmentPayload { + if payload == nil { + return nil + } + cloned, ok := proto.Clone(payload).(*storepb.AttachmentPayload) + if !ok { + return nil + } + return cloned +} diff --git a/server/runner/s3presign/runner_test.go b/server/runner/s3presign/runner_test.go new file mode 100644 index 000000000..94a382d6b --- /dev/null +++ b/server/runner/s3presign/runner_test.go @@ -0,0 +1,34 @@ +package s3presign + +import ( + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" + + storepb "github.com/usememos/memos/proto/gen/store" +) + +func TestCloneAttachmentPayloadPreservesMotionMedia(t *testing.T) { + payload := &storepb.AttachmentPayload{ + Payload: &storepb.AttachmentPayload_S3Object_{ + S3Object: &storepb.AttachmentPayload_S3Object{ + Key: "photos/live.jpg", + }, + }, + MotionMedia: &storepb.MotionMedia{ + Family: storepb.MotionMediaFamily_ANDROID_MOTION_PHOTO, + Role: storepb.MotionMediaRole_CONTAINER, + GroupId: "motion-group", + }, + } + + cloned := cloneAttachmentPayload(payload) + require.NotNil(t, cloned) + require.NotSame(t, payload, cloned) + require.Equal(t, payload.MotionMedia, cloned.MotionMedia) + + cloned.GetS3Object().LastPresignedTime = timestamppb.Now() + require.Nil(t, payload.GetS3Object().LastPresignedTime) + require.Equal(t, "motion-group", cloned.MotionMedia.GroupId) +}