Move settings whether to generate thumbnails for S3 images to storage related settings

pull/5185/head
Florian Dewald 1 week ago
parent 2d4acb98f0
commit 067f67869d

@ -84,6 +84,9 @@ message Attachment {
// Optional. The related memo. Refer to `Memo.name`.
// Format: memos/{memo}
optional string memo = 8 [(google.api.field_behavior) = OPTIONAL];
// Optional. Output only. Whether to use thumbnails for this attachment when stored in S3.
optional bool use_thumbnail_for_s3_image = 9 [(google.api.field_behavior) = OUTPUT_ONLY];
}
message CreateAttachmentRequest {

@ -147,6 +147,9 @@ message WorkspaceSetting {
}
// The S3 config.
S3Config s3_config = 4;
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
bool use_thumbnails_for_s3_images = 5;
}
// Memo-related workspace settings and policies.
@ -169,9 +172,6 @@ message WorkspaceSetting {
bool enable_blur_nsfw_content = 9;
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
repeated string nsfw_tags = 10;
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
bool use_thumbnails_for_s3_images = 11;
}
}

@ -45,9 +45,12 @@ type Attachment struct {
Size int64 `protobuf:"varint,7,opt,name=size,proto3" json:"size,omitempty"`
// Optional. The related memo. Refer to `Memo.name`.
// Format: memos/{memo}
Memo *string `protobuf:"bytes,8,opt,name=memo,proto3,oneof" json:"memo,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Memo *string `protobuf:"bytes,8,opt,name=memo,proto3,oneof" json:"memo,omitempty"`
// Optional. Output only. Whether to use thumbnails for this attachment when stored in S3.
// This is determined by the workspace setting at the time of attachment creation.
UseThumbnailForS3Image *bool `protobuf:"varint,9,opt,name=use_thumbnail_for_s3_image,json=useThumbnailForS3Image,proto3,oneof" json:"use_thumbnail_for_s3_image,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Attachment) Reset() {
@ -136,6 +139,13 @@ func (x *Attachment) GetMemo() string {
return ""
}
func (x *Attachment) GetUseThumbnailForS3Image() bool {
if x != nil && x.UseThumbnailForS3Image != nil {
return *x.UseThumbnailForS3Image
}
return false
}
type CreateAttachmentRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Required. The attachment to create.
@ -549,7 +559,7 @@ var File_api_v1_attachment_service_proto protoreflect.FileDescriptor
const file_api_v1_attachment_service_proto_rawDesc = "" +
"\n" +
"\x1fapi/v1/attachment_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/httpbody.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xfb\x02\n" +
"\x1fapi/v1/attachment_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/httpbody.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe0\x03\n" +
"\n" +
"Attachment\x12\x17\n" +
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12@\n" +
@ -560,10 +570,12 @@ const file_api_v1_attachment_service_proto_rawDesc = "" +
"\rexternal_link\x18\x05 \x01(\tB\x03\xe0A\x01R\fexternalLink\x12\x17\n" +
"\x04type\x18\x06 \x01(\tB\x03\xe0A\x02R\x04type\x12\x17\n" +
"\x04size\x18\a \x01(\x03B\x03\xe0A\x03R\x04size\x12\x1c\n" +
"\x04memo\x18\b \x01(\tB\x03\xe0A\x01H\x00R\x04memo\x88\x01\x01:O\xeaAL\n" +
"\x04memo\x18\b \x01(\tB\x03\xe0A\x01H\x00R\x04memo\x88\x01\x01\x12D\n" +
"\x1ause_thumbnail_for_s3_image\x18\t \x01(\bB\x03\xe0A\x03H\x01R\x16useThumbnailForS3Image\x88\x01\x01:O\xeaAL\n" +
"\x17memos.api.v1/Attachment\x12\x18attachments/{attachment}*\vattachments2\n" +
"attachmentB\a\n" +
"\x05_memo\"\x82\x01\n" +
"\x05_memoB\x1d\n" +
"\x1b_use_thumbnail_for_s3_image\"\x82\x01\n" +
"\x17CreateAttachmentRequest\x12=\n" +
"\n" +
"attachment\x18\x01 \x01(\v2\x18.memos.api.v1.AttachmentB\x03\xe0A\x02R\n" +

@ -589,9 +589,12 @@ type WorkspaceSetting_StorageSetting struct {
// The max upload size in megabytes.
UploadSizeLimitMb int64 `protobuf:"varint,3,opt,name=upload_size_limit_mb,json=uploadSizeLimitMb,proto3" json:"upload_size_limit_mb,omitempty"`
// The S3 config.
S3Config *WorkspaceSetting_StorageSetting_S3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
S3Config *WorkspaceSetting_StorageSetting_S3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
UseThumbnailsForS3Images bool `protobuf:"varint,5,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WorkspaceSetting_StorageSetting) Reset() {
@ -652,6 +655,13 @@ func (x *WorkspaceSetting_StorageSetting) GetS3Config() *WorkspaceSetting_Storag
return nil
}
func (x *WorkspaceSetting_StorageSetting) GetUseThumbnailsForS3Images() bool {
if x != nil {
return x.UseThumbnailsForS3Images
}
return false
}
// Memo-related workspace settings and policies.
type WorkspaceSetting_MemoRelatedSetting struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -672,12 +682,9 @@ type WorkspaceSetting_MemoRelatedSetting struct {
// enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW).
EnableBlurNsfwContent bool `protobuf:"varint,9,opt,name=enable_blur_nsfw_content,json=enableBlurNsfwContent,proto3" json:"enable_blur_nsfw_content,omitempty"`
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
UseThumbnailsForS3Images bool `protobuf:"varint,11,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WorkspaceSetting_MemoRelatedSetting) Reset() {
@ -773,13 +780,6 @@ func (x *WorkspaceSetting_MemoRelatedSetting) GetNsfwTags() []string {
return nil
}
func (x *WorkspaceSetting_MemoRelatedSetting) GetUseThumbnailsForS3Images() bool {
if x != nil {
return x.UseThumbnailsForS3Images
}
return false
}
// Custom profile configuration for workspace branding.
type WorkspaceSetting_GeneralSetting_CustomProfile struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -965,12 +965,13 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" +
"\x06locale\x18\x04 \x01(\tR\x06locale\x1a\xbe\x04\n" +
"\x06locale\x18\x04 \x01(\tR\x06locale\x1a\xfe\x04\n" +
"\x0eStorageSetting\x12\\\n" +
"\fstorage_type\x18\x01 \x01(\x0e29.memos.api.v1.WorkspaceSetting.StorageSetting.StorageTypeR\vstorageType\x12+\n" +
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
"\x14upload_size_limit_mb\x18\x03 \x01(\x03R\x11uploadSizeLimitMb\x12S\n" +
"\ts3_config\x18\x04 \x01(\v26.memos.api.v1.WorkspaceSetting.StorageSetting.S3ConfigR\bs3Config\x1a\xcc\x01\n" +
"\ts3_config\x18\x04 \x01(\v26.memos.api.v1.WorkspaceSetting.StorageSetting.S3ConfigR\bs3Config\x12>\n" +
"\x1cuse_thumbnails_for_s3_images\x18\x05 \x01(\bR\x18useThumbnailsForS3Images\x1a\xcc\x01\n" +
"\bS3Config\x12\"\n" +
"\raccess_key_id\x18\x01 \x01(\tR\vaccessKeyId\x12*\n" +
"\x11access_key_secret\x18\x02 \x01(\tR\x0faccessKeySecret\x12\x1a\n" +
@ -982,7 +983,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
"\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\f\n" +
"\bDATABASE\x10\x01\x12\t\n" +
"\x05LOCAL\x10\x02\x12\x06\n" +
"\x02S3\x10\x03\x1a\x98\x04\n" +
"\x02S3\x10\x03\x1a\xd8\x03\n" +
"\x12MemoRelatedSetting\x12<\n" +
"\x1adisallow_public_visibility\x18\x01 \x01(\bR\x18disallowPublicVisibility\x127\n" +
"\x18display_with_update_time\x18\x02 \x01(\bR\x15displayWithUpdateTime\x120\n" +
@ -993,8 +994,7 @@ const file_api_v1_workspace_service_proto_rawDesc = "" +
"\x1adisable_markdown_shortcuts\x18\b \x01(\bR\x18disableMarkdownShortcuts\x127\n" +
"\x18enable_blur_nsfw_content\x18\t \x01(\bR\x15enableBlurNsfwContent\x12\x1b\n" +
"\tnsfw_tags\x18\n" +
" \x03(\tR\bnsfwTags\x12>\n" +
"\x1cuse_thumbnails_for_s3_images\x18\v \x01(\bR\x18useThumbnailsForS3Images\"F\n" +
" \x03(\tR\bnsfwTags\"F\n" +
"\x03Key\x12\x13\n" +
"\x0fKEY_UNSPECIFIED\x10\x00\x12\v\n" +
"\aGENERAL\x10\x01\x12\v\n" +

@ -2166,6 +2166,10 @@ components:
memo:
type: string
description: "Optional. The related memo. Refer to `Memo.name`.\r\n Format: memos/{memo}"
useThumbnailForS3Image:
readOnly: true
type: boolean
description: Optional. Output only. Whether to use thumbnails for this attachment when stored in S3.
CreateSessionRequest:
type: object
properties:
@ -3242,9 +3246,6 @@ components:
items:
type: string
description: nsfw_tags is the list of tags that mark content as NSFW for blurring.
useThumbnailsForS3Images:
type: boolean
description: "use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.\r\n When false, images stored in S3 will not have thumbnails generated."
description: Memo-related workspace settings and policies.
WorkspaceSetting_StorageSetting:
type: object
@ -3268,6 +3269,9 @@ components:
allOf:
- $ref: '#/components/schemas/StorageSetting_S3Config'
description: The S3 config.
useThumbnailsForS3Images:
type: boolean
description: "use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.\r\n When false, images stored in S3 will not have thumbnails generated."
description: Storage configuration settings for workspace attachments.
tags:
- name: ActivityService

@ -509,9 +509,12 @@ type WorkspaceStorageSetting struct {
// The max upload size in megabytes.
UploadSizeLimitMb int64 `protobuf:"varint,3,opt,name=upload_size_limit_mb,json=uploadSizeLimitMb,proto3" json:"upload_size_limit_mb,omitempty"`
// The S3 config.
S3Config *StorageS3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
S3Config *StorageS3Config `protobuf:"bytes,4,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
UseThumbnailsForS3Images bool `protobuf:"varint,5,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WorkspaceStorageSetting) Reset() {
@ -572,6 +575,13 @@ func (x *WorkspaceStorageSetting) GetS3Config() *StorageS3Config {
return nil
}
func (x *WorkspaceStorageSetting) GetUseThumbnailsForS3Images() bool {
if x != nil {
return x.UseThumbnailsForS3Images
}
return false
}
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
type StorageS3Config struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -676,12 +686,9 @@ type WorkspaceMemoRelatedSetting struct {
// enable_blur_nsfw_content enables blurring of content marked as not safe for work (NSFW).
EnableBlurNsfwContent bool `protobuf:"varint,9,opt,name=enable_blur_nsfw_content,json=enableBlurNsfwContent,proto3" json:"enable_blur_nsfw_content,omitempty"`
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
UseThumbnailsForS3Images bool `protobuf:"varint,11,opt,name=use_thumbnails_for_s3_images,json=useThumbnailsForS3Images,proto3" json:"use_thumbnails_for_s3_images,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
NsfwTags []string `protobuf:"bytes,10,rep,name=nsfw_tags,json=nsfwTags,proto3" json:"nsfw_tags,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *WorkspaceMemoRelatedSetting) Reset() {
@ -777,13 +784,6 @@ func (x *WorkspaceMemoRelatedSetting) GetNsfwTags() []string {
return nil
}
func (x *WorkspaceMemoRelatedSetting) GetUseThumbnailsForS3Images() bool {
if x != nil {
return x.UseThumbnailsForS3Images
}
return false
}
var File_store_workspace_setting_proto protoreflect.FileDescriptor
const file_store_workspace_setting_proto_rawDesc = "" +
@ -814,12 +814,13 @@ const file_store_workspace_setting_proto_rawDesc = "" +
"\x05title\x18\x01 \x01(\tR\x05title\x12 \n" +
"\vdescription\x18\x02 \x01(\tR\vdescription\x12\x19\n" +
"\blogo_url\x18\x03 \x01(\tR\alogoUrl\x12\x16\n" +
"\x06locale\x18\x04 \x01(\tR\x06locale\"\xd5\x02\n" +
"\x06locale\x18\x04 \x01(\tR\x06locale\"\x95\x03\n" +
"\x17WorkspaceStorageSetting\x12S\n" +
"\fstorage_type\x18\x01 \x01(\x0e20.memos.store.WorkspaceStorageSetting.StorageTypeR\vstorageType\x12+\n" +
"\x11filepath_template\x18\x02 \x01(\tR\x10filepathTemplate\x12/\n" +
"\x14upload_size_limit_mb\x18\x03 \x01(\x03R\x11uploadSizeLimitMb\x129\n" +
"\ts3_config\x18\x04 \x01(\v2\x1c.memos.store.StorageS3ConfigR\bs3Config\"L\n" +
"\ts3_config\x18\x04 \x01(\v2\x1c.memos.store.StorageS3ConfigR\bs3Config\x12>\n" +
"\x1cuse_thumbnails_for_s3_images\x18\x05 \x01(\bR\x18useThumbnailsForS3Images\"L\n" +
"\vStorageType\x12\x1c\n" +
"\x18STORAGE_TYPE_UNSPECIFIED\x10\x00\x12\f\n" +
"\bDATABASE\x10\x01\x12\t\n" +
@ -831,7 +832,7 @@ const file_store_workspace_setting_proto_rawDesc = "" +
"\bendpoint\x18\x03 \x01(\tR\bendpoint\x12\x16\n" +
"\x06region\x18\x04 \x01(\tR\x06region\x12\x16\n" +
"\x06bucket\x18\x05 \x01(\tR\x06bucket\x12$\n" +
"\x0euse_path_style\x18\x06 \x01(\bR\fusePathStyle\"\xa1\x04\n" +
"\x0euse_path_style\x18\x06 \x01(\bR\fusePathStyle\"\xe1\x03\n" +
"\x1bWorkspaceMemoRelatedSetting\x12<\n" +
"\x1adisallow_public_visibility\x18\x01 \x01(\bR\x18disallowPublicVisibility\x127\n" +
"\x18display_with_update_time\x18\x02 \x01(\bR\x15displayWithUpdateTime\x120\n" +
@ -842,8 +843,7 @@ const file_store_workspace_setting_proto_rawDesc = "" +
"\x1adisable_markdown_shortcuts\x18\b \x01(\bR\x18disableMarkdownShortcuts\x127\n" +
"\x18enable_blur_nsfw_content\x18\t \x01(\bR\x15enableBlurNsfwContent\x12\x1b\n" +
"\tnsfw_tags\x18\n" +
" \x03(\tR\bnsfwTags\x12>\n" +
"\x1cuse_thumbnails_for_s3_images\x18\v \x01(\bR\x18useThumbnailsForS3Images*s\n" +
" \x03(\tR\bnsfwTags*s\n" +
"\x13WorkspaceSettingKey\x12%\n" +
"!WORKSPACE_SETTING_KEY_UNSPECIFIED\x10\x00\x12\t\n" +
"\x05BASIC\x10\x01\x12\v\n" +

@ -83,6 +83,9 @@ message WorkspaceStorageSetting {
int64 upload_size_limit_mb = 3;
// The S3 config.
StorageS3Config s3_config = 4;
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
bool use_thumbnails_for_s3_images = 5;
}
// Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/
@ -114,7 +117,4 @@ message WorkspaceMemoRelatedSetting {
bool enable_blur_nsfw_content = 9;
// nsfw_tags is the list of tags that mark content as NSFW for blurring.
repeated string nsfw_tags = 10;
// use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
// When false, images stored in S3 will not have thumbnails generated.
bool use_thumbnails_for_s3_images = 11;
}

@ -119,7 +119,7 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
return nil, status.Errorf(codes.Internal, "failed to create attachment: %v", err)
}
return convertAttachmentFromStore(attachment), nil
return s.convertAttachmentFromStore(ctx, attachment), nil
}
func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAttachmentsRequest) (*v1pb.ListAttachmentsResponse, error) {
@ -164,7 +164,7 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt
response := &v1pb.ListAttachmentsResponse{}
for _, attachment := range attachments {
response.Attachments = append(response.Attachments, convertAttachmentFromStore(attachment))
response.Attachments = append(response.Attachments, s.convertAttachmentFromStore(ctx, attachment))
}
// For simplicity, set total size to the number of returned attachments.
@ -191,7 +191,7 @@ func (s *APIV1Service) GetAttachment(ctx context.Context, request *v1pb.GetAttac
if attachment == nil {
return nil, status.Errorf(codes.NotFound, "attachment not found")
}
return convertAttachmentFromStore(attachment), nil
return s.convertAttachmentFromStore(ctx, attachment), nil
}
func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.GetAttachmentBinaryRequest) (*httpbody.HttpBody, error) {
@ -235,10 +235,10 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
// Check if we should generate thumbnails for S3 images
shouldGenerateThumbnail := true
if attachment.StorageType == storepb.AttachmentStorageType_S3 {
memoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
storageSetting, err := s.Store.GetWorkspaceStorageSetting(ctx)
if err != nil {
slog.Warn("failed to get workspace memo related setting", slog.Any("error", err))
} else if !memoRelatedSetting.UseThumbnailsForS3Images {
slog.Warn("failed to get workspace storage setting", slog.Any("error", err))
} else if !storageSetting.UseThumbnailsForS3Images {
shouldGenerateThumbnail = false
}
}
@ -381,7 +381,7 @@ func (s *APIV1Service) DeleteAttachment(ctx context.Context, request *v1pb.Delet
return &emptypb.Empty{}, nil
}
func convertAttachmentFromStore(attachment *store.Attachment) *v1pb.Attachment {
func (s *APIV1Service) convertAttachmentFromStore(ctx context.Context, attachment *store.Attachment) *v1pb.Attachment {
attachmentMessage := &v1pb.Attachment{
Name: fmt.Sprintf("%s%s", AttachmentNamePrefix, attachment.UID),
CreateTime: timestamppb.New(time.Unix(attachment.CreatedTs, 0)),
@ -397,6 +397,17 @@ func convertAttachmentFromStore(attachment *store.Attachment) *v1pb.Attachment {
attachmentMessage.ExternalLink = attachment.Reference
}
// Populate use_thumbnail_for_s3_image based on workspace setting and storage type
if attachment.StorageType == storepb.AttachmentStorageType_S3 {
storageSetting, err := s.Store.GetWorkspaceStorageSetting(ctx)
if err != nil {
slog.Warn("failed to get workspace storage setting", slog.Any("error", err))
} else {
useThumbnail := storageSetting.UseThumbnailsForS3Images
attachmentMessage.UseThumbnailForS3Image = &useThumbnail
}
}
return attachmentMessage
}

@ -96,7 +96,7 @@ func (s *APIV1Service) ListMemoAttachments(ctx context.Context, request *v1pb.Li
Attachments: []*v1pb.Attachment{},
}
for _, attachment := range attachments {
response.Attachments = append(response.Attachments, convertAttachmentFromStore(attachment))
response.Attachments = append(response.Attachments, s.convertAttachmentFromStore(ctx, attachment))
}
return response, nil
}

@ -62,7 +62,7 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
memoMessage.Attachments = []*v1pb.Attachment{}
for _, attachment := range attachments {
attachmentResponse := convertAttachmentFromStore(attachment)
attachmentResponse := s.convertAttachmentFromStore(ctx, attachment)
memoMessage.Attachments = append(memoMessage.Attachments, attachmentResponse)
}

@ -211,9 +211,10 @@ func convertWorkspaceStorageSettingFromStore(settingpb *storepb.WorkspaceStorage
return nil
}
setting := &v1pb.WorkspaceSetting_StorageSetting{
StorageType: v1pb.WorkspaceSetting_StorageSetting_StorageType(settingpb.StorageType),
FilepathTemplate: settingpb.FilepathTemplate,
UploadSizeLimitMb: settingpb.UploadSizeLimitMb,
StorageType: v1pb.WorkspaceSetting_StorageSetting_StorageType(settingpb.StorageType),
FilepathTemplate: settingpb.FilepathTemplate,
UploadSizeLimitMb: settingpb.UploadSizeLimitMb,
UseThumbnailsForS3Images: settingpb.UseThumbnailsForS3Images,
}
if settingpb.S3Config != nil {
setting.S3Config = &v1pb.WorkspaceSetting_StorageSetting_S3Config{
@ -233,9 +234,10 @@ func convertWorkspaceStorageSettingToStore(setting *v1pb.WorkspaceSetting_Storag
return nil
}
settingpb := &storepb.WorkspaceStorageSetting{
StorageType: storepb.WorkspaceStorageSetting_StorageType(setting.StorageType),
FilepathTemplate: setting.FilepathTemplate,
UploadSizeLimitMb: setting.UploadSizeLimitMb,
StorageType: storepb.WorkspaceStorageSetting_StorageType(setting.StorageType),
FilepathTemplate: setting.FilepathTemplate,
UploadSizeLimitMb: setting.UploadSizeLimitMb,
UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images,
}
if setting.S3Config != nil {
settingpb.S3Config = &storepb.StorageS3Config{
@ -264,7 +266,6 @@ func convertWorkspaceMemoRelatedSettingFromStore(setting *storepb.WorkspaceMemoR
DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts,
EnableBlurNsfwContent: setting.EnableBlurNsfwContent,
NsfwTags: setting.NsfwTags,
UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images,
}
}
@ -282,7 +283,6 @@ func convertWorkspaceMemoRelatedSettingToStore(setting *v1pb.WorkspaceSetting_Me
DisableMarkdownShortcuts: setting.DisableMarkdownShortcuts,
EnableBlurNsfwContent: setting.EnableBlurNsfwContent,
NsfwTags: setting.NsfwTags,
UseThumbnailsForS3Images: setting.UseThumbnailsForS3Images,
}
}

@ -1,11 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Memos</title>
</head>
<body>
No embeddable frontend found.
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/webp" href="/logo.webp" />
<link rel="manifest" href="/site.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<!-- memos.metadata.head -->
<title>Memos</title>
<script type="module" crossorigin src="/assets/index-DWNrA8PH.js"></script>
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-CZ4GW7MU.js">
<link rel="modulepreload" crossorigin href="/assets/leaflet-vendor-DhA_n75-.js">
<link rel="modulepreload" crossorigin href="/assets/mermaid-vendor-Be1fRuBG.js">
<link rel="modulepreload" crossorigin href="/assets/katex-vendor-DsmCZfJr.js">
<link rel="stylesheet" crossorigin href="/assets/index-XuioeehA.css">
</head>
<body class="text-base w-full min-h-svh">
<div id="root" class="relative w-full min-h-full"></div>
<!-- memos.metadata.body -->
</body>
</html>

@ -175,13 +175,6 @@ const MemoRelatedSettings = observer(() => {
</div>
</div>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.use-thumbnails-for-s3-images")}</span>
<Switch
checked={memoRelatedSetting.useThumbnailsForS3Images}
onCheckedChange={(checked) => updatePartialSetting({ useThumbnailsForS3Images: checked })}
/>
</div>
<div className="mt-2 w-full flex justify-end">
<Button disabled={isEqual(memoRelatedSetting, originalSetting)} onClick={updateSetting}>
{t("common.save")}

@ -240,6 +240,18 @@ const StorageSection = observer(() => {
</div>
</>
)}
<div className="w-full flex flex-row justify-between items-center">
<span>{t("setting.memo-related-settings.use-thumbnails-for-s3-images")}</span>
<Switch
checked={workspaceStorageSetting.useThumbnailsForS3Images}
onCheckedChange={(checked) =>
setWorkspaceStorageSetting({
...workspaceStorageSetting,
useThumbnailsForS3Images: checked,
})
}
/>
</div>
<div>
<Button disabled={!allowSaveStorageSetting} onClick={saveWorkspaceStorageSetting}>
{t("common.save")}

@ -37,7 +37,14 @@ export interface Attachment {
* Optional. The related memo. Refer to `Memo.name`.
* Format: memos/{memo}
*/
memo?: string | undefined;
memo?:
| string
| undefined;
/**
* Optional. Output only. Whether to use thumbnails for this attachment when stored in S3.
* This is determined by the workspace setting at the time of attachment creation.
*/
useThumbnailForS3Image?: boolean | undefined;
}
export interface CreateAttachmentRequest {
@ -138,6 +145,7 @@ function createBaseAttachment(): Attachment {
type: "",
size: 0,
memo: undefined,
useThumbnailForS3Image: undefined,
};
}
@ -167,6 +175,9 @@ export const Attachment: MessageFns<Attachment> = {
if (message.memo !== undefined) {
writer.uint32(66).string(message.memo);
}
if (message.useThumbnailForS3Image !== undefined) {
writer.uint32(72).bool(message.useThumbnailForS3Image);
}
return writer;
},
@ -241,6 +252,14 @@ export const Attachment: MessageFns<Attachment> = {
message.memo = reader.string();
continue;
}
case 9: {
if (tag !== 72) {
break;
}
message.useThumbnailForS3Image = reader.bool();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@ -263,6 +282,7 @@ export const Attachment: MessageFns<Attachment> = {
message.type = object.type ?? "";
message.size = object.size ?? 0;
message.memo = object.memo ?? undefined;
message.useThumbnailForS3Image = object.useThumbnailForS3Image ?? undefined;
return message;
},
};

@ -141,7 +141,14 @@ export interface WorkspaceSetting_StorageSetting {
/** The max upload size in megabytes. */
uploadSizeLimitMb: number;
/** The S3 config. */
s3Config?: WorkspaceSetting_StorageSetting_S3Config | undefined;
s3Config?:
| WorkspaceSetting_StorageSetting_S3Config
| undefined;
/**
* use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
* When false, images stored in S3 will not have thumbnails generated.
*/
useThumbnailsForS3Images: boolean;
}
/** Storage type enumeration for different storage backends. */
@ -230,11 +237,6 @@ export interface WorkspaceSetting_MemoRelatedSetting {
enableBlurNsfwContent: boolean;
/** nsfw_tags is the list of tags that mark content as NSFW for blurring. */
nsfwTags: string[];
/**
* use_thumbnails_for_s3_images enables thumbnail generation for images stored in S3.
* When false, images stored in S3 will not have thumbnails generated.
*/
useThumbnailsForS3Images: boolean;
}
/** Request message for GetWorkspaceSetting method. */
@ -710,6 +712,7 @@ function createBaseWorkspaceSetting_StorageSetting(): WorkspaceSetting_StorageSe
filepathTemplate: "",
uploadSizeLimitMb: 0,
s3Config: undefined,
useThumbnailsForS3Images: false,
};
}
@ -727,6 +730,9 @@ export const WorkspaceSetting_StorageSetting: MessageFns<WorkspaceSetting_Storag
if (message.s3Config !== undefined) {
WorkspaceSetting_StorageSetting_S3Config.encode(message.s3Config, writer.uint32(34).fork()).join();
}
if (message.useThumbnailsForS3Images !== false) {
writer.uint32(40).bool(message.useThumbnailsForS3Images);
}
return writer;
},
@ -769,6 +775,14 @@ export const WorkspaceSetting_StorageSetting: MessageFns<WorkspaceSetting_Storag
message.s3Config = WorkspaceSetting_StorageSetting_S3Config.decode(reader, reader.uint32());
continue;
}
case 5: {
if (tag !== 40) {
break;
}
message.useThumbnailsForS3Images = reader.bool();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@ -789,6 +803,7 @@ export const WorkspaceSetting_StorageSetting: MessageFns<WorkspaceSetting_Storag
message.s3Config = (object.s3Config !== undefined && object.s3Config !== null)
? WorkspaceSetting_StorageSetting_S3Config.fromPartial(object.s3Config)
: undefined;
message.useThumbnailsForS3Images = object.useThumbnailsForS3Images ?? false;
return message;
},
};
@ -910,7 +925,6 @@ function createBaseWorkspaceSetting_MemoRelatedSetting(): WorkspaceSetting_MemoR
disableMarkdownShortcuts: false,
enableBlurNsfwContent: false,
nsfwTags: [],
useThumbnailsForS3Images: false,
};
}
@ -943,9 +957,6 @@ export const WorkspaceSetting_MemoRelatedSetting: MessageFns<WorkspaceSetting_Me
for (const v of message.nsfwTags) {
writer.uint32(82).string(v!);
}
if (message.useThumbnailsForS3Images !== false) {
writer.uint32(88).bool(message.useThumbnailsForS3Images);
}
return writer;
},
@ -1028,14 +1039,6 @@ export const WorkspaceSetting_MemoRelatedSetting: MessageFns<WorkspaceSetting_Me
message.nsfwTags.push(reader.string());
continue;
}
case 11: {
if (tag !== 88) {
break;
}
message.useThumbnailsForS3Images = reader.bool();
continue;
}
}
if ((tag & 7) === 4 || tag === 0) {
break;
@ -1059,7 +1062,6 @@ export const WorkspaceSetting_MemoRelatedSetting: MessageFns<WorkspaceSetting_Me
message.disableMarkdownShortcuts = object.disableMarkdownShortcuts ?? false;
message.enableBlurNsfwContent = object.enableBlurNsfwContent ?? false;
message.nsfwTags = object.nsfwTags?.map((e) => e) || [];
message.useThumbnailsForS3Images = object.useThumbnailsForS3Images ?? false;
return message;
},
};

@ -1,4 +1,3 @@
import workspaceStore from "@/store/workspace";
import { Attachment } from "@/types/proto/api/v1/attachment_service";
export const getAttachmentUrl = (attachment: Attachment) => {
@ -10,12 +9,8 @@ export const getAttachmentUrl = (attachment: Attachment) => {
};
export const getAttachmentThumbnailUrl = (attachment: Attachment) => {
// Check if thumbnails are disabled for S3 images
const isS3Image = !!attachment.externalLink;
const useThumbnailsForS3Images = workspaceStore.state.memoRelatedSetting.useThumbnailsForS3Images;
// Don't request thumbnails for S3 images if the setting is disabled
if (isS3Image && !useThumbnailsForS3Images) {
if (attachment.externalLink && !attachment.useThumbnailForS3Image) {
return getAttachmentUrl(attachment);
}

Loading…
Cancel
Save